use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use bevy::asset::{AssetApp, AssetLoader, LoadContext, io::Reader};
use bevy::prelude::*;
use noesis_runtime::xaml_provider::XamlProvider;
#[derive(Asset, TypePath, Debug, Clone)]
pub struct XamlAsset {
pub bytes: Arc<Vec<u8>>,
}
#[derive(Default, TypePath)]
pub struct XamlAssetLoader;
impl AssetLoader for XamlAssetLoader {
type Asset = XamlAsset;
type Settings = ();
type Error = std::io::Error;
async fn load(
&self,
reader: &mut dyn Reader,
_settings: &(),
_load_context: &mut LoadContext<'_>,
) -> Result<XamlAsset, std::io::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
Ok(XamlAsset {
bytes: Arc::new(bytes),
})
}
fn extensions(&self) -> &[&str] {
&["xaml"]
}
}
#[derive(Resource, Default, Clone)]
pub struct XamlRegistry {
pub(crate) entries: HashMap<String, Arc<Vec<u8>>>,
}
impl XamlRegistry {
#[must_use]
pub fn get(&self, uri: &str) -> Option<&Arc<Vec<u8>>> {
self.entries.get(uri)
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn keys(&self) -> impl Iterator<Item = &str> {
self.entries.keys().map(String::as_str)
}
pub fn insert(&mut self, uri: impl Into<String>, bytes: Arc<Vec<u8>>) {
self.entries.insert(uri.into(), bytes);
}
}
#[allow(clippy::needless_pass_by_value)] pub fn update_xaml_registry(
mut events: MessageReader<AssetEvent<XamlAsset>>,
assets: Res<Assets<XamlAsset>>,
asset_server: Res<AssetServer>,
mut registry: ResMut<XamlRegistry>,
mut keys: Local<HashMap<AssetId<XamlAsset>, String>>,
) {
for event in events.read() {
match *event {
AssetEvent::Added { id } | AssetEvent::Modified { id } => {
let Some(path) = asset_server.get_path(id) else {
continue;
};
let Some(asset) = assets.get(id) else {
continue;
};
info!(
"update_xaml_registry: inserting {} ({} bytes)",
path,
asset.bytes.len(),
);
let key = path.to_string();
keys.insert(id, key.clone());
registry.entries.insert(key, Arc::clone(&asset.bytes));
}
AssetEvent::Removed { id } | AssetEvent::Unused { id } => {
let Some(key) = keys.remove(&id) else {
continue;
};
registry.entries.remove(&key);
}
AssetEvent::LoadedWithDependencies { .. } => {}
}
}
}
#[derive(Clone, Default)]
pub struct SharedXamlMap(pub(crate) Arc<Mutex<HashMap<String, Arc<Vec<u8>>>>>);
impl SharedXamlMap {
pub fn sync_from(&self, registry: &XamlRegistry) {
let mut guard = self.0.lock().expect("SharedXamlMap mutex poisoned");
guard.clone_from(®istry.entries);
}
}
pub struct BevyXamlProvider {
shared: SharedXamlMap,
current: Option<Arc<Vec<u8>>>,
}
impl BevyXamlProvider {
#[must_use]
pub fn new_shared() -> (Self, SharedXamlMap) {
let shared = SharedXamlMap::default();
(Self::from_shared(shared.clone()), shared)
}
#[must_use]
pub fn from_shared(map: SharedXamlMap) -> Self {
Self {
shared: map,
current: None,
}
}
}
impl XamlProvider for BevyXamlProvider {
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn load_xaml(&mut self, uri: &str) -> Option<&[u8]> {
let arc = {
let guard = self.shared.0.lock().expect("SharedXamlMap mutex poisoned");
guard.get(uri).cloned()?
};
self.current = Some(arc);
self.current.as_deref().map(Vec::as_slice)
}
}
pub struct XamlAssetPlugin;
impl Plugin for XamlAssetPlugin {
fn build(&self, app: &mut App) {
app.init_asset::<XamlAsset>()
.init_asset_loader::<XamlAssetLoader>()
.init_resource::<XamlRegistry>()
.add_systems(Update, update_xaml_registry);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn provider_returns_bytes_then_forgets_on_next_load() {
let (mut provider, shared) = BevyXamlProvider::new_shared();
let bytes_a = Arc::new(b"<Grid Background=\"Red\"/>".to_vec());
let bytes_b = Arc::new(b"<Grid Background=\"Blue\"/>".to_vec());
let mut registry = XamlRegistry::default();
registry
.entries
.insert("a.xaml".into(), Arc::clone(&bytes_a));
registry
.entries
.insert("b.xaml".into(), Arc::clone(&bytes_b));
shared.sync_from(®istry);
let slice_a = provider.load_xaml("a.xaml").expect("a.xaml missing");
assert_eq!(slice_a, bytes_a.as_slice());
let slice_b = provider.load_xaml("b.xaml").expect("b.xaml missing");
assert_eq!(slice_b, bytes_b.as_slice());
assert!(provider.load_xaml("missing.xaml").is_none());
}
#[test]
fn provider_sees_registry_changes_after_sync() {
let (mut provider, shared) = BevyXamlProvider::new_shared();
let mut registry = XamlRegistry::default();
registry
.entries
.insert("a.xaml".into(), Arc::new(b"v1".to_vec()));
shared.sync_from(®istry);
assert_eq!(provider.load_xaml("a.xaml"), Some(b"v1".as_slice()));
registry
.entries
.insert("a.xaml".into(), Arc::new(b"v2".to_vec()));
shared.sync_from(®istry);
assert_eq!(provider.load_xaml("a.xaml"), Some(b"v2".as_slice()));
registry.entries.remove("a.xaml");
shared.sync_from(®istry);
assert_eq!(provider.load_xaml("a.xaml"), None);
}
}