vanth, workspace: Add entity ID system and serialization dependencies

- Added new modules `entity` and `store` to `vanth` crate for ECS foundations
- Implemented generic `Id` type with serialization/deserialization support
- Introduced `serde` and `serde_json` dependencies in workspace and vanth crate
- Defined `Vanth` struct and `ContentHash` component in `lib.rs`
- Added placeholder `Store` component in new module
- Updated workspace configuration with `resolver = "3"` and dependency versions
This commit is contained in:
Markus Scully 2025-07-21 23:09:35 +03:00
parent 93e114a23a
commit be75844fdd
Signed by: mascully
GPG key ID: 5E77BA046064DD90
7 changed files with 243 additions and 4 deletions

View file

@ -1,8 +1,7 @@
[package]
name = "vanth"
version = "0.1.0"
edition = "2024"
publish = false
version.workspace = true
edition.workspace = true
[lib]
path = "src/lib.rs"
@ -10,3 +9,5 @@ path = "src/lib.rs"
[dependencies]
bevy_app.workspace = true
bevy_ecs.workspace = true
serde.workspace = true
serde_json.workspace = true

144
crates/vanth/src/entity.rs Normal file
View file

@ -0,0 +1,144 @@
use serde::{Deserialize, Serialize};
use std::fmt::{self, Debug, Display};
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
/// A generic identifier type that can be used for different entity types
#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct Id<T: ?Sized> {
/// The raw identifier value
pub value: [u8; 32],
/// Phantom data to associate the ID with a specific type
#[serde(skip)]
_marker: PhantomData<T>,
}
impl<T: ?Sized> Id<T> {
/// Create a new ID with the given value
pub fn new(value: [u8; 32]) -> Self {
Self {
value,
_marker: PhantomData,
}
}
/// Generate a random ID
pub fn random() -> Self {
let mut value = [0u8; 32];
// Simple random generation for demonstration
for i in 0..32 {
value[i] = (std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos() as u8)
.wrapping_add(i as u8);
}
Self::new(value)
}
/// Convert the ID to a u64 for easier handling (uses only first 8 bytes)
pub fn to_u64(&self) -> u64 {
let mut result = 0u64;
for i in 0..8 {
if i < self.value.len() {
result |= (self.value[i] as u64) << (i * 8);
}
}
result
}
/// Create an ID from a u64 (fills only first 8 bytes)
pub fn from_u64(value: u64) -> Self {
let mut bytes = [0u8; 32];
for i in 0..8 {
bytes[i] = ((value >> (i * 8)) & 0xFF) as u8;
}
Self::new(bytes)
}
/// Convert the ID to a pair of u128 values for handling larger values
/// Returns (high_bits, low_bits)
pub fn to_u128_pair(&self) -> (u128, u128) {
let mut high = 0u128;
let mut low = 0u128;
// First 16 bytes go to low
for i in 0..16 {
low |= (self.value[i] as u128) << (i * 8);
}
// Next 16 bytes go to high
for i in 0..16 {
high |= (self.value[i + 16] as u128) << (i * 8);
}
(high, low)
}
/// Create an ID from a pair of u128 values
pub fn from_u128_pair(high: u128, low: u128) -> Self {
let mut bytes = [0u8; 32];
// Low bits fill first 16 bytes
for i in 0..16 {
bytes[i] = ((low >> (i * 8)) & 0xFF) as u8;
}
// High bits fill next 16 bytes
for i in 0..16 {
bytes[i + 16] = ((high >> (i * 8)) & 0xFF) as u8;
}
Self::new(bytes)
}
}
impl<T: ?Sized> PartialEq for Id<T> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<T: ?Sized> Eq for Id<T> {}
impl<T: ?Sized> Hash for Id<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.value.hash(state);
}
}
impl<T: ?Sized> Debug for Id<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (high, low) = self.to_u128_pair();
write!(
f,
"Id<{}>({:016x}{:016x})",
std::any::type_name::<T>(),
high,
low
)
}
}
impl<T: ?Sized> Display for Id<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (high, low) = self.to_u128_pair();
write!(f, "{:016x}{:016x}", high, low)
}
}
pub struct ContentHash {
value: [u8; 32],
}
impl ContentHash {}
pub trait Entity {
fn entity_id() -> Id<dyn Entity> where Self: Sized;
}
pub trait Component: Send + Sync + 'static {
fn component_id() -> &'static str;
}
// impl<'de, T> Component for T where T: Serialize + Deserialize<'de> {}

View file

@ -1,6 +1,12 @@
use std::marker::PhantomData;
/// Library crate for the `vanth` ECS-based database node.
use bevy_app::App;
use bevy_ecs::prelude::*;
use bevy_ecs::{prelude::*, query::QueryData};
use serde::{Deserialize, Serialize};
pub mod store;
pub mod entity;
/// A server node wrapping a Bevy App without running it.
pub struct Node {
@ -20,4 +26,37 @@ impl Node {
// Query for no components returns one item per entity.
// self.app.world().entities().len()
}
// TODO
pub fn run() {
}
}
#[derive(Debug, Deserialize, Component, Serialize)]
pub struct Vanth<T: VanthTuple + QueryData> {
id: crate::entity::Id<Self>,
_marker: PhantomData<T>,
}
impl <T: VanthTuple + QueryData> Vanth<T> {
fn save_system(query: Query<T>) {
}
}
// TODO: Impl for different tuple sizes
pub trait VanthTuple {
}
// use a macro to implement VanthTuiple here.
#[derive(Debug, Deserialize, Component, Serialize)]
pub struct ContentHash {
pub hash: [u8; 32],
}
// 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.

13
crates/vanth/src/store.rs Normal file
View file

@ -0,0 +1,13 @@
use std::path::PathBuf;
use bevy_ecs::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Component, Serialize)]
pub struct Store {
pub path: PathBuf,
}
impl Store {
}

View file

@ -0,0 +1,9 @@
use bevy_ecs::component::Component;
use serde::{Deserialize, Serialize};
use vanth::Vanth;
// TODO: derive `Vanth`
#[derive(Debug, Deserialize, Component, Serialize)]
struct Foo {
}