diff --git a/crates/vanth/src/lib.rs b/crates/vanth/src/lib.rs index 120c1ec..03d5375 100644 --- a/crates/vanth/src/lib.rs +++ b/crates/vanth/src/lib.rs @@ -49,10 +49,10 @@ impl Node { Ok(()) } - pub fn load(entity_id: impl Into) -> Result> { - // TODO - Ok(None) - } + // pub fn load(entity_id: impl Into) -> Result> { + // // TODO + // Ok(None) + // } } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -76,11 +76,17 @@ pub struct Value { data: Vec, } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, Eq, Hash)] pub struct Ty { pub path: Vec, } +impl ToString for Ty { + fn to_string(&self) -> String { + self.path.join("::") + } +} + impl PartialEq for Ty { fn eq(&self, other: &Self) -> bool { self.path == other.path @@ -89,7 +95,7 @@ impl PartialEq for Ty { impl > PartialEq for Ty { fn eq(&self, other: &T) -> bool { - self.path.join("::") == *other.as_ref() + self.to_string() == *other.as_ref() } } @@ -102,16 +108,16 @@ pub trait VanthTuple { } -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct EntityContents { - components: Vec -} +// #[derive(Clone, Debug, Deserialize, Serialize)] +// pub struct EntityContents { +// components: Vec +// } -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ComponentContents { - id: String, +#[derive(Clone, Debug)] +pub struct ComponentContents { content_hash: ContentHash, data: Vec, + _marker: PhantomData, } pub trait Component: Serialize { @@ -120,7 +126,7 @@ pub trait Component: Serialize { // use a macro to implement VanthTuiple here. -#[derive(Copy, Clone, Debug, Deserialize, Component, Serialize)] +#[derive(Copy, Clone, Debug, Deserialize, Component, Serialize, PartialEq, Eq, Hash)] pub struct ContentHash { pub hash: [u8; 32], } diff --git a/crates/vanth/src/store.rs b/crates/vanth/src/store.rs index c7b7dd2..1a05119 100644 --- a/crates/vanth/src/store.rs +++ b/crates/vanth/src/store.rs @@ -1,115 +1,256 @@ -use std::{collections::HashMap, path::PathBuf}; +use std::{collections::HashMap, marker::PhantomData, path::PathBuf}; -use rusqlite::{Connection, params}; +use rusqlite::{Connection, params, named_params}; use bevy_ecs::prelude::*; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; -#[derive(Debug, Deserialize, Component, Serialize)] +use crate::{hash, ComponentContents, ContentHash, Ty, Vanth}; + +#[derive(Debug)] pub struct Store { - backend: Backend, + backend: Box, } -type Result = std::result::Result; +type Result = std::result::Result; + +#[derive(Debug, Deserialize, Serialize)] +pub enum Error { + Serializiation(String), + Database(String), +} + +impl From for Error { + fn from(err: serde_json::Error) -> Self { + Error::Serializiation(err.to_string()) + } +} + +impl From for Error { + fn from(err: rusqlite::Error) -> Self { + Error::Database(err.to_string()) + } +} impl Store { + /// Use an SQLite backend with a database file at the provided path. pub fn from_path(path: PathBuf) -> Result { - let conn = Connection::open(&path).map_err(|e| e.to_string())?; - conn.execute( - "CREATE TABLE IF NOT EXISTS kv (key BLOB PRIMARY KEY, value BLOB)", - params![], - ).map_err(|e| e.to_string())?; Ok(Self { - backend: Backend::Sqlite(Sqlite { path }), + backend: Box::new(Sqlite::new(path)?), }) } - + + /// Use an in-memory backend. pub fn in_memory() -> Result { Ok(Self { - backend: Backend::Memory(Memory::new()), + backend: Box::new(Memory::new()), }) } - - pub fn read(&mut self, key: impl AsRef<[u8]>) -> Result>> { - match &mut self.backend { - Backend::Memory(mem) => Ok(mem.values.get(key.as_ref()).cloned()), - Backend::Sqlite(sql) => { - let conn = Connection::open(&sql.path).map_err(|e| e.to_string())?; - let mut stmt = conn.prepare("SELECT value FROM kv WHERE key = ?1").map_err(|e| e.to_string())?; - let mut rows = stmt.query(params![key.as_ref()]).map_err(|e| e.to_string())?; - if let Some(row) = rows.next().map_err(|e| e.to_string())? { - let value: Vec = row.get(0).map_err(|e| e.to_string())?; - Ok(Some(value)) - } else { - Ok(None) - } - } - } + + pub fn get_from_hash(&mut self, content_hash: ContentHash) -> Result> { + let Some(raw) = self.get_raw_from_hash::(content_hash)? else { return Ok(None) }; + + let deserialized: T = serde_json::from_slice(&raw)?; + Ok(Some(deserialized)) } - - pub fn write(&mut self, key: impl AsRef<[u8]>, value: impl AsRef<[u8]>) -> Result<()> { - match &mut self.backend { - Backend::Memory(mem) => { - mem.values.insert(key.as_ref().to_vec(), value.as_ref().to_vec()); - Ok(()) - } - Backend::Sqlite(sql) => { - let conn = Connection::open(&sql.path).map_err(|e| e.to_string())?; - conn.execute( - "INSERT OR REPLACE INTO kv (key, value) VALUES (?1, ?2)", - params![key.as_ref(), value.as_ref()], - ).map_err(|e| e.to_string())?; - Ok(()) - } - } + + pub fn get_raw_from_hash(&mut self, content_hash: ContentHash) -> Result>> { + self.backend.get_from_hash(T::ty(), content_hash) } - - pub fn delete(&mut self, key: impl AsRef<[u8]>) -> Result<()> { - match &mut self.backend { - Backend::Memory(mem) => { - mem.values.remove(key.as_ref()); - Ok(()) - } - Backend::Sqlite(sql) => { - let conn = Connection::open(&sql.path).map_err(|e| e.to_string())?; - conn.execute( - "DELETE FROM kv WHERE key = ?1", - params![key.as_ref()], - ).map_err(|e| e.to_string())?; - Ok(()) - } + + pub fn get_all_of_type(&mut self) -> Result>> { + let raw_items = self.backend.get_all_of_ty(T::ty())?; + let mut results = Vec::new(); + for (content_hash, data) in raw_items { + results.push(ComponentContents { + _marker: PhantomData, + content_hash, + data, + }); } + Ok(results) + } + + pub fn write(&mut self, value: &T) -> Result<()> { + let content_hash = hash(&value); + let data = serde_json::to_vec(&value)?; + self.backend.write(T::ty(), content_hash, data) + } + + pub fn write_raw(&mut self, content_hash: ContentHash, content: Vec) -> Result<()> { + self.backend.write(T::ty(), content_hash, content) + } + + pub fn delete(&mut self, content_hash: ContentHash) -> Result<()> { + self.backend.delete_by_hash(T::ty(), content_hash) + } + + pub fn delete_all(&mut self) -> Result<()> { + self.backend.delete_all_of_ty(T::ty()) } } #[derive(Debug, Deserialize, Component, Serialize)] pub struct Cache { size_limit_bytes: u64, - backend: Backend, + // backend: Backend, } -#[derive(Debug, Deserialize, Serialize)] -pub enum Backend { - Memory(Memory), - Sqlite(Sqlite) +pub trait Backend: std::fmt::Debug { + fn get_from_hash(&mut self, ty: Ty, content_hash: ContentHash) -> Result>>; + + fn get_all_of_ty(&mut self, ty: Ty) -> Result)>>; + + fn write(&mut self, ty: Ty, content_hash: ContentHash, content: Vec) -> Result<()>; + + fn delete_by_hash(&mut self, ty: Ty, content_hash: ContentHash) -> Result<()>; + + fn delete_all_of_ty(&mut self, ty: Ty) -> Result<()>; } -/// One table, key-value store. Keys and values are both blobs. -#[derive(Debug, Deserialize, Serialize)] +/// One table per type. Keys and values are both blobs. +#[derive(Debug)] pub struct Sqlite { - path: PathBuf, + conn: Connection, } -/// One table, key-value store. Keys and values are both blobs. +impl Sqlite { + pub fn new(path: PathBuf) -> Result { + let conn = Connection::open(path)?; + Ok(Self { conn }) + } + + fn ensure_table_exists(&self, ty: &Ty) -> Result<()> { + let table_name = Self::table_name(ty); + let query = format!( + "CREATE TABLE IF NOT EXISTS \"{}\" ( + content_hash BLOB PRIMARY KEY, + content BLOB NOT NULL + )", + table_name + ); + self.conn.execute(&query, [])?; + Ok(()) + } + + fn table_name(ty: &Ty) -> String { + format!("ty_{}", ty.to_string()) + } +} + +impl Backend for Sqlite { + fn get_from_hash(&mut self, ty: Ty, content_hash: ContentHash) -> Result>> { + self.ensure_table_exists(&ty)?; + let table_name = Self::table_name(&ty); + let query = format!("SELECT content FROM \"{}\" WHERE content_hash = :hash", table_name); + + match self.conn.query_row(&query, named_params! {":hash": content_hash.hash.as_slice()}, |row| { + row.get::<_, Vec>(0) + }) { + Ok(content) => Ok(Some(content)), + Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None), + Err(e) => Err(e.into()), + } + } + + fn get_all_of_ty(&mut self, ty: Ty) -> Result)>> { + self.ensure_table_exists(&ty)?; + let table_name = Self::table_name(&ty); + let query = format!("SELECT content_hash, content FROM \"{}\"", table_name); + + let mut stmt = self.conn.prepare(&query)?; + let rows = stmt.query_map([], |row| { + let hash_bytes: Vec = row.get(0)?; + let content: Vec = row.get(1)?; + let mut hash_array = [0u8; 32]; + hash_array.copy_from_slice(&hash_bytes); + Ok((ContentHash { hash: hash_array }, content)) + })?; + + let mut results = Vec::new(); + for row in rows { + results.push(row?); + } + Ok(results) + } + + fn write(&mut self, ty: Ty, content_hash: ContentHash, content: Vec) -> Result<()> { + self.ensure_table_exists(&ty)?; + let table_name = Self::table_name(&ty); + let query = format!( + "INSERT OR REPLACE INTO \"{}\" (content_hash, content) VALUES (:hash, :content)", + table_name + ); + self.conn.execute(&query, named_params! {":hash": content_hash.hash.as_slice(), ":content": content})?; + Ok(()) + } + + fn delete_by_hash(&mut self, ty: Ty, content_hash: ContentHash) -> Result<()> { + self.ensure_table_exists(&ty)?; + let table_name = Self::table_name(&ty); + let query = format!("DELETE FROM \"{}\" WHERE content_hash = :hash", table_name); + self.conn.execute(&query, named_params! {":hash": content_hash.hash.as_slice()})?; + Ok(()) + } + + fn delete_all_of_ty(&mut self, ty: Ty) -> Result<()> { + let table_name = Self::table_name(&ty); + let query = format!("DROP TABLE IF EXISTS \"{}\"", table_name); + self.conn.execute(&query, [])?; + Ok(()) + } +} + +/// In-memory storage with one table per type. #[derive(Debug, Deserialize, Serialize)] pub struct Memory { - values: HashMap, Vec>, + tables: HashMap>>, } impl Memory { pub fn new() -> Self { Self { - values: HashMap::new(), + tables: HashMap::new(), } } } + +impl Backend for Memory { + fn get_from_hash(&mut self, ty: Ty, content_hash: ContentHash) -> Result>> { + Ok(self.tables + .get(&ty) + .and_then(|table| table.get(&content_hash)) + .cloned()) + } + + fn get_all_of_ty(&mut self, ty: Ty) -> Result)>> { + Ok(self.tables + .get(&ty) + .map(|table| { + table.iter() + .map(|(k, v)| (*k, v.clone())) + .collect() + }) + .unwrap_or_else(Vec::new)) + } + + fn write(&mut self, ty: Ty, content_hash: ContentHash, content: Vec) -> Result<()> { + self.tables + .entry(ty) + .or_insert_with(HashMap::new) + .insert(content_hash, content); + Ok(()) + } + + fn delete_by_hash(&mut self, ty: Ty, content_hash: ContentHash) -> Result<()> { + if let Some(table) = self.tables.get_mut(&ty) { + table.remove(&content_hash); + } + Ok(()) + } + + fn delete_all_of_ty(&mut self, ty: Ty) -> Result<()> { + self.tables.remove(&ty); + Ok(()) + } +} diff --git a/crates/vanth/tests/integration/store.rs b/crates/vanth/tests/integration/store.rs index 06e3af0..af27d6d 100644 --- a/crates/vanth/tests/integration/store.rs +++ b/crates/vanth/tests/integration/store.rs @@ -1,26 +1,47 @@ -use vanth::store::Store; +use serde::{Deserialize, Serialize}; +use vanth::{hash, store::Store, Vanth}; use std::path::PathBuf; use tempfile::TempDir; +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Vanth)] +struct Foo { + inner: i32, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Vanth)] +struct Bar { + inner: String, +} + #[test] fn test_sqlite_store() { let dir = TempDir::new().unwrap(); let path = dir.path().join("test.db"); let mut store = Store::from_path(path.clone()).unwrap(); - assert_eq!(store.read(b"key_1"), Ok(None)); - assert_eq!(store.write(b"key_1", b"value_1"), Ok(())); + let foo_1 = Foo { inner: 1 }; + let foo_2 = Foo { inner: 2 }; + let bar_1 = Bar { inner: "hello".into() }; - let value = store.read(b"key_1").unwrap(); - assert_eq!(value.as_deref(), Some(b"value_1" as &[u8])); + assert_eq!(store.get_all_of_type::().unwrap().len(), 0); + assert_eq!(store.get_all_of_type::().unwrap().len(), 0); - drop(store); + store.write(&foo_1).unwrap(); + store.write(&foo_2).unwrap(); + store.write(&bar_1).unwrap(); + assert_eq!(store.get_all_of_type::().unwrap().len(), 2); + assert_eq!(store.get_all_of_type::().unwrap().len(), 1); - let mut store = Store::from_path(path.clone()).unwrap(); + let foo_2_hash = hash(&foo_2); + let foo_2_fetched = store.get_from_hash(foo_2_hash).unwrap().unwrap(); + assert_ne!(foo_1, foo_2_fetched); + assert_eq!(foo_2, foo_2_fetched); - let value = store.read(b"key_1").unwrap(); - assert_eq!(value.as_deref(), Some(b"value_1" as &[u8])); + store.delete::(foo_2_hash).unwrap(); + assert_eq!(store.get_all_of_type::().unwrap().len(), 1); - store.delete(b"key_1").unwrap(); - assert_eq!(store.read(b"key_1"), Ok(None)); + store.delete_all::().unwrap(); + store.delete_all::().unwrap(); + assert_eq!(store.get_all_of_type::().unwrap().len(), 0); + assert_eq!(store.get_all_of_type::().unwrap().len(), 0); } diff --git a/crates/varo/src/lib.rs b/crates/varo/src/lib.rs index 4b804a9..af7fd7b 100644 --- a/crates/varo/src/lib.rs +++ b/crates/varo/src/lib.rs @@ -38,11 +38,11 @@ pub fn rng_gen_f32(rng: &mut Rng) -> f32 { } pub fn rng_gen_gaussian(rng: &mut Rng, mean: f32, std_dev: f32) -> f32 { - let u = rng_gen_f32(rng); - let v = rng_gen_f32(rng); - let s = (-2.0 * (1.0 - u).ln()).sqrt(); - let angle = 2.0 * PI * v; - mean + std_dev * s * angle.cos() + let uniform_for_radius_calc = rng_gen_f32(rng); + let uniform_for_angle = rng_gen_f32(rng); + let radius = (-2.0 * (1.0 - uniform_for_radius_calc).ln()).sqrt(); + let theta = 2.0 * PI * uniform_for_angle; + mean + std_dev * radius * theta.cos() } pub trait Varo { @@ -58,10 +58,12 @@ pub struct Distribution { impl Distribution { pub fn sample(&self, digest: &mut Rng) -> f32 { - if self.moments.len() >= 2 { - rng_gen_gaussian(digest, self.moments[0], self.moments[1].sqrt()) - } else { + if self.moments.is_empty() { rng_gen_f32(digest) + } else if self.moments.len() == 1 { + rng_gen_gaussian(digest, self.moments[0], 1.0) + } else { + rng_gen_gaussian(digest, self.moments[0], self.moments[1].sqrt()) } } }