diff options
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | common/object/Cargo.toml | 3 | ||||
| -rw-r--r-- | common/object/src/buffer.rs | 12 | ||||
| -rw-r--r-- | common/object/src/lib.rs | 5 | ||||
| -rw-r--r-- | common/object/src/registry.rs | 27 | ||||
| -rw-r--r-- | common/object/src/tests.rs | 12 | ||||
| -rw-r--r-- | common/object/src/value.rs | 34 | ||||
| -rw-r--r-- | common/src/lib.rs | 27 | ||||
| -rw-r--r-- | common/src/routes.rs | 6 | ||||
| -rw-r--r-- | database/src/indices/key.rs | 26 | ||||
| -rw-r--r-- | database/src/indices/mod.rs | 11 | ||||
| -rw-r--r-- | database/src/indices/order.rs | 21 | ||||
| -rw-r--r-- | database/src/table.rs | 24 |
13 files changed, 158 insertions, 51 deletions
@@ -1888,6 +1888,7 @@ name = "jellyobject" version = "0.1.0" dependencies = [ "bytemuck", + "log", ] [[package]] diff --git a/common/object/Cargo.toml b/common/object/Cargo.toml index 04dc228..a5825ee 100644 --- a/common/object/Cargo.toml +++ b/common/object/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] -bytemuck = "1.24.0" +bytemuck = { version = "1.24.0", features = ["extern_crate_std"] } +log = "0.4.29" diff --git a/common/object/src/buffer.rs b/common/object/src/buffer.rs index 56b8caf..dbde833 100644 --- a/common/object/src/buffer.rs +++ b/common/object/src/buffer.rs @@ -5,6 +5,7 @@ */ use crate::{Object, Tag, ValueStore}; +use bytemuck::try_cast_vec; pub struct ObjectBuffer(pub Vec<u32>); @@ -48,3 +49,14 @@ impl ObjectBuffer { ) } } + +impl From<Vec<u8>> for ObjectBuffer { + fn from(value: Vec<u8>) -> Self { + ObjectBuffer(try_cast_vec(value).unwrap_or_else(|(_, v)| { + v.into_iter() + .array_chunks() + .map(u32::from_ne_bytes) + .collect() + })) + } +} diff --git a/common/object/src/lib.rs b/common/object/src/lib.rs index 831dee7..522b6c1 100644 --- a/common/object/src/lib.rs +++ b/common/object/src/lib.rs @@ -3,12 +3,15 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ +#![feature(iter_array_chunks)] mod buffer; +mod registry; #[cfg(test)] mod tests; mod value; pub use buffer::*; +pub use registry::*; pub use value::*; use std::marker::PhantomData; @@ -18,7 +21,7 @@ use std::marker::PhantomData; pub struct Tag(pub u32); pub struct TypedTag<T>(pub Tag, pub PhantomData<T>); -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct Object<'a> { tags: &'a [u32], offsets: &'a [u32], diff --git a/common/object/src/registry.rs b/common/object/src/registry.rs new file mode 100644 index 0000000..7148efd --- /dev/null +++ b/common/object/src/registry.rs @@ -0,0 +1,27 @@ +/* + This file is part of jellything (https://codeberg.org/metamuffin/jellything) + which is licensed under the GNU Affero General Public License (version 3); see /COPYING. + Copyright (C) 2026 metamuffin <metamuffin.org> +*/ + +use crate::Tag; +use log::error; +use std::{any::TypeId, collections::BTreeMap}; + +#[derive(Default)] +pub struct Registry { + tags: BTreeMap<Tag, TagInfo>, +} +impl Registry { + pub fn add(&mut self, tag: Tag, info: TagInfo) { + if let Some(other) = self.tags.get(&tag) { + error!("Tag conflict: {:?} vs {:?}", info.name, other.name) + } + self.tags.insert(tag, info); + } +} + +pub struct TagInfo { + pub name: &'static str, + pub r#type: Option<TypeId>, +} diff --git a/common/object/src/tests.rs b/common/object/src/tests.rs index 35a29ba..143782c 100644 --- a/common/object/src/tests.rs +++ b/common/object/src/tests.rs @@ -37,3 +37,15 @@ fn read_multi_field() { assert_eq!(friends.next(), Some("Charlie")); assert_eq!(friends.next(), None); } + +#[test] +fn vec_align_test() { + let mut c = 0; + for _ in 0..10_000 { + let x = Vec::<u8>::with_capacity(16); + if x.as_ptr().align_offset(4) == 0 { + c += 1; + } + } + assert_eq!(c, 10_000, "correctly aligned vecs by system allocator") +} diff --git a/common/object/src/value.rs b/common/object/src/value.rs index d77d53a..1b24e79 100644 --- a/common/object/src/value.rs +++ b/common/object/src/value.rs @@ -107,3 +107,37 @@ impl ValueStore for ObjectBuffer { self.0.len() * 4 } } +impl<'a> Value<'a> for &'a [u8] { + const ALIGNED: bool = false; + fn load_unaligned(buf: &'a [u8]) -> Option<Self> { + Some(buf) + } +} +impl ValueStore for &[u8] { + fn is_aligned(&self) -> bool { + false + } + fn store_unaligned(&self, buf: &mut Vec<u8>) { + buf.extend(*self); + } + fn size(&self) -> usize { + self.len() + } +} +impl<'a> Value<'a> for &'a [u32] { + const ALIGNED: bool = true; + fn load_aligned(buf: &'a [u32]) -> Option<Self> { + Some(buf) + } +} +impl ValueStore for &[u32] { + fn is_aligned(&self) -> bool { + true + } + fn store_aligned(&self, buf: &mut Vec<u32>) { + buf.extend(*self); + } + fn size(&self) -> usize { + self.len() * 4 + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index 9ab0b91..1359c73 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -4,26 +4,39 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ #![feature(array_try_map)] -pub mod api; pub mod routes; -pub mod user; -use jellyobject::{Object, Tag, TypedTag}; +use jellyobject::{Object, Registry, Tag, TagInfo, TypedTag}; pub use jellystream_types as stream; -use std::marker::PhantomData; +use std::{any::TypeId, marker::PhantomData, sync::LazyLock}; + +pub use jellyobject; + +pub static TAGREG: LazyLock<Registry> = LazyLock::new(|| { + let mut reg = Registry::default(); + register_fields(&mut reg); + register_enums(&mut reg); + reg +}); macro_rules! fields { ($($id:ident: $type:ty = $tag:literal $name:literal;)*) => { $(pub const $id: TypedTag<$type> = TypedTag(Tag($tag), PhantomData);)* + fn register_fields(reg: &mut Registry) { + $(reg.add(Tag($tag), TagInfo { name: $name, r#type: Some(TypeId::of::<$type>()) });)* + } }; } macro_rules! enums { ($($id:ident = $tag:literal $name:literal;)*) => { $(pub const $id: Tag = Tag($tag);)* + fn register_enums(reg: &mut Registry) { + $(reg.add(Tag($tag), TagInfo { name: $name, r#type: None });)* + } }; } fields! { - // Tag counter: 32 + // Tag counter: 36 NO_KIND: Tag = 1 "kind"; NO_TITLE: &str = 2 "title"; @@ -44,8 +57,8 @@ fields! { NO_CREDIT: Object = 33 "credit"; // multi CR_NODE: u64 = 34 "node"; - CR_KIND: Tag = 34 "kind"; - CR_JOB: &str = 34 "node"; + CR_KIND: Tag = 35 "kind"; + CR_ROLE: &str = 36 "role"; // multi TR_KIND: Tag = 16 "kind"; TR_SOURCE: &str = 17 "source"; diff --git a/common/src/routes.rs b/common/src/routes.rs index b11a622..48f975a 100644 --- a/common/src/routes.rs +++ b/common/src/routes.rs @@ -3,8 +3,6 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::api::NodeFilterSort; - pub fn u_home() -> String { "/home".to_owned() } @@ -23,7 +21,7 @@ pub fn u_node_slug_player_time(node: &str, time: f64) -> String { pub fn u_node_image(node: &str, slot: &str, size: usize) -> String { format!("/n/{node}/image/{slot}?size={size}") } -pub fn u_node_slug_watched(node: &str, state: ApiWatchedState) -> String { +pub fn u_node_slug_watched(node: &str, state: &str) -> String { format!("/n/{node}/watched?state={state}") } pub fn u_node_slug_person_asset(node: &str, group: &str, index: usize, size: usize) -> String { @@ -41,7 +39,7 @@ pub fn u_node_slug_progress(node: &str, time: f64) -> String { pub fn u_items() -> String { "/items".to_string() } -pub fn u_items_filter(page: usize, _filter: &NodeFilterSort) -> String { +pub fn u_items_filter(page: usize) -> String { // TODO format!("/items?page={page}") } diff --git a/database/src/indices/key.rs b/database/src/indices/key.rs index ab38d71..2790220 100644 --- a/database/src/indices/key.rs +++ b/database/src/indices/key.rs @@ -8,18 +8,18 @@ use crate::{ backends::{ReadTransaction, WriteTransaction}, indices::Index, prefix_iterator::PrefixIterator, - table::{RowNum, Table, TableNum}, + table::{RowNum, TableNum}, }; use anyhow::Result; +use jellycommon::jellyobject::{Object, Tag}; -pub struct KeyIndex<T> { +pub struct KeyIndex { id: TableNum, - key: fn(&T) -> &[u8], + key: Vec<Tag>, } -impl<T: 'static> KeyIndex<T> { - pub fn new(table: &mut Table<T>, id: TableNum, key: fn(&T) -> &[u8]) -> Self { - table.indices.push(Box::new(Self { id, key })); +impl KeyIndex { + pub fn new(id: TableNum, key: Vec<Tag>) -> Self { Self { id, key } } pub fn key(&self, id: RowNum, key: &[u8]) -> Vec<u8> { @@ -43,15 +43,15 @@ impl<T: 'static> KeyIndex<T> { }) } } -impl<T: 'static> Index<T> for KeyIndex<T> { - fn add(&self, db: &mut dyn WriteTransaction, id: RowNum, val: &T) -> Result<()> { - db.set(&self.key(id, (self.key)(val)), &[]) +impl Index for KeyIndex { + fn add(&self, db: &mut dyn WriteTransaction, id: RowNum, val: Object) -> Result<()> { + // db.set(&self.key(id, (self.key)(val)), &[]) } - fn remove(&self, db: &mut dyn WriteTransaction, id: RowNum, val: &T) -> Result<()> { - db.del(&self.key(id, (self.key)(val))) + fn remove(&self, db: &mut dyn WriteTransaction, id: RowNum, val: Object) -> Result<()> { + // db.del(&self.key(id, (self.key)(val))) } - fn compare(&self, before: &T, after: &T) -> bool { - (self.key)(before) == (self.key)(after) + fn compare(&self, before: Object, after: Object) -> bool { + // (self.key)(before) == (self.key)(after) } } diff --git a/database/src/indices/mod.rs b/database/src/indices/mod.rs index ab37589..ad3d00f 100644 --- a/database/src/indices/mod.rs +++ b/database/src/indices/mod.rs @@ -6,14 +6,15 @@ use crate::{backends::WriteTransaction, table::RowNum}; use anyhow::Result; +use jellycommon::jellyobject::Object; -pub mod order; pub mod key; +pub mod order; -pub trait Index<T> { - fn add(&self, db: &mut dyn WriteTransaction, row: RowNum, val: &T) -> Result<()>; - fn remove(&self, db: &mut dyn WriteTransaction, row: RowNum, val: &T) -> Result<()>; - fn compare(&self, before: &T, after: &T) -> bool { +pub trait Index { + fn add(&self, db: &mut dyn WriteTransaction, row: RowNum, val: Object) -> Result<()>; + fn remove(&self, db: &mut dyn WriteTransaction, row: RowNum, val: Object) -> Result<()>; + fn compare(&self, before: Object, after: Object) -> bool { let _ = (before, after); true } diff --git a/database/src/indices/order.rs b/database/src/indices/order.rs index 9163681..911ec37 100644 --- a/database/src/indices/order.rs +++ b/database/src/indices/order.rs @@ -7,34 +7,35 @@ use crate::{ backends::WriteTransaction, indices::Index, - table::{RowNum, Table, TableNum}, + table::{RowNum, TableNum}, }; use anyhow::Result; use bytemuck::{NoUninit, bytes_of}; +use jellycommon::jellyobject::{Object, Tag}; -pub struct OrderIndex<T> { +#[derive(Clone)] +pub struct OrderIndex { id: TableNum, - value: fn(&T) -> [u8; 8], + key: Vec<Tag>, } #[repr(C)] #[derive(NoUninit, Clone, Copy)] struct Key(TableNum, [u8; 8], RowNum); -impl<T: 'static> OrderIndex<T> { - pub fn new(table: &mut Table<T>, id: TableNum, value: fn(&T) -> [u8; 8]) -> Self { - table.indices.push(Box::new(Self { id, value })); +impl OrderIndex { + pub fn new(id: TableNum, value: fn(Object) -> [u8; 8]) -> Self { Self { id, value } } } -impl<T: 'static> Index<T> for OrderIndex<T> { - fn add(&self, db: &mut dyn WriteTransaction, id: RowNum, val: &T) -> Result<()> { +impl Index for OrderIndex { + fn add(&self, db: &mut dyn WriteTransaction, id: RowNum, val: Object) -> Result<()> { db.set(bytes_of(&Key(self.id, (self.value)(val), id)), &[]) } - fn remove(&self, db: &mut dyn WriteTransaction, id: RowNum, val: &T) -> Result<()> { + fn remove(&self, db: &mut dyn WriteTransaction, id: RowNum, val: Object) -> Result<()> { db.del(bytes_of(&Key(self.id, (self.value)(val), id))) } - fn compare(&self, before: &T, after: &T) -> bool { + fn compare(&self, before: Object, after: Object) -> bool { (self.value)(before) == (self.value)(after) } } diff --git a/database/src/table.rs b/database/src/table.rs index ba95f8e..42e03a3 100644 --- a/database/src/table.rs +++ b/database/src/table.rs @@ -4,21 +4,23 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ +use std::sync::Arc; + use crate::{ backends::{ReadTransaction, WriteTransaction}, indices::Index, }; use anyhow::Result; -use serde::{Serialize, de::DeserializeOwned}; +use jellycommon::jellyobject::{Object, ObjectBuffer}; pub type TableNum = u64; pub type RowNum = u64; -pub struct Table<T> { +pub struct Table { id: u32, - pub(crate) indices: Vec<Box<dyn Index<T>>>, + pub(crate) indices: Vec<Box<dyn Index>>, } -impl<T: Serialize + DeserializeOwned> Table<T> { +impl Table { pub fn new(id: u32) -> Self { Self { id, @@ -49,18 +51,20 @@ impl<T: Serialize + DeserializeOwned> Table<T> { Ok(id_counter) } - pub fn get(&self, db: &dyn ReadTransaction, row: RowNum) -> Result<Option<T>> { - Ok(db - .get(&self.key(row))? - .map(|v| serde_json::from_slice(&v)) - .transpose()?) + pub fn add_index<T: Index + Clone + 'static>(&mut self, index: T) -> T { + self.indices.push(Box::new(index.clone())); + index + } + pub fn get(&self, db: &dyn ReadTransaction, row: RowNum) -> Result<Option<ObjectBuffer>> { + Ok(db.get(&self.key(row))?.map(ObjectBuffer::from)) } pub fn remove(&self, db: &mut dyn WriteTransaction, row: RowNum) -> Result<bool> { let Some(entry) = self.get(db, row)? else { return Ok(false); }; + let ob = entry.as_object(); for idx in &self.indices { - idx.remove(db, row, &entry)?; + idx.remove(db, row, ob)?; } db.del(&self.key(row))?; Ok(true) |