♻️🍱💩👷 crates/vanth, project: Refactor entity system and setup; migrate to Nix flakes

- Add `.cargo/config.toml` with target directory and rustflags configuration.
- Remove devenv files and set up Nix flake configuration with `flake.nix` and `flake.lock`.
- Add dependencies on `blake3`, `digest`, and hashing-related crates for content hashing functionality.
- Implement `EntityId` as hashed representation and `ContentHash` component using Blake3.
- Add `hashing_serializer` module for serializing and hashing components.
- Remove unused `parser` module and restructure lib.rs with new `Reference` type and storage interfaces.
- Add test files for `store` and `reference` functionality with TODOs for implementation.
- Introduce `VanthPlugin` skeleton and entity save/load interfaces with placeholder implementations.
- Add `Reference` type with retrieval state machine and async handling stubs.
This commit is contained in:
Markus Scully 2025-08-04 21:03:15 +03:00
parent be75844fdd
commit 45a68865b6
Signed by: mascully
GPG key ID: 93CA5814B698101C
19 changed files with 918 additions and 261 deletions

View file

@ -9,5 +9,7 @@ path = "src/lib.rs"
[dependencies]
bevy_app.workspace = true
bevy_ecs.workspace = true
digest.workspace = true
serde.workspace = true
serde_json.workspace = true
blake3.workspace = true

View file

@ -3,6 +3,18 @@ use std::fmt::{self, Debug, Display};
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use crate::hash;
use crate::hashing_serializer::{self, HashingSerializer};
#[derive(Clone, Copy, Deserialize)]
pub struct EntityId([u8; 32]);
impl From<String> for EntityId {
fn from(value: String) -> Self {
Self(hash(&value).hash)
}
}
/// A generic identifier type that can be used for different entity types
#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct Id<T: ?Sized> {

View file

@ -0,0 +1,390 @@
// Stolen and adapted from https://github.com/fjarri/hashing-serializer/blob/master/src/lib.rs
use std::fmt;
use digest::Update;
use serde::{
ser::{
self, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple,
SerializeTupleStruct, SerializeTupleVariant,
},
Serialize, Serializer,
};
use crate::ContentHash;
pub fn hash(value: &impl Serialize) -> ContentHash {
let mut digest = blake3::Hasher::new();
let mut serializer = HashingSerializer { digest: &mut digest };
// TODO: Don't unwrap.
serializer.serialize_value(value).unwrap();
ContentHash { hash: *serializer.digest.finalize().as_bytes() }
}
/// A serializer that hashes the data instead of serializing it.
pub struct HashingSerializer<'a, T: Update> {
/// A reference to the digest that will accumulate the data.
pub digest: &'a mut T,
}
/// Possible errors during serialization.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Error {
/// The type's [`serde::Serialize`] impl tried to serialize a sequence of undefined length.
UndefinedSequenceLength,
/// Sequence length does not fit into `u128`.
///
/// Really, this shouldn't ever happen.
SequenceLengthTooLarge,
/// [`Serializer::collect_str`] got called, but heap memory allocation is not available.
///
/// Can only be returned if `alloc` feature not enabled.
CannotCollectStr,
/// Custom `serde` error, but memory allocation is not available.
/// Set a breakpoint where this is thrown for more information.
///
/// Can only be returned if `alloc` feature not enabled).
CustomErrorNoAlloc,
/// Custom `serde` error.
CustomError(String),
}
impl ser::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self {
Self::CustomError(format!("{}", msg))
}
}
impl ser::StdError for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
/// Converts the `usize` sequence length to a fixed length type,
/// since we want the result to be portable.
fn try_into_sequence_length(len: usize) -> Result<u128, Error> {
u128::try_from(len)
.ok()
.ok_or(Error::SequenceLengthTooLarge)
}
// Implement `serialize_$ty` for int types
macro_rules! impl_trivial_serialize {
($method_name:ident , $t:ty) => {
fn $method_name(self, v: $t) -> Result<Self::Ok, Self::Error> {
self.digest.update(&v.to_be_bytes());
Ok(())
}
};
}
macro_rules! impl_serialize_trait {
($method_name:ident , $t:ty) => {
fn $method_name(self, v: $t) -> Result<Self::Ok, Self::Error> {
self.digest.update(&v.to_be_bytes());
Ok(())
}
};
}
impl<'a, T: Update> Serializer for HashingSerializer<'a, T> {
type Ok = ();
type Error = Error;
type SerializeSeq = Self;
type SerializeTuple = Self;
type SerializeTupleStruct = Self;
type SerializeTupleVariant = Self;
type SerializeMap = Self;
type SerializeStruct = Self;
type SerializeStructVariant = Self;
fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {
self.digest.update(&(v as u8).to_be_bytes());
Ok(())
}
impl_trivial_serialize!(serialize_i8, i8);
impl_trivial_serialize!(serialize_i16, i16);
impl_trivial_serialize!(serialize_i32, i32);
impl_trivial_serialize!(serialize_i64, i64);
impl_trivial_serialize!(serialize_u8, u8);
impl_trivial_serialize!(serialize_u16, u16);
impl_trivial_serialize!(serialize_u32, u32);
impl_trivial_serialize!(serialize_u64, u64);
impl_trivial_serialize!(serialize_f32, f32);
impl_trivial_serialize!(serialize_f64, f64);
fn serialize_char(self, v: char) -> Result<Self::Ok, Self::Error> {
// `char` is always at most 4 bytes, regardless of the platform,
// so this conversion is safe.
self.digest.update(&u64::from(v).to_be_bytes());
Ok(())
}
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
self.digest.update(v.as_ref());
Ok(())
}
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error> {
self.digest.update(v);
Ok(())
}
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
self.digest.update(&[0]);
Ok(())
}
fn serialize_some<V: ?Sized + Serialize>(self, value: &V) -> Result<Self::Ok, Self::Error> {
self.digest.update(&[1]);
value.serialize(self)
}
fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
Ok(())
}
fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Self::Error> {
Ok(())
}
fn serialize_unit_variant(
self,
_name: &'static str,
variant_index: u32,
_variant: &'static str,
) -> Result<Self::Ok, Self::Error> {
self.digest.update(&variant_index.to_be_bytes());
Ok(())
}
fn serialize_newtype_struct<V: ?Sized + Serialize>(
self,
_name: &'static str,
value: &V,
) -> Result<Self::Ok, Self::Error> {
value.serialize(self)
}
fn serialize_newtype_variant<V: ?Sized + Serialize>(
self,
_name: &'static str,
variant_index: u32,
_variant: &'static str,
value: &V,
) -> Result<Self::Ok, Self::Error> {
self.digest.update(&variant_index.to_be_bytes());
value.serialize(self)
}
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
let len = len.ok_or(Error::UndefinedSequenceLength)?;
self.digest
.update(&try_into_sequence_length(len)?.to_be_bytes());
Ok(self)
}
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Self::Error> {
self.digest
.update(&try_into_sequence_length(len)?.to_be_bytes());
Ok(self)
}
fn serialize_tuple_struct(
self,
_name: &'static str,
len: usize,
) -> Result<Self::SerializeTupleStruct, Self::Error> {
self.digest
.update(&try_into_sequence_length(len)?.to_be_bytes());
Ok(self)
}
fn serialize_tuple_variant(
self,
_name: &'static str,
variant_index: u32,
_variant: &'static str,
len: usize,
) -> Result<Self::SerializeTupleVariant, Self::Error> {
self.digest.update(&variant_index.to_be_bytes());
self.digest
.update(&try_into_sequence_length(len)?.to_be_bytes());
Ok(self)
}
fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
let len = len.ok_or(Error::UndefinedSequenceLength)?;
self.digest
.update(&try_into_sequence_length(len)?.to_be_bytes());
Ok(self)
}
fn serialize_struct(
self,
_name: &'static str,
len: usize,
) -> Result<Self::SerializeStruct, Self::Error> {
self.digest
.update(&try_into_sequence_length(len)?.to_be_bytes());
Ok(self)
}
fn serialize_struct_variant(
self,
_name: &'static str,
variant_index: u32,
_variant: &'static str,
len: usize,
) -> Result<Self::SerializeStructVariant, Self::Error> {
self.digest.update(&variant_index.to_be_bytes());
self.digest
.update(&try_into_sequence_length(len)?.to_be_bytes());
Ok(self)
}
fn collect_str<V: fmt::Display + ?Sized>(self, _: &V) -> Result<Self::Ok, Self::Error> {
Err(Error::CannotCollectStr)
}
fn is_human_readable(&self) -> bool {
false
}
}
impl<'a, T: Update> SerializeSeq for HashingSerializer<'a, T> {
type Ok = ();
type Error = Error;
fn serialize_element<V: ?Sized + Serialize>(&mut self, value: &V) -> Result<Self::Ok, Error> {
value.serialize(HashingSerializer {
digest: self.digest,
})?;
Ok(())
}
fn end(self) -> Result<Self::Ok, Error> {
Ok(())
}
}
impl<'a, T: Update> SerializeTuple for HashingSerializer<'a, T> {
type Ok = ();
type Error = Error;
fn serialize_element<V: ?Sized + Serialize>(&mut self, value: &V) -> Result<Self::Ok, Error> {
value.serialize(HashingSerializer {
digest: self.digest,
})?;
Ok(())
}
fn end(self) -> Result<Self::Ok, Error> {
Ok(())
}
}
impl<'a, T: Update> SerializeTupleStruct for HashingSerializer<'a, T> {
type Ok = ();
type Error = Error;
fn serialize_field<V: ?Sized + Serialize>(&mut self, value: &V) -> Result<Self::Ok, Error> {
value.serialize(HashingSerializer {
digest: self.digest,
})?;
Ok(())
}
fn end(self) -> Result<Self::Ok, Error> {
Ok(())
}
}
impl<'a, T: Update> SerializeTupleVariant for HashingSerializer<'a, T> {
type Ok = ();
type Error = Error;
fn serialize_field<V: ?Sized + Serialize>(&mut self, value: &V) -> Result<Self::Ok, Error> {
value.serialize(HashingSerializer {
digest: self.digest,
})?;
Ok(())
}
fn end(self) -> Result<Self::Ok, Error> {
Ok(())
}
}
impl<'a, T: Update> SerializeMap for HashingSerializer<'a, T> {
type Ok = ();
type Error = Error;
fn serialize_key<K: ?Sized + Serialize>(&mut self, key: &K) -> Result<Self::Ok, Error> {
key.serialize(HashingSerializer {
digest: self.digest,
})?;
Ok(())
}
fn serialize_value<V: ?Sized + Serialize>(&mut self, value: &V) -> Result<Self::Ok, Error> {
value.serialize(HashingSerializer {
digest: self.digest,
})?;
Ok(())
}
fn end(self) -> Result<Self::Ok, Error> {
Ok(())
}
}
impl<'a, T: Update> SerializeStruct for HashingSerializer<'a, T> {
type Ok = ();
type Error = Error;
fn serialize_field<V: ?Sized + Serialize>(
&mut self,
_key: &'static str,
value: &V,
) -> Result<Self::Ok, Error> {
value.serialize(HashingSerializer {
digest: self.digest,
})?;
Ok(())
}
fn end(self) -> Result<Self::Ok, Error> {
Ok(())
}
}
impl<'a, T: Update> SerializeStructVariant for HashingSerializer<'a, T> {
type Ok = ();
type Error = Error;
fn serialize_field<V: ?Sized + Serialize>(
&mut self,
_key: &'static str,
value: &V,
) -> Result<Self::Ok, Error> {
value.serialize(HashingSerializer {
digest: self.digest,
})?;
Ok(())
}
fn end(self) -> Result<Self::Ok, Error> {
Ok(())
}
}

View file

@ -1,12 +1,23 @@
use std::marker::PhantomData;
/// Library crate for the `vanth` ECS-based database node.
use bevy_app::App;
use bevy_app::{App, Plugin};
use bevy_ecs::{prelude::*, query::QueryData};
use serde::{Deserialize, Serialize};
use crate::entity::EntityId;
pub mod store;
pub mod entity;
pub mod hashing_serializer;
pub use hashing_serializer::hash;
pub type Result<T> = std::result::Result<T, Error>;
pub enum Error {
Other(String),
}
/// A server node wrapping a Bevy App without running it.
pub struct Node {
@ -31,6 +42,16 @@ impl Node {
pub fn run() {
}
pub fn save(entity_id: impl Into<EntityId>) -> Result<()> {
// TODO
Ok(())
}
pub fn load(entity_id: impl Into<EntityId>) -> Result<Option<EntityContents>> {
// TODO
Ok(None)
}
}
#[derive(Debug, Deserialize, Component, Serialize)]
@ -50,13 +71,81 @@ pub trait VanthTuple {
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EntityContents {
components: Vec<ComponentContents>
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ComponentContents {
id: String,
content_hash: ContentHash,
data: Vec<u8>,
}
pub trait Component: Serialize {
fn id() -> String;
}
// use a macro to implement VanthTuiple here.
#[derive(Debug, Deserialize, Component, Serialize)]
#[derive(Copy, Clone, Debug, Deserialize, Component, Serialize)]
pub struct ContentHash {
pub hash: [u8; 32],
}
#[derive(Clone, Debug, Deserialize, Component, Serialize)]
pub struct Reference<T: Clone + Serialize> {
value: ReferenceValue,
_marker: PhantomData<T>
}
#[derive( Clone, Debug, Deserialize, Component, Serialize)]
pub enum ReferenceValue {
Absent,
Retrieving(ReferenceRetrievalTask),
Present(Vec<u8>)
}
impl <T: Clone + Serialize> Reference<T> {
pub async fn take() -> T {
todo!()
}
pub async fn get() -> Handle<T> {
todo!()
}
}
#[derive(Component, Clone, Debug, Deserialize, Serialize)]
struct ReferenceRetrievalTask {
}
impl Future for ReferenceRetrievalTask {
type Output = Vec<u8>;
fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
todo!()
}
}
pub struct Handle<T> {
_marker: PhantomData<T>
}
// TODO:
// A trait is derivable for ECS components
// The components must have a content hash, not the entity. For efficiency and ergonomics. This means that a hash of each relevant component must be stored in the Vanth component of the entity, in a `HashMap` or something. The ID of the component used by Vanth should be a method on the derived trait.
pub struct VanthPlugin;
impl Plugin for VanthPlugin {
fn build(&self, app: &mut App) {
}
}
// fn run_reference_tasks(tasks: Query<(&ReferenceGetTask<>)>) {
// }

View file

@ -1,6 +1,3 @@
#![allow(unused)]
mod parser;
mod util;
mod vanth;

View file

@ -11,3 +11,20 @@ pub struct Store {
impl Store {
}
#[derive(Debug, Deserialize, Component, Serialize)]
pub struct Cache {
size_limit_bytes: u64,
backend: Backend,
}
#[derive(Debug, Deserialize, Serialize)]
pub enum Backend {
Memory,
Sqlite(Sqlite)
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Sqlite {
path: PathBuf,
}

View file

@ -0,0 +1,28 @@
use serde::{Deserialize, Serialize};
use vanth::{Component, Node, Reference};
// #[test]
// fn test_store() {
// #[derive(Clone, Deserialize, Serialize)]
// struct Foo {
// bar: Reference<Bar>,
// }
// #[derive(Clone, Deserialize, Serialize)]
// struct Bar {
// foo: Reference<Foo>,
// }
// impl Component for Foo {
// fn id() -> String {
// "foo".into()
// }
// }
// let node = Node::in_memory();
// let entity_id = "entity_1";
// let entity_components = (Foo { a: 5, b: 6.0 },);
// node.save("entity_1", entity_components);
// }

View file

@ -0,0 +1,24 @@
use serde::{Deserialize, Serialize};
use vanth::{Component, Node};
// #[test]
// fn test_store() {
// #[derive(Deserialize, Serialize)]
// struct Foo {
// a: u32,
// b: f32,
// }
// impl Component for Foo {
// fn id() -> String {
// "foo".into()
// }
// }
// let node = Node::in_memory();
// let entity_id = "entity_1";
// let entity_components = (Foo { a: 5, b: 6.0 },);
// node.save("entity_1", entity_components);
// }