Skip to content

Instantly share code, notes, and snippets.

Created September 3, 2016 22:26
Show Gist options
  • Save anonymous/176941be70ac1747c557ba68c47b56eb to your computer and use it in GitHub Desktop.
Save anonymous/176941be70ac1747c557ba68c47b56eb to your computer and use it in GitHub Desktop.
Shared via Rust Playground
#![feature(raw)]
use std::collections::HashMap;
use std::ops::Deref;
// Optional traits, could also be removed
trait Asset: std::any::Any {}
trait AssetSource<T: Asset>: std::any::Any {}
// Set of traits needed to be implemented for an asset loader
// Each asset loader is a combination of `Source` and `Asset` type
trait AssetSubLoader<T: Asset, U: AssetSource<T>>: std::any::Any + AssetLoader<T> {
fn from_raw(&self, data: &[u8]) -> U;
fn from_data(&self, data: &U) -> T;
}
// These can be usually derived directly if from the AssetSubLoader trait
// A macro to automatically implement these might be useful
trait AssetLoader<T: Asset>: std::any::Any + AssetUntypedLoader {
fn load_asset(&self, data: &[u8]) -> T;
}
trait AssetUntypedLoader {
fn load_asset_untyped(&self, data: &[u8]) -> Box<Asset>;
}
// HACK: need to store multiple vtables
struct AssetLoaderStorage {
loader_vtable: *mut (),
sub_loader_vtable: *mut (),
untyped_loader_vtable: *mut (),
data: *mut (),
}
impl AssetLoaderStorage {
fn as_loader<T: Asset>(&self) -> &AssetLoader<T> {
unsafe { ::std::mem::transmute(
::std::raw::TraitObject {
data: self.data,
vtable: self.loader_vtable,
}
)}
}
fn as_sub_loader<T: Asset, U: AssetSource<T>>(&self) -> &AssetSubLoader<T, U> {
unsafe { ::std::mem::transmute(
::std::raw::TraitObject {
data: self.data,
vtable: self.sub_loader_vtable,
}
)}
}
fn as_untyped_loader(&self) -> &AssetUntypedLoader {
unsafe { ::std::mem::transmute(
::std::raw::TraitObject {
data: self.data,
vtable: self.untyped_loader_vtable,
}
)}
}
}
// Central asset manager, storing all assets and loaders
// Possible pipeline:
// Asset request: AssetStores -> (raw data) -> [AssetSubLoader(raw) -> (data) -> AssetSubLoader(asset)] -> Asset
//
// AssetStore: A storage (like a simple package manager), which returns a [u8] bit stream for a specific asset request
// Possible storages would be a file archive(zip), directory or http-server
//
// It should be possible to run the different stages parallel and asynchronous.
// If the corresponding loader and some asset stores are registered to the asset manager, an assert can be loaded directly
// by an identifier(e.g. file extension)
struct AssetManager {
loader_ids: HashMap<String, std::any::TypeId>,
sub_loaders: HashMap<std::any::TypeId, Box<std::any::Any>>,
loaders_vtable: HashMap<std::any::TypeId, AssetLoaderStorage>,
}
impl AssetManager {
fn new() -> AssetManager {
AssetManager {
loader_ids: HashMap::new(),
sub_loaders: HashMap::new(),
loaders_vtable: HashMap::new(),
}
}
// Register an asset loader for a specific <Asset, Data> combination
fn register_loader<T: Asset, U: AssetSource<T>, K: AssetSubLoader<T, U>>(&mut self, file_id: &str, loader: K) {
let sub_loader_id = std::any::TypeId::of::<AssetSubLoader<T, U>>();
self.loader_ids.insert(file_id.to_string(), sub_loader_id);
self.sub_loaders.insert(sub_loader_id, Box::new(loader));
let loader_vtable = {
let loader_vtable = {
let s: &K = unsafe { ::std::mem::uninitialized() };
let t: &AssetLoader<T> = s;
let r: ::std::raw::TraitObject = unsafe { ::std::mem::transmute(t) };
r.vtable
};
let sub_loader_vtable = {
let s: &K = unsafe { ::std::mem::uninitialized() };
let t: &AssetSubLoader<T, U> = s;
let r: ::std::raw::TraitObject = unsafe { ::std::mem::transmute(t) };
r.vtable
};
let untyped_loader_vtable = {
let s: &K = unsafe { ::std::mem::uninitialized() };
let t: &AssetUntypedLoader = s;
let r: ::std::raw::TraitObject = unsafe { ::std::mem::transmute(t) };
r.vtable
};
AssetLoaderStorage {
loader_vtable: loader_vtable,
sub_loader_vtable: sub_loader_vtable,
untyped_loader_vtable: untyped_loader_vtable,
data: self.sub_loaders.get(&sub_loader_id).unwrap().deref() as *const _ as *mut _,
}
};
self.loaders_vtable.insert(sub_loader_id, loader_vtable);
}
fn load_asset_from_data<T: Asset, U: AssetSource<T>>(&mut self, data: &U) -> T {
self.loaders_vtable.get(&std::any::TypeId::of::<AssetSubLoader<T, U>>())
.expect("Unregistered subloader")
.as_sub_loader::<T, U>()
.from_data(data)
}
fn load_asset<T: Asset>(&self, file_id: &str, raw: &[u8]) -> T {
self.loaders_vtable
.get(self.loader_ids.get(file_id)
.expect("Unregistered subloader"))
.expect("Unregistered subloader")
.as_loader::<T>()
.load_asset(raw)
}
// Preload assets, which can be accessed later
// E.g. loaded from a larger archive in real apps
fn preload_asset(&mut self, file_id: &str, raw: &[u8]) {
self.loaders_vtable
.get(self.loader_ids.get(file_id)
.expect("Unregistered subloader"))
.expect("Unregistered subloader")
.as_untyped_loader()
.load_asset_untyped(raw);
}
}
// Example:
struct MeshLoader;
impl Asset for u64 {}
impl AssetSource<u64> for String {}
impl AssetSubLoader<u64, String> for MeshLoader {
fn from_raw(&self, data: &[u8]) -> String {
match data[0] {
0 => "0".into(),
_ => "Invalid".into(),
}
}
fn from_data(&self, _data: &String) -> u64 {
1254
}
}
impl AssetLoader<u64> for MeshLoader {
fn load_asset(&self, data: &[u8]) -> u64 {
let data = self.from_raw(data);
self.from_data(&data);
10000 // Note: Non-standard way, mainly for debug
}
}
impl AssetUntypedLoader for MeshLoader {
fn load_asset_untyped(&self, data: &[u8]) -> Box<Asset> {
let data = self.from_raw(data);
Box::new(self.from_data(&data))
}
}
fn main() {
let mut asset_manager = AssetManager::new();
asset_manager.register_loader::<u64, String, MeshLoader>("obj", MeshLoader);
println!("{:?}", asset_manager.load_asset_from_data::<u64, String>(&"hello".into()));
println!("{:?}", asset_manager.load_asset::<u64>("obj", &[0; 5]));
println!("{:?}", asset_manager.preload_asset("obj", &[0; 5]));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment