From ce5ed3f54e873fff9135313a4ed9fa6656caf741 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Tue, 6 Jan 2026 02:44:35 +0100 Subject: draft object serialization --- Cargo.lock | 7 +++ Cargo.toml | 1 + common/object/Cargo.toml | 7 +++ common/object/src/lib.rs | 132 +++++++++++++++++++++++++++++++++++++++++++++++ common/src/lib.rs | 29 +++++++++++ 5 files changed, 176 insertions(+) create mode 100644 common/object/Cargo.toml create mode 100644 common/object/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index a50ba6a..a6a3ff4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1912,6 +1912,13 @@ dependencies = [ "tokio", ] +[[package]] +name = "jellyobject" +version = "0.1.0" +dependencies = [ + "bytemuck", +] + [[package]] name = "jellyremuxer" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index b418dbe..86832cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "stream/types", "ui", "remuxer", + "common/object", ] resolver = "3" diff --git a/common/object/Cargo.toml b/common/object/Cargo.toml new file mode 100644 index 0000000..04dc228 --- /dev/null +++ b/common/object/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "jellyobject" +version = "0.1.0" +edition = "2024" + +[dependencies] +bytemuck = "1.24.0" diff --git a/common/object/src/lib.rs b/common/object/src/lib.rs new file mode 100644 index 0000000..33d38d1 --- /dev/null +++ b/common/object/src/lib.rs @@ -0,0 +1,132 @@ +/* + 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) 2025 metamuffin +*/ + +use std::marker::PhantomData; + +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Tag(pub u32); +pub struct TypedTag(pub Tag, pub PhantomData); + +pub struct ObjectBuffer(pub Vec); + +impl ObjectBuffer { + pub fn new() -> Self { + Self(vec![0]) + } + pub fn as_object<'a>(&'a self) -> Object<'a> { + Object::load(&self.0).unwrap() + } +} + +pub struct Object<'a> { + tags: &'a [u32], + offsets: &'a [u32], + values: &'a [u32], +} +impl<'a> Object<'a> { + pub fn load(buf: &'a [u32]) -> Option { + let nf = *buf.get(0)? as usize; + Some(Self { + tags: &buf[1..1 + nf], + offsets: &buf[1 + nf..1 + nf + nf], + values: &buf[1 + nf + nf..], + }) + } + pub fn get_aligned(&self, tag: Tag) -> Option<&[u32]> { + let index = self.tags.binary_search(&tag.0).ok()?; + let start_raw = *self.offsets.get(index)?; + let end_raw = self + .offsets + .get(index) + .copied() + .unwrap_or((self.values.len() as u32) << 2); + + let start = start_raw >> 2; + let end = end_raw >> 2; + + Some(&self.values[start as usize..end as usize]) + } + pub fn get_unaligned(&self, tag: Tag) -> Option<&[u8]> { + let index = self.tags.binary_search(&tag.0).ok()?; + let start_raw = *self.offsets.get(index)?; + let end_raw = self + .offsets + .get(index) + .copied() + .unwrap_or((self.values.len() as u32) << 2); + + let start = (start_raw >> 2) * 4; + let padding = start_raw & 0b11; + let end = (end_raw >> 2) * 4 - padding; + + let values_u8: &[u8] = bytemuck::cast_slice(self.values); + Some(&values_u8[start as usize..end as usize]) + } + pub fn get_str(&self, tag: Tag) -> Option<&str> { + self.get_unaligned(tag).and_then(|b| str::from_utf8(b).ok()) + } + + pub fn get<'b: 'a, T: Value<'b>>(&'b self, tag: TypedTag) -> Option { + if T::ALIGNED { + T::load_aligned(self.get_aligned(tag.0)?) + } else { + T::load_unaligned(self.get_unaligned(tag.0)?) + } + } +} + +pub trait Value<'a>: Sized { + const ALIGNED: bool; + fn load_aligned(buf: &'a [u32]) -> Option { + let _ = buf; + None + } + fn load_unaligned(buf: &'a [u8]) -> Option { + let _ = buf; + None + } + fn store(&self, buf: &mut Vec); + fn size(&self) -> usize; +} +impl<'a> Value<'a> for &'a str { + const ALIGNED: bool = false; + fn load_unaligned(buf: &'a [u8]) -> Option { + str::from_utf8(buf).ok() + } + fn store(&self, buf: &mut Vec) { + buf.extend(self.as_bytes()); + } + fn size(&self) -> usize { + self.len() + } +} +impl Value<'_> for u32 { + const ALIGNED: bool = false; + fn load_aligned(buf: &[u32]) -> Option { + buf.get(0).copied() + } + fn store(&self, buf: &mut Vec) { + buf.extend(self.to_ne_bytes()); + } + fn size(&self) -> usize { + 4 + } +} +impl Value<'_> for u64 { + const ALIGNED: bool = false; + fn load_aligned(buf: &[u32]) -> Option { + let hi = *buf.get(0)? as u64; + let lo = *buf.get(1)? as u64; + Some(hi << 32 | lo) + } + fn store(&self, buf: &mut Vec) { + buf.extend(self.to_ne_bytes()); + } + fn size(&self) -> usize { + 8 + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index 81d6d15..769cfe2 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -53,6 +53,35 @@ pub enum NodeIDOrSlug { Slug(String), } +macro_rules! keys { + ($($id:ident = $tag:literal $name:literal;)*) => { + $(const $id: Tag = Tag($tag);)* + }; +} + +keys! { + N_KIND = 1 "kind"; + N_TITLE = 2 "title"; + N_PARENT = 3 "parent"; + N_TAGLINE = 4 "tagline"; + N_DESCRIPTION = 5 "description"; + N_RELEASEDATE = 6 "releasedate"; + N_INDEX = 7 "index"; + N_SEASON_INDEX = 8 "season_index"; + N_MEDIA = 9 "media"; + N_TAG = 10 "tag"; + N_RATINGS = 11 "ratings"; + N_PICTURES = 12 "pictures"; + N_IDENTIFIERS = 13 "identifiers"; + N_VISIBILITY = 14 "visibility"; + N_STORAGE_SIZE = 15 "storage_size"; + + LANG_NATIVE = 0xa001 "native"; + LANG_ENG = 0xa002 "eng"; + LANG_DEU = 0xa003 "deu"; + LANG_JPN = 0xa003 "jpn"; +} + #[derive(Debug, Clone, Deserialize, Serialize, Default)] pub struct Node { pub slug: String, -- cgit v1.3