1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
//! Global schema registry.
use std::{
alloc::Layout,
sync::atomic::{AtomicU32, Ordering::SeqCst},
};
use append_only_vec::AppendOnlyVec;
use bones_utils::Deref;
use crate::prelude::*;
/// A unique identifier for a schema registered in the [`SCHEMA_REGISTRY`].
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)]
pub struct SchemaId {
id: u32,
}
// Note: The schema type is here in the registry module to prevent modification of registered
// schemas by other modules. The idea is that once a schema is registered, it is unchangable and
// "certified" so to speak.
#[doc(hidden)]
/// A schema registered with the [`SCHEMA_REGISTRY`].
///
/// ## Known Limitations
///
/// Currently there isn't a known-safe way to construct a schema for a recursive struct. For
/// example, this struct is troublesome:
///
/// ```rust
/// struct Data {
/// others: Vec<Data>,
/// }
/// ```
///
/// This is because nested schemas are required to be a `&'static Schema`, and it would probalby
/// require unsafe code to create a schema that references itself.
///
/// If this is a problem for your use-case, please open an issue. We would like to remove this
/// limitation or find a suitable workaround in the future.
#[derive(Deref, Clone, Debug)]
pub struct Schema {
id: SchemaId,
#[deref]
data: SchemaData,
layout: Layout,
field_offsets: &'static [(Option<String>, usize)],
}
impl PartialEq for Schema {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for Schema {}
impl Schema {
/// Get the registered, unique ID of the [`Schema`].
#[inline]
pub fn id(&self) -> SchemaId {
self.id
}
/// Get a static reference to the [`Schema`] that was registered.
#[inline]
pub fn schema(&self) -> &SchemaData {
&self.data
}
/// Get the [`Layout`] of the [`Schema`].
#[inline]
pub fn layout(&self) -> Layout {
self.layout
}
/// If this schema represents a struct, this returns the list of fields, with the names of the
/// fields, and their byte offsets from the beginning of the struct.
#[inline]
pub fn field_offsets(&self) -> &'static [(Option<String>, usize)] {
self.field_offsets
}
/// Helper function to make sure that this schema matches another or return a
/// [`SchemaMismatchError`].
pub fn ensure_match(&self, other: &Self) -> Result<(), SchemaMismatchError> {
if self == other {
Ok(())
} else {
Err(SchemaMismatchError)
}
}
}
/// A schema registry that alloates [`SchemaId`]s for [`SchemaData`]s and returns a registered
/// [`&'static Schema`][Schema].
pub struct SchemaRegistry {
next_id: AtomicU32,
/// The registered schemas.
pub schemas: AppendOnlyVec<Schema>,
}
impl SchemaRegistry {
/// Register a schema with the registry.
#[track_caller]
pub fn register(&self, schema_data: SchemaData) -> &Schema {
// Allocate a new schema ID
let id = SchemaId {
id: self.next_id.fetch_add(1, SeqCst),
};
assert_ne!(id.id, u32::MAX, "Exhausted all {} schema IDs", u32::MAX);
// Compute the schema layout info so we can cache it with the Schema.
let SchemaLayoutInfo {
layout,
field_offsets,
} = schema_data.kind.compute_layout_info();
// Leak the field offsets to get static references
let field_offsets: Box<_> = field_offsets
.into_iter()
.map(|(name, offset)| (name.map(|n| n.to_string()), offset))
.collect();
let field_offsets = Box::leak(field_offsets);
// Create the schema struct.
let schema = Schema {
id,
data: schema_data,
layout,
field_offsets,
};
// Insert the schema into the registry.
let idx = self.schemas.push(schema);
&self.schemas[idx]
}
}
/// Global [`SchemaRegistry`] used to register [`SchemaData`]s and produce [`Schema`]s.
pub static SCHEMA_REGISTRY: SchemaRegistry = SchemaRegistry {
next_id: AtomicU32::new(0),
schemas: AppendOnlyVec::new(),
};
#[cfg(test)]
mod test {
use bones_utils::default;
use super::*;
#[test]
fn registry_smoke() {
let mut schemas = Vec::new();
for i in 0..100 {
let data = SchemaData {
name: format!("data{i}").into(),
full_name: format!("data{i}").into(),
kind: SchemaKind::Primitive(Primitive::U8),
type_data: default(),
type_id: None,
clone_fn: None,
drop_fn: None,
default_fn: None,
hash_fn: None,
eq_fn: None,
};
let schema = SCHEMA_REGISTRY.register(data.clone());
schemas.push(schema);
}
for (i, schema) in schemas.iter().enumerate() {
assert_eq!(schema.data.name, format!("data{i}"));
}
}
}