背景:我正在使用Rust编写光线追踪器,但是在以文件系统不可知的方式加载场景方面遇到了困难。我使用serde,以便我不必发明自己的文件格式(尚未)。资产(图像纹理和网格数据)与场景文件分别存储。场景文件仅存储这些文件的路径。由于光线追踪器本身应该是一个平台无关的库(我希望能够将其编译为WebAssembly用于浏览器),光线追踪器本身对文件系统一无所知。我打算在反序列化场景时加载资产,但现在这给我带来了真正的问题:
我需要向serde传递一个文件系统接口的实现代码的实现,以便我可以在Deserialize :: deserialize()
中使用,但似乎没有任何简单的方法。我想出了一种用泛型的方法,但我对此并不满意。
这是我目前正在使用的方法,作为MCVE剥离(使用的软件包是serde
和serde_json
):
库代码(lib.rs):
use std::marker::PhantomData;
use serde::{Serialize, Serializer, Deserialize, Deserializer};
pub struct Image {}
pub struct Texture<L: AssetLoader> {
path: String,
image: Image,
phantom: PhantomData<L>,
}
impl<L: AssetLoader> Serialize for Texture<L> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.path.serialize(serializer)
}
}
impl<'de, L: AssetLoader> Deserialize<'de> for Texture<L> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Texture<L>, D::Error> {
let path = String::deserialize(deserializer)?;
// This is where I'd much rather have an instance of AssetLoader
let image = L::load_image(&path);
Ok(Texture {
path,
image,
phantom: PhantomData,
})
}
}
pub trait AssetLoader {
fn load_image(path: &str) -> Image;
// load_mesh(), load_hdr(), ...
}
#[derive(Serialize, Deserialize)]
pub struct Scene<L: AssetLoader> {
textures: Vec<Texture<L>>,
// meshes, materials, lights, ...
}
特定平台的代码(main.rs):
use serde::{Serialize, Deserialize};
use assetloader_mcve::{AssetLoader, Image, Scene};
#[derive(Serialize, Deserialize)]
struct AssetLoaderImpl {}
impl AssetLoader for AssetLoaderImpl {
fn load_image(path: &str) -> Image {
println!("Loading image: {}", path);
// Load the file from disk, the web, ...
Image {}
}
}
fn main() {
let scene_str = r#"
{
"textures": [
"texture1.jpg",
"texture2.jpg"
]
}
"#;
let scene: Scene<AssetLoaderImpl> = serde_json::from_str(scene_str).unwrap();
// ...
}
我对这种方法不喜欢的地方:
AssetLoaderImpl
必须实现Serialize
和Deserialize
,即使它从未被序列化或反序列化过- 我还使用了typetag,导致编译错误,因为“尚不支持泛型impl的反序列化”
- 缓存资产将非常困难,因为我没有
AssetLoaderImpl
的实例,可以在成员变量中缓存它们 - 当
Texture
(或其他资源)嵌套更深时,传递AssetLoader
类型参数变得笨拙 - 感觉不太对,主要是因为
PhantomData
和滥用范型
这让我觉得我可能不是以正确的方式解决问题,但我正在苦苦挣扎想出更好的解决方案。我考虑过在库中使用一个可变的全局变量,保存AssetLoader
的实例(也许使用lazy_static
),但这也不太合适。理想情况下,我希望能够在反序列化时传递一个AssetLoader
的实例(可能是Box<dyn AssetLoader>
),以便我可以在impl Deserialize for Texture
中访问它。我还没有找到任何方法可以做到这一点,如果有人能指点我正确的方向,我将不胜感激。
Deserialize
没有什么不同。不同之处在于,对于DeserializeSeed
,没有像Deserialize
那样的派生宏,因此目前只有这种繁琐的方式可供选择。您可以随时打开一个问题请求一个派生宏,或者找到一个现有的问题并在那里陈述您的用例。 - Anders Evensen