aboutsummaryrefslogtreecommitdiff
path: root/common/object/src
diff options
context:
space:
mode:
Diffstat (limited to 'common/object/src')
-rw-r--r--common/object/src/lib.rs132
1 files changed, 132 insertions, 0 deletions
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
+ }
+}