Skip to content

Commit

Permalink
Implement async loading
Browse files Browse the repository at this point in the history
  • Loading branch information
hasenbanck committed Jan 1, 2025
1 parent 39603d5 commit ae36766
Show file tree
Hide file tree
Showing 21 changed files with 394 additions and 288 deletions.
2 changes: 1 addition & 1 deletion korangar/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ korangar_interface = { workspace = true, features = ["serde", "cgmath"] }
korangar_networking = { workspace = true, features = ["debug"] }
korangar_util = { workspace = true, features = ["interface"] }
lunify = { workspace = true }
mlua = { workspace = true, features = ["lua51", "send", "vendored"] }
mlua = { workspace = true, features = ["lua51", "vendored"] }
num = { workspace = true }
option-ext = { workspace = true }
pollster = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion korangar/src/graphics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ impl GlobalContext {
RgbaImage::from_raw(1, 1, vec![255, 255, 255, 255]).unwrap().as_raw(),
false,
));
let walk_indicator_texture = texture_loader.get("grid.tga", ImageType::Color).unwrap();
let walk_indicator_texture = texture_loader.get_or_load("grid.tga", ImageType::Color).unwrap();
let forward_textures = Self::create_forward_textures(device, forward_size, msaa);
let picker_textures = Self::create_picker_textures(device, screen_size);
let directional_shadow_map_texture = Self::create_directional_shadow_texture(device, directional_shadow_size);
Expand Down
4 changes: 2 additions & 2 deletions korangar/src/interface/cursor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ pub struct MouseCursor {

impl MouseCursor {
pub fn new(sprite_loader: &SpriteLoader, action_loader: &ActionLoader) -> Self {
let sprite = sprite_loader.get("cursors.spr").unwrap();
let actions = action_loader.get("cursors.act").unwrap();
let sprite = sprite_loader.get_or_load("cursors.spr").unwrap();
let actions = action_loader.get_or_load("cursors.act").unwrap();
let animation_state = SpriteAnimationState::new(ClientTick(0));
let shown = true;

Expand Down
5 changes: 1 addition & 4 deletions korangar/src/interface/windows/debug/profiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,7 @@ impl PrototypeWindow<InterfaceSettings> for ProfilerWindow {
PickList::default()
.with_options(vec![
("Main thread", crate::threads::Enum::Main),
("Picker thread", crate::threads::Enum::Picker),
("Shadow thread", crate::threads::Enum::Shadow),
("Point shadow thread", crate::threads::Enum::PointShadow),
("Deferred thread", crate::threads::Enum::Deferred),
("Loader thread", crate::threads::Enum::Loader),
])
.with_selected(self.visible_thread.clone())
.with_width(dimension_bound!(150))
Expand Down
4 changes: 2 additions & 2 deletions korangar/src/inventory/skills.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ impl SkillTree {
.into_iter()
.map(|skill_data| {
let file_path = format!("¾ÆÀÌÅÛ\\{}", skill_data.skill_name);
let sprite = sprite_loader.get(&format!("{file_path}.spr")).unwrap();
let actions = action_loader.get(&format!("{file_path}.act")).unwrap();
let sprite = sprite_loader.get_or_load(&format!("{file_path}.spr")).unwrap();
let actions = action_loader.get_or_load(&format!("{file_path}.act")).unwrap();

Skill {
skill_id: skill_data.skill_id,
Expand Down
6 changes: 3 additions & 3 deletions korangar/src/loaders/action/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl ActionLoader {
print_debug!("Replacing with fallback");
}

return self.get(FALLBACK_ACTIONS_FILE);
return self.get_or_load(FALLBACK_ACTIONS_FILE);
}
};
let mut byte_reader: ByteReader<Option<InternalVersion>> = ByteReader::with_default_metadata(&bytes);
Expand All @@ -62,7 +62,7 @@ impl ActionLoader {
print_debug!("Replacing with fallback");
}

return self.get(FALLBACK_ACTIONS_FILE);
return self.get_or_load(FALLBACK_ACTIONS_FILE);
}
};

Expand Down Expand Up @@ -107,7 +107,7 @@ impl ActionLoader {
Ok(sprite)
}

pub fn get(&self, path: &str) -> Result<Arc<Actions>, LoadError> {
pub fn get_or_load(&self, path: &str) -> Result<Arc<Actions>, LoadError> {
let mut lock = self.cache.lock().unwrap();
match lock.get(path) {
Some(sprite) => Ok(sprite.clone()),
Expand Down
21 changes: 4 additions & 17 deletions korangar/src/loaders/animation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ impl AnimationLoader {
let animation_pairs: Vec<AnimationPair> = entity_part_files
.iter()
.map(|file_path| AnimationPair {
sprites: sprite_loader.get(&format!("{file_path}.spr")).unwrap(),
actions: action_loader.get(&format!("{file_path}.act")).unwrap(),
sprites: sprite_loader.get_or_load(&format!("{file_path}.spr")).unwrap(),
actions: action_loader.get_or_load(&format!("{file_path}.act")).unwrap(),
})
.collect();

Expand Down Expand Up @@ -310,22 +310,9 @@ impl AnimationLoader {
Ok(animation_data)
}

pub fn get(
&self,
sprite_loader: &SpriteLoader,
action_loader: &ActionLoader,
entity_type: EntityType,
entity_part_files: &[String],
) -> Result<Arc<AnimationData>, LoadError> {
pub fn get(&self, entity_part_files: &[String]) -> Option<Arc<AnimationData>> {
let mut lock = self.cache.lock().unwrap();
match lock.get(entity_part_files) {
Some(animation_data) => Ok(animation_data.clone()),
None => {
// We need to drop to avoid a deadlock here.
drop(lock);
self.load(sprite_loader, action_loader, entity_type, entity_part_files)
}
}
lock.get(entity_part_files).cloned()
}
}

Expand Down
171 changes: 171 additions & 0 deletions korangar/src/loaders/async/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
use std::cmp::PartialEq;
use std::sync::{Arc, Mutex};

use hashbrown::HashMap;
#[cfg(feature = "debug")]
use korangar_debug::logging::print_debug;
#[cfg(feature = "debug")]
use korangar_util::texture_atlas::AtlasAllocation;
use ragnarok_packets::{EntityId, TilePosition};
use rayon::{ThreadPool, ThreadPoolBuilder};

use crate::loaders::error::LoadError;
use crate::loaders::{ActionLoader, AnimationLoader, MapLoader, ModelLoader, SpriteLoader, TextureLoader};
#[cfg(feature = "debug")]
use crate::threads;
use crate::world::{AnimationData, EntityType, Map};

#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub enum LoaderId {
AnimationData(EntityId),
Map(String),
}

pub enum LoadableResource {
AnimationData(Arc<AnimationData>),
Map { map: Box<Map>, player_position: TilePosition },
}

enum LoadStatus {
Loading,
Completed(LoadableResource),
Failed(LoadError),
}

impl PartialEq for LoadStatus {
fn eq(&self, other: &Self) -> bool {
std::mem::discriminant(self) == std::mem::discriminant(other)
}
}

pub struct AsyncLoader {
action_loader: Arc<ActionLoader>,
animation_loader: Arc<AnimationLoader>,
map_loader: Arc<MapLoader>,
model_loader: Arc<ModelLoader>,
sprite_loader: Arc<SpriteLoader>,
texture_loader: Arc<TextureLoader>,
pending_loads: Arc<Mutex<HashMap<LoaderId, LoadStatus>>>,
thread_pool: ThreadPool,
}

impl AsyncLoader {
pub fn new(
action_loader: Arc<ActionLoader>,
animation_loader: Arc<AnimationLoader>,
map_loader: Arc<MapLoader>,
model_loader: Arc<ModelLoader>,
sprite_loader: Arc<SpriteLoader>,
texture_loader: Arc<TextureLoader>,
) -> Self {
let thread_pool = ThreadPoolBuilder::new()
.num_threads(1)
.thread_name(|_| "async loader".to_string())
.build()
.unwrap();

Self {
action_loader,
animation_loader,
map_loader,
model_loader,
sprite_loader,
texture_loader,
pending_loads: Arc::new(Mutex::new(HashMap::new())),
thread_pool,
}
}

pub fn request_animation_data_load(&self, entity_id: EntityId, entity_type: EntityType, entity_part_files: Vec<String>) {
let sprite_loader = self.sprite_loader.clone();
let action_loader = self.action_loader.clone();
let animation_loader = self.animation_loader.clone();

self.request_load(LoaderId::AnimationData(entity_id), move || {
let animation_data = animation_loader
.load(&sprite_loader, &action_loader, entity_type, &entity_part_files)
.unwrap();
Ok(LoadableResource::AnimationData(animation_data))
});
}

pub fn request_map_load(
&self,
map_name: String,
player_position: TilePosition,
#[cfg(feature = "debug")] tile_texture_mapping: Arc<Vec<AtlasAllocation>>,
) {
let map_loader = self.map_loader.clone();
let model_loader = self.model_loader.clone();
let texture_loader = self.texture_loader.clone();

self.request_load(LoaderId::Map(map_name.clone()), move || {
let map = map_loader.load(
map_name,
&model_loader,
texture_loader,
#[cfg(feature = "debug")]
&tile_texture_mapping,
)?;
Ok(LoadableResource::Map { map, player_position })
});
}

fn request_load<F>(&self, id: LoaderId, load_function: F)
where
F: FnOnce() -> Result<LoadableResource, LoadError> + Send + 'static,
{
let pending_loads = Arc::clone(&self.pending_loads);

pending_loads.lock().unwrap().insert(id.clone(), LoadStatus::Loading);

self.thread_pool.spawn(move || {
#[cfg(feature = "debug")]
let _measurement = threads::Loader::start_frame();

let result = load_function();

let mut pending_loads = pending_loads.lock().unwrap();

if !pending_loads.contains_key(&id) {
return;
}

let status = match result {
Ok(resource) => LoadStatus::Completed(resource),
Err(err) => LoadStatus::Failed(err),
};

pending_loads.insert(id, status);
});
}

pub fn take_completed(&self) -> impl Iterator<Item = (LoaderId, LoadableResource)> + '_ {
std::iter::from_fn({
let pending_loads = Arc::clone(&self.pending_loads);

move || {
let mut pending_loads = pending_loads.lock().unwrap();

let completed_id = pending_loads
.iter()
.find(|(_, status)| matches!(status, LoadStatus::Completed(_) | LoadStatus::Failed(_)))
.map(|(id, _)| id.clone());

if let Some(id) = completed_id {
match pending_loads.remove(&id).unwrap() {
LoadStatus::Failed(_error) => {
#[cfg(feature = "debug")]
print_debug!("Async load error: {:?}", _error);
None
}
LoadStatus::Completed(resource) => Some((id, resource)),
_ => unreachable!(),
}
} else {
None
}
}
})
}
}
4 changes: 2 additions & 2 deletions korangar/src/loaders/effect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ impl EffectLoader {
.into_iter()
.map(|name| {
let path = format!("effect\\{}{}", prefix, name.name);
texture_loader.get(&path, ImageType::Color).unwrap()
texture_loader.get_or_load(&path, ImageType::Color).unwrap()
})
.collect(),
{
Expand Down Expand Up @@ -156,7 +156,7 @@ impl EffectLoader {
Ok(effect)
}

pub fn get(&self, path: &str, texture_loader: &TextureLoader) -> Result<Arc<Effect>, LoadError> {
pub fn get_or_load(&self, path: &str, texture_loader: &TextureLoader) -> Result<Arc<Effect>, LoadError> {
let mut lock = self.cache.lock().unwrap();
match lock.get(path) {
Some(effect) => Ok(effect.clone()),
Expand Down
10 changes: 7 additions & 3 deletions korangar/src/loaders/map/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ impl MapLoader {
model_loader: &ModelLoader,
texture_loader: Arc<TextureLoader>,
#[cfg(feature = "debug")] tile_texture_mapping: &[AtlasAllocation],
) -> Result<Map, LoadError> {
) -> Result<Box<Map>, LoadError> {
#[cfg(feature = "debug")]
let timer = Timer::new_dynamic(format!("load map from {}", &resource_file));

Expand Down Expand Up @@ -121,7 +121,11 @@ impl MapLoader {
let water_paths = get_water_texture_paths(water_type);
water_paths
.iter()
.map(|path| texture_loader.get(path, ImageType::Color).expect("Can't load water texture"))
.map(|path| {
texture_loader
.get_or_load(path, ImageType::Color)
.expect("Can't load water texture")
})
.collect()
});

Expand Down Expand Up @@ -232,7 +236,7 @@ impl MapLoader {
#[cfg(feature = "debug")]
timer.stop();

Ok(map)
Ok(Box::new(map))
}

fn generate_vertex_buffer_and_atlas_texture(
Expand Down
2 changes: 2 additions & 0 deletions korangar/src/loaders/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod action;
mod animation;
mod archive;

mod r#async;
mod effect;
pub mod error;
mod font;
Expand All @@ -20,6 +21,7 @@ pub use self::font::{FontLoader, FontSize, GlyphInstruction, Scaling, TextLayout
pub use self::gamefile::*;
pub use self::map::{MapLoader, MAP_TILE_SIZE};
pub use self::model::*;
pub use self::r#async::*;
pub use self::script::{ResourceMetadata, ScriptLoader};
pub use self::server::{load_client_info, ClientInfo, ServiceId};
pub use self::sprite::*;
Expand Down
4 changes: 2 additions & 2 deletions korangar/src/loaders/script/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ end

let resource_name = self.get_item_resource_from_id(item.item_id, is_identified);
let full_path = format!("À¯ÀúÀÎÅÍÆäÀ̽º\\item\\{resource_name}.bmp");
let texture = texture_loader.get(&full_path, ImageType::Color).unwrap();
let texture = texture_loader.get_or_load(&full_path, ImageType::Color).unwrap();
let name = self.get_item_name_from_id(item.item_id, is_identified);

let metadata = ResourceMetadata { texture, name };
Expand All @@ -163,7 +163,7 @@ end
pub fn load_market_item_metadata(&self, texture_loader: &TextureLoader, item: ShopItem<NoMetadata>) -> ShopItem<ResourceMetadata> {
let resource_name = self.get_item_resource_from_id(item.item_id, true);
let full_path = format!("À¯ÀúÀÎÅÍÆäÀ̽º\\item\\{resource_name}.bmp");
let texture = texture_loader.get(&full_path, ImageType::Color).unwrap();
let texture = texture_loader.get_or_load(&full_path, ImageType::Color).unwrap();
let name = self.get_item_name_from_id(item.item_id, true);

let metadata = ResourceMetadata { texture, name };
Expand Down
6 changes: 3 additions & 3 deletions korangar/src/loaders/sprite/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ impl SpriteLoader {
print_debug!("Replacing with fallback");
}

return self.get(FALLBACK_SPRITE_FILE);
return self.get_or_load(FALLBACK_SPRITE_FILE);
}
};
let mut byte_reader: ByteReader<Option<InternalVersion>> = ByteReader::with_default_metadata(&bytes);
Expand All @@ -83,7 +83,7 @@ impl SpriteLoader {
print_debug!("Replacing with fallback");
}

return self.get(FALLBACK_SPRITE_FILE);
return self.get_or_load(FALLBACK_SPRITE_FILE);
}
};

Expand Down Expand Up @@ -191,7 +191,7 @@ impl SpriteLoader {
Ok(sprite)
}

pub fn get(&self, path: &str) -> Result<Arc<Sprite>, LoadError> {
pub fn get_or_load(&self, path: &str) -> Result<Arc<Sprite>, LoadError> {
let mut lock = self.cache.lock().unwrap();
match lock.get(path) {
Some(sprite) => Ok(sprite.clone()),
Expand Down
Loading

0 comments on commit ae36766

Please sign in to comment.