#![doc(html_favicon_url = "../../../icon/noatun-black128.png")]
#![allow(clippy::collapsible_if)]
#![allow(clippy::comparison_chain)]
#![allow(clippy::bool_comparison)]
#![deny(clippy::missing_safety_doc)]
#![deny(clippy::undocumented_unsafe_blocks)]
#![deny(missing_docs)]
#![allow(clippy::let_and_return)]
#![allow(clippy::collapsible_else_if)]
#![allow(clippy::expect_fun_call)]
#![allow(clippy::needless_late_init)]
#![allow(clippy::derivable_impls)]
#[cfg_attr(
feature = "tokio",
doc = " * [`communication::DatabaseCommunication`] - Networking support."
)]
pub use crate::data_types::{NoatunCell, OpaqueNoatunCell};
use crate::noatun_instant::Instant;
use crate::private::Sealed;
use crate::sequence_nr::Tracker;
use anyhow::{bail, Result};
use chrono::{DateTime, SecondsFormat, Utc};
pub use cutoff::{CutOffDuration, CutOffState};
pub use database::{Database, DatabaseSettings, OpenMode};
use datetime_literal::datetime;
#[doc(hidden)]
pub use paste::paste;
pub(crate) use projection_store::DatabaseContextData;
use rand::RngCore;
use savefile::Deserializer;
pub use savefile_derive::Savefile;
#[cfg(feature = "serde")]
use serde::de::DeserializeOwned;
#[cfg(feature = "serde")]
use serde::Serialize;
use sha2::{Digest, Sha256};
use std::borrow::Borrow;
use std::cell::Cell;
use std::cmp::Ordering;
use std::fmt::{Debug, Display, Formatter};
use std::hash::{Hash, Hasher};
use std::io::Write;
use std::marker::PhantomData;
use std::mem::{transmute_copy, MaybeUninit};
use std::ops::{Add, Sub};
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::path::{Path, PathBuf};
use std::pin::Pin;
use std::ptr::null_mut;
use std::slice;
use std::time::Duration;
use tracing::error;
pub mod diagnostics;
mod disk_abstraction;
mod message_store;
mod mini_pather;
mod projection_store;
#[cfg(feature = "debug_color")]
mod term_colors;
mod undo_store;
pub mod ratatui_inspector;
#[doc = include_str!("docs-svg.md")] pub mod guide {}
#[cfg(not(target_endian = "little"))]
compile_error!("Noatun currently only supports little-endian machines");
pub mod noatun_instant;
#[cfg(feature = "debug_color")]
use term_colors as colors;
#[cfg(not(feature = "debug_color"))]
mod dummy_term_colors;
#[cfg(not(feature = "debug_color"))]
use dummy_term_colors as colors;
#[cfg(feature = "debug_color")]
use crate::colors::{colored_hex_int, colored_hex_sint};
use crate::cutoff::CutOffTime;
#[cfg(feature = "tokio")]
pub mod communication;
pub mod distributor;
pub mod sequence_nr;
pub(crate) mod cutoff;
pub mod data_types;
pub mod database;
mod projector;
mod update_head_tracker;
mod private {
pub trait Sealed {}
}
pub(crate) mod platform_specific;
mod boot_checksum;
pub(crate) mod disk_access;
mod sha2_helper;
pub mod simple_metrics;
mod xxh3_vendored;
pub mod prelude {
pub use crate::data_types::{
NoatunCell, NoatunHashMap, NoatunKey, NoatunString, NoatunVec, OpaqueNoatunCell,
OpaqueNoatunString, OpaqueNoatunVec,
};
pub use crate::database::{Database, DatabaseSession, DatabaseSessionMut, DatabaseSettings};
pub use crate::{
CutOffDuration, CutOffState, Message, MessageId, NoatunPod, NoatunStorable, NoatunTime,
Object,
};
}
#[doc(hidden)]
#[cfg(feature = "debug")]
#[macro_export]
macro_rules! dprintln {
($($x:tt)*) => { std::println!($($x)*) }
}
#[doc(hidden)]
#[cfg(not(feature = "debug"))]
#[macro_export]
macro_rules! dprintln {
($($x:tt)*) => {{}};
}
pub struct SchemaHasher(Sha256);
impl SchemaHasher {
pub fn write_str(&mut self, s: &str) {
self.0.update([0u8]);
self.0.update(s.len().to_le_bytes());
self.0.update(s.as_bytes());
}
pub fn write_usize(&mut self, n: usize) {
self.0.update([1u8]);
self.0.update(n.to_le_bytes());
}
}
pub fn calculate_schema_hash<T: Object>() -> [u8; 16] {
let mut temp = SchemaHasher(Sha256::default());
T::hash_object_schema(&mut temp);
use sha2::digest::FixedOutput;
temp.0.finalize_fixed()[0..16].try_into().unwrap()
}
impl Write for SchemaHasher {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.update(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be stored in a noatun database.",
label = "`{Self}` does not implement NoatunStorable.",
note = "For structs, use the `noatun_object!`-macro to create a noatun object type.",
note = "For collections, try `NoatunHashMap` or `NoatunVec`.",
note = "Manually implementing this trait is not recommended, but can be possible. See trait docs."
)]
pub unsafe trait NoatunStorable: Sized + 'static {
fn zeroed<T: NoatunStorable>() -> T {
unsafe { MaybeUninit::<T>::zeroed().assume_init() }
}
fn copy_from(&mut self, source: &Self) {
unsafe {
let temp = (source as *const Self).read();
(self as *mut Self).write(temp);
}
}
fn initialize(dest: &mut MaybeUninit<Self>, source: &Self) {
unsafe {
let temp = (source as *const Self).read();
(dest as *mut MaybeUninit<Self> as *mut Self).write(temp);
}
}
fn hash_schema(hasher: &mut SchemaHasher);
}
pub unsafe trait NoatunPod: Copy + NoatunStorable {}
mod noatun_storable_impls {
use super::NoatunStorable;
use crate::SchemaHasher;
macro_rules! make_noatun_storable_primitive {
($t: ident) => {
unsafe impl NoatunStorable for $t {
fn hash_schema(hasher: &mut SchemaHasher) {
hasher.write_str(concat!("std::", stringify!($t)));
}
}
unsafe impl $crate::NoatunPod for $t {}
};
}
make_noatun_storable_primitive!(u8);
make_noatun_storable_primitive!(u16);
make_noatun_storable_primitive!(u32);
make_noatun_storable_primitive!(u64);
make_noatun_storable_primitive!(u128);
make_noatun_storable_primitive!(i8);
make_noatun_storable_primitive!(i16);
make_noatun_storable_primitive!(i32);
make_noatun_storable_primitive!(i64);
make_noatun_storable_primitive!(i128);
make_noatun_storable_primitive!(isize);
make_noatun_storable_primitive!(usize);
unsafe impl<T: NoatunStorable, const N: usize> NoatunStorable for [T; N] {
fn hash_schema(hasher: &mut SchemaHasher) {
hasher.write_str(concat!("std::[_:", stringify!(N), "]"));
T::hash_schema(hasher);
}
}
}
thread_local! {
pub static CONTEXT: Cell<*mut DatabaseContextData> = const { Cell::new(null_mut()) };
}
#[cfg(any(feature = "debug", debug_assertions))]
thread_local! {
pub static DEBUG_NODE: Cell<u16> = const { Cell::new(0) };
}
#[doc(hidden)]
#[cfg(any(feature = "debug", debug_assertions))]
#[macro_export]
macro_rules! track_node {
($node:expr) => {
$crate::DEBUG_NODE.set($node);
};
}
#[doc(hidden)]
#[cfg(not(any(feature = "debug", debug_assertions)))]
#[macro_export]
macro_rules! track_node {
($_node:expr) => {};
}
#[doc(hidden)]
#[inline]
pub fn cur_node() -> u16 {
#[cfg(any(feature = "debug", debug_assertions))]
{
DEBUG_NODE.get()
}
#[cfg(not(any(feature = "debug", debug_assertions)))]
{
65535
}
}
#[derive(Clone, Copy)]
pub struct NoatunContext;
#[inline]
fn get_context_mut_ptr() -> *mut DatabaseContextData {
let context_ptr = CONTEXT.get();
if context_ptr.is_null() {
panic!(
"No NoatunContext is presently available on this thread. The noatun-database \
may only be accessed from within Message apply, and from `with_root`-methods."
);
}
unsafe { (*context_ptr).assert_mutable() }
context_ptr
}
#[inline]
fn get_context_ptr() -> *const DatabaseContextData {
let context_ptr = CONTEXT.get();
if context_ptr.is_null() {
panic!("No NoatunContext is presently available on this thread");
}
context_ptr
}
impl NoatunContext {
#[inline]
pub fn assert_opaque_access_allowed(self, used_type: &str, alternative_type: &str) {
let context = unsafe { &*get_context_ptr() };
if context.is_message_apply() {
panic!(
"An attempt was made to read from {used_type} within Message::apply. \
This is not allowed. Avoid reading this data, or change to use {alternative_type} instead."
);
}
}
pub unsafe fn update_tracker_ptr(self, seq: *mut Tracker, opaque: bool) {
let context_ptr = get_context_mut_ptr();
unsafe { (*context_ptr).update_tracker_ptr(seq, opaque) }
}
pub unsafe fn clear_tracker_ptr(self, seq: *mut Tracker, opaque: bool) {
let context_ptr = get_context_mut_ptr();
unsafe { (*context_ptr).clear_registrar_ptr(seq, opaque) }
}
pub fn start_ptr_mut(self) -> *mut u8 {
let context_ptr = get_context_mut_ptr();
unsafe { (*context_ptr).start_ptr_mut() }
}
pub fn start_ptr(self) -> *const u8 {
let context_ptr = get_context_ptr();
unsafe { (*context_ptr).start_ptr() }
}
pub fn index_of<T: Object + ?Sized>(self, t: &T) -> T::Ptr {
let context_ptr = get_context_ptr();
unsafe { (*context_ptr).index_of(t) }
}
#[cfg(test)]
pub(crate) unsafe fn rewind(self, new_time: crate::sequence_nr::SequenceNr) {
let context_ptr = get_context_mut_ptr();
unsafe { (*context_ptr).rewind(new_time) }
}
#[cfg(test)]
pub(crate) fn set_next_seqnr(self, seqnr: crate::sequence_nr::SequenceNr) {
let context_ptr = get_context_mut_ptr();
unsafe { (*context_ptr).set_next_seqnr(seqnr) }
}
pub unsafe fn copy_ptr(&self, src: FatPtr, dest_index: ThinPtr) {
let context_ptr = get_context_mut_ptr();
unsafe { (*context_ptr).copy_bytes(src, dest_index) }
}
pub unsafe fn zero(&self, dst: FatPtr) {
let context_ptr = get_context_mut_ptr();
unsafe { (*context_ptr).zero(dst) }
}
pub fn zero_storable<T: NoatunStorable>(&self, dst: Pin<&mut T>) {
let context_ptr = get_context_mut_ptr();
unsafe { (*context_ptr).zero_storable(dst) }
}
pub fn zero_internal<T: NoatunStorable>(&self, dst: &mut T) {
let context_ptr = get_context_mut_ptr();
unsafe { (*context_ptr).zero_internal(dst) }
}
pub unsafe fn copy_sized(&self, src: ThinPtr, dest_index: ThinPtr, size_bytes: usize) {
let context_ptr = get_context_mut_ptr();
unsafe { (*context_ptr).copy_bytes_len(src, dest_index, size_bytes) }
}
pub fn copy<T: NoatunStorable>(&self, src: &T, dst: &mut T) {
let context_ptr = get_context_mut_ptr();
unsafe { (*context_ptr).copy_storable(src, dst) }
}
pub fn index_of_ptr(&self, ptr: *const u8) -> ThinPtr {
let context_ptr = CONTEXT.get();
if context_ptr.is_null() {
panic!("No NoatunContext available");
}
unsafe { (*context_ptr).index_of_ptr(ptr) }
}
pub fn allocate_raw(&self, size: usize, align: usize) -> *mut u8 {
let context_ptr = get_context_mut_ptr();
unsafe { (*context_ptr).allocate_raw(size, align) }
}
pub fn update_tracker(&self, tracker: &mut Tracker, opaque: bool) {
let context_ptr = get_context_mut_ptr();
unsafe {
(*context_ptr).update_registrar(tracker, opaque);
}
}
pub fn write<T: NoatunStorable>(&self, value: T, dest: Pin<&mut T>) {
let context_ptr = get_context_mut_ptr();
unsafe { (*context_ptr).write_storable(value, dest) }
}
pub fn wrote_tombstone(&self) {
let context_ptr = get_context_mut_ptr();
unsafe { (*context_ptr).set_wrote_tombstone() }
}
pub(crate) fn write_internal<T: NoatunStorable>(&self, value: T, dest: &mut T) {
let context_ptr = get_context_mut_ptr();
unsafe { (*context_ptr).write_storable(value, Pin::new_unchecked(dest)) }
}
#[allow(clippy::not_unsafe_ptr_arg_deref)] pub fn write_ptr<T: NoatunStorable>(&self, value: T, dest: *mut T) {
let context_ptr = get_context_mut_ptr();
unsafe { (*context_ptr).write_storable_ptr(value, dest) }
}
pub unsafe fn allocate<'a, T: NoatunStorable>(&self) -> Pin<&'a mut T> {
let context_ptr = get_context_mut_ptr();
unsafe { (*context_ptr).allocate_storable() }
}
pub unsafe fn allocate_obj<'a, T: Object>(&self) -> Pin<&'a mut T> {
let context_ptr = get_context_mut_ptr();
unsafe { (*context_ptr).allocate_obj() }
}
pub unsafe fn access_thin<'a, T: ?Sized>(&self, ptr: ThinPtr) -> &'a T {
let context_ptr = CONTEXT.get();
if context_ptr.is_null() {
panic!("No NoatunContext available");
}
unsafe { (*context_ptr).access_thin::<T>(ptr) }
}
pub unsafe fn access_thin_mut<'a, T: ?Sized>(&self, ptr: ThinPtr) -> &'a mut T {
let context_ptr = CONTEXT.get();
if context_ptr.is_null() {
panic!("No NoatunContext available");
}
unsafe { (*context_ptr).access_thin_mut::<T>(ptr) }
}
pub unsafe fn access_fat<'a, T: ?Sized>(&self, ptr: FatPtr) -> &'a T {
let context_ptr = CONTEXT.get();
if context_ptr.is_null() {
panic!("No NoatunContext available");
}
unsafe { (*context_ptr).access_fat::<T>(ptr) }
}
pub unsafe fn access_fat_mut<'a, T: ?Sized>(&self, ptr: FatPtr) -> &'a mut T {
let context_ptr = CONTEXT.get();
if context_ptr.is_null() {
panic!("No NoatunContext available");
}
unsafe { (*context_ptr).access_fat_mut::<T>(ptr) }
}
pub fn observe_registrar(self, tracker: Tracker) {
let context_ptr = CONTEXT.get();
if context_ptr.is_null() {
return;
}
if unsafe { (*(context_ptr as *const DatabaseContextData)).is_message_apply == false } {
return;
}
unsafe { (*context_ptr).observe_registrar(tracker) }
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Savefile)]
#[repr(transparent)]
pub struct MessageId {
data: [u32; 4],
}
impl Hash for MessageId {
fn hash<H: Hasher>(&self, state: &mut H) {
let data1: [u64; 2] = unsafe { std::mem::transmute(self.data) };
state.write_u64(data1[0]);
state.write_u64(data1[1]);
}
}
unsafe impl NoatunStorable for MessageId {
fn hash_schema(hasher: &mut SchemaHasher) {
hasher.write_str("noatun::MessageId/1")
}
}
#[cfg(not(target_arch = "wasm32"))]
const _ASSURE_SUPPORTED_USIZE: () = const {
if size_of::<usize>() != 8 {
panic!("noatun currently only supports 64 bit platforms with 64 bit usize");
}
};
impl MessageId {
pub fn short(&self) -> String {
format!("{:08x}", self.data[3])
}
}
impl Display for MessageId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let time_ms = self.timestamp();
let time = chrono::DateTime::from_timestamp_millis(time_ms.as_ms() as i64).unwrap();
let time_str = time.to_rfc3339_opts(SecondsFormat::Millis, true);
#[cfg(feature = "debug_color")]
{
write!(
f,
"{:?}-{}-{}-{}",
time_str,
colored_hex_sint((self.data[1] & 0xffff).wrapping_sub(0x4000) as i32),
colored_hex_int(self.data[2]),
colored_hex_int(self.data[3])
)
}
#[cfg(not(feature = "debug_color"))]
{
if cfg!(all(test)) {
write!(
f,
"{:?}-{:x}-{:x}-{:x}",
time_str,
(self.data[1] & 0xffff).wrapping_sub(0x4000) as i32,
self.data[2],
self.data[3]
)
} else {
write!(
f,
"{:?}-{:04x}-{:08x}-{:08x}",
time_str,
(self.data[1] & 0xffff).wrapping_sub(0x4000) as i32,
self.data[2],
self.data[3]
)
}
}
}
}
impl Debug for MessageId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if cfg!(test) && self.data[0] == 0 {
write!(
f,
"#{:x}_{:x}_{:x}",
self.data[1], self.data[2], self.data[3]
)
} else {
write!(f, "{self}")
}
}
}
#[cfg(test)]
static FOR_TEST_NON_RANDOM_ID: bool = true;
#[cfg(test)]
static NON_RANDOM_ID_COUNTER: std::sync::atomic::AtomicUsize =
std::sync::atomic::AtomicUsize::new(0);
thread_local! {
static TEST_EPOCH: Cell<Option<Instant>> = const { Cell::new(None) };
}
#[cfg(test)]
pub fn reset_random_id() {
crate::distributor::NON_RANDOM_EPHEMERAL_NODE_ID_COUNTER
.store(0, std::sync::atomic::Ordering::SeqCst);
NON_RANDOM_ID_COUNTER.store(0, std::sync::atomic::Ordering::Relaxed);
}
pub fn test_epoch() -> Instant {
TEST_EPOCH.with(|epoch| match epoch.get() {
None => {
let now = Instant::now();
epoch.set(Some(now));
now
}
Some(val) => val,
})
}
pub fn set_test_epoch(instant: Instant) {
TEST_EPOCH.with(|epoch| {
epoch.set(Some(instant));
})
}
pub fn test_elapsed() -> Duration {
test_epoch().elapsed()
}
impl MessageId {
pub const ZERO: MessageId = MessageId { data: [0u32; 4] };
pub fn min(self, other: MessageId) -> MessageId {
if self < other {
self
} else {
other
}
}
pub fn as_bytes(&self) -> &[u8] {
cast_slice(&self.data)
}
pub fn is_zero(&self) -> bool {
self.data[0] == 0 && self.data[1] == 0
}
pub fn successor(&self) -> MessageId {
let mut temp = *self;
for element in temp.data.iter_mut().rev() {
if *element < u32::MAX {
*element += 1;
return temp;
}
*element = 0;
}
panic!("successor() invoked on MessageId::MAX");
}
pub fn new_debug(nr: u32) -> Self {
Self {
data: [0, 0, 0, nr],
}
}
pub fn debug_value(&self) -> u32 {
self.data[3]
}
pub fn new_debug2(nr: u64) -> Self {
MessageId::from_parts_for_test(
NoatunTime::from_datetime(datetime!(2020-01-01 T 00:00:00 Z)),
nr,
)
}
pub fn new_random() -> Result<Self> {
Self::generate_for_time(NoatunTime::now())
}
pub fn generate_for_time(time: NoatunTime) -> Result<MessageId> {
let mut random_part = [0u8; 10];
#[cfg(test)]
{
if FOR_TEST_NON_RANDOM_ID {
random_part[0..8].copy_from_slice(
&NON_RANDOM_ID_COUNTER
.fetch_add(1, std::sync::atomic::Ordering::SeqCst)
.to_le_bytes(),
);
} else {
rand::thread_rng().fill_bytes(&mut random_part);
}
}
#[cfg(not(test))]
{
rand::thread_rng().fill_bytes(&mut random_part);
}
let mut temp = Self::from_parts(time, random_part)?;
temp.pred_succ_reserve();
Ok(temp)
}
fn pred_succ_reserve(&mut self) {
let mut bits = (self.data[1] >> 14) & 3;
if bits == 0 {
bits = 1;
}
if bits == 3 {
bits = 2;
}
self.data[1] &= 0xffff_3fff;
self.data[1] |= bits << 14;
}
pub fn unique_successor(&self) -> Result<MessageId> {
let mut counter = self.data[1] & 0xffff;
if counter == 0xffff {
bail!(
"unique successors of {} exhausted, no more can be generated",
self
);
}
counter += 1;
let time = self.timestamp();
let mut raw = Self::generate_for_time(time)?;
raw.data[1] &= 0xffff_0000;
raw.data[1] |= counter;
Ok(raw)
}
pub fn unique_predecessor(&self) -> Result<MessageId> {
let mut counter = self.data[1] & 0xffff;
if counter == 0 {
bail!(
"unique predecessors of {} exhausted, no more can be generated",
self
);
}
counter -= 1;
let time = self.timestamp();
let mut raw = Self::generate_for_time(time)?;
raw.data[1] &= 0xffff_0000;
raw.data[1] |= counter;
Ok(raw)
}
pub fn from_parts_for_test(time: NoatunTime, random: u64) -> MessageId {
let mut data = [0u8; 10];
data[2..10].copy_from_slice(&random.to_le_bytes());
let mut temp = Self::from_parts(time, data).unwrap();
temp.pred_succ_reserve();
temp
}
pub fn timestamp(&self) -> NoatunTime {
let mut alternative_value = (self.data[0] as u64) << 16;
alternative_value |= (self.data[1] >> 16) as u64;
NoatunTime(alternative_value)
}
pub fn from_parts(time: NoatunTime, random: [u8; 10]) -> Result<MessageId> {
let t: u64 = time.as_ms();
Self::from_parts_raw(t, random)
}
pub fn is_time_valid_for_message(time: NoatunTime) -> bool {
time.as_ms() < 1 << 48
}
pub fn from_parts_raw(time: u64, random: [u8; 10]) -> Result<MessageId> {
if time >= 1 << 48 {
bail!("Time value is too large");
}
let mut data = [0u8; 16];
let time_le_bytes = time.to_le_bytes();
data[0..4].copy_from_slice(&time_le_bytes[2..6]);
data[4..6].copy_from_slice(&random[0..2]);
data[6..8].copy_from_slice(&time_le_bytes[0..2]);
data[8..16].copy_from_slice(&random[2..10]);
Ok(MessageId {
data: cast_storable(data),
})
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Savefile)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
pub struct NoatunTime(pub u64);
unsafe impl NoatunStorable for NoatunTime {
fn hash_schema(hasher: &mut SchemaHasher) {
hasher.write_str("noatun::NoatunTime/1")
}
}
unsafe impl NoatunPod for NoatunTime {}
impl Add<Duration> for NoatunTime {
type Output = NoatunTime;
fn add(self, rhs: Duration) -> Self::Output {
NoatunTime(self.0 + rhs.as_millis() as u64).truncated()
}
}
impl Sub<Duration> for NoatunTime {
type Output = NoatunTime;
fn sub(self, rhs: Duration) -> Self::Output {
NoatunTime(self.0.saturating_sub(rhs.as_millis() as u64)).truncated()
}
}
impl Add<NoatunTime> for Duration {
type Output = NoatunTime;
fn add(self, rhs: NoatunTime) -> Self::Output {
NoatunTime(rhs.0 + self.as_millis() as u64).truncated()
}
}
impl PartialEq<CutOffTime> for NoatunTime {
fn eq(&self, other: &CutOffTime) -> bool {
*self == other.to_noatun_time()
}
}
impl PartialOrd<CutOffTime> for NoatunTime {
fn partial_cmp(&self, other: &CutOffTime) -> Option<Ordering> {
Some(self.cmp(&other.to_noatun_time()))
}
}
impl From<DateTime<Utc>> for NoatunTime {
fn from(value: DateTime<Utc>) -> Self {
let ms = value.timestamp_millis();
if ms < 0 {
NoatunTime(0)
} else {
NoatunTime(ms as u64).truncated()
}
}
}
impl From<NoatunTime> for DateTime<Utc> {
fn from(value: NoatunTime) -> Self {
value.to_datetime()
}
}
impl Display for NoatunTime {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let time = chrono::DateTime::from_timestamp_millis(self.0 as i64).unwrap();
let time_str = time.to_rfc3339_opts(SecondsFormat::Millis, true);
write!(f, "{time_str}")
}
}
impl Debug for NoatunTime {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let time = chrono::DateTime::from_timestamp_millis(self.0 as i64).unwrap();
let time_str = time.to_rfc3339_opts(SecondsFormat::Millis, true);
write!(f, "{time_str}")
}
}
impl Add for NoatunTime {
type Output = NoatunTime;
fn add(self, rhs: Self) -> Self::Output {
NoatunTime(self.0 + rhs.0).truncated()
}
}
impl Sub for NoatunTime {
type Output = NoatunTime;
fn sub(self, rhs: Self) -> Self::Output {
NoatunTime(self.0 - rhs.0).truncated()
}
}
impl NoatunTime {
pub fn elapsed_ms_since(self, other: NoatunTime) -> u64 {
let ms = self.0.saturating_sub(other.0);
ms
}
#[doc(hidden)]
pub fn debug_time(minutes: u64) -> NoatunTime {
let noatun = NoatunTime::from(datetime!(2020-01-01 T 00:00:00 Z));
(noatun + Duration::from_secs(minutes * 60)).truncated()
}
pub fn next_multiple_of(self, other: NoatunTime) -> Option<NoatunTime> {
NoatunTime(self.0.checked_next_multiple_of(other.0)?).validate()
}
pub fn prev_multiple_of(self, other: NoatunTime) -> Option<NoatunTime> {
Some(NoatunTime(
self.0.checked_next_multiple_of(other.0)? - other.0,
))
}
pub fn successor(&self) -> NoatunTime {
NoatunTime(self.0 + 1).truncated()
}
pub fn saturating_predecessor(&self) -> NoatunTime {
NoatunTime(self.0.saturating_sub(1))
}
pub const EPOCH: NoatunTime = NoatunTime(0);
pub const MAX: NoatunTime = NoatunTime((1 << 48) - 1);
pub fn now() -> Self {
let millis = Utc::now().timestamp_millis().clamp(0, (1 << 48) - 1);
Self(millis as u64)
}
#[must_use]
pub fn as_ms(self) -> u64 {
self.0
}
#[must_use]
pub fn add_ms(self, ms: u64) -> NoatunTime {
NoatunTime(self.0.saturating_add(ms)).truncated()
}
pub fn sub_ms(self, ms: u64) -> NoatunTime {
NoatunTime(self.0.saturating_sub(ms)).truncated()
}
pub fn to_datetime(&self) -> DateTime<Utc> {
DateTime::<Utc>::from_timestamp_millis(self.0 as i64).unwrap()
}
const fn validate(self) -> Option<NoatunTime> {
if self.0 > NoatunTime::MAX.0 {
return None;
}
Some(self)
}
const fn truncated(self) -> NoatunTime {
if self.0 > NoatunTime::MAX.0 {
return NoatunTime::MAX;
}
self
}
pub const fn from_datetime(t: DateTime<Utc>) -> NoatunTime {
let ts = t.timestamp_millis();
if ts < 0 {
return NoatunTime::EPOCH;
}
NoatunTime(ts as u64).truncated()
}
pub const fn try_from_datetime(t: DateTime<Utc>) -> Option<NoatunTime> {
let ts = t.timestamp_millis();
if ts < 0 {
return None;
}
Some(NoatunTime(ts as u64).truncated())
}
}
pub enum Persistence {
UntilOverwritten,
AtLeastUntilCutoff,
}
pub trait Message: Debug + Sized + 'static {
type Root: Object + NoatunStorable;
type Serializer: NoatunMessageSerializer<Self>;
fn apply(&self, id: MessageId, root: Pin<&mut Self::Root>);
fn persistence(&self) -> Persistence {
Persistence::UntilOverwritten
}
}
pub trait MessageExt: Message {
fn serialize<W: Write>(&self, writer: W) -> Result<()> {
Self::Serializer::serialize(self, writer)
}
fn deserialize(buf: &[u8]) -> Result<Self>
where
Self: Sized,
{
Self::Serializer::deserialize(buf)
}
}
impl<T> MessageExt for T where T: Message {}
#[derive(Debug, Clone, PartialEq)]
pub struct MessageHeader {
pub id: MessageId,
pub parents: Vec<MessageId>,
}
#[derive(Debug)]
pub struct MessageFrame<M: Message> {
pub header: MessageHeader,
pub payload: M,
}
impl<M: Message> PartialEq for MessageFrame<M>
where
M: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.header == other.header && self.payload == other.payload
}
}
impl<M: Message> MessageFrame<M> {
pub fn new(id: MessageId, parents: Vec<MessageId>, payload: M) -> Self {
Self {
header: MessageHeader { id, parents },
payload,
}
}
pub fn id(&self) -> MessageId {
self.header.id
}
}
pub(crate) fn catch_and_log(f: impl FnOnce(), abort: bool) {
match catch_unwind(AssertUnwindSafe(f)) {
Ok(()) => {}
Err(err) => {
if let Some(err) = (*err).downcast_ref::<String>() {
error!("apply method panicked: {:?}", err);
} else if let Some(err) = err.downcast_ref::<&'static str>() {
error!("apply method panicked: {:?}", err);
} else {
error!("apply method panicked.");
}
if abort {
std::process::abort();
}
}
}
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct DummyUnitObject;
unsafe impl NoatunStorable for DummyUnitObject {
fn hash_schema(hasher: &mut SchemaHasher) {
hasher.write_str("noatun::DummyUnitObject/1")
}
}
impl Object for DummyUnitObject {
type Ptr = ThinPtr;
type NativeType = ();
type NativeOwnedType = ();
fn export(&self) -> Self::NativeType {}
fn destroy(self: Pin<&mut Self>) {}
fn init_from(self: Pin<&mut Self>, _detached: &Self::NativeType) {}
unsafe fn allocate_from<'a>(_detached: &Self::NativeType) -> Pin<&'a mut Self> {
unsafe { Pin::new_unchecked(&mut *std::ptr::dangling_mut::<DummyUnitObject>()) }
}
fn hash_object_schema(hasher: &mut SchemaHasher) {
hasher.write_str("noatun::DummyUnitObject/1");
}
}
pub enum GenPtr {
Thin(ThinPtr),
Fat(FatPtr),
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
struct SerializableGenPtr {
ptr: usize,
count: usize, }
unsafe impl NoatunStorable for SerializableGenPtr {
fn hash_schema(hasher: &mut SchemaHasher) {
hasher.write_str("noatun::SerializableGenPtr/1")
}
}
impl From<SerializableGenPtr> for GenPtr {
fn from(value: SerializableGenPtr) -> Self {
if value.count == usize::MAX {
GenPtr::Thin(ThinPtr(value.ptr))
} else {
GenPtr::Fat(FatPtr {
start: value.ptr,
count: value.count,
})
}
}
}
impl From<GenPtr> for SerializableGenPtr {
fn from(ptr: GenPtr) -> Self {
match ptr {
GenPtr::Thin(t) => Self {
ptr: t.0,
count: usize::MAX,
},
GenPtr::Fat(f) => Self {
ptr: f.start,
count: f.count,
},
}
}
}
pub trait Pointer: NoatunStorable + Sealed + Copy + Debug + 'static {
fn start(self) -> usize;
fn create<T: ?Sized>(addr: &T, buffer_start: *const u8) -> Self;
fn as_generic(&self) -> GenPtr;
fn is_null(&self) -> bool;
unsafe fn access<'a, T: ?Sized>(&self) -> &'a T;
unsafe fn access_mut<'a, T: ?Sized>(&self) -> Pin<&'a mut T>;
unsafe fn access_ctx<'a, T: ?Sized>(&self, context: &DatabaseContextData) -> &'a T;
unsafe fn access_ctx_mut<'a, T: ?Sized>(&self, context: &mut DatabaseContextData) -> &'a mut T;
}
pub fn from_bytes<T: NoatunStorable>(s: &[u8]) -> &T {
assert_eq!(s.len(), size_of::<T>());
assert!((s.as_ptr() as *mut T).is_aligned());
unsafe { &*s.as_ptr().cast::<T>() }
}
pub fn from_bytes_mut<T: NoatunStorable>(s: &mut [u8]) -> &mut T {
assert_eq!(s.len(), size_of::<T>());
assert!((s.as_mut_ptr() as *mut T).is_aligned());
unsafe { &mut *s.as_mut_ptr().cast::<T>() }
}
pub fn cast_storable<I: NoatunStorable, O: NoatunStorable>(i: I) -> O {
const {
if size_of::<I>() != size_of::<O>() {
panic!("Source and destination size must be the same");
}
}
unsafe { transmute_copy::<I, O>(&i) }
}
pub fn bytes_of<T: NoatunStorable>(t: &T) -> &[u8] {
unsafe { slice::from_raw_parts(t as *const _ as *const u8, size_of::<T>()) }
}
pub fn bytes_of_mut<T: NoatunStorable>(t: &mut T) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(t as *mut _ as *mut u8, size_of::<T>()) }
}
pub fn bytes_of_mut_object<T: Object>(t: &mut T) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(t as *mut _ as *mut u8, size_of::<T>()) }
}
pub fn cast_slice_mut<I: NoatunStorable, O: NoatunStorable>(s: &mut [I]) -> &mut [O] {
const {
assert!(align_of::<O>() <= align_of::<I>());
}
let tot_size_i = size_of_val(s);
let count_o = tot_size_i / size_of::<O>();
assert_eq!(tot_size_i, size_of::<O>() * count_o);
unsafe { slice::from_raw_parts_mut(s.as_mut_ptr() as *mut O, count_o) }
}
pub fn cast_slice<I: NoatunStorable, O: NoatunStorable>(s: &[I]) -> &[O] {
const {
assert!(align_of::<O>() <= align_of::<I>());
}
let tot_size_i = size_of_val(s);
let count_o = tot_size_i / size_of::<O>();
assert_eq!(tot_size_i, size_of::<O>() * count_o);
unsafe { slice::from_raw_parts(s.as_ptr() as *const O, count_o) }
}
pub fn dyn_cast_slice<I: NoatunStorable, O: NoatunStorable>(s: &[I]) -> &[O] {
assert!((s.as_ptr() as *mut O).is_aligned());
let tot_size_i = size_of_val(s);
let count_o = tot_size_i / size_of::<O>();
assert_eq!(tot_size_i, size_of::<O>() * count_o);
unsafe { slice::from_raw_parts(s.as_ptr() as *const O, count_o) }
}
pub fn dyn_cast_slice_mut<I: NoatunStorable, O: NoatunStorable>(s: &mut [I]) -> &mut [O] {
assert!((s.as_ptr() as *mut O).is_aligned());
let tot_size_i = size_of_val(s);
let count_o = tot_size_i / size_of::<O>();
assert_eq!(tot_size_i, size_of::<O>() * count_o);
unsafe { slice::from_raw_parts_mut(s.as_mut_ptr() as *mut O, count_o) }
}
pub fn read_unaligned<T>(data: &[u8]) -> T {
if data.len() != size_of::<T>() {
panic!("Slice is not the correct size");
}
let raw = data as *const [u8] as *const T;
unsafe { raw.read_unaligned() }
}
#[diagnostic::on_unimplemented(
message = "`{Self}` is not a noatun Object, and can't appear on its own in a noatun database.",
label = "`{Self}` does not implement Object.",
note = "For primitives, try wrapping in NoatunCell.",
note = "For structs, use `noatun_object!`-macro.",
note = "For collections, try `NoatunHashMap` or `NoatunVec`.",
note = "Manually implementing this trait is not recommended, but can be possible. See trait docs."
)]
pub trait Object {
type Ptr: Pointer;
type NativeType: ?Sized;
type NativeOwnedType: Borrow<Self::NativeType>;
fn export(&self) -> Self::NativeOwnedType;
fn destroy(self: Pin<&mut Self>);
fn init_from(self: Pin<&mut Self>, external: &Self::NativeType);
unsafe fn allocate_from<'a>(external: &Self::NativeType) -> Pin<&'a mut Self>;
fn hash_object_schema(hasher: &mut SchemaHasher);
}
#[macro_export]
#[doc(hidden)]
macro_rules! count_ast_nodes {
() => (0usize);
( $x:tt $($xs:tt)* ) => (1usize + $crate::count_ast_nodes!($($xs)*));
}
#[macro_export]
macro_rules! noatun_pod {
( $(#[derive($($trait_name: ident),*)])*
$(#[doc = $doc:expr])*
struct $name: ident {
$(
$(#[doc = $field_doc:expr])*
$field_name:ident : $field_type: ty
),*$(,)?
}$(;)*) => {
$(#[derive($($trait_name),*)])*
#[derive(Clone, Copy, Debug, $crate::Savefile)]
$(#[doc = $doc])*
#[repr(C)]
pub struct $name {
$(
$(#[doc = $field_doc])*
pub $field_name: $field_type,
)*
}
unsafe impl $crate::NoatunStorable for $name where
$($field_type: $crate::NoatunStorable,)*
{
fn hash_schema(hasher: &mut $crate::SchemaHasher) {
hasher.write_str(std::any::type_name::<Self>());
hasher.write_usize($crate::count_ast_nodes!($($field_type)*));
$(
hasher.write_str(stringify!($field_name));
<$field_type as $crate::NoatunStorable>::hash_schema(hasher);
)*
}
}
unsafe impl $crate::NoatunPod for $name where
$($field_type: $crate::NoatunPod,)*
{
}
};
( $(#[derive($($trait_name: ident),*)])*
$(#[doc = $doc:expr])*
struct $name: ident(
$(
$(#[doc = $field_doc:expr])*
$field_type: ty
),* $(,)?
)$(;)?) => {
$(#[derive($($trait_name),*)])*
#[derive(Clone, Copy, Debug, $crate::Savefile)]
$(#[doc = $doc])*
#[repr(C)]
pub struct $name (
$(
$(#[doc = $field_doc])*
pub $field_type,
)*
);
unsafe impl $crate::NoatunStorable for $name where
$($field_type: $crate::NoatunStorable,)*
{
fn hash_schema(hasher: &mut $crate::SchemaHasher) {
hasher.write_str(std::any::type_name::<Self>());
hasher.write_usize($crate::count_ast_nodes!($($field_type)*));
$(
<$field_type as $crate::NoatunStorable>::hash_schema(hasher);
)*
}
}
unsafe impl $crate::NoatunPod for $name where
$($field_type: $crate::NoatunPod,)*
{
}
};
}
#[macro_export]
macro_rules! noatun_object {
( bounded_type pod $typ:ty) => {
$crate::NoatunCell<$typ>
};
( declare_field pod $typ: ty ) => {
$crate::NoatunCell<$typ>
};
( declare_detached_field pod $typ: ty ) => {
$typ
};
( new_declare_param pod $typ: ty ) => {
$typ
};
( new_assign_field pod $self: ident $name: ident $typ: ty ) => {
unsafe { ::std::pin::Pin::new_unchecked(&mut $self.$name).set($name); }
};
( getter pod $name:ident $typ: ty ) => {
#[doc = "Get the value for field"]
#[doc = stringify!($name)]
#[allow(unused)]
pub fn $name(&self) -> $typ {
self.$name.get()
}
};
( setter pod $name:ident $typ: ty ) => {
$crate::paste!(
#[doc = "Set a new value for field"]
#[doc = stringify!($name)]
#[allow(unused)]
pub fn [<set_ $name>](self: ::std::pin::Pin<&mut Self>, val: $typ) {
unsafe { ::std::pin::Pin::new_unchecked(&mut self.get_unchecked_mut().$name).set(val); }
}
);
};
( bounded_type opod $typ:ty) => {
$crate::OpaqueNoatunCell<$typ>
};
( declare_field opod $typ: ty ) => {
$crate::OpaqueNoatunCell<$typ>
};
( declare_detached_field opod $typ: ty ) => {
$typ
};
( new_declare_param opod $typ: ty ) => {
$typ
};
( new_assign_field opod $self: ident $name: ident $typ: ty ) => {
unsafe { ::std::pin::Pin::new_unchecked(&mut $self.$name).set($name); }
};
( getter opod $name:ident $typ: ty ) => {
};
( setter opod $name:ident $typ: ty ) => {
$crate::paste!(
#[doc = "Set a new value for field"]
#[doc = stringify!($name)]
#[allow(unused)]
pub fn [<set_ $name>](self: ::std::pin::Pin<&mut Self>, val: $typ) {
unsafe { ::std::pin::Pin::new_unchecked(&mut self.get_unchecked_mut().$name).set(val); }
}
);
};
( bounded_type object $typ:ty) => {
$typ
};
( declare_field object $typ: ty ) => {
$typ
};
( declare_detached_field object $typ: ty ) => {
<$typ as $crate::Object>::NativeOwnedType
};
( new_declare_param object $typ: ty ) => {
&<$typ as $crate::Object>::NativeType
};
( new_assign_field object $self:ident $name: ident $typ: ty ) => {
unsafe { <_ as $crate::Object>::init_from(::std::pin::Pin::new_unchecked(&mut $self.$name), $name); }
};
( getter object $name:ident $typ: ty ) => {
#[doc ="Get the value of field"]
#[doc=stringify!($name)]
#[allow(unused)]
pub fn $name(&self) -> &$typ {
&self.$name
}
};
( setter object $name:ident $typ: ty ) => {
$crate::paste!(
#[doc ="Set the value of field"]
#[doc=stringify!($name)]
#[allow(unused)]
pub fn [<$name _mut>](self: ::std::pin::Pin<&mut Self>) -> ::std::pin::Pin<&mut $typ> {
let tself = unsafe { self.get_unchecked_mut() };
unsafe { ::std::pin::Pin::new_unchecked(&mut tself.$name) }
}
);
};
( detached_type $n_detached: ident) => {
$n_detached
};
( $(#[derive( $($derive_item:ident),* )])?
$(#[doc = $doc:expr])*
struct $n:ident { $( $(#[doc = $field_doc:expr])* $kind:ident $name: ident : $typ:ty $(,)* )* } $(;)* ) => {
const _:() = {
const fn __items_in_noatun_db_must_impl_noatun_object_trait() where $( noatun_object!(bounded_type $kind $typ) : $crate::Object ),* {}
static __ITEMS_IN_NOATUN_DB_MUST_IMPL_NOATUN_OBJECT_TRAIT_CHECKER: () = __items_in_noatun_db_must_impl_noatun_object_trait();
};
#[derive(Debug)]
#[repr(C)]
$(#[doc = $doc])*
pub struct $n
{
phantom: ::std::marker::PhantomPinned,
$(
$(#[doc = $field_doc])*
#[allow(unused)]
pub $name : noatun_object!(declare_field $kind $typ)
),*
}
unsafe impl $crate::NoatunStorable for $n {
fn hash_schema(hasher: &mut $crate::SchemaHasher) {
hasher.write_str(std::any::type_name::<Self>());
hasher.write_usize($crate::count_ast_nodes!($($name)*));
$(
<$typ as $crate::NoatunStorable>::hash_schema(hasher);
)*
}
}
$crate::paste!(
#[doc ="pin_project helper for"]
#[doc = stringify!($n)]
#[derive(Debug)]
pub struct [<$n PinProject>]<'a> {
$(
$(#[doc = $field_doc])*
#[allow(unused)]
pub $name: ::std::pin::Pin<&'a mut noatun_object!(declare_field $kind $typ)>
),*
}
);
impl $n {
#[doc = "Initialize an instance of"]
#[doc = stringify!($n)]
#[doc = "with an explicit value for each field"]
#[allow(unused)]
pub fn init(
&mut self,
$( $name: noatun_object!(new_declare_param $kind $typ) ),*
) {
$( noatun_object!(new_assign_field $kind self $name $typ); )*
}
$( noatun_object!(getter $kind $name $typ); )*
$(
noatun_object!(setter $kind $name $typ);
)*
$crate::paste! {
#[doc ="Give pinned access to each field in"]
#[doc=stringify!($n)]
#[allow(unused)]
pub fn pin_project(self: ::std::pin::Pin<&mut Self>) -> [<$n PinProject>]<'_> {
unsafe {
let $n {
$($name),* , ..
} = self.get_unchecked_mut();
[<$n PinProject>] {
$($name: ::std::pin::Pin::new_unchecked($name)),*
}
}
}
}
}
$crate::paste! {
$(#[derive($($derive_item,)*)])*
#[derive(Debug,Clone,$crate::Savefile)]
$(#[doc = $doc])*
#[doc = ""]
#[doc = "This is a external version of"]
#[doc = stringify!($n)]
pub struct [<$n Native>]
{
$(
$(#[doc = $field_doc])*
#[allow(unused)]
$name : $crate::noatun_object!(declare_detached_field $kind $typ)
),*
}
}
impl $crate::Object for $n {
type Ptr = $crate::ThinPtr;
type NativeType = $crate::paste!(noatun_object!(detached_type [<$n Native>]));
type NativeOwnedType = $crate::paste!(noatun_object!(detached_type [<$n Native>]));
fn destroy(self: ::std::pin::Pin<&mut Self>) {
let tself = unsafe { self.get_unchecked_mut() };
$( unsafe { ::std::pin::Pin::new_unchecked(&mut tself.$name).destroy(); } )*
}
fn export(&self) -> Self::NativeOwnedType {
Self::NativeOwnedType {
$(
$name: self.$name.export()
),*
}
}
fn init_from(mut self: ::std::pin::Pin<&mut Self>, external: &Self::NativeType) {
$(
unsafe {
::std::pin::Pin::new_unchecked(&mut self.as_mut().get_unchecked_mut().$name).init_from(&external.$name);
}
)*
}
unsafe fn allocate_from<'a>(external: &Self::NativeType) -> ::std::pin::Pin<&'a mut Self> {
let mut ret: ::std::pin::Pin<&mut Self> = $crate::NoatunContext.allocate();
ret.as_mut().init_from(external);
ret
}
fn hash_object_schema(hasher: &mut $crate::SchemaHasher) {
<Self as $crate::NoatunStorable>::hash_schema(hasher);
}
}
};
}
pub trait FixedSizeObject: Object<Ptr = ThinPtr> + NoatunStorable + Sized + 'static
where
<Self as Object>::NativeOwnedType: Sized,
{
}
impl<T: Object<Ptr = ThinPtr> + NoatunStorable + 'static> FixedSizeObject for T {}
#[derive(Debug)]
#[repr(C)]
struct RawFatPtr {
data: *const u8,
size: usize,
}
#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct FatPtr {
start: usize,
count: usize,
}
impl FatPtr {
pub fn from_idx_count(start: usize, count: usize) -> FatPtr {
FatPtr { start, count }
}
pub fn from_thin_size(thin: ThinPtr, size_bytes: usize) -> FatPtr {
FatPtr {
start: thin.0,
count: size_bytes,
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct ThinPtr(pub usize);
unsafe impl NoatunStorable for ThinPtr {
fn hash_schema(hasher: &mut SchemaHasher) {
hasher.write_str("noatun::ThinPtr/1")
}
}
unsafe impl NoatunStorable for FatPtr {
fn hash_schema(hasher: &mut SchemaHasher) {
hasher.write_str("noatun::FatPtr/1")
}
}
impl Sealed for ThinPtr {}
impl Sealed for FatPtr {}
impl Pointer for ThinPtr {
fn start(self) -> usize {
self.0
}
fn create<T: ?Sized>(addr: &T, buffer_start: *const u8) -> Self {
let index = (addr as *const T as *const u8 as usize) - (buffer_start as usize);
ThinPtr(index)
}
fn as_generic(&self) -> GenPtr {
GenPtr::Thin(*self)
}
fn is_null(&self) -> bool {
self.0 == 0
}
unsafe fn access<'a, T: ?Sized>(&self) -> &'a T {
NoatunContext.access_thin::<T>(*self)
}
unsafe fn access_mut<'a, T: ?Sized>(&self) -> Pin<&'a mut T> {
Pin::new_unchecked(NoatunContext.access_thin_mut::<T>(*self))
}
#[doc(hidden)]
unsafe fn access_ctx<'a, T: ?Sized>(&self, context: &DatabaseContextData) -> &'a T {
context.access_thin::<T>(*self)
}
#[doc(hidden)]
unsafe fn access_ctx_mut<'a, T: ?Sized>(&self, context: &mut DatabaseContextData) -> &'a mut T {
context.access_thin_mut::<T>(*self)
}
}
impl ThinPtr {
pub fn null() -> ThinPtr {
ThinPtr(0)
}
}
impl Pointer for FatPtr {
fn start(self) -> usize {
self.start
}
fn create<T: ?Sized>(addr: &T, buffer_start: *const u8) -> Self {
assert_eq!(
std::mem::size_of::<*const T>(),
std::mem::size_of::<RawFatPtr>(),
);
let raw: RawFatPtr = unsafe { transmute_copy(&(addr as *const T)) };
FatPtr {
start: ((addr as *const T as *const u8 as usize) - (buffer_start as usize)),
count: raw.size,
}
}
fn as_generic(&self) -> GenPtr {
GenPtr::Fat(*self)
}
fn is_null(&self) -> bool {
self.start == 0
}
unsafe fn access<'a, T: ?Sized>(&self) -> &'a T {
NoatunContext.access_fat::<T>(*self)
}
unsafe fn access_mut<'a, T: ?Sized>(&self) -> Pin<&'a mut T> {
Pin::new_unchecked(NoatunContext.access_fat_mut::<T>(*self))
}
unsafe fn access_ctx<'a, T: ?Sized>(&self, context: &DatabaseContextData) -> &'a T {
context.access_fat::<T>(*self)
}
unsafe fn access_ctx_mut<'a, T: ?Sized>(&self, context: &mut DatabaseContextData) -> &'a mut T {
context.access_fat_mut::<T>(*self)
}
}
impl<T: FixedSizeObject> Object for [T]
where
T::NativeType: Sized,
{
type Ptr = FatPtr;
type NativeType = [T::NativeOwnedType];
type NativeOwnedType = Vec<T::NativeOwnedType>;
fn export(&self) -> Self::NativeOwnedType {
self.iter().map(|x| x.export()).collect()
}
fn destroy(self: Pin<&mut Self>) {
let tself = unsafe { self.get_unchecked_mut() };
for item in tself {
unsafe { Pin::new_unchecked(item).destroy() };
}
}
fn init_from(self: Pin<&mut Self>, external: &Self::NativeType) {
unsafe {
for (dst, src) in self.get_unchecked_mut().iter_mut().zip(external.iter()) {
Pin::new_unchecked(dst).init_from(src.borrow());
}
}
}
unsafe fn allocate_from<'a>(external: &Self::NativeType) -> Pin<&'a mut Self> {
let bytes = size_of::<T>() * external.len();
let alloc = NoatunContext.allocate_raw(bytes, align_of::<T>());
let slice: &mut [T] = unsafe { slice::from_raw_parts_mut(alloc as *mut T, external.len()) };
for (src, dst) in external.iter().zip(&mut *slice) {
Pin::new_unchecked(dst).init_from(src.borrow());
}
Pin::new_unchecked(slice)
}
fn hash_object_schema(hasher: &mut SchemaHasher) {
hasher.write_str("noatun::[T]/1");
<T as NoatunStorable>::hash_schema(hasher);
}
}
#[derive(Clone)]
pub enum Target {
OpenExisting(PathBuf),
CreateNewOrOverwrite(PathBuf),
CreateNew(PathBuf),
}
impl Target {
pub fn path(&self) -> &Path {
let (Target::CreateNew(x) | Target::CreateNewOrOverwrite(x) | Target::OpenExisting(x)) =
self;
x
}
pub fn create(&self) -> bool {
matches!(self, Target::CreateNewOrOverwrite(_) | Target::CreateNew(_))
}
pub fn overwrite(&self) -> bool {
matches!(self, Target::CreateNewOrOverwrite(_))
}
}
struct ContextGuard;
impl ContextGuard {
fn new(context: &DatabaseContextData) -> ContextGuard {
if !CONTEXT.get().is_null() {
panic!(
"'with_root' must not be called within an existing database access context.
For example, it cannot be called within a 'with_root', 'with_root_mut' or
message apply operation.
"
);
}
CONTEXT.set(context as *const _ as *mut _);
ContextGuard
}
}
impl Drop for ContextGuard {
fn drop(&mut self) {
CONTEXT.set(null_mut());
}
}
struct ContextGuardMut;
impl ContextGuardMut {
fn new(context: &mut DatabaseContextData, is_message_apply: bool) -> ContextGuardMut {
if !CONTEXT.get().is_null() {
panic!(
"'with_root' must not be called within an existing database access context.
For example, it cannot be called within a 'with_root', 'with_root_mut' or
message apply operation.
"
);
}
context.is_mutable = true;
context.is_message_apply = is_message_apply;
CONTEXT.set(context as *mut _);
ContextGuardMut
}
}
impl Drop for ContextGuardMut {
fn drop(&mut self) {
unsafe {
(*CONTEXT.get()).is_mutable = false;
(*CONTEXT.get()).is_message_apply = false;
}
CONTEXT.set(null_mut());
}
}
fn msg_serialize<T: savefile::Serialize + savefile::Packed>(
obj: &T,
mut writer: impl Write,
) -> anyhow::Result<()> {
Ok(savefile::Serializer::bare_serialize(&mut writer, 0, obj)?)
}
fn msg_deserialize<T: savefile::Deserialize + savefile::Packed>(buf: &[u8]) -> anyhow::Result<T> {
Ok(Deserializer::bare_deserialize(
&mut std::io::Cursor::new(buf),
0,
)?)
}
pub trait NoatunMessageSerializer<MSG> {
fn deserialize(buf: &[u8]) -> anyhow::Result<MSG>
where
Self: Sized;
fn serialize<W: Write>(msg: &MSG, writer: W) -> anyhow::Result<()>;
}
#[cfg(feature = "postcard")]
pub struct PostcardMessageSerializer<MSG: Serialize + DeserializeOwned>(pub PhantomData<MSG>);
#[cfg(feature = "postcard")]
impl<MSG: Serialize + DeserializeOwned> NoatunMessageSerializer<MSG>
for PostcardMessageSerializer<MSG>
{
fn deserialize(buf: &[u8]) -> anyhow::Result<MSG>
where
Self: Sized,
{
Ok(postcard::from_bytes(buf)?)
}
fn serialize<W: Write>(msg: &MSG, writer: W) -> anyhow::Result<()> {
postcard::to_io(msg, writer)?;
Ok(())
}
}
pub struct SavefileMessageSerializer<
MSG: savefile::Serialize + savefile::Deserialize + savefile::Packed,
>(pub PhantomData<MSG>);
pub struct DummyMessageSerializer;
impl<MSG> NoatunMessageSerializer<MSG> for DummyMessageSerializer {
fn deserialize(_buf: &[u8]) -> Result<MSG>
where
Self: Sized,
{
panic!("attempt to deserialize message using dummy serializer")
}
fn serialize<W: Write>(_msg: &MSG, _writer: W) -> Result<()> {
panic!("attempt to serialize message using dummy serializer")
}
}
impl<MSG: savefile::Serialize + savefile::Deserialize + savefile::Packed>
NoatunMessageSerializer<MSG> for SavefileMessageSerializer<MSG>
{
fn deserialize(buf: &[u8]) -> Result<MSG>
where
Self: Sized,
{
msg_deserialize(buf)
}
fn serialize<W: Write>(msg: &MSG, writer: W) -> Result<()> {
msg_serialize(msg, writer)
}
}
#[cfg(test)]
mod tests;