Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement async loading #221

Merged
merged 1 commit into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
173 changes: 173 additions & 0 deletions korangar/src/loaders/async/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
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 = match animation_loader.get(&entity_part_files) {
None => animation_loader.load(&sprite_loader, &action_loader, entity_type, &entity_part_files)?,
Some(animation_data) => animation_data,
};

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
Loading