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
use crate::abi::Status;
use crate::services::Service;
use super::{Load, Off, ok, service_spec};
impl Load<'_> {
/// Publishes `table` as the service its type declares, for other mods.
///
/// The type comes from [`#[service]`](macro@crate::service), and the
/// loader keeps its layout to check every user against.
///
/// # Errors
///
/// [`Off`] when another mod publishes the same name and major.
///
/// # Examples
///
/// ```
/// #[enshwick::service(name = "you.trading-post:prices")]
/// pub struct Prices {
/// pub price: extern "system" fn(item: u32) -> u32,
/// }
///
/// extern "system" fn price(_item: u32) -> u32 {
/// 0
/// }
///
/// static PRICES: Prices = Prices { price };
///
/// fn init(load: &mut enshwick::Load<'_>) -> Result<(), enshwick::Off> {
/// load.publish(&PRICES)
/// }
/// ```
pub fn publish<T: Service>(&mut self, table: &'static T) -> Result<(), Off> {
let spec = service_spec::<T>(core::ptr::from_ref(table).cast());
let status = (self.api().publish)(self.host.handle(), &raw const spec);
ok(status, (), || {
format!(
"service {} v{}: published by another mod",
T::NAME,
T::MAJOR
)
})
}
/// The service `T` declares, published by another mod, once the loader
/// has checked that the published table starts with every field of
/// `T`.
///
/// A publisher built against a longer declaration serves this mod; one
/// built against a shorter declaration does not, and the error names
/// the first field it lacks.
///
/// # Errors
///
/// [`Off`] with the reason when no running mod publishes the service,
/// or the published table lacks or changed a field of `T`. Declare the
/// publisher in `requires` so it loads first.
///
/// # Examples
///
/// ```
/// #[enshwick::service(name = "you.trading-post:prices")]
/// pub struct Prices {
/// pub price: extern "system" fn(item: u32) -> u32,
/// }
///
/// fn init(load: &mut enshwick::Load<'_>) -> Result<(), enshwick::Off> {
/// let prices: &'static Prices = load.service()?;
/// let _ = (prices.price)(0);
/// Ok(())
/// }
/// ```
pub fn service<T: Service>(&self) -> Result<&'static T, Off> {
self.find_service::<T>()
.map_err(|(_, reason)| Off::new(reason))
}
/// The service `T` declares when a running mod publishes a table that
/// serves it, `None` otherwise: for a service the mod can do without,
/// with the publisher in `after`. A table that does not serve `T` is
/// logged as a warning.
///
/// # Examples
///
/// ```
/// #[enshwick::service(name = "you.trading-post:prices")]
/// pub struct Prices {
/// pub price: extern "system" fn(item: u32) -> u32,
/// }
///
/// fn init(load: &mut enshwick::Load<'_>) -> Result<(), enshwick::Off> {
/// if let Some(prices) = load.optional_service::<Prices>() {
/// let _ = (prices.price)(0);
/// }
/// Ok(())
/// }
/// ```
#[must_use]
pub fn optional_service<T: Service>(&self) -> Option<&'static T> {
match self.find_service::<T>() {
Ok(table) => Some(table),
Err((status, reason)) => {
let level = if status == Status::MISSING {
crate::abi::Level::DEBUG
} else {
crate::abi::Level::WARN
};
self.host.log(level, &reason);
None
}
}
}
pub(super) fn find_service<T: Service>(&self) -> Result<&'static T, (Status, String)> {
let spec = service_spec::<T>(core::ptr::null());
let mut found = crate::abi::ServiceFound::EMPTY;
let status = (self.api().service)(self.host.handle(), &raw const spec, &raw mut found);
let what = format!("service {} v{}", T::NAME, T::MAJOR);
if !status.is_ok() {
// SAFETY: the loader keeps the reason for the process.
let reason = unsafe { found.reason.as_str() }.unwrap_or_default();
return Err((
status,
if reason.is_empty() {
format!("{what}: not found")
} else {
format!("{what}: {reason}")
},
));
}
// SAFETY: the loader checked the published layout starts with
// every field of `T`, and tables are `'static`.
unsafe { found.table.cast::<T>().as_ref() }
.ok_or_else(|| (Status::MISSING, format!("{what}: null table")))
}
}