1use std::os::raw::c_void;
5use yog_sync::Arc;
6
7use yog_abi::{YogApi, YogBytes, YogReply, YogStatus, YogStr, status};
8use yog_data::Schema;
9use yog_event::{EventPhase, Message, Service, keyed};
10
11use crate::bus::{EventCx, Subscription};
12use crate::registry::{Registry, global_api, guard};
13
14unsafe extern "C" fn on_call<S: Service, F>(
17 ud: *mut c_void,
18 payload: YogBytes,
19 reply: *const YogReply,
20) -> YogStatus
21where
22 F: Fn(&S, &EventCx) -> S::Reply + Send + Sync,
23{
24 let s = unsafe { &*(ud as *const Subscription<S, F>) };
25 let Some(request) = S::from_bytes(unsafe { payload.as_slice() }) else {
26 return status::BAD_PAYLOAD;
27 };
28 let Some(answer) = guard(None, || Some((s.handler())(&request, &s.cx()))) else {
29 return status::FAILED;
30 };
31 unsafe { YogReply::send(reply, &answer.to_bytes()) };
32 status::OK
33}
34
35impl Registry {
36 pub fn provide<S: Service + 'static>(
39 &mut self,
40 handler: impl Fn(&S, &EventCx) -> S::Reply + Send + Sync + 'static,
41 ) -> bool {
42 self.providing::<S, _>(S::ADDRESS, Arc::new(handler))
43 }
44
45 pub fn provide_key<S: Service + 'static>(
48 &mut self,
49 key: &str,
50 handler: impl Fn(&S, &EventCx) -> S::Reply + Send + Sync + 'static,
51 ) -> bool {
52 self.providing::<S, _>(&keyed(S::ADDRESS, key), Arc::new(handler))
53 }
54
55 pub(crate) fn provide_raw(&mut self, address: &str, handler: yog_abi::YogHandlerFn) -> bool {
57 let bus = unsafe { &*self.api().bus };
58 let st = unsafe {
59 let at = (bus.resolve)(bus.ctx, YogStr::from_str(address));
60 (bus.provide)(bus.ctx, at, std::ptr::null_mut(), handler)
61 };
62 if st != status::OK {
63 crate::error!("{address}: someone provides it already");
64 }
65 st == status::OK
66 }
67
68 pub(crate) fn declare(
71 &self,
72 address: &str,
73 doc: &str,
74 shape: &yog_data::Shape,
75 reply: &yog_data::Shape,
76 interfaces: Vec<String>,
77 ) {
78 use yog_event::bus::{AddressKind, Descriptor};
79 let d = Descriptor {
80 address: address.to_owned(),
81 kind: AddressKind::Service,
82 doc: doc.to_owned(),
83 shape: shape.describe(),
84 reply: Some(reply.describe()),
85 interfaces,
86 };
87 self.call(&d);
88 }
89
90 fn providing<S: Service + 'static, F>(&mut self, address: &str, handler: Arc<F>) -> bool
91 where
92 F: Fn(&S, &EventCx) -> S::Reply + Send + Sync + 'static,
93 {
94 let api = self.api();
95 let bus = unsafe { &*api.bus };
96 let sub = Box::into_raw(Box::new(Subscription::<S, F>::new(
97 handler,
98 EventPhase::Post,
99 )));
100 let st = unsafe {
101 let at = (bus.resolve)(bus.ctx, YogStr::from_str(address));
102 (bus.provide)(bus.ctx, at, sub.cast::<c_void>(), on_call::<S, F>)
103 };
104 if st != status::OK {
105 crate::error!("{address}: someone provides it already");
106 return false;
107 }
108 let doc = match S::shape() {
109 yog_data::Shape::Struct(s) => s.doc,
110 _ => "",
111 };
112 self.declare(S::ADDRESS, doc, &S::shape(), &S::Reply::shape(), Vec::new());
113 true
114 }
115}
116
117pub fn call<S: Service>(request: &S) -> Option<S::Reply> {
120 let api = global_api();
121 if api.is_null() {
122 return None;
123 }
124 call_on(unsafe { &*api }, request)
125}
126
127impl Registry {
128 pub fn call<S: Service>(&self, request: &S) -> Option<S::Reply> {
130 call_on(self.api(), request)
131 }
132}
133
134fn call_on<S: Service>(api: &YogApi, request: &S) -> Option<S::Reply> {
135 let reply = request.with_bytes(|payload| call_bytes(api, request.address(), payload))?;
136 S::Reply::from_bytes(&reply)
137}
138
139pub(crate) fn call_at(address: yog_event::Address, payload: &[u8]) -> Option<Vec<u8>> {
142 let api = global_api();
143 if api.is_null() {
144 return None;
145 }
146 call_bytes(unsafe { &*api }, address, payload)
147}
148
149pub(crate) const ATOMS: yog_data::atom::Table = yog_data::atom::Table {
152 intern: atom,
153 name: atom_name,
154};
155
156fn atom(name: &str) -> u32 {
157 let bus = unsafe { &*(*global_api()).bus };
158 unsafe { (bus.atom)(bus.ctx, YogStr::from_str(name)) }
159}
160
161fn atom_name(n: u32) -> Option<&'static str> {
162 let bus = unsafe { &*(*global_api()).bus };
163 let s = unsafe { (bus.atom_name)(bus.ctx, n) };
164 (!s.ptr.is_null()).then(|| unsafe {
166 std::str::from_utf8_unchecked(std::slice::from_raw_parts(s.ptr, s.len as usize))
167 })
168}
169
170fn call_bytes(api: &YogApi, address: yog_event::Address, payload: &[u8]) -> Option<Vec<u8>> {
171 let bus = unsafe { &*api.bus };
172 let mut out = Vec::new();
173 let st = unsafe {
174 (bus.call)(
175 bus.ctx,
176 address,
177 YogBytes::new(payload),
178 &YogReply::to_vec(&mut out),
179 )
180 };
181 (st == status::OK).then_some(out)
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use yog_event::block::Insert;
188
189 fn answer<F: Fn(&Insert, &EventCx) -> bool + Send + Sync>(
191 handler: F,
192 request: &[u8],
193 ) -> (YogStatus, Vec<u8>) {
194 let sub = Subscription::<Insert, F>::new(Arc::new(handler), EventPhase::Post);
195 let mut out = Vec::new();
196 let ud = (&sub as *const Subscription<Insert, F>)
197 .cast_mut()
198 .cast::<c_void>();
199 let st = unsafe {
200 on_call::<Insert, F>(ud, YogBytes::new(request), &YogReply::to_vec(&mut out))
201 };
202 (st, out)
203 }
204
205 fn insert(item: &str) -> Insert {
206 Insert {
207 block_id: "mymod:crate".into(),
208 pos: yog_core::BlockPos { x: 0, y: 64, z: 0 },
209 dimension: "minecraft:overworld".into(),
210 item_id: item.into(),
211 count: 1,
212 nbt: String::new(),
213 simulate: true,
214 }
215 }
216
217 #[test]
218 fn a_provider_answers_with_its_reply() {
219 let takes_stone = |i: &Insert, _: &EventCx| i.item_id == "minecraft:stone";
220 let (st, out) = answer(takes_stone, &insert("minecraft:stone").to_bytes());
221 assert_eq!((st, bool::from_bytes(&out)), (status::OK, Some(true)));
222 let (_, out) = answer(takes_stone, &insert("minecraft:dirt").to_bytes());
223 assert_eq!(bool::from_bytes(&out), Some(false));
224 }
225
226 #[test]
227 fn a_bad_request_or_a_panic_isnt_an_answer() {
228 let ok = |_: &Insert, _: &EventCx| true;
229 assert_eq!(answer(ok, b"junk").0, status::BAD_PAYLOAD);
230 let panics = |_: &Insert, _: &EventCx| -> bool { panic!("mod bug") };
231 assert_eq!(
232 answer(panics, &insert("minecraft:stone").to_bytes()).0,
233 status::FAILED
234 );
235 }
236}