use crate::audio_source::{Ym2149AudioSource, Ym2149Metadata};
use crate::playback::{TrackSource, Ym2149Playback};
use bevy::prelude::*;
use bevy::tasks::{IoTaskPool, Task, block_on, poll_once};
use std::collections::hash_map::Entry;
pub(in crate::plugin) struct PendingFileRead {
pub path: String,
pub task: Task<Result<Vec<u8>, String>>,
}
impl PendingFileRead {
pub fn new(path: String) -> Self {
let task_path = path.clone();
let task = IoTaskPool::get().spawn(async move {
std::fs::read(&task_path)
.map_err(|err| format!("Failed to read YM file '{task_path}': {err}"))
});
Self { path, task }
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub(in crate::plugin) enum PendingSlot {
Primary,
Crossfade,
}
pub(super) struct LoadedBytes {
pub data: Vec<u8>,
pub metadata: Option<Ym2149Metadata>,
}
pub(super) enum SourceLoadResult {
Pending,
Ready(LoadedBytes),
Failed(String),
}
pub(super) fn current_track_source(playback: &Ym2149Playback) -> Option<TrackSource> {
playback
.source_bytes()
.map(TrackSource::Bytes)
.or_else(|| {
playback
.source_path()
.map(|path| TrackSource::File(path.to_owned()))
})
.or_else(|| playback.source_asset().cloned().map(TrackSource::Asset))
}
pub(super) fn load_track_source(
entity: Entity,
slot: PendingSlot,
source: &TrackSource,
pending_reads: &mut std::collections::HashMap<(Entity, PendingSlot), PendingFileRead>,
assets: &Assets<Ym2149AudioSource>,
) -> SourceLoadResult {
match source {
TrackSource::Bytes(bytes) => SourceLoadResult::Ready(LoadedBytes {
data: bytes.as_ref().clone(),
metadata: None,
}),
TrackSource::File(path) => match pending_reads.entry((entity, slot)) {
Entry::Occupied(mut entry) => {
if entry.get().path != *path {
entry.insert(PendingFileRead::new(path.clone()));
return SourceLoadResult::Pending;
}
match block_on(poll_once(&mut entry.get_mut().task)) {
Some(Ok(bytes)) => {
pending_reads.remove(&(entity, slot));
SourceLoadResult::Ready(LoadedBytes {
data: bytes,
metadata: None,
})
}
Some(Err(err)) => {
pending_reads.remove(&(entity, slot));
SourceLoadResult::Failed(err)
}
None => SourceLoadResult::Pending,
}
}
Entry::Vacant(vacant) => {
vacant.insert(PendingFileRead::new(path.clone()));
SourceLoadResult::Pending
}
},
TrackSource::Asset(handle) => match assets.get(handle) {
Some(asset) => SourceLoadResult::Ready(LoadedBytes {
data: asset.data.clone(),
metadata: Some(asset.metadata.clone()),
}),
None => SourceLoadResult::Pending,
},
}
}