#![warn(missing_docs)]
pub mod blackboard;
pub mod error;
pub mod field;
mod impls;
pub mod pod;
mod reader;
mod writer;
pub use fyrox_core_derive::Visit;
pub mod prelude {
pub use super::{Visit, VisitResult, Visitor};
pub use crate::visitor::error::VisitError;
}
use crate::pool::PoolError;
use crate::{
array_as_u8_slice_mut,
io::{self},
pool::{Handle, Pool},
visitor::{
reader::{ascii::AsciiReader, binary::BinaryReader, Reader},
writer::{ascii::AsciiWriter, binary::BinaryWriter, Writer},
},
};
use bitflags::bitflags;
use blackboard::Blackboard;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use error::VisitError;
use field::{Field, FieldKind};
use fxhash::FxHashMap;
use std::{
any::Any,
fmt::{Debug, Formatter},
fs::File,
hash::Hash,
io::{BufWriter, Cursor, Read, Write},
ops::{Deref, DerefMut},
path::Path,
rc::Rc,
sync::Arc,
};
#[repr(u32)]
pub enum VisitorVersion {
FirstStableRelease,
Last,
}
pub const CURRENT_VERSION: u32 = (VisitorVersion::Last as u32).saturating_sub(1);
pub struct BinaryBlob<'a, T>
where
T: Copy,
{
pub vec: &'a mut Vec<T>,
}
impl<T> Visit for BinaryBlob<'_, T>
where
T: Copy + bytemuck::Pod,
{
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
if visitor.reading {
if let Some(field) = visitor.find_field(name) {
match &field.kind {
FieldKind::BinaryBlob(data) => {
let len = data.len() / size_of::<T>();
let mut vec = Vec::<T>::with_capacity(len);
unsafe {
std::ptr::copy_nonoverlapping(
data.as_ptr(),
array_as_u8_slice_mut(&mut vec).as_mut_ptr(),
data.len(),
);
vec.set_len(len);
}
*self.vec = vec;
Ok(())
}
_ => Err(VisitError::FieldTypeDoesNotMatch {
expected: stringify!(FieldKind::BinaryBlob),
actual: format!("{:?}", field.kind),
}),
}
} else {
Err(VisitError::field_does_not_exist(name, visitor))
}
} else if visitor.find_field(name).is_some() {
Err(VisitError::FieldAlreadyExists(name.to_owned()))
} else {
let node = visitor.current_node();
let len_bytes = self.vec.len() * std::mem::size_of::<T>();
let mut bytes = Vec::<u8>::with_capacity(len_bytes);
bytes.extend_from_slice(unsafe {
std::slice::from_raw_parts(self.vec.as_ptr() as *const u8, len_bytes)
});
node.fields
.push(Field::new(name, FieldKind::BinaryBlob(bytes)));
Ok(())
}
}
}
pub type VisitResult = Result<(), VisitError>;
trait VisitableElementaryField {
fn write(&self, file: &mut dyn Write) -> VisitResult;
fn read(&mut self, file: &mut dyn Read) -> VisitResult;
}
macro_rules! impl_visitable_elementary_field {
($ty:ty, $write:ident, $read:ident $(, $endian:ident)*) => {
impl VisitableElementaryField for $ty {
fn write(&self, file: &mut dyn Write) -> VisitResult {
file.$write::<$($endian)*>(*self)?;
Ok(())
}
fn read(&mut self, file: &mut dyn Read) -> VisitResult {
*self = file.$read::<$($endian)*>()?;
Ok(())
}
}
};
}
impl_visitable_elementary_field!(f64, write_f64, read_f64, LittleEndian);
impl_visitable_elementary_field!(f32, write_f32, read_f32, LittleEndian);
impl_visitable_elementary_field!(u8, write_u8, read_u8);
impl_visitable_elementary_field!(i8, write_i8, read_i8);
impl_visitable_elementary_field!(u16, write_u16, read_u16, LittleEndian);
impl_visitable_elementary_field!(i16, write_i16, read_i16, LittleEndian);
impl_visitable_elementary_field!(u32, write_u32, read_u32, LittleEndian);
impl_visitable_elementary_field!(i32, write_i32, read_i32, LittleEndian);
impl_visitable_elementary_field!(u64, write_u64, read_u64, LittleEndian);
impl_visitable_elementary_field!(i64, write_i64, read_i64, LittleEndian);
#[derive(Debug)]
pub struct VisitorNode {
name: String,
fields: Vec<Field>,
parent: Handle<VisitorNode>,
children: Vec<Handle<VisitorNode>>,
}
impl VisitorNode {
fn new(name: &str, parent: Handle<VisitorNode>) -> Self {
Self {
name: name.to_owned(),
fields: Vec::new(),
parent,
children: Vec::new(),
}
}
}
impl Default for VisitorNode {
fn default() -> Self {
Self {
name: String::new(),
fields: Vec::new(),
parent: Handle::NONE,
children: Vec::new(),
}
}
}
#[must_use = "the guard must be used"]
pub struct RegionGuard<'a>(&'a mut Visitor);
impl Deref for RegionGuard<'_> {
type Target = Visitor;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl DerefMut for RegionGuard<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.0
}
}
impl Drop for RegionGuard<'_> {
fn drop(&mut self) {
self.0.leave_region().unwrap();
}
}
bitflags! {
#[derive(Debug)]
pub struct VisitorFlags: u32 {
const NONE = 0;
const SERIALIZE_EVERYTHING = 1 << 1;
}
}
pub struct Visitor {
nodes: Pool<VisitorNode>,
unique_id_counter: u64,
type_name_map: FxHashMap<u64, &'static str>,
rc_map: FxHashMap<u64, Rc<dyn Any>>,
arc_map: FxHashMap<u64, Arc<dyn Any + Send + Sync>>,
reading: bool,
current_node: Handle<VisitorNode>,
root: Handle<VisitorNode>,
version: u32,
pub blackboard: Blackboard,
pub flags: VisitorFlags,
}
impl Debug for Visitor {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut output = f.debug_struct("Visitor");
output.field("flags", &self.flags);
for (i, node) in self.nodes.iter().enumerate() {
output.field(&format!("node{i}"), node);
}
output.finish()
}
}
pub trait Visit {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult;
}
impl Default for Visitor {
fn default() -> Self {
Self::new()
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[must_use]
pub enum Format {
Unknown,
Binary,
Ascii,
}
impl Visitor {
pub const MAGIC_BINARY_CURRENT: &'static str = "FBAF";
pub const MAGIC_ASCII_CURRENT: &'static str = "FTAX";
#[must_use]
pub fn is_supported(src: &mut dyn Read) -> bool {
Self::detect_format(src) != Format::Unknown
}
pub fn detect_format(src: &mut dyn Read) -> Format {
let mut magic: [u8; 4] = Default::default();
if src.read_exact(&mut magic).is_ok() {
if magic.eq(Visitor::MAGIC_BINARY_CURRENT.as_bytes()) {
return Format::Binary;
} else if magic.eq(Visitor::MAGIC_ASCII_CURRENT.as_bytes()) {
return Format::Ascii;
}
}
Format::Unknown
}
pub fn detect_format_from_slice(data: &[u8]) -> Format {
let mut src = Cursor::new(data);
Self::detect_format(&mut src)
}
pub fn new() -> Self {
let mut nodes = Pool::new();
let root = nodes.spawn(VisitorNode::new("__ROOT__", Handle::NONE));
Self {
nodes,
unique_id_counter: 1,
type_name_map: FxHashMap::default(),
rc_map: FxHashMap::default(),
arc_map: FxHashMap::default(),
reading: false,
current_node: root,
root,
version: CURRENT_VERSION,
blackboard: Blackboard::new(),
flags: VisitorFlags::NONE,
}
}
fn gen_unique_id(&mut self) -> u64 {
let id = self.unique_id_counter;
self.unique_id_counter += 1;
id
}
fn rc_id<T>(&mut self, rc: &Rc<T>) -> (u64, bool)
where
T: Any,
{
if let Some(id) = self.rc_map.iter().find_map(|(id, ptr)| {
if Rc::as_ptr(ptr) as *const T == Rc::as_ptr(rc) {
Some(*id)
} else {
None
}
}) {
(id, false)
} else {
let id = self.gen_unique_id();
self.type_name_map.insert(id, std::any::type_name::<T>());
self.rc_map.insert(id, rc.clone());
(id, true)
}
}
fn arc_id<T>(&mut self, arc: &Arc<T>) -> (u64, bool)
where
T: Any + Send + Sync,
{
if let Some(id) = self.arc_map.iter().find_map(|(id, ptr)| {
if Arc::as_ptr(ptr) as *const T == Arc::as_ptr(arc) {
Some(*id)
} else {
None
}
}) {
(id, false)
} else {
let id = self.gen_unique_id();
self.type_name_map.insert(id, std::any::type_name::<T>());
self.arc_map.insert(id, arc.clone());
(id, true)
}
}
pub fn find_field(&mut self, name: &str) -> Option<&mut Field> {
self.nodes
.borrow_mut(self.current_node)
.fields
.iter_mut()
.find(|field| field.name == name)
}
pub fn find_node(&self, name: &str) -> Option<&VisitorNode> {
self.nodes.iter().find(|n| n.name == name)
}
pub fn is_reading(&self) -> bool {
self.reading
}
fn current_node(&mut self) -> &mut VisitorNode {
self.nodes.borrow_mut(self.current_node)
}
pub fn version(&self) -> u32 {
self.version
}
pub fn has_region(&self, name: &str) -> bool {
let node = self.nodes.borrow(self.current_node);
node.children
.iter()
.any(|child| self.nodes.borrow(*child).name == name)
}
pub fn enter_region(&mut self, name: &str) -> Result<RegionGuard, VisitError> {
let node = self.nodes.borrow(self.current_node);
if self.reading {
let mut region = Handle::NONE;
for child_handle in node.children.iter() {
let child = self.nodes.borrow(*child_handle);
if child.name == name {
region = *child_handle;
break;
}
}
if region.is_some() {
self.current_node = region;
Ok(RegionGuard(self))
} else {
Err(VisitError::RegionDoesNotExist(
self.breadcrumbs() + " > " + name,
))
}
} else {
for child_handle in node.children.iter() {
let child = self.nodes.borrow(*child_handle);
if child.name == name {
return Err(VisitError::RegionAlreadyExists(name.to_owned()));
}
}
let node_handle = self.nodes.spawn(VisitorNode::new(name, self.current_node));
self.nodes
.borrow_mut(self.current_node)
.children
.push(node_handle);
self.current_node = node_handle;
Ok(RegionGuard(self))
}
}
pub fn breadcrumbs(&self) -> String {
self.build_breadcrumb(" > ")
}
fn build_breadcrumb(&self, separator: &str) -> String {
let mut rev = String::new();
let mut handle = self.current_node;
loop {
let node = self.nodes.try_borrow(handle);
let Ok(node) = node else {
break;
};
if !rev.is_empty() {
rev.extend(separator.chars().rev());
}
rev.extend(node.name.chars().rev());
handle = node.parent;
}
rev.chars().rev().collect()
}
pub fn current_region(&self) -> Result<&str, PoolError> {
self.nodes
.try_borrow(self.current_node)
.map(|n| n.name.as_str())
}
fn leave_region(&mut self) -> VisitResult {
self.current_node = self.nodes.borrow(self.current_node).parent;
if self.current_node.is_none() {
Err(VisitError::NoActiveNode)
} else {
Ok(())
}
}
pub fn debug(&self) -> String {
let mut w = Cursor::new(Vec::<u8>::new());
let result = self.debug_to(&mut w);
match result {
Ok(()) => String::from_utf8_lossy(w.get_ref()).into_owned(),
Err(err) => err.to_string(),
}
}
pub fn debug_to<W: Write>(&self, w: &mut W) -> VisitResult {
let writer = AsciiWriter::default();
writer.write_node(self, &self.nodes[self.current_node], 0, w)?;
writeln!(w)?;
w.flush()?;
Ok(())
}
pub fn save_ascii_to_string(&self) -> String {
let mut cursor = Cursor::<Vec<u8>>::default();
self.save_ascii_to_memory(&mut cursor).unwrap();
String::from_utf8(cursor.into_inner()).unwrap()
}
pub fn save_ascii_to_file(&self, path: impl AsRef<Path>) -> VisitResult {
let mut writer = BufWriter::new(File::create(path)?);
let text = self.save_ascii_to_string();
writer.write_all(text.as_bytes())?;
Ok(())
}
pub fn save_ascii_to_memory(&self, mut dest: impl Write) -> VisitResult {
let writer = AsciiWriter::default();
writer.write(self, &mut dest)
}
pub fn load_ascii_from_memory(data: &[u8]) -> Result<Self, VisitError> {
let mut src = Cursor::new(data);
let mut reader = AsciiReader::new(&mut src);
reader.read()
}
pub async fn load_ascii_from_file(path: impl AsRef<Path>) -> Result<Self, VisitError> {
Self::load_ascii_from_memory(&io::load_file(path).await?)
}
pub fn save_binary_to_memory(&self, mut dest: impl Write) -> VisitResult {
let writer = BinaryWriter::default();
writer.write(self, &mut dest)
}
pub fn save_binary_to_vec(&self) -> Result<Vec<u8>, VisitError> {
let mut writer = Cursor::new(Vec::new());
self.save_binary_to_memory(&mut writer)?;
Ok(writer.into_inner())
}
pub fn save_binary_to_file(&self, path: impl AsRef<Path>) -> VisitResult {
let writer = BufWriter::new(File::create(path)?);
self.save_binary_to_memory(writer)
}
pub async fn load_binary_from_file(path: impl AsRef<Path>) -> Result<Self, VisitError> {
Self::load_binary_from_memory(&io::load_file(path).await?)
}
pub fn load_binary_from_memory(data: &[u8]) -> Result<Self, VisitError> {
let mut src = Cursor::new(data);
let mut reader = BinaryReader::new(&mut src);
reader.read()
}
pub async fn load_from_file(path: impl AsRef<Path>) -> Result<Self, VisitError> {
Self::load_from_memory(&io::load_file(path).await?)
}
pub fn load_from_memory(data: &[u8]) -> Result<Self, VisitError> {
match Self::detect_format_from_slice(data) {
Format::Unknown => Err(VisitError::NotSupportedFormat),
Format::Binary => Self::load_binary_from_memory(data),
Format::Ascii => Self::load_ascii_from_memory(data),
}
}
}
#[cfg(test)]
mod test {
use crate::visitor::{BinaryBlob, Visit, VisitResult, Visitor};
use nalgebra::{
Matrix2, Matrix3, Matrix4, UnitComplex, UnitQuaternion, Vector2, Vector3, Vector4,
};
use std::sync::Arc;
use std::{fs::File, io::Write, path::Path, rc, rc::Rc, sync};
use uuid::{uuid, Uuid};
#[derive(Visit, Default, PartialEq, Debug)]
pub struct Model {
data: u64,
}
#[derive(Default, PartialEq, Debug)]
pub struct Texture {
data: Vec<u8>,
}
impl Visit for Texture {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
let mut region = visitor.enter_region(name)?;
let mut proxy = BinaryBlob {
vec: &mut self.data,
};
proxy.visit("Data", &mut region)?;
Ok(())
}
}
#[allow(dead_code)]
#[derive(Visit, PartialEq, Debug, Default)]
pub enum ResourceKind {
#[default]
Unknown,
Model(Model),
Texture(Texture),
}
#[derive(Visit, PartialEq, Debug)]
struct Resource {
kind: ResourceKind,
data: u16,
}
impl Resource {
fn new(kind: ResourceKind) -> Self {
Self { kind, data: 0 }
}
}
impl Default for Resource {
fn default() -> Self {
Self {
kind: ResourceKind::Unknown,
data: 0,
}
}
}
#[derive(Default, Visit, Debug)]
struct Weaks {
weak_resource_arc: Option<sync::Weak<Resource>>,
weak_resource_rc: Option<rc::Weak<Resource>>,
}
impl PartialEq for Weaks {
fn eq(&self, other: &Self) -> bool {
self.weak_resource_arc.as_ref().and_then(|r| r.upgrade())
== other.weak_resource_arc.as_ref().and_then(|r| r.upgrade())
&& self.weak_resource_rc.as_ref().and_then(|r| r.upgrade())
== other.weak_resource_rc.as_ref().and_then(|r| r.upgrade())
}
}
#[derive(Default, Visit, Debug, PartialEq)]
struct Foo {
boolean: bool,
num_u8: u8,
num_i8: i8,
num_u16: u16,
num_i16: i16,
num_u32: u32,
num_i32: i32,
num_u64: u64,
num_i64: i64,
num_f32: f32,
num_f64: f64,
quat: UnitQuaternion<f32>,
mat4: Matrix4<f32>,
array: Vec<u8>,
mat3: Matrix3<f32>,
uuid: Uuid,
complex: UnitComplex<f32>,
mat2: Matrix2<f32>,
vec2_u8: Vector2<u8>,
vec2_i8: Vector2<i8>,
vec2_u16: Vector2<u16>,
vec2_i16: Vector2<i16>,
vec2_u32: Vector2<u32>,
vec2_i32: Vector2<i32>,
vec2_u64: Vector2<u64>,
vec2_i64: Vector2<i64>,
vec3_u8: Vector3<u8>,
vec3_i8: Vector3<i8>,
vec3_u16: Vector3<u16>,
vec3_i16: Vector3<i16>,
vec3_u32: Vector3<u32>,
vec3_i32: Vector3<i32>,
vec3_u64: Vector3<u64>,
vec3_i64: Vector3<i64>,
vec4_u8: Vector4<u8>,
vec4_i8: Vector4<i8>,
vec4_u16: Vector4<u16>,
vec4_i16: Vector4<i16>,
vec4_u32: Vector4<u32>,
vec4_i32: Vector4<i32>,
vec4_u64: Vector4<u64>,
vec4_i64: Vector4<i64>,
string: String,
vec2_f32: Vector2<f32>,
vec2_f64: Vector2<f64>,
vec3_f32: Vector3<f32>,
vec3_f64: Vector3<f64>,
vec4_f32: Vector4<f32>,
vec4_f64: Vector4<f64>,
shared_resource: Option<Rc<Resource>>,
shared_resource_arc: Option<Arc<Resource>>,
weaks: Weaks,
}
impl Foo {
fn new(resource: Rc<Resource>, arc_resource: Arc<Resource>) -> Self {
Self {
boolean: true,
num_u8: 123,
num_i8: -123,
num_u16: 123,
num_i16: -123,
num_u32: 123,
num_i32: -123,
num_u64: 123,
num_i64: -123,
num_f32: 123.321,
num_f64: 123.321,
quat: UnitQuaternion::from_euler_angles(1.0, 2.0, 3.0),
mat4: Matrix4::new_scaling(3.0),
array: vec![1, 2, 3, 4],
mat3: Matrix3::new_scaling(3.0),
uuid: uuid!("51a582c0-30d7-4dbc-b5a0-da8ea186edce"),
complex: UnitComplex::new(0.0),
mat2: Matrix2::new_scaling(2.0),
vec2_u8: Vector2::new(1, 2),
vec2_i8: Vector2::new(-1, -2),
vec2_u16: Vector2::new(1, 2),
vec2_i16: Vector2::new(-1, -2),
vec2_u32: Vector2::new(1, 2),
vec2_i32: Vector2::new(-1, -2),
vec2_u64: Vector2::new(1, 2),
vec2_i64: Vector2::new(-1, -2),
vec3_u8: Vector3::new(1, 2, 3),
vec3_i8: Vector3::new(-1, -2, -3),
vec3_u16: Vector3::new(1, 2, 3),
vec3_i16: Vector3::new(-1, -2, -3),
vec3_u32: Vector3::new(1, 2, 3),
vec3_i32: Vector3::new(-1, -2, -3),
vec3_u64: Vector3::new(1, 2, 3),
vec3_i64: Vector3::new(-1, -2, -3),
vec4_u8: Vector4::new(1, 2, 3, 4),
vec4_i8: Vector4::new(-1, -2, -3, -4),
vec4_u16: Vector4::new(1, 2, 3, 4),
vec4_i16: Vector4::new(-1, -2, -3, -4),
vec4_u32: Vector4::new(1, 2, 3, 4),
vec4_i32: Vector4::new(-1, -2, -3, -4),
vec4_u64: Vector4::new(1, 2, 3, 4),
vec4_i64: Vector4::new(-1, -2, -3, -4),
vec2_f32: Vector2::new(123.321, 234.432),
vec2_f64: Vector2::new(123.321, 234.432),
vec3_f32: Vector3::new(123.321, 234.432, 567.765),
vec3_f64: Vector3::new(123.321, 234.432, 567.765),
vec4_f32: Vector4::new(123.321, 234.432, 567.765, 890.098),
vec4_f64: Vector4::new(123.321, 234.432, 567.765, 890.098),
weaks: Weaks {
weak_resource_arc: Some(Arc::downgrade(&arc_resource)),
weak_resource_rc: Some(Rc::downgrade(&resource)),
},
shared_resource: Some(resource),
shared_resource_arc: Some(arc_resource),
string: "This Is A String With Reserved Characters <>:;{}[\\\\\\\\\\] \
and \"quotes\" many \"\"\"quotes\"\"\"\" and line\nbreak\ttabs\t\t\t\t"
.to_string(),
}
}
}
fn resource() -> Rc<Resource> {
Rc::new(Resource::new(ResourceKind::Model(Model { data: 555 })))
}
fn resource_arc() -> Arc<Resource> {
Arc::new(Resource::new(ResourceKind::Model(Model { data: 555 })))
}
fn objects(resource: Rc<Resource>, arc_resource: Arc<Resource>) -> Vec<Foo> {
vec![
Foo::new(resource.clone(), arc_resource.clone()),
Foo::new(resource, arc_resource),
]
}
fn serialize() -> Visitor {
let mut resource = resource();
let mut resource_arc = resource_arc();
let mut objects = objects(resource.clone(), resource_arc.clone());
let mut visitor = Visitor::new();
resource.visit("SharedResource", &mut visitor).unwrap();
resource_arc
.visit("SharedResourceArc", &mut visitor)
.unwrap();
objects.visit("Objects", &mut visitor).unwrap();
visitor
}
#[test]
fn visitor_test_binary() {
let path = Path::new("test.bin");
{
let visitor = serialize();
visitor.save_binary_to_file(path).unwrap();
if let Ok(mut file) = File::create(Path::new("test.txt")) {
file.write_all(visitor.save_ascii_to_string().as_bytes())
.unwrap();
}
}
{
let expected_resource = resource();
let expected_resource_arc = resource_arc();
let expected_objects =
objects(expected_resource.clone(), expected_resource_arc.clone());
let mut visitor = futures::executor::block_on(Visitor::load_from_file(path)).unwrap();
let mut resource: Rc<Resource> = Rc::new(Default::default());
resource.visit("SharedResource", &mut visitor).unwrap();
assert_eq!(resource, expected_resource);
let mut resource_arc: Arc<Resource> = Arc::new(Default::default());
resource_arc
.visit("SharedResourceArc", &mut visitor)
.unwrap();
assert_eq!(resource_arc, expected_resource_arc);
let mut objects: Vec<Foo> = Vec::new();
objects.visit("Objects", &mut visitor).unwrap();
assert_eq!(objects, expected_objects);
}
}
#[test]
fn visitor_test_ascii() {
let path = Path::new("test_ascii.txt");
{
let visitor = serialize();
visitor.save_ascii_to_file(path).unwrap();
}
{
let expected_resource = resource();
let expected_resource_arc = resource_arc();
let expected_objects =
objects(expected_resource.clone(), expected_resource_arc.clone());
let mut visitor =
futures::executor::block_on(Visitor::load_ascii_from_file(path)).unwrap();
let mut resource: Rc<Resource> = Rc::new(Default::default());
resource.visit("SharedResource", &mut visitor).unwrap();
assert_eq!(resource, expected_resource);
let mut resource_arc: Arc<Resource> = Arc::new(Default::default());
resource_arc
.visit("SharedResourceArc", &mut visitor)
.unwrap();
assert_eq!(resource_arc, expected_resource_arc);
let mut objects: Vec<Foo> = Vec::new();
objects.visit("Objects", &mut visitor).unwrap();
assert_eq!(objects, expected_objects);
}
}
}