aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2026-01-06 02:44:35 +0100
committermetamuffin <metamuffin@disroot.org>2026-01-06 02:44:35 +0100
commitce5ed3f54e873fff9135313a4ed9fa6656caf741 (patch)
tree80e94a83d636308cb8f3758b18b96c3ded0edcf8
parentd543f6fe11a32dcead2310f1fb4c2abd303f5f8c (diff)
downloadjellything-ce5ed3f54e873fff9135313a4ed9fa6656caf741.tar
jellything-ce5ed3f54e873fff9135313a4ed9fa6656caf741.tar.bz2
jellything-ce5ed3f54e873fff9135313a4ed9fa6656caf741.tar.zst
draft object serialization
-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml1
-rw-r--r--common/object/Cargo.toml7
-rw-r--r--common/object/src/lib.rs132
-rw-r--r--common/src/lib.rs29
5 files changed, 176 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a50ba6a..a6a3ff4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1913,6 +1913,13 @@ dependencies = [
]
[[package]]
+name = "jellyobject"
+version = "0.1.0"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
name = "jellyremuxer"
version = "0.1.0"
dependencies = [
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 <metamuffin.org>
+*/
+
+use std::marker::PhantomData;
+
+#[repr(transparent)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Tag(pub u32);
+pub struct TypedTag<T>(pub Tag, pub PhantomData<T>);
+
+pub struct ObjectBuffer(pub Vec<u32>);
+
+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<Self> {
+ 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<T>) -> Option<T> {
+ 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<Self> {
+ let _ = buf;
+ None
+ }
+ fn load_unaligned(buf: &'a [u8]) -> Option<Self> {
+ let _ = buf;
+ None
+ }
+ fn store(&self, buf: &mut Vec<u8>);
+ fn size(&self) -> usize;
+}
+impl<'a> Value<'a> for &'a str {
+ const ALIGNED: bool = false;
+ fn load_unaligned(buf: &'a [u8]) -> Option<Self> {
+ str::from_utf8(buf).ok()
+ }
+ fn store(&self, buf: &mut Vec<u8>) {
+ 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<Self> {
+ buf.get(0).copied()
+ }
+ fn store(&self, buf: &mut Vec<u8>) {
+ 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<Self> {
+ let hi = *buf.get(0)? as u64;
+ let lo = *buf.get(1)? as u64;
+ Some(hi << 32 | lo)
+ }
+ fn store(&self, buf: &mut Vec<u8>) {
+ 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,