[PERF] Async Background Work Lanes for Assets and FS

This commit is contained in:
bQUARKz 2026-07-01 10:01:51 +01:00
parent 0adec90f23
commit b84946a3a0
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
2 changed files with 74 additions and 65 deletions

View File

@ -359,6 +359,16 @@ struct AssetLoadJob {
entry: AssetEntry,
}
struct AssetLoadJobResources<'a> {
handles: &'a Arc<RwLock<HandleTable>>,
target_generations: &'a Arc<RwLock<TargetGenerationTable>>,
assets_data: &'a Arc<RwLock<AssetsPayloadSource>>,
pipeline_telemetry: &'a Arc<Mutex<AssetPipelineTelemetryState>>,
gfx_policy: &'a BankPolicy<GlyphBank>,
sound_policy: &'a BankPolicy<SoundBank>,
scene_policy: &'a BankPolicy<SceneBank>,
}
#[derive(Default)]
struct AssetLoadQueueState {
pending: VecDeque<AssetLoadJob>,
@ -475,17 +485,17 @@ impl AssetLoadWorker {
let queue = Arc::new(AssetLoadQueue::default());
let worker_queue = Arc::clone(&queue);
let join_handle = thread::spawn(move || {
let resources = AssetLoadJobResources {
handles: &handles,
target_generations: &target_generations,
assets_data: &assets_data,
pipeline_telemetry: &pipeline_telemetry,
gfx_policy: &gfx_policy,
sound_policy: &sound_policy,
scene_policy: &scene_policy,
};
while let Some(job) = worker_queue.next_job() {
AssetManager::process_load_job(
job.clone(),
&handles,
&target_generations,
&assets_data,
&pipeline_telemetry,
&gfx_policy,
&sound_policy,
&scene_policy,
);
AssetManager::process_load_job(job.clone(), &resources);
worker_queue.complete_active(job.handle_id, job.request_generation);
}
});
@ -942,24 +952,18 @@ impl AssetManager {
Ok(handle_id)
}
fn process_load_job(
job: AssetLoadJob,
handles: &Arc<RwLock<HandleTable>>,
target_generations: &Arc<RwLock<TargetGenerationTable>>,
assets_data: &Arc<RwLock<AssetsPayloadSource>>,
pipeline_telemetry: &Arc<Mutex<AssetPipelineTelemetryState>>,
gfx_policy: &BankPolicy<GlyphBank>,
sound_policy: &BankPolicy<SoundBank>,
scene_policy: &BankPolicy<SceneBank>,
) {
if !Self::is_current_target_generation(target_generations, job.slot, job.request_generation)
{
pipeline_telemetry.lock().unwrap().record_stale_discard();
fn process_load_job(job: AssetLoadJob, resources: &AssetLoadJobResources<'_>) {
if !Self::is_current_target_generation(
resources.target_generations,
job.slot,
job.request_generation,
) {
resources.pipeline_telemetry.lock().unwrap().record_stale_discard();
return;
}
{
let mut handles_map = handles.write().unwrap();
let mut handles_map = resources.handles.write().unwrap();
let Some(handle) = handles_map.get_mut(&job.handle_id) else {
return;
};
@ -972,52 +976,53 @@ impl AssetManager {
handle.progress = ASSET_PROGRESS_LOADING;
}
let started_at = Instant::now();
pipeline_telemetry.lock().unwrap().record_started(0);
resources.pipeline_telemetry.lock().unwrap().record_started(0);
match job.entry.bank_type {
BankType::GLYPH => {
let result = Self::perform_load_glyph_bank(&job.entry, Arc::clone(assets_data));
let result =
Self::perform_load_glyph_bank(&job.entry, Arc::clone(resources.assets_data));
match result {
Ok(tilebank) => {
if !Self::is_current_target_generation(
target_generations,
resources.target_generations,
job.slot,
job.request_generation,
) {
pipeline_telemetry.lock().unwrap().record_stale_discard();
resources.pipeline_telemetry.lock().unwrap().record_stale_discard();
return;
}
let bank_arc = Arc::new(tilebank);
let resident_arc = gfx_policy.put_resident(
let resident_arc = resources.gfx_policy.put_resident(
job.asset_id,
bank_arc,
job.entry.decoded_size as usize,
);
if !Self::is_current_target_generation(
target_generations,
resources.target_generations,
job.slot,
job.request_generation,
) {
pipeline_telemetry.lock().unwrap().record_stale_discard();
resources.pipeline_telemetry.lock().unwrap().record_stale_discard();
return;
}
gfx_policy.stage(
resources.gfx_policy.stage(
job.handle_id,
resident_arc,
job.entry.decoded_size as usize,
);
Self::complete_load_job(handles, &job, LoadStatus::READY);
Self::complete_load_job(resources.handles, &job, LoadStatus::READY);
Self::record_load_job_finished(
pipeline_telemetry,
resources.pipeline_telemetry,
&job,
started_at,
LoadStatus::READY,
);
}
Err(_) => {
Self::complete_load_job(handles, &job, LoadStatus::ERROR);
Self::complete_load_job(resources.handles, &job, LoadStatus::ERROR);
Self::record_load_job_finished(
pipeline_telemetry,
resources.pipeline_telemetry,
&job,
started_at,
LoadStatus::ERROR,
@ -1026,48 +1031,49 @@ impl AssetManager {
}
}
BankType::SOUNDS => {
let result = Self::perform_load_sound_bank(&job.entry, Arc::clone(assets_data));
let result =
Self::perform_load_sound_bank(&job.entry, Arc::clone(resources.assets_data));
match result {
Ok(soundbank) => {
if !Self::is_current_target_generation(
target_generations,
resources.target_generations,
job.slot,
job.request_generation,
) {
pipeline_telemetry.lock().unwrap().record_stale_discard();
resources.pipeline_telemetry.lock().unwrap().record_stale_discard();
return;
}
let bank_arc = Arc::new(soundbank);
let resident_arc = sound_policy.put_resident(
let resident_arc = resources.sound_policy.put_resident(
job.asset_id,
bank_arc,
job.entry.decoded_size as usize,
);
if !Self::is_current_target_generation(
target_generations,
resources.target_generations,
job.slot,
job.request_generation,
) {
pipeline_telemetry.lock().unwrap().record_stale_discard();
resources.pipeline_telemetry.lock().unwrap().record_stale_discard();
return;
}
sound_policy.stage(
resources.sound_policy.stage(
job.handle_id,
resident_arc,
job.entry.decoded_size as usize,
);
Self::complete_load_job(handles, &job, LoadStatus::READY);
Self::complete_load_job(resources.handles, &job, LoadStatus::READY);
Self::record_load_job_finished(
pipeline_telemetry,
resources.pipeline_telemetry,
&job,
started_at,
LoadStatus::READY,
);
}
Err(_) => {
Self::complete_load_job(handles, &job, LoadStatus::ERROR);
Self::complete_load_job(resources.handles, &job, LoadStatus::ERROR);
Self::record_load_job_finished(
pipeline_telemetry,
resources.pipeline_telemetry,
&job,
started_at,
LoadStatus::ERROR,
@ -1076,48 +1082,49 @@ impl AssetManager {
}
}
BankType::SCENE => {
let result = Self::perform_load_scene_bank(&job.entry, Arc::clone(assets_data));
let result =
Self::perform_load_scene_bank(&job.entry, Arc::clone(resources.assets_data));
match result {
Ok(scenebank) => {
if !Self::is_current_target_generation(
target_generations,
resources.target_generations,
job.slot,
job.request_generation,
) {
pipeline_telemetry.lock().unwrap().record_stale_discard();
resources.pipeline_telemetry.lock().unwrap().record_stale_discard();
return;
}
let bank_arc = Arc::new(scenebank);
let resident_arc = scene_policy.put_resident(
let resident_arc = resources.scene_policy.put_resident(
job.asset_id,
bank_arc,
job.entry.decoded_size as usize,
);
if !Self::is_current_target_generation(
target_generations,
resources.target_generations,
job.slot,
job.request_generation,
) {
pipeline_telemetry.lock().unwrap().record_stale_discard();
resources.pipeline_telemetry.lock().unwrap().record_stale_discard();
return;
}
scene_policy.stage(
resources.scene_policy.stage(
job.handle_id,
resident_arc,
job.entry.decoded_size as usize,
);
Self::complete_load_job(handles, &job, LoadStatus::READY);
Self::complete_load_job(resources.handles, &job, LoadStatus::READY);
Self::record_load_job_finished(
pipeline_telemetry,
resources.pipeline_telemetry,
&job,
started_at,
LoadStatus::READY,
);
}
Err(_) => {
Self::complete_load_job(handles, &job, LoadStatus::ERROR);
Self::complete_load_job(resources.handles, &job, LoadStatus::ERROR);
Self::record_load_job_finished(
pipeline_telemetry,
resources.pipeline_telemetry,
&job,
started_at,
LoadStatus::ERROR,

View File

@ -150,11 +150,11 @@ impl AsyncWorkCancelToken {
}
pub fn cancel(&self) {
self.canceled.store(true, Ordering::Release);
self.canceled.store(true, Ordering::Relaxed);
}
pub fn is_canceled(&self) -> bool {
self.canceled.load(Ordering::Acquire)
self.canceled.load(Ordering::Relaxed)
}
}
@ -277,11 +277,13 @@ impl AsyncWorkLane {
pub fn request_cancel(&self, job_id: AsyncWorkJobId) -> bool {
let state = self.state.lock().unwrap();
if state.active.is_some_and(|active| active.id == job_id) {
if let Some(token) = &state.active_cancel_token {
token.cancel();
return true;
}
if let Some(token) = state
.active
.filter(|active| active.id == job_id)
.and(state.active_cancel_token.as_ref())
{
token.cancel();
return true;
}
for pending in &state.pending {