Skip to content

Commit d2af7d3

Browse files
committed
Implement async loading
1 parent 52ddb26 commit d2af7d3

File tree

6 files changed

+290
-85
lines changed

6 files changed

+290
-85
lines changed

korangar/src/interface/windows/debug/profiler.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,7 @@ impl PrototypeWindow<InterfaceSettings> for ProfilerWindow {
115115

116116
let elements = vec![
117117
PickList::default()
118-
.with_options(vec![
119-
("Main thread", crate::threads::Enum::Main),
120-
("Picker thread", crate::threads::Enum::Picker),
121-
("Shadow thread", crate::threads::Enum::Shadow),
122-
("Point shadow thread", crate::threads::Enum::PointShadow),
123-
("Deferred thread", crate::threads::Enum::Deferred),
124-
])
118+
.with_options(vec![("Main thread", crate::threads::Enum::Main)])
125119
.with_selected(self.visible_thread.clone())
126120
.with_width(dimension_bound!(150))
127121
.with_event(Box::new(Vec::new))

korangar/src/loaders/async/mod.rs

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
use std::cmp::PartialEq;
2+
use std::sync::{Arc, Mutex};
3+
4+
use hashbrown::{HashMap, HashSet};
5+
#[cfg(feature = "debug")]
6+
use korangar_debug::logging::print_debug;
7+
use korangar_networking::EntityData;
8+
use korangar_util::texture_atlas::AtlasAllocation;
9+
use ragnarok_packets::{ClientTick, EntityId, TilePosition};
10+
use rayon::{ThreadPool, ThreadPoolBuilder};
11+
12+
use crate::loaders::error::LoadError;
13+
use crate::loaders::{ActionLoader, AnimationLoader, MapLoader, ModelLoader, ScriptLoader, SpriteLoader, TextureLoader};
14+
use crate::world::{Map, Npc};
15+
16+
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
17+
pub enum LoaderId {
18+
Npc(EntityId),
19+
Map(String),
20+
}
21+
22+
pub enum LoadableResource {
23+
Npc(Box<Npc>),
24+
Map { map: Arc<Map>, player_position: TilePosition },
25+
}
26+
27+
enum LoadStatus {
28+
Loading,
29+
Completed(LoadableResource),
30+
Failed(LoadError),
31+
}
32+
33+
impl PartialEq for LoadStatus {
34+
fn eq(&self, other: &Self) -> bool {
35+
std::mem::discriminant(self) == std::mem::discriminant(other)
36+
}
37+
}
38+
39+
pub struct AsyncLoader {
40+
action_loader: Arc<ActionLoader>,
41+
animation_loader: Arc<AnimationLoader>,
42+
map_loader: Arc<MapLoader>,
43+
model_loader: Arc<ModelLoader>,
44+
script_loader: Arc<ScriptLoader>,
45+
sprite_loader: Arc<SpriteLoader>,
46+
texture_loader: Arc<TextureLoader>,
47+
resources: Arc<Mutex<AsyncLoaderResources>>,
48+
thread_pool: ThreadPool,
49+
}
50+
51+
pub struct AsyncLoaderResources {
52+
pending_loads: HashMap<LoaderId, LoadStatus>,
53+
cancelled_loads: HashSet<LoaderId>,
54+
}
55+
56+
impl AsyncLoader {
57+
pub fn new(
58+
action_loader: Arc<ActionLoader>,
59+
animation_loader: Arc<AnimationLoader>,
60+
map_loader: Arc<MapLoader>,
61+
model_loader: Arc<ModelLoader>,
62+
script_loader: Arc<ScriptLoader>,
63+
sprite_loader: Arc<SpriteLoader>,
64+
texture_loader: Arc<TextureLoader>,
65+
) -> Self {
66+
let thread_pool = ThreadPoolBuilder::new()
67+
.num_threads(1)
68+
.thread_name(|_| "async loader".to_string())
69+
.build()
70+
.unwrap();
71+
72+
Self {
73+
action_loader,
74+
animation_loader,
75+
map_loader,
76+
model_loader,
77+
script_loader,
78+
sprite_loader,
79+
texture_loader,
80+
resources: Arc::new(Mutex::new(AsyncLoaderResources {
81+
pending_loads: HashMap::new(),
82+
cancelled_loads: HashSet::new(),
83+
})),
84+
thread_pool,
85+
}
86+
}
87+
88+
pub fn request_npc_load(&self, map: Arc<Map>, entity_data: EntityData, client_tick: ClientTick) {
89+
let sprite_loader = self.sprite_loader.clone();
90+
let action_loader = self.action_loader.clone();
91+
let animation_loader = self.animation_loader.clone();
92+
let script_loader = self.script_loader.clone();
93+
94+
self.request_load(LoaderId::Npc(entity_data.entity_id), move || {
95+
let npc = Npc::new(
96+
&sprite_loader,
97+
&action_loader,
98+
&animation_loader,
99+
&script_loader,
100+
&map,
101+
entity_data,
102+
client_tick,
103+
);
104+
Ok(LoadableResource::Npc(Box::new(npc)))
105+
});
106+
}
107+
108+
pub fn request_map_load(
109+
&self,
110+
map_name: String,
111+
player_position: TilePosition,
112+
#[cfg(feature = "debug")] tile_texture_mapping: Arc<Vec<AtlasAllocation>>,
113+
) {
114+
let map_loader = self.map_loader.clone();
115+
let model_loader = self.model_loader.clone();
116+
let texture_loader = self.texture_loader.clone();
117+
118+
self.request_load(LoaderId::Map(map_name.clone()), move || {
119+
let map = map_loader.load(
120+
map_name,
121+
&model_loader,
122+
texture_loader,
123+
#[cfg(feature = "debug")]
124+
&tile_texture_mapping,
125+
)?;
126+
Ok(LoadableResource::Map { map, player_position })
127+
});
128+
}
129+
130+
fn request_load<F>(&self, id: LoaderId, load_function: F)
131+
where
132+
F: FnOnce() -> Result<LoadableResource, LoadError> + Send + 'static,
133+
{
134+
let resources = Arc::clone(&self.resources);
135+
136+
resources.lock().unwrap().pending_loads.insert(id.clone(), LoadStatus::Loading);
137+
138+
self.thread_pool.spawn(move || {
139+
let result = load_function();
140+
141+
let mut resources = resources.lock().unwrap();
142+
143+
if !resources.pending_loads.contains_key(&id) {
144+
return;
145+
}
146+
147+
if resources.cancelled_loads.contains(&id) {
148+
resources.pending_loads.remove(&id);
149+
resources.cancelled_loads.remove(&id);
150+
return;
151+
}
152+
153+
let status = match result {
154+
Ok(resource) => LoadStatus::Completed(resource),
155+
Err(err) => LoadStatus::Failed(err),
156+
};
157+
158+
resources.pending_loads.insert(id, status);
159+
});
160+
}
161+
162+
pub fn remove_loading_and_cancelled_npc_load(&self, entity_id: EntityId) {
163+
let mut resources = self.resources.lock().unwrap();
164+
let id = LoaderId::Npc(entity_id);
165+
resources.pending_loads.remove(&id);
166+
resources.cancelled_loads.remove(&id);
167+
}
168+
169+
pub fn cancel_npc_load(&self, entity_id: EntityId) {
170+
let mut resources = self.resources.lock().unwrap();
171+
172+
if resources.pending_loads.contains_key(&LoaderId::Npc(entity_id)) {
173+
resources.cancelled_loads.insert(LoaderId::Npc(entity_id));
174+
}
175+
}
176+
177+
pub fn take_completed(&self) -> impl Iterator<Item = (LoaderId, LoadableResource)> + '_ {
178+
std::iter::from_fn({
179+
let resources = Arc::clone(&self.resources);
180+
move || {
181+
let mut guard = resources.lock().unwrap();
182+
183+
let completed_id = guard
184+
.pending_loads
185+
.iter()
186+
.find(|(_, status)| matches!(status, LoadStatus::Completed(_) | LoadStatus::Failed(_)))
187+
.map(|(id, _)| id.clone());
188+
189+
if let Some(id) = completed_id {
190+
match guard.pending_loads.remove(&id).unwrap() {
191+
LoadStatus::Failed(_error) => {
192+
#[cfg(feature = "debug")]
193+
print_debug!("Async load error: {:?}", _error);
194+
None
195+
}
196+
LoadStatus::Completed(resource) => Some((id, resource)),
197+
_ => unreachable!(),
198+
}
199+
} else {
200+
None
201+
}
202+
}
203+
})
204+
}
205+
}

korangar/src/loaders/map/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ impl MapLoader {
6161
model_loader: &ModelLoader,
6262
texture_loader: Arc<TextureLoader>,
6363
#[cfg(feature = "debug")] tile_texture_mapping: &[AtlasAllocation],
64-
) -> Result<Map, LoadError> {
64+
) -> Result<Arc<Map>, LoadError> {
6565
#[cfg(feature = "debug")]
6666
let timer = Timer::new_dynamic(format!("load map from {}", &resource_file));
6767

@@ -232,7 +232,7 @@ impl MapLoader {
232232
#[cfg(feature = "debug")]
233233
timer.stop();
234234

235-
Ok(map)
235+
Ok(Arc::new(map))
236236
}
237237

238238
fn generate_vertex_buffer_and_atlas_texture(

korangar/src/loaders/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod action;
22
mod animation;
33
mod archive;
44

5+
mod r#async;
56
mod effect;
67
pub mod error;
78
mod font;
@@ -20,6 +21,7 @@ pub use self::font::{FontLoader, FontSize, GlyphInstruction, Scaling, TextLayout
2021
pub use self::gamefile::*;
2122
pub use self::map::{MapLoader, MAP_TILE_SIZE};
2223
pub use self::model::*;
24+
pub use self::r#async::*;
2325
pub use self::script::{ResourceMetadata, ScriptLoader};
2426
pub use self::server::{load_client_info, ClientInfo, ServiceId};
2527
pub use self::sprite::*;

0 commit comments

Comments
 (0)