#[cfg(feature = "unstable")]
mod calls;
#[cfg(feature = "unstable")]
mod described;
use core::marker::PhantomData;
use crate::Host;
use crate::abi::{Layout, LayoutField, Str};
use crate::shape::ShapeHasher;
#[cfg(feature = "unstable")]
pub use calls::{Call, CallError, Caller, DescribedCall, DescribedCaller};
#[cfg(all(feature = "test", feature = "unstable"))]
pub(crate) use calls::{Served, run_typed};
#[cfg(feature = "unstable")]
pub use described::{DescribedEmitter, DescribedEvent};
pub unsafe trait Service: Sync + 'static {
const NAME: &'static str;
const MAJOR: u32;
const LAYOUT: &'static [LayoutField];
}
pub unsafe trait ModEvent: Copy + 'static {
const NAME: &'static str;
const LAYOUT: &'static [LayoutField];
const DESCRIBED: bool = false;
const SCHEMA: &'static str = "";
}
#[must_use]
pub const fn layout_hash(name: &str, shape: u64, size: usize, align: usize, offset: usize) -> u64 {
ShapeHasher::new("field")
.text(name)
.number(shape)
.number(size as u64)
.number(align as u64)
.number(offset as u64)
.finish()
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum LayoutError {
#[error(
"the publisher has {offered} of the {wanted} fields this mod needs: `{first}` and later are newer than the publisher"
)]
Newer {
wanted: usize,
offered: usize,
first: String,
},
#[error(
"field {index} is `{wanted}` here and `{offered}` at the publisher: its type, place or a field, variant or value inside it differs"
)]
Field {
index: usize,
wanted: String,
offered: String,
},
}
pub fn compare(wanted: &[(&str, u64)], offered: &[(&str, u64)]) -> Result<(), LayoutError> {
for (index, (want, offer)) in wanted.iter().zip(offered).enumerate() {
if want.1 != offer.1 {
return Err(LayoutError::Field {
index,
wanted: want.0.to_owned(),
offered: offer.0.to_owned(),
});
}
}
match wanted.get(offered.len()) {
Some(first) => Err(LayoutError::Newer {
wanted: wanted.len(),
offered: offered.len(),
first: first.0.to_owned(),
}),
None => Ok(()),
}
}
#[must_use]
pub unsafe fn read_layout(layout: &Layout) -> Option<Vec<(String, u64)>> {
if layout.count == 0 {
return Some(Vec::new());
}
if layout.fields.is_null() {
return None;
}
let fields = unsafe { core::slice::from_raw_parts(layout.fields, layout.count as usize) };
fields
.iter()
.map(|f| unsafe { f.name.as_str() }.map(|name| (name.to_owned(), f.hash)))
.collect()
}
pub(crate) fn layout(fields: &'static [LayoutField], size: usize) -> Layout {
Layout {
fields: fields.as_ptr(),
count: u32::try_from(fields.len()).unwrap_or(u32::MAX),
size: u32::try_from(size).unwrap_or(u32::MAX),
}
}
pub(crate) fn name(name: &'static str) -> Str {
Str::new(name)
}
pub struct Emitter<E> {
host: Host,
id: u32,
_event: PhantomData<fn(&E)>,
}
impl<E> Clone for Emitter<E> {
fn clone(&self) -> Self {
*self
}
}
impl<E> Copy for Emitter<E> {}
impl<E> core::fmt::Debug for Emitter<E> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Emitter")
.field("id", &self.id)
.finish_non_exhaustive()
}
}
impl<E: ModEvent> Emitter<E> {
pub(crate) fn new(host: Host, id: u32) -> Self {
Self {
host,
id,
_event: PhantomData,
}
}
#[inline]
pub fn send(&self, event: &E) {
let _status = (self.host.api().send_event)(
self.host.handle(),
self.id,
core::ptr::from_ref(event).cast(),
);
}
}
#[cfg(test)]
mod tests {
use super::{LayoutError, compare, layout_hash};
#[crate::event(name = "you.my-mod:direct")]
#[derive(Clone, Copy, Debug)]
struct Direct {
best: u32,
pair: [u16; 2],
}
macro_rules! through {
($best:ty, $pair:ty) => {
#[crate::event(name = "you.my-mod:through")]
#[derive(Clone, Copy, Debug)]
struct Through {
best: $best,
pair: $pair,
}
};
}
through!(u32, [u16; 2]);
#[test]
fn a_type_passed_through_a_macro_hashes_alike() {
use super::ModEvent;
let hashes = |layout: &[crate::abi::LayoutField]| -> Vec<u64> {
layout.iter().map(|field| field.hash).collect()
};
assert_eq!(hashes(Direct::LAYOUT), hashes(Through::LAYOUT));
}
#[test]
fn an_older_user_reads_a_newer_table() {
let older = [("a", 1), ("b", 2)];
let newer = [("a", 1), ("b", 2), ("c", 3)];
assert_eq!(compare(&older, &newer), Ok(()));
}
#[test]
fn a_newer_user_names_the_first_missing_field() {
let older = [("a", 1)];
let newer = [("a", 1), ("b", 2), ("c", 3)];
assert_eq!(
compare(&newer, &older),
Err(LayoutError::Newer {
wanted: 3,
offered: 1,
first: "b".to_owned()
})
);
}
#[test]
fn a_changed_field_is_named() {
let here = [("a", 1), ("b", 2)];
let there = [("a", 1), ("b", 9)];
assert_eq!(
compare(&here, &there),
Err(LayoutError::Field {
index: 1,
wanted: "b".to_owned(),
offered: "b".to_owned()
})
);
}
#[test]
fn the_hash_sees_name_shape_size_and_place() {
use crate::Shape;
let base = layout_hash("a", u32::SHAPE, 4, 4, 0);
assert_ne!(base, layout_hash("b", u32::SHAPE, 4, 4, 0));
assert_ne!(base, layout_hash("a", u64::SHAPE, 4, 4, 0));
assert_ne!(base, layout_hash("a", u32::SHAPE, 8, 4, 0));
assert_ne!(base, layout_hash("a", u32::SHAPE, 4, 8, 0));
assert_ne!(base, layout_hash("a", u32::SHAPE, 4, 4, 8));
assert_eq!(base, layout_hash("a", u32::SHAPE, 4, 4, 0));
}
}