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

26
Cargo.lock generated
View file

@ -456,6 +456,12 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.77" version = "0.3.77"
@ -607,6 +613,12 @@ version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
@ -633,6 +645,18 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "serde_json"
version = "1.0.141"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.10" version = "0.4.10"
@ -762,6 +786,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bevy_app", "bevy_app",
"bevy_ecs", "bevy_ecs",
"serde",
"serde_json",
] ]
[[package]] [[package]]

View file

@ -1,6 +1,13 @@
[workspace] [workspace]
members = ["crates/*"] members = ["crates/*"]
resolver = "3"
[workspace.package]
version = "0.1.0"
edition = "2024"
[workspace.dependencies] [workspace.dependencies]
bevy_app = "0.16.1" bevy_app = "0.16.1"
bevy_ecs = "0.16.1" bevy_ecs = "0.16.1"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"

View file

@ -1,8 +1,7 @@
[package] [package]
name = "vanth" name = "vanth"
version = "0.1.0" version.workspace = true
edition = "2024" edition.workspace = true
publish = false
[lib] [lib]
path = "src/lib.rs" path = "src/lib.rs"
@ -10,3 +9,5 @@ path = "src/lib.rs"
[dependencies] [dependencies]
bevy_app.workspace = true bevy_app.workspace = true
bevy_ecs.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. /// Library crate for the `vanth` ECS-based database node.
use bevy_app::App; 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. /// A server node wrapping a Bevy App without running it.
pub struct Node { pub struct Node {
@ -20,4 +26,37 @@ impl Node {
// Query for no components returns one item per entity. // Query for no components returns one item per entity.
// self.app.world().entities().len() // 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 {
}