use std::hash::Hasher;
use serde::{ser::SerializeStruct, Serialize};
use crate::{
asset::ResourceId,
resource::{resource_handler::MultiResourceReader, ReadTargets, ReadTargetsMut},
to_vec, DataStorage, LoadResults, Model, Resource, StreamDescription,
};
#[derive(Debug)]
pub struct Reference<T: Resource> {
pub id: String,
pub hash: u64,
pub size: usize,
pub resource: T,
reader: Option<MultiResourceReader>,
streams: Option<Vec<StreamDescription>>,
}
impl<'a, T: Resource + 'a> Serialize for Reference<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut state = serializer.serialize_struct("TypedDocument", 3)?;
state.serialize_field("id", &self.id)?;
state.serialize_field("hash", &self.hash)?;
state.serialize_field("class", &self.resource.get_class())?;
state.end()
}
}
impl<'a, T: Resource + 'a> Reference<T> {
pub fn from_model(model: ReferenceModel<T::Model>, resource: T, reader: MultiResourceReader) -> Self {
Reference {
id: model.id,
hash: model.hash,
size: model.size,
resource,
reader: Some(reader),
streams: model.streams,
}
}
pub fn id(&self) -> &str {
&self.id
}
pub fn hash(&self) -> u64 {
self.hash
}
pub fn get_hash(&self) -> u64 {
self.hash
}
pub fn resource(&self) -> &T {
&self.resource
}
pub fn resource_mut(&mut self) -> &mut T {
&mut self.resource
}
pub fn into_resource(self) -> T {
self.resource
}
pub fn consume_reader(&mut self) -> MultiResourceReader {
self.reader.take().unwrap()
}
pub fn map(self, f: impl FnOnce(T) -> T) -> Self {
Reference {
resource: f(self.resource),
..self
}
}
pub fn load<'s>(&'s mut self, read_target: ReadTargetsMut<'a>) -> Result<ReadTargets<'a>, LoadResults> {
let reader = self.reader.take().ok_or(LoadResults::NoReadTarget)?;
if matches!(read_target, ReadTargetsMut::BackingStorage) {
return match reader.into_backing_storage() {
Ok(backing) => Ok(ReadTargets::Backing(backing)),
Err(mut reader) => {
let read_target = ReadTargetsMut::create_buffer(self);
reader
.read_into(self.streams.as_deref(), read_target)
.map_err(|_| LoadResults::LoadFailed)
}
};
}
let mut reader = reader;
reader
.read_into(self.streams.as_deref(), read_target)
.map_err(|_| LoadResults::LoadFailed)
}
}
impl<T: Resource> std::hash::Hash for Reference<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
self.hash.hash(state);
self.size.hash(state);
self.resource.get_class().hash(state);
}
}
#[derive(Debug, serde::Deserialize, Serialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
pub struct ReferenceModel<T: Model> {
id: String,
hash: u64,
size: usize,
class: String,
pub(crate) resource: DataStorage, #[serde(skip)]
#[rkyv(with = rkyv::with::Skip)]
phantom: std::marker::PhantomData<T>,
streams: Option<Vec<StreamDescription>>,
}
impl<T: Model> Clone for ReferenceModel<T> {
fn clone(&self) -> Self {
Self {
id: self.id.clone(),
hash: self.hash,
size: self.size,
class: self.class.clone(),
resource: self.resource.clone(),
phantom: std::marker::PhantomData,
streams: self.streams.clone(),
}
}
}
impl<T: Model> ReferenceModel<T> {
pub fn new(id: &str, hash: u64, size: usize, resource: &T, streams: Option<Vec<StreamDescription>>) -> Self {
Self::new_serialized(id, hash, size, to_vec(resource).unwrap(), streams)
}
pub fn new_serialized(
id: &str,
hash: u64,
size: usize,
resource: DataStorage,
streams: Option<Vec<StreamDescription>>,
) -> Self {
ReferenceModel {
id: id.to_string(),
hash,
size,
class: T::get_class().to_string(),
resource,
phantom: std::marker::PhantomData,
streams,
}
}
pub fn id(&self) -> ResourceId<'_> {
ResourceId::new(&self.id)
}
pub fn class(&self) -> &str {
&self.class
}
}
#[cfg(test)]
mod tests {
use std::{
fs,
io::Write,
path::PathBuf,
time::{SystemTime, UNIX_EPOCH},
};
use super::{Reference, ReferenceModel};
use crate::{
resource::{
reader::{redb::FileResourceReader, ResourceReaderBacking},
ReadTargets, ReadTargetsMut,
},
Model, Resource,
};
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
struct DefaultLoadModel;
impl Model for DefaultLoadModel {
fn get_class() -> &'static str {
"DefaultLoad"
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
struct NonCloneModel;
impl Model for NonCloneModel {
fn get_class() -> &'static str {
"NonClone"
}
}
#[derive(Debug)]
struct DefaultLoadResource;
impl Resource for DefaultLoadResource {
type Model = DefaultLoadModel;
}
fn temporary_file_path() -> PathBuf {
std::env::temp_dir().join(format!(
"byte-engine-reference-default-load-{}-{}.bin",
std::process::id(),
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos()
))
}
#[test]
fn reference_model_clone_does_not_require_model_clone() {
let original = ReferenceModel::<NonCloneModel>::new("non-clone", 42, 7, &NonCloneModel, None);
let cloned = original.clone();
assert_eq!(cloned.id, original.id);
assert_eq!(cloned.hash, original.hash);
assert_eq!(cloned.size, original.size);
assert_eq!(cloned.class, original.class);
assert_eq!(cloned.resource, original.resource);
assert!(cloned.streams.is_none());
assert!(original.streams.is_none());
}
#[test]
fn default_reference_load_uses_reader_backing_storage() {
let path = temporary_file_path();
let expected = b"default-load-bytes";
{
let mut file = fs::File::create(&path).unwrap();
file.write_all(expected).unwrap();
file.sync_all().unwrap();
}
let model = ReferenceModel::<DefaultLoadModel>::new("default-load", 0, expected.len(), &DefaultLoadModel, None);
let reader = Box::new(FileResourceReader::new(fs::File::open(&path).unwrap()));
let mut reference = Reference::from_model(model, DefaultLoadResource, reader);
assert_eq!(reference.resource.get_class(), DefaultLoadModel::get_class());
let target = ReadTargetsMut::from(&reference);
let result = reference.load(target).unwrap();
assert_eq!(result.buffer().unwrap(), expected);
assert!(matches!(result, ReadTargets::Backing(ResourceReaderBacking::MappedFile(_))));
fs::remove_file(path).unwrap();
}
}