implements PLN-0125 asset backlog stable handles
This commit is contained in:
parent
7598a15782
commit
30c24a4ea7
@ -18,10 +18,10 @@ use prometeu_hal::scene_layer::{ParallaxFactor, SceneLayer};
|
||||
use prometeu_hal::sound_bank::SoundBank;
|
||||
use prometeu_hal::tile::Tile;
|
||||
use prometeu_hal::tilemap::TileMap;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::io::Read;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::sync::{Arc, Condvar, Mutex, RwLock};
|
||||
use std::thread;
|
||||
use std::time::Instant;
|
||||
|
||||
@ -31,6 +31,8 @@ type StagingMap<T> = HashMap<HandleId, StagedValue<T>>;
|
||||
|
||||
type AssetTable = HashMap<AssetId, AssetEntry>;
|
||||
type HandleTable = HashMap<HandleId, LoadHandleInfo>;
|
||||
type TargetHandleTable = HashMap<SlotRef, HandleId>;
|
||||
type TargetGenerationTable = HashMap<SlotRef, u64>;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct GlyphAssetSlotIndex {
|
||||
@ -182,6 +184,8 @@ impl<T> BankPolicy<T> {
|
||||
pub struct AssetManager {
|
||||
assets: Arc<RwLock<AssetTable>>,
|
||||
handles: Arc<RwLock<HandleTable>>,
|
||||
target_handles: Arc<RwLock<TargetHandleTable>>,
|
||||
target_generations: Arc<RwLock<TargetGenerationTable>>,
|
||||
next_handle_id: Mutex<HandleId>,
|
||||
assets_data: Arc<RwLock<AssetsPayloadSource>>,
|
||||
|
||||
@ -203,14 +207,123 @@ pub struct AssetManager {
|
||||
/// Residency policy for scene banks.
|
||||
scene_policy: BankPolicy<SceneBank>,
|
||||
|
||||
load_worker: AssetLoadWorker,
|
||||
|
||||
// Commits that are ready to be applied at the next frame boundary.
|
||||
pending_commits: Mutex<Vec<HandleId>>,
|
||||
pending_commits: Mutex<Vec<(HandleId, u64)>>,
|
||||
}
|
||||
|
||||
struct LoadHandleInfo {
|
||||
_asset_id: AssetId,
|
||||
slot: SlotRef,
|
||||
status: LoadStatus,
|
||||
request_generation: u64,
|
||||
}
|
||||
|
||||
struct AssetLoadJob {
|
||||
handle_id: HandleId,
|
||||
asset_id: AssetId,
|
||||
slot: SlotRef,
|
||||
request_generation: u64,
|
||||
entry: AssetEntry,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct AssetLoadQueueState {
|
||||
pending: VecDeque<AssetLoadJob>,
|
||||
shutdown: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct AssetLoadQueue {
|
||||
state: Mutex<AssetLoadQueueState>,
|
||||
ready: Condvar,
|
||||
}
|
||||
|
||||
impl AssetLoadQueue {
|
||||
fn submit(&self, job: AssetLoadJob) {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
if state.shutdown {
|
||||
return;
|
||||
}
|
||||
state.pending.push_back(job);
|
||||
self.ready.notify_one();
|
||||
}
|
||||
|
||||
fn next_job(&self) -> Option<AssetLoadJob> {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
loop {
|
||||
if let Some(job) = state.pending.pop_front() {
|
||||
return Some(job);
|
||||
}
|
||||
if state.shutdown {
|
||||
return None;
|
||||
}
|
||||
state = self.ready.wait(state).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_pending(&self) {
|
||||
self.state.lock().unwrap().pending.clear();
|
||||
}
|
||||
|
||||
fn shutdown(&self) {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state.pending.clear();
|
||||
state.shutdown = true;
|
||||
self.ready.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
struct AssetLoadWorker {
|
||||
queue: Arc<AssetLoadQueue>,
|
||||
join_handle: Option<thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl AssetLoadWorker {
|
||||
fn new(
|
||||
handles: Arc<RwLock<HandleTable>>,
|
||||
target_generations: Arc<RwLock<TargetGenerationTable>>,
|
||||
assets_data: Arc<RwLock<AssetsPayloadSource>>,
|
||||
gfx_policy: BankPolicy<GlyphBank>,
|
||||
sound_policy: BankPolicy<SoundBank>,
|
||||
scene_policy: BankPolicy<SceneBank>,
|
||||
) -> Self {
|
||||
let queue = Arc::new(AssetLoadQueue::default());
|
||||
let worker_queue = Arc::clone(&queue);
|
||||
let join_handle = thread::spawn(move || {
|
||||
while let Some(job) = worker_queue.next_job() {
|
||||
AssetManager::process_load_job(
|
||||
job,
|
||||
&handles,
|
||||
&target_generations,
|
||||
&assets_data,
|
||||
&gfx_policy,
|
||||
&sound_policy,
|
||||
&scene_policy,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Self { queue, join_handle: Some(join_handle) }
|
||||
}
|
||||
|
||||
fn submit(&self, job: AssetLoadJob) {
|
||||
self.queue.submit(job);
|
||||
}
|
||||
|
||||
fn clear_pending(&self) {
|
||||
self.queue.clear_pending();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AssetLoadWorker {
|
||||
fn drop(&mut self) {
|
||||
self.queue.shutdown();
|
||||
if let Some(join_handle) = self.join_handle.take() {
|
||||
let _ = join_handle.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@ -333,6 +446,21 @@ impl AssetManager {
|
||||
asset_map.insert(entry.asset_id, entry);
|
||||
}
|
||||
|
||||
let handles = Arc::new(RwLock::new(HashMap::new()));
|
||||
let target_generations = Arc::new(RwLock::new(HashMap::new()));
|
||||
let assets_data = Arc::new(RwLock::new(assets_data));
|
||||
let gfx_policy = BankPolicy::new();
|
||||
let sound_policy = BankPolicy::new();
|
||||
let scene_policy = BankPolicy::new();
|
||||
let load_worker = AssetLoadWorker::new(
|
||||
Arc::clone(&handles),
|
||||
Arc::clone(&target_generations),
|
||||
Arc::clone(&assets_data),
|
||||
gfx_policy.clone(),
|
||||
sound_policy.clone(),
|
||||
scene_policy.clone(),
|
||||
);
|
||||
|
||||
Self {
|
||||
assets: Arc::new(RwLock::new(asset_map)),
|
||||
gfx_installer,
|
||||
@ -342,12 +470,15 @@ impl AssetManager {
|
||||
glyph_slot_index: GlyphAssetSlotIndex::new(),
|
||||
sound_slots: Arc::new(RwLock::new(std::array::from_fn(|_| None))),
|
||||
scene_slots: Arc::new(RwLock::new(std::array::from_fn(|_| None))),
|
||||
gfx_policy: BankPolicy::new(),
|
||||
sound_policy: BankPolicy::new(),
|
||||
scene_policy: BankPolicy::new(),
|
||||
handles: Arc::new(RwLock::new(HashMap::new())),
|
||||
gfx_policy,
|
||||
sound_policy,
|
||||
scene_policy,
|
||||
handles,
|
||||
target_handles: Arc::new(RwLock::new(HashMap::new())),
|
||||
target_generations,
|
||||
next_handle_id: Mutex::new(1),
|
||||
assets_data: Arc::new(RwLock::new(assets_data)),
|
||||
assets_data,
|
||||
load_worker,
|
||||
pending_commits: Mutex::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
@ -435,6 +566,51 @@ impl AssetManager {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_for_slot(&self, slot: SlotRef) -> HandleId {
|
||||
if let Some(handle_id) = self.target_handles.read().unwrap().get(&slot).copied() {
|
||||
return handle_id;
|
||||
}
|
||||
|
||||
let mut target_handles = self.target_handles.write().unwrap();
|
||||
if let Some(handle_id) = target_handles.get(&slot).copied() {
|
||||
return handle_id;
|
||||
}
|
||||
|
||||
let mut next_id = self.next_handle_id.lock().unwrap();
|
||||
let handle_id = *next_id;
|
||||
*next_id += 1;
|
||||
target_handles.insert(slot, handle_id);
|
||||
handle_id
|
||||
}
|
||||
|
||||
fn bump_target_generation(&self, slot: SlotRef) -> u64 {
|
||||
let mut generations = self.target_generations.write().unwrap();
|
||||
let generation = generations.entry(slot).or_insert(0);
|
||||
*generation += 1;
|
||||
*generation
|
||||
}
|
||||
|
||||
fn current_target_generation(
|
||||
target_generations: &Arc<RwLock<TargetGenerationTable>>,
|
||||
slot: SlotRef,
|
||||
) -> u64 {
|
||||
target_generations.read().unwrap().get(&slot).copied().unwrap_or(0)
|
||||
}
|
||||
|
||||
fn is_current_target_generation(
|
||||
target_generations: &Arc<RwLock<TargetGenerationTable>>,
|
||||
slot: SlotRef,
|
||||
request_generation: u64,
|
||||
) -> bool {
|
||||
Self::current_target_generation(target_generations, slot) == request_generation
|
||||
}
|
||||
|
||||
fn clear_staging_for_handle(&self, handle_id: HandleId) {
|
||||
self.gfx_policy.take_staging(handle_id);
|
||||
self.sound_policy.take_staging(handle_id);
|
||||
self.scene_policy.take_staging(handle_id);
|
||||
}
|
||||
|
||||
pub fn load(&self, asset_id: AssetId, slot_index: usize) -> Result<HandleId, AssetLoadError> {
|
||||
if slot_index >= 16 {
|
||||
return Err(AssetLoadError::SlotIndexInvalid);
|
||||
@ -449,9 +625,9 @@ impl AssetManager {
|
||||
BankType::SCENE => SlotRef::scene(slot_index),
|
||||
};
|
||||
|
||||
let mut next_id = self.next_handle_id.lock().unwrap();
|
||||
let handle_id = *next_id;
|
||||
*next_id += 1;
|
||||
let handle_id = self.handle_for_slot(slot);
|
||||
let request_generation = self.bump_target_generation(slot);
|
||||
self.clear_staging_for_handle(handle_id);
|
||||
|
||||
// Check if already resident (Dedup)
|
||||
let already_resident = match entry.bank_type {
|
||||
@ -484,7 +660,12 @@ impl AssetManager {
|
||||
if already_resident {
|
||||
self.handles.write().unwrap().insert(
|
||||
handle_id,
|
||||
LoadHandleInfo { _asset_id: asset_id, slot, status: LoadStatus::READY },
|
||||
LoadHandleInfo {
|
||||
_asset_id: asset_id,
|
||||
slot,
|
||||
status: LoadStatus::READY,
|
||||
request_generation,
|
||||
},
|
||||
);
|
||||
return Ok(handle_id);
|
||||
}
|
||||
@ -492,124 +673,173 @@ impl AssetManager {
|
||||
// Not resident, start loading
|
||||
self.handles.write().unwrap().insert(
|
||||
handle_id,
|
||||
LoadHandleInfo { _asset_id: asset_id, slot, status: LoadStatus::PENDING },
|
||||
LoadHandleInfo {
|
||||
_asset_id: asset_id,
|
||||
slot,
|
||||
status: LoadStatus::PENDING,
|
||||
request_generation,
|
||||
},
|
||||
);
|
||||
|
||||
let handles = self.handles.clone();
|
||||
let assets_data = self.assets_data.clone();
|
||||
let entry_clone = entry.clone();
|
||||
|
||||
// Capture policies for the worker thread
|
||||
let gfx_policy = self.gfx_policy.clone();
|
||||
let sound_policy = self.sound_policy.clone();
|
||||
let scene_policy = self.scene_policy.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
// Update status to LOADING
|
||||
{
|
||||
let mut handles_map = handles.write().unwrap();
|
||||
if let Some(h) = handles_map.get_mut(&handle_id) {
|
||||
if h.status == LoadStatus::PENDING {
|
||||
h.status = LoadStatus::LOADING;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
match entry_clone.bank_type {
|
||||
BankType::GLYPH => {
|
||||
let result = Self::perform_load_glyph_bank(&entry_clone, assets_data);
|
||||
if let Ok(tilebank) = result {
|
||||
let bank_arc = Arc::new(tilebank);
|
||||
let resident_arc = gfx_policy.put_resident(
|
||||
asset_id,
|
||||
bank_arc,
|
||||
entry_clone.decoded_size as usize,
|
||||
);
|
||||
gfx_policy.stage(
|
||||
handle_id,
|
||||
resident_arc,
|
||||
entry_clone.decoded_size as usize,
|
||||
);
|
||||
|
||||
let mut handles_map = handles.write().unwrap();
|
||||
if let Some(h) = handles_map.get_mut(&handle_id) {
|
||||
if h.status == LoadStatus::LOADING {
|
||||
h.status = LoadStatus::READY;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut handles_map = handles.write().unwrap();
|
||||
if let Some(h) = handles_map.get_mut(&handle_id) {
|
||||
h.status = LoadStatus::ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
BankType::SOUNDS => {
|
||||
let result = Self::perform_load_sound_bank(&entry_clone, assets_data);
|
||||
if let Ok(soundbank) = result {
|
||||
let bank_arc = Arc::new(soundbank);
|
||||
let resident_arc = sound_policy.put_resident(
|
||||
asset_id,
|
||||
bank_arc,
|
||||
entry_clone.decoded_size as usize,
|
||||
);
|
||||
sound_policy.stage(
|
||||
handle_id,
|
||||
resident_arc,
|
||||
entry_clone.decoded_size as usize,
|
||||
);
|
||||
|
||||
let mut handles_map = handles.write().unwrap();
|
||||
if let Some(h) = handles_map.get_mut(&handle_id) {
|
||||
if h.status == LoadStatus::LOADING {
|
||||
h.status = LoadStatus::READY;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut handles_map = handles.write().unwrap();
|
||||
if let Some(h) = handles_map.get_mut(&handle_id) {
|
||||
h.status = LoadStatus::ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
BankType::SCENE => {
|
||||
let result = Self::perform_load_scene_bank(&entry_clone, assets_data);
|
||||
if let Ok(scenebank) = result {
|
||||
let bank_arc = Arc::new(scenebank);
|
||||
let resident_arc = scene_policy.put_resident(
|
||||
asset_id,
|
||||
bank_arc,
|
||||
entry_clone.decoded_size as usize,
|
||||
);
|
||||
scene_policy.stage(
|
||||
handle_id,
|
||||
resident_arc,
|
||||
entry_clone.decoded_size as usize,
|
||||
);
|
||||
|
||||
let mut handles_map = handles.write().unwrap();
|
||||
if let Some(h) = handles_map.get_mut(&handle_id) {
|
||||
if h.status == LoadStatus::LOADING {
|
||||
h.status = LoadStatus::READY;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut handles_map = handles.write().unwrap();
|
||||
if let Some(h) = handles_map.get_mut(&handle_id) {
|
||||
h.status = LoadStatus::ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.load_worker.submit(AssetLoadJob {
|
||||
handle_id,
|
||||
asset_id,
|
||||
slot,
|
||||
request_generation,
|
||||
entry,
|
||||
});
|
||||
|
||||
Ok(handle_id)
|
||||
}
|
||||
|
||||
fn process_load_job(
|
||||
job: AssetLoadJob,
|
||||
handles: &Arc<RwLock<HandleTable>>,
|
||||
target_generations: &Arc<RwLock<TargetGenerationTable>>,
|
||||
assets_data: &Arc<RwLock<AssetsPayloadSource>>,
|
||||
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)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
let mut handles_map = handles.write().unwrap();
|
||||
let Some(handle) = handles_map.get_mut(&job.handle_id) else {
|
||||
return;
|
||||
};
|
||||
if handle.request_generation != job.request_generation
|
||||
|| handle.status != LoadStatus::PENDING
|
||||
{
|
||||
return;
|
||||
}
|
||||
handle.status = LoadStatus::LOADING;
|
||||
}
|
||||
|
||||
match job.entry.bank_type {
|
||||
BankType::GLYPH => {
|
||||
let result = Self::perform_load_glyph_bank(&job.entry, Arc::clone(assets_data));
|
||||
match result {
|
||||
Ok(tilebank) => {
|
||||
if !Self::is_current_target_generation(
|
||||
target_generations,
|
||||
job.slot,
|
||||
job.request_generation,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let bank_arc = Arc::new(tilebank);
|
||||
let resident_arc = gfx_policy.put_resident(
|
||||
job.asset_id,
|
||||
bank_arc,
|
||||
job.entry.decoded_size as usize,
|
||||
);
|
||||
if !Self::is_current_target_generation(
|
||||
target_generations,
|
||||
job.slot,
|
||||
job.request_generation,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
gfx_policy.stage(
|
||||
job.handle_id,
|
||||
resident_arc,
|
||||
job.entry.decoded_size as usize,
|
||||
);
|
||||
Self::complete_load_job(handles, &job, LoadStatus::READY);
|
||||
}
|
||||
Err(_) => Self::complete_load_job(handles, &job, LoadStatus::ERROR),
|
||||
}
|
||||
}
|
||||
BankType::SOUNDS => {
|
||||
let result = Self::perform_load_sound_bank(&job.entry, Arc::clone(assets_data));
|
||||
match result {
|
||||
Ok(soundbank) => {
|
||||
if !Self::is_current_target_generation(
|
||||
target_generations,
|
||||
job.slot,
|
||||
job.request_generation,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let bank_arc = Arc::new(soundbank);
|
||||
let resident_arc = sound_policy.put_resident(
|
||||
job.asset_id,
|
||||
bank_arc,
|
||||
job.entry.decoded_size as usize,
|
||||
);
|
||||
if !Self::is_current_target_generation(
|
||||
target_generations,
|
||||
job.slot,
|
||||
job.request_generation,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
sound_policy.stage(
|
||||
job.handle_id,
|
||||
resident_arc,
|
||||
job.entry.decoded_size as usize,
|
||||
);
|
||||
Self::complete_load_job(handles, &job, LoadStatus::READY);
|
||||
}
|
||||
Err(_) => Self::complete_load_job(handles, &job, LoadStatus::ERROR),
|
||||
}
|
||||
}
|
||||
BankType::SCENE => {
|
||||
let result = Self::perform_load_scene_bank(&job.entry, Arc::clone(assets_data));
|
||||
match result {
|
||||
Ok(scenebank) => {
|
||||
if !Self::is_current_target_generation(
|
||||
target_generations,
|
||||
job.slot,
|
||||
job.request_generation,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let bank_arc = Arc::new(scenebank);
|
||||
let resident_arc = scene_policy.put_resident(
|
||||
job.asset_id,
|
||||
bank_arc,
|
||||
job.entry.decoded_size as usize,
|
||||
);
|
||||
if !Self::is_current_target_generation(
|
||||
target_generations,
|
||||
job.slot,
|
||||
job.request_generation,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
scene_policy.stage(
|
||||
job.handle_id,
|
||||
resident_arc,
|
||||
job.entry.decoded_size as usize,
|
||||
);
|
||||
Self::complete_load_job(handles, &job, LoadStatus::READY);
|
||||
}
|
||||
Err(_) => Self::complete_load_job(handles, &job, LoadStatus::ERROR),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn complete_load_job(
|
||||
handles: &Arc<RwLock<HandleTable>>,
|
||||
job: &AssetLoadJob,
|
||||
status: LoadStatus,
|
||||
) {
|
||||
let mut handles_map = handles.write().unwrap();
|
||||
if let Some(handle) = handles_map.get_mut(&job.handle_id) {
|
||||
if handle.request_generation == job.request_generation
|
||||
&& handle.status == LoadStatus::LOADING
|
||||
{
|
||||
handle.status = status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn perform_load_glyph_bank(
|
||||
entry: &AssetEntry,
|
||||
assets_data: Arc<RwLock<AssetsPayloadSource>>,
|
||||
@ -960,7 +1190,7 @@ impl AssetManager {
|
||||
return AssetOpStatus::UnknownHandle;
|
||||
};
|
||||
if h.status == LoadStatus::READY {
|
||||
self.pending_commits.lock().unwrap().push(handle);
|
||||
self.pending_commits.lock().unwrap().push((handle, h.request_generation));
|
||||
AssetOpStatus::Ok
|
||||
} else {
|
||||
AssetOpStatus::InvalidState
|
||||
@ -969,23 +1199,30 @@ impl AssetManager {
|
||||
|
||||
pub fn cancel(&self, handle: HandleId) -> AssetOpStatus {
|
||||
let mut final_status = AssetOpStatus::UnknownHandle;
|
||||
let mut handles_map = self.handles.write().unwrap();
|
||||
if let Some(h) = handles_map.get_mut(&handle) {
|
||||
final_status = match h.status {
|
||||
LoadStatus::PENDING | LoadStatus::LOADING | LoadStatus::READY => AssetOpStatus::Ok,
|
||||
LoadStatus::CANCELED => AssetOpStatus::Ok,
|
||||
_ => AssetOpStatus::InvalidState,
|
||||
};
|
||||
match h.status {
|
||||
LoadStatus::PENDING | LoadStatus::LOADING | LoadStatus::READY => {
|
||||
h.status = LoadStatus::CANCELED;
|
||||
let mut canceled_slot = None;
|
||||
{
|
||||
let mut handles_map = self.handles.write().unwrap();
|
||||
if let Some(h) = handles_map.get_mut(&handle) {
|
||||
final_status = match h.status {
|
||||
LoadStatus::PENDING | LoadStatus::LOADING | LoadStatus::READY => {
|
||||
AssetOpStatus::Ok
|
||||
}
|
||||
LoadStatus::CANCELED => AssetOpStatus::Ok,
|
||||
_ => AssetOpStatus::InvalidState,
|
||||
};
|
||||
match h.status {
|
||||
LoadStatus::PENDING | LoadStatus::LOADING | LoadStatus::READY => {
|
||||
h.status = LoadStatus::CANCELED;
|
||||
canceled_slot = Some(h.slot);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
self.gfx_policy.take_staging(handle);
|
||||
self.sound_policy.take_staging(handle);
|
||||
self.scene_policy.take_staging(handle);
|
||||
if let Some(slot) = canceled_slot {
|
||||
self.bump_target_generation(slot);
|
||||
}
|
||||
self.clear_staging_for_handle(handle);
|
||||
final_status
|
||||
}
|
||||
|
||||
@ -993,9 +1230,9 @@ impl AssetManager {
|
||||
let mut pending = self.pending_commits.lock().unwrap();
|
||||
let mut handles = self.handles.write().unwrap();
|
||||
|
||||
for handle_id in pending.drain(..) {
|
||||
for (handle_id, request_generation) in pending.drain(..) {
|
||||
if let Some(h) = handles.get_mut(&handle_id) {
|
||||
if h.status == LoadStatus::READY {
|
||||
if h.status == LoadStatus::READY && h.request_generation == request_generation {
|
||||
match h.slot.asset_type {
|
||||
BankType::GLYPH => {
|
||||
if let Some((bank, _)) = self.gfx_policy.take_staging(handle_id) {
|
||||
@ -1131,6 +1368,11 @@ impl AssetManager {
|
||||
self.sound_policy.clear();
|
||||
self.scene_policy.clear();
|
||||
self.handles.write().unwrap().clear();
|
||||
self.target_handles.write().unwrap().clear();
|
||||
for generation in self.target_generations.write().unwrap().values_mut() {
|
||||
*generation += 1;
|
||||
}
|
||||
self.load_worker.clear_pending();
|
||||
self.pending_commits.lock().unwrap().clear();
|
||||
self.gfx_slots.write().unwrap().fill(None);
|
||||
self.glyph_slot_index.clear();
|
||||
@ -1526,6 +1768,80 @@ mod tests {
|
||||
assert!(Arc::ptr_eq(bank1, bank2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_asset_load_uses_stable_handle_for_target_slot() {
|
||||
let banks = Arc::new(MemoryBanks::new());
|
||||
let gfx_installer = Arc::clone(&banks) as Arc<dyn GlyphBankPoolInstaller>;
|
||||
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
|
||||
let scene_installer = Arc::clone(&banks) as Arc<dyn SceneBankPoolInstaller>;
|
||||
|
||||
let data = test_glyph_asset_data();
|
||||
let asset_entry = test_glyph_asset_entry("test_glyphs", 16, 16);
|
||||
|
||||
let am = AssetManager::new(
|
||||
vec![asset_entry],
|
||||
AssetsPayloadSource::from_bytes(data),
|
||||
gfx_installer,
|
||||
sound_installer,
|
||||
scene_installer,
|
||||
);
|
||||
|
||||
let first_handle = am.load(0, 0).unwrap();
|
||||
let second_handle = am.load(0, 0).unwrap();
|
||||
|
||||
assert_eq!(first_handle, second_handle);
|
||||
|
||||
let start = Instant::now();
|
||||
while am.status(second_handle) != LoadStatus::READY && start.elapsed().as_secs() < 5 {
|
||||
thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
assert_eq!(am.status(second_handle), LoadStatus::READY);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_asset_load_supersedes_previous_request_for_target_slot() {
|
||||
let banks = Arc::new(MemoryBanks::new());
|
||||
let gfx_installer = Arc::clone(&banks) as Arc<dyn GlyphBankPoolInstaller>;
|
||||
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
|
||||
let scene_installer = Arc::clone(&banks) as Arc<dyn SceneBankPoolInstaller>;
|
||||
|
||||
let first_payload = test_glyph_asset_data();
|
||||
let second_payload = test_glyph_asset_data();
|
||||
let second_offset = first_payload.len();
|
||||
let mut data = first_payload;
|
||||
data.extend_from_slice(&second_payload);
|
||||
|
||||
let first_asset = test_glyph_asset_entry("first_glyphs", 16, 16);
|
||||
let mut second_asset = test_glyph_asset_entry("second_glyphs", 16, 16);
|
||||
second_asset.asset_id = 1;
|
||||
second_asset.offset = second_offset as u64;
|
||||
|
||||
let am = AssetManager::new(
|
||||
vec![first_asset, second_asset],
|
||||
AssetsPayloadSource::from_bytes(data),
|
||||
gfx_installer,
|
||||
sound_installer,
|
||||
scene_installer,
|
||||
);
|
||||
|
||||
let first_handle = am.load(0, 0).unwrap();
|
||||
let second_handle = am.load(1, 0).unwrap();
|
||||
|
||||
assert_eq!(first_handle, second_handle);
|
||||
|
||||
let start = Instant::now();
|
||||
while am.status(second_handle) != LoadStatus::READY && start.elapsed().as_secs() < 5 {
|
||||
thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
assert_eq!(am.status(second_handle), LoadStatus::READY);
|
||||
|
||||
assert_eq!(am.commit(second_handle), AssetOpStatus::Ok);
|
||||
am.apply_commits();
|
||||
|
||||
let slot = am.slot_info(SlotRef::gfx(0));
|
||||
assert_eq!(slot.asset_id, Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sound_asset_loading() {
|
||||
let banks = Arc::new(MemoryBanks::new());
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
{"type":"discussion","id":"DSC-0006","status":"open","ticket":"vm-owned-random-service","title":"Agenda - VM-Owned Random Service","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0005","file":"AGD-0005-vm-owned-random-service.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0007","status":"open","ticket":"app-home-filesystem-surface-and-semantics","title":"Agenda - App Home Filesystem Surface and Semantics","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0006","file":"AGD-0006-app-home-filesystem-surface-and-semantics.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0008","status":"done","ticket":"perf-runtime-telemetry-hot-path","title":"Agenda - [PERF] Runtime Telemetry Hot Path","created_at":"2026-03-27","updated_at":"2026-06-04","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0026","file":"discussion/lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
||||
{"type":"discussion","id":"DSC-0009","status":"in_progress","ticket":"perf-async-background-work-lanes-for-assets-and-fs","title":"Agenda - [PERF] Async Background Work Lanes for Assets and FS","created_at":"2026-03-27","updated_at":"2026-06-28","tags":["perf","asset","fs","async","scheduler","runtime"],"agendas":[{"id":"AGD-0008","file":"AGD-0008-perf-async-background-work-lanes-for-assets-and-fs.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-06-28"}],"decisions":[{"id":"DEC-0034","file":"DEC-0034-async-work-lane-and-asset-backlog-contract.md","status":"accepted","created_at":"2026-06-28","updated_at":"2026-06-28","ref_agenda":"AGD-0008"}],"plans":[{"id":"PLN-0123","file":"PLN-0123-async-work-lane-specification-propagation.md","status":"done","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0124","file":"PLN-0124-async-work-lane-runtime-infrastructure.md","status":"done","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0125","file":"PLN-0125-asset-backlog-and-stable-slot-handles.md","status":"open","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0126","file":"PLN-0126-asset-backlog-public-api-and-status-surface.md","status":"open","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0127","file":"PLN-0127-async-lane-memcard-and-fs-integration-boundaries.md","status":"open","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0128","file":"PLN-0128-async-asset-pipeline-contract-tests-and-telemetry.md","status":"open","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]}],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0009","status":"in_progress","ticket":"perf-async-background-work-lanes-for-assets-and-fs","title":"Agenda - [PERF] Async Background Work Lanes for Assets and FS","created_at":"2026-03-27","updated_at":"2026-06-28","tags":["perf","asset","fs","async","scheduler","runtime"],"agendas":[{"id":"AGD-0008","file":"AGD-0008-perf-async-background-work-lanes-for-assets-and-fs.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-06-28"}],"decisions":[{"id":"DEC-0034","file":"DEC-0034-async-work-lane-and-asset-backlog-contract.md","status":"accepted","created_at":"2026-06-28","updated_at":"2026-06-28","ref_agenda":"AGD-0008"}],"plans":[{"id":"PLN-0123","file":"PLN-0123-async-work-lane-specification-propagation.md","status":"done","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0124","file":"PLN-0124-async-work-lane-runtime-infrastructure.md","status":"done","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0125","file":"PLN-0125-asset-backlog-and-stable-slot-handles.md","status":"done","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0126","file":"PLN-0126-asset-backlog-public-api-and-status-surface.md","status":"open","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0127","file":"PLN-0127-async-lane-memcard-and-fs-integration-boundaries.md","status":"open","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0128","file":"PLN-0128-async-asset-pipeline-contract-tests-and-telemetry.md","status":"open","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]}],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0010","status":"done","ticket":"perf-host-desktop-frame-pacing-and-presentation","title":"Agenda - [PERF] Host Desktop Frame Pacing and Presentation","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0036","file":"discussion/lessons/DSC-0010-perf-host-desktop-frame-pacing-and-presentation/LSN-0036-frame-publication-and-host-invalidation-must-be-separate.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20"}]}
|
||||
{"type":"discussion","id":"DSC-0011","status":"abandoned","ticket":"perf-gfx-render-pipeline-and-dirty-regions","title":"Agenda - [PERF] GFX Render Pipeline and Dirty Regions","created_at":"2026-03-27","updated_at":"2026-06-28","tags":[],"agendas":[{"id":"AGD-0010","file":"AGD-0010-perf-gfx-render-pipeline-and-dirty-regions.md","status":"abandoned","created_at":"2026-03-27","updated_at":"2026-06-28","_override_reason":"User explicitly chose to remove this agenda because it no longer applies to the current render scenario; future optimizations can be discussed when needed."}],"decisions":[],"plans":[],"lessons":[],"_override_reason":"User explicitly chose to abandon this discussion because it no longer applies to the current render scenario; future optimizations can be discussed when needed."}
|
||||
{"type":"discussion","id":"DSC-0012","status":"done","ticket":"perf-runtime-introspection-syscalls","title":"Agenda - [PERF] Runtime Introspection Syscalls","created_at":"2026-03-27","updated_at":"2026-04-19","tags":["perf","runtime","syscall","telemetry","debug","asset"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0034","file":"discussion/lessons/DSC-0012-perf-runtime-introspection-syscalls/LSN-0034-host-owned-debug-boundaries.md","status":"done","created_at":"2026-04-19","updated_at":"2026-04-19"}]}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
id: PLN-0125
|
||||
ticket: perf-async-background-work-lanes-for-assets-and-fs
|
||||
title: Asset Backlog and Stable Slot Handles
|
||||
status: open
|
||||
status: done
|
||||
created: 2026-06-28
|
||||
completed:
|
||||
ref_decisions: [DEC-0034]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user