diff --git a/Cargo.lock b/Cargo.lock index 83a9dc4..38b3690 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -456,6 +456,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "js-sys" version = "0.3.77" @@ -607,6 +613,12 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "scopeguard" version = "1.2.0" @@ -633,6 +645,18 @@ dependencies = [ "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]] name = "slab" version = "0.4.10" @@ -762,6 +786,8 @@ version = "0.1.0" dependencies = [ "bevy_app", "bevy_ecs", + "serde", + "serde_json", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8df760e..cc3d195 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,13 @@ [workspace] members = ["crates/*"] +resolver = "3" + +[workspace.package] +version = "0.1.0" +edition = "2024" [workspace.dependencies] bevy_app = "0.16.1" bevy_ecs = "0.16.1" +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" diff --git a/crates/vanth/Cargo.toml b/crates/vanth/Cargo.toml index f28dcaf..a8f0139 100644 --- a/crates/vanth/Cargo.toml +++ b/crates/vanth/Cargo.toml @@ -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 diff --git a/crates/vanth/src/entity.rs b/crates/vanth/src/entity.rs new file mode 100644 index 0000000..07ddd6c --- /dev/null +++ b/crates/vanth/src/entity.rs @@ -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 { + /// The raw identifier value + pub value: [u8; 32], + /// Phantom data to associate the ID with a specific type + #[serde(skip)] + _marker: PhantomData, +} + +impl Id { + /// 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 PartialEq for Id { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl Eq for Id {} + +impl Hash for Id { + fn hash(&self, state: &mut H) { + self.value.hash(state); + } +} + +impl Debug for Id { + 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::(), + high, + low + ) + } +} + +impl Display for Id { + 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 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> {} diff --git a/crates/vanth/src/lib.rs b/crates/vanth/src/lib.rs index b466110..9bf253a 100644 --- a/crates/vanth/src/lib.rs +++ b/crates/vanth/src/lib.rs @@ -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 { + id: crate::entity::Id, + _marker: PhantomData, +} + +impl Vanth { + fn save_system(query: Query) { + + } +} + +// 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. diff --git a/crates/vanth/src/store.rs b/crates/vanth/src/store.rs new file mode 100644 index 0000000..b051826 --- /dev/null +++ b/crates/vanth/src/store.rs @@ -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 { + +} diff --git a/crates/vanth/tests/derive.rs b/crates/vanth/tests/derive.rs new file mode 100644 index 0000000..01a5a85 --- /dev/null +++ b/crates/vanth/tests/derive.rs @@ -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 { + +}