use bevy::asset::LoadContext;
use bevy::platform::collections::HashMap;
use std::{
io::{Cursor, Error as IoError, ErrorKind, Read},
path::{Path, PathBuf},
sync::Arc,
};
pub(crate) struct BytesResourceReader<'a> {
bytes: Arc<[u8]>,
cache: &'a HashMap<PathBuf, Vec<u8>>,
}
impl<'a> BytesResourceReader<'a> {
pub(crate) fn new(bytes: &[u8], cache: &'a HashMap<PathBuf, Vec<u8>>) -> Self {
Self {
bytes: Arc::from(bytes),
cache,
}
}
}
impl tiled::ResourceReader for BytesResourceReader<'_> {
type Resource = Box<dyn Read>;
type Error = IoError;
fn read_from(&mut self, path: &Path) -> std::result::Result<Self::Resource, Self::Error> {
if let Some(extension) = path.extension() {
if extension == "tsx" || extension == "tx" {
if let Some(data) = self.cache.get(path) {
return Ok(Box::new(Cursor::new(data.clone())));
}
return Err(IoError::new(
ErrorKind::NotFound,
format!(
"External tileset/template '{}' not found in cache. \
For WASM builds, all external tilesets must be pre-loaded.",
path.display()
),
));
}
}
Ok(Box::new(Cursor::new(self.bytes.clone())))
}
}
pub(crate) fn extract_external_paths(xml_content: &[u8]) -> Vec<PathBuf> {
let mut paths = Vec::new();
let content = String::from_utf8_lossy(xml_content);
for line in content.lines() {
if let Some(start) = line.find("source=\"") {
let rest = &line[start + 8..];
if let Some(end) = rest.find('"') {
let path = &rest[..end];
if path.ends_with(".tsx") || path.ends_with(".tx") {
paths.push(PathBuf::from(path));
}
}
}
}
paths
}
fn resolve_relative_path(asset_path: &Path, relative_path: &Path) -> PathBuf {
if let Some(parent) = asset_path.parent() {
let joined = parent.join(relative_path);
let mut components = Vec::new();
for component in joined.components() {
match component {
std::path::Component::ParentDir => {
components.pop();
}
std::path::Component::CurDir => {}
c => components.push(c),
}
}
components.iter().collect()
} else {
relative_path.to_path_buf()
}
}
pub(crate) async fn preload_external_resources(
xml_content: &[u8],
load_context: &mut LoadContext<'_>,
) -> HashMap<PathBuf, Vec<u8>> {
let mut cache = HashMap::default();
let paths = extract_external_paths(xml_content);
let asset_path = load_context.path().path().to_path_buf();
let asset_parent = asset_path.parent().map(|p| p.to_path_buf());
for relative_path in paths {
let resolved_path = resolve_relative_path(&asset_path, &relative_path);
let cache_key = if let Some(ref parent) = asset_parent {
parent.join(&relative_path)
} else {
relative_path.clone()
};
match load_context.read_asset_bytes(resolved_path.clone()).await {
Ok(bytes) => {
let nested = extract_external_paths(&bytes);
for nested_relative in nested {
let nested_resolved = resolve_relative_path(&resolved_path, &nested_relative);
let nested_cache_key = if let Some(tileset_parent) = resolved_path.parent() {
tileset_parent.join(&nested_relative)
} else {
nested_relative.clone()
};
if !cache.contains_key(&nested_cache_key) {
if let Ok(nested_bytes) =
load_context.read_asset_bytes(nested_resolved).await
{
cache.insert(nested_cache_key, nested_bytes);
}
}
}
cache.insert(cache_key, bytes);
}
Err(e) => {
log::warn!(
"Failed to pre-load external resource '{}': {}",
relative_path.display(),
e
);
}
}
}
cache
}