feat(assets): implement virtual AssetManager with non-destructive mod overlays
This commit is contained in:
parent
f6e7bbd7a5
commit
1969f7aae9
3 changed files with 87 additions and 0 deletions
71
crates/molotov-core/src/assets/manager.rs
Normal file
71
crates/molotov-core/src/assets/manager.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
use super::asset::AssetFile;
|
||||
use super::dir::AssetDir;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct AssetManager {
|
||||
base_assets: HashMap<String, Vec<u8>>,
|
||||
mod_overrides: HashMap<String, Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Default for AssetManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AssetManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
base_assets: HashMap::new(),
|
||||
mod_overrides: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_asset(&mut self, path: &str, data: Vec<u8>) {
|
||||
self.base_assets.insert(path.to_string(), data);
|
||||
}
|
||||
|
||||
pub fn add_mod_override(&mut self, path: &str, data: Vec<u8>) {
|
||||
self.mod_overrides.insert(path.to_string(), data);
|
||||
}
|
||||
|
||||
pub fn open(&self, filename: &str) -> Option<AssetFile> {
|
||||
// 1. Check user mod overrides first (non-destructive mod engine)
|
||||
if let Some(data) = self.mod_overrides.get(filename) {
|
||||
tracing::info!("AssetManager: redirected '{}' to active user mod", filename);
|
||||
return Some(AssetFile::new(data.clone()));
|
||||
}
|
||||
|
||||
// 2. Fallback to base asset
|
||||
if let Some(data) = self.base_assets.get(filename) {
|
||||
return Some(AssetFile::new(data.clone()));
|
||||
}
|
||||
|
||||
tracing::warn!("AssetManager: asset '{}' not found", filename);
|
||||
None
|
||||
}
|
||||
|
||||
pub fn open_dir(&self, dir_name: &str) -> AssetDir {
|
||||
let prefix = if dir_name.is_empty() || dir_name == "." {
|
||||
"".to_string()
|
||||
} else if !dir_name.ends_with('/') {
|
||||
format!("{}/", dir_name)
|
||||
} else {
|
||||
dir_name.to_string()
|
||||
};
|
||||
|
||||
let mut files = Vec::new();
|
||||
for key in self.base_assets.keys().chain(self.mod_overrides.keys()) {
|
||||
if key.starts_with(&prefix) {
|
||||
let rest = &key[prefix.len()..];
|
||||
if !rest.contains('/') {
|
||||
files.push(rest.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
files.sort();
|
||||
files.dedup();
|
||||
AssetDir::new(files)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pub mod asset;
|
||||
pub mod dir;
|
||||
pub mod manager;
|
||||
pub mod types;
|
||||
|
|
|
|||
15
crates/molotov-core/tests/asset_manager_virtual_test.rs
Normal file
15
crates/molotov-core/tests/asset_manager_virtual_test.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
use molotov_core::assets::manager::AssetManager;
|
||||
|
||||
#[test]
|
||||
fn test_asset_manager_open_with_mod_override() {
|
||||
let mut mgr = AssetManager::new();
|
||||
mgr.add_asset("content/sounds/ouch.ogg", b"BASE_SOUND".to_vec());
|
||||
mgr.add_mod_override("content/sounds/ouch.ogg", b"CUSTOM_MOD_SOUND".to_vec());
|
||||
|
||||
let mut asset = mgr.open("content/sounds/ouch.ogg").expect("Should open asset");
|
||||
let mut content = vec![0u8; asset.length() as usize];
|
||||
asset.read(&mut content);
|
||||
|
||||
assert_eq!(&content, b"CUSTOM_MOD_SOUND", "Must redirect to user mod bytes");
|
||||
}
|
||||
Reference in a new issue