♻️ bevy_vanth, vanth, vanth_derive, flake: Add Bevy integration crate and refactor entity/networking

- Created new crate `bevy_vanth` with basic plugin structure for Bevy integration.
- Refactored `Id` generation in `vanth` to use `OsRng` and removed redundant `to_u128_pair`/`from_u128_pair` methods.
- Moved networking functionality into new `net` module with `Node`, `Packet`, and `Message` types.
- Updated `vanth_derive` to use `proc-macro-crate` for reliable crate path resolution.
- Added `rand` dependency to replace custom ID generation logic.
- Updated `Cargo.toml`/`Cargo.lock` with new dependencies: `bevy_app`, `nix`, `cfg_aliases`, `proc-macro-crate`.
- Modified `README.md` with improved project description.
- Added commented clippy check in `flake.nix`.
This commit is contained in:
Markus Scully 2025-08-17 11:56:34 +03:00
parent 5262a266c0
commit 3b193c5aa3
Signed by: mascully
GPG key ID: 93CA5814B698101C
13 changed files with 298 additions and 127 deletions

101
Cargo.lock generated
View file

@ -141,6 +141,37 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bevy_app"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4491cc4c718ae76b4c6883df58b94cc88b32dcd894ea8d5b603c7c7da72ca967"
dependencies = [
"bevy_derive",
"bevy_ecs",
"bevy_platform",
"bevy_reflect",
"bevy_tasks",
"bevy_utils",
"cfg-if",
"ctrlc",
"downcast-rs",
"log",
"thiserror",
"variadics_please",
]
[[package]]
name = "bevy_derive"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b837bf6c51806b10ebfa9edf1844ad80a3a0760d6c5fac4e90761df91a8901a"
dependencies = [
"bevy_macro_utils",
"quote",
"syn",
]
[[package]]
name = "bevy_ecs"
version = "0.16.1"
@ -282,6 +313,14 @@ dependencies = [
"thread_local",
]
[[package]]
name = "bevy_vanth"
version = "0.1.0"
dependencies = [
"bevy_app",
"bevy_ecs",
]
[[package]]
name = "bitflags"
version = "2.9.1"
@ -352,6 +391,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "clap"
version = "4.5.42"
@ -445,6 +490,16 @@ dependencies = [
"typenum",
]
[[package]]
name = "ctrlc"
version = "3.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73"
dependencies = [
"nix",
"windows-sys",
]
[[package]]
name = "derive_more"
version = "1.0.0"
@ -750,6 +805,18 @@ version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "nix"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "nonmax"
version = "0.5.5"
@ -876,6 +943,15 @@ dependencies = [
"termtree",
]
[[package]]
name = "proc-macro-crate"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
@ -901,10 +977,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand_chacha"
version = "0.3.1"
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
@ -913,9 +999,12 @@ dependencies = [
[[package]]
name = "rand_core"
version = "0.6.4"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom",
]
[[package]]
name = "redox_syscall"
@ -1260,6 +1349,7 @@ dependencies = [
"bevy_ecs",
"blake3",
"digest",
"rand",
"rusqlite",
"serde",
"serde_json",
@ -1286,6 +1376,7 @@ dependencies = [
name = "vanth_derive"
version = "0.1.0"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",

View file

@ -12,6 +12,7 @@ name = "vanth"
[workspace.dependencies]
clap = { version = "4.5.42", features = ["derive"] }
bevy_ecs = "0.16.1"
bevy_app = "0.16.1"
bincode = "2.0.1"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
@ -22,8 +23,10 @@ syn = { version = "2.0", features = ["full"] }
proc-macro2 = "1.0"
rusqlite = { version = "0.32.1", features = ["bundled"] }
tempfile = "3.12.0"
rand_core = "0.6.4"
rand_chacha = { version = "0.3.1", features = ["serde1"] }
rand = "0.9.2"
rand_chacha = { version = "0.9.0", features = ["serde"] }
rand_core = "0.9.0"
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
assert_cmd = "2.0.17"
proc-macro-crate = "3.3.0"

View file

@ -1,6 +1,8 @@
# Vanth
Vanth is a content-addressed database as a library designed for entity-component-system (ECS) applications.
Vanth is a content-addressed data storage and task memoization framework designed for entity-component-system (ECS) applications.
Inspired by [Salsa](https://github.com/salsa-rs/salsa) and [Bevy](https://bevy.org/).
It is currently experimental and should not be used for anything.

View file

@ -0,0 +1,8 @@
[package]
name = "bevy_vanth"
version.workspace = true
edition.workspace = true
[dependencies]
bevy_ecs.workspace = true
bevy_app.workspace = true

View file

@ -0,0 +1,20 @@
use bevy_app::Plugin;
use bevy_ecs::component::Component;
#[derive(Component)]
pub struct VanthRoot {}
pub struct VanthPlugin {}
impl Plugin for VanthPlugin {
fn build(&self, app: &mut bevy_app::App) {
// TODO: Allow specifying custom schedules.
app.add_systems(bevy_app::FixedPreUpdate, run_vanth).finish();
}
}
impl VanthPlugin {}
fn run_vanth() {
}

View file

@ -16,6 +16,7 @@ blake3.workspace = true
vanth_derive = { path = "../vanth_derive" }
rusqlite.workspace = true
tracing.workspace = true
rand = { workspace = true }
[dev-dependencies]
tempfile = { workspace = true }

View file

@ -1,12 +1,12 @@
use serde::{Deserialize, Serialize};
use vanth::{hash, Vanth};
// use serde::{Deserialize, Serialize};
// use vanth::{hash, Vanth};
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
struct Foo {
value: i32,
}
// #[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
// struct Foo {
// value: i32,
// }
fn main() {
let x = "hello";
println!("Hash: {:?}", hash(&x).hex());
// let x = "hello";
// println!("Hash: {:?}", hash(&x).hex());
}

View file

@ -1,11 +1,12 @@
use crate::hash;
use crate::hashing_serializer::{self, HashingSerializer};
use rand::rngs::OsRng;
use rand::TryRngCore;
use serde::{Deserialize, Serialize};
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]);
@ -16,7 +17,7 @@ impl From<String> for EntityId {
}
/// A generic identifier type that can be used for different entity types
#[derive(Clone, Copy, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Id<T: ?Sized> {
/// The raw identifier value
pub value: [u8; 32],
@ -37,14 +38,7 @@ impl<T: ?Sized> Id<T> {
/// 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);
}
OsRng.try_fill_bytes(&mut value).unwrap();
Self::new(value)
}
@ -67,42 +61,6 @@ impl<T: ?Sized> Id<T> {
}
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> {
@ -119,20 +77,6 @@ impl<T: ?Sized> Hash for Id<T> {
}
}
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],
}

View file

@ -7,10 +7,12 @@ use crate::entity::EntityId;
pub mod entity;
pub mod hashing_serializer;
pub mod net;
pub mod nix;
pub mod store;
pub use hashing_serializer::hash;
pub use net::Node;
pub use vanth_derive::Vanth;
pub type Result<T> = std::result::Result<T, Error>;
@ -19,41 +21,6 @@ pub enum Error {
Other(String),
}
/// A view of all of the [`Node`]s in a cluster.
pub struct Network {
// TODO
}
/// A Vanth server.
pub struct Node {
// TODO
}
impl Node {
pub fn new() -> Self {
Self {}
}
pub fn entity_count(&self) -> usize {
todo!()
}
pub fn run() {
todo!()
}
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(Clone, Debug, Deserialize, Serialize)]
pub struct HashedValue {
content_hash: ContentHash,
@ -75,9 +42,15 @@ pub struct Value {
data: Vec<u8>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub struct EventTy(Ty);
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub struct TaskTy(Ty);
/// A wrapper for the fully-qualified name of a Rust type. This should be univerisally unique for a given type within a
/// given project.
#[derive(Clone, Debug, Deserialize, Serialize, Eq, Hash)]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
pub struct Ty {
pub path: Vec<String>,
}
@ -88,12 +61,6 @@ impl ToString for Ty {
}
}
impl PartialEq for Ty {
fn eq(&self, other: &Self) -> bool {
self.path == other.path
}
}
impl<T: AsRef<str>> PartialEq<T> for Ty {
fn eq(&self, other: &T) -> bool {
self.to_string() == *other.as_ref()
@ -182,5 +149,49 @@ pub struct Handle<T> {
_marker: PhantomData<T>,
}
pub trait Task {
type Output: Vanth + Serialize + DeserializeOwned;
// TODO: Make async?
fn execute(self) -> Self::Output;
}
/// A representation of the approximate computational cost of completing a task.
pub struct Cost {
value: f64,
}
/// A world which Vanth entities live in. Lifetimes `'v` of [`Vanth<'v>`] types are tied to the lifetime of the `Root`.
pub struct Root {}
pub struct Root {
current_epoch: u64,
}
pub trait Module {
fn register(world_context: &mut ModuleRegistration);
}
pub struct ModuleRegistration {
events: Vec<EventTy>,
}
impl ModuleRegistration {}
pub trait ModuleTools: Module {
type Tool: Tool;
fn tools(&self) -> impl Iterator<Item = Self::Tool>;
}
pub trait Tool: Send + Sync {
type Input;
type Output;
type Error: std::error::Error;
fn name<'a>() -> &'a str;
fn description<'a>() -> &'a str;
fn call(
&self,
input: Self::Input,
) -> impl Future<Output = std::result::Result<Self::Output, Self::Error>> + Send;
}

63
crates/vanth/src/net.rs Normal file
View file

@ -0,0 +1,63 @@
use serde::{Deserialize, Serialize};
use crate::{Error, Result};
use crate::{
Vanth,
entity::{EntityId, Id},
};
pub struct Packet {
pub source_node: Id<Node>,
pub signature: Vec<u8>,
pub message: Message,
}
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
pub struct Message {
pub events: Vec<Event>,
}
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
pub enum Event {}
/// A view of all of the [`Node`]s in a cluster.
pub struct Network {
// TODO
}
/// A Vanth server.
pub struct Node {
id: Id<Node>,
}
impl Node {
pub fn new() -> Self {
Self {
id: Id::random(),
}
}
pub fn with_id(id: Id<Node>) -> Self {
Self {
id,
}
}
pub fn entity_count(&self) -> usize {
todo!()
}
pub fn run() {
todo!()
}
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)
// }
}

View file

@ -9,4 +9,5 @@ proc-macro = true
[dependencies]
quote.workspace = true
syn.workspace = true
proc-macro2.workspace = true
proc-macro2.workspace = true
proc-macro-crate.workspace = true

View file

@ -1,4 +1,6 @@
use proc_macro::TokenStream;
use proc_macro_crate::{crate_name, FoundCrate};
use proc_macro2::Span;
use quote::quote;
use syn::parse_quote;
use syn::{DeriveInput, GenericParam, Generics, parse_macro_input};
@ -11,6 +13,15 @@ pub fn vanth_derive(input: TokenStream) -> TokenStream {
let mut generics = input.generics.clone();
let vanth_path = match crate_name("vanth") {
Ok(FoundCrate::Itself) => quote!(crate),
Ok(FoundCrate::Name(name)) => {
let ident = syn::Ident::new(&name, Span::call_site());
quote!(#ident)
}
Err(_) => quote!(vanth),
};
let type_params: Vec<syn::Ident> = generics
.params
.iter()
@ -25,7 +36,7 @@ pub fn vanth_derive(input: TokenStream) -> TokenStream {
let mut where_clause = generics.where_clause.clone().unwrap_or_else(|| parse_quote!(where));
for tp in &type_params {
where_clause.predicates.push(parse_quote!(#tp : vanth::Vanth));
where_clause.predicates.push(parse_quote!(#tp : #vanth_path::Vanth));
}
let (impl_generics, ty_generics, _) = generics.split_for_impl();
@ -34,19 +45,19 @@ pub fn vanth_derive(input: TokenStream) -> TokenStream {
quote! { String::new() }
} else {
quote! {
format!("<{}>", [#(#type_params::ty().path.join("::")),*].join(","))
format!("<{}>", [#( <#type_params as #vanth_path::Vanth>::ty().path.join("::") ),*].join(","))
}
};
let expanded = quote! {
impl #impl_generics vanth::Vanth for #name #ty_generics #where_clause {
fn ty() -> vanth::Ty {
impl #impl_generics #vanth_path::Vanth for #name #ty_generics #where_clause {
fn ty() -> #vanth_path::Ty {
let module_path = module_path!();
let mut path: Vec<String> = module_path.split("::").map(|s| s.to_string()).collect();
let base_name = stringify!(#name);
let generics_str = #generics_code;
path.push(format!("{}{}", base_name, generics_str));
vanth::Ty { path }
#vanth_path::Ty { path }
}
}
};

View file

@ -94,6 +94,18 @@
'';
}
);
clippyCheck = craneLib.cargoClippy (
commonArgs
// craneArgs
// {
inherit cargoArtifacts;
pname = "vanth-clippy";
version = "0.1.0";
# Fail on any lint warning and cover all targets/features.
cargoClippyExtraArgs = "--all-targets --all-features -- -D warnings";
}
);
in
{
packages = rec {
@ -101,6 +113,10 @@
default = vanth;
};
checks = {
# clippy = clippyCheck;
};
devShells.default = craneLib.devShell {
inputsFrom = [ vanth ];
packages = packages;