1use std::collections::HashMap;
2use std::sync::{Arc, OnceLock, RwLock};
3
4use crate::engine_access::EngineAccess;
5use crate::error::HookError;
6use crate::hooks::HookContext;
7use crate::result::{ExecuteResult, HookResult, Verdict};
8use crate::runtime::RawProviderEvent;
9use crate::wire::ActionWire;
10
11pub struct BuiltinHookCall<'a> {
17 pub ctx: &'a HookContext<'a>,
18 pub data_override: Option<&'a [u8]>,
19 pub engine_access: &'a dyn EngineAccess,
20 pub now: f64,
21 pub provider_events_enabled: bool,
22}
23
24#[derive(Default)]
26pub struct BuiltinHookHost {
27 injected_actions: Vec<ActionWire>,
28 provider_events: Vec<RawProviderEvent>,
29 modified_data: Option<Vec<u8>>,
30}
31
32impl BuiltinHookHost {
33 pub fn inject_action(&mut self, action: ActionWire) {
34 self.injected_actions.push(action);
35 }
36
37 pub fn emit_event(
38 &mut self,
39 call: &BuiltinHookCall<'_>,
40 payload_type: impl Into<String>,
41 payload: impl Into<Vec<u8>>,
42 ) -> Result<(), HookError> {
43 if !call.provider_events_enabled {
44 return Err(HookError::Trap(
45 "provider events are not enabled for this invocation".into(),
46 ));
47 }
48 self.provider_events.push(RawProviderEvent {
49 payload_type: payload_type.into(),
50 payload: payload.into(),
51 });
52 Ok(())
53 }
54
55 pub fn set_modified_data(&mut self, data: impl Into<Vec<u8>>) {
56 self.modified_data = Some(data.into());
57 }
58
59 fn finish(self, result: HookResult) -> ExecuteResult {
60 ExecuteResult {
61 hook_result: Some(result),
62 injected_actions: self.injected_actions,
63 provider_events: self
64 .provider_events
65 .into_iter()
66 .map(|event| crate::result::EmittedProviderEvent {
67 hook_name: String::new(),
68 payload_type: event.payload_type,
69 payload: event.payload,
70 })
71 .collect(),
72 modified_data: self.modified_data,
73 }
74 }
75}
76
77pub trait BuiltinHook: Send + Sync + 'static {
78 fn call(
79 &self,
80 call: BuiltinHookCall<'_>,
81 host: &mut BuiltinHookHost,
82 ) -> Result<HookResult, HookError>;
83}
84
85impl<F> BuiltinHook for F
86where
87 F: for<'a, 'b> Fn(
88 BuiltinHookCall<'a>,
89 &'b mut BuiltinHookHost,
90 ) -> Result<HookResult, HookError>
91 + Send
92 + Sync
93 + 'static,
94{
95 fn call(
96 &self,
97 call: BuiltinHookCall<'_>,
98 host: &mut BuiltinHookHost,
99 ) -> Result<HookResult, HookError> {
100 self(call, host)
101 }
102}
103
104#[derive(Clone)]
105pub struct BuiltinProgram {
106 id: String,
107 hook: Arc<dyn BuiltinHook>,
108}
109
110impl BuiltinProgram {
111 pub fn load(id: impl Into<String>) -> Result<Self, HookError> {
112 let id = id.into();
113 let hook = resolve_builtin_hook(&id).ok_or_else(|| {
114 HookError::CompileError(format!("built-in hook '{}' is not registered", id))
115 })?;
116 Ok(Self { id, hook })
117 }
118
119 pub fn id(&self) -> &str {
120 &self.id
121 }
122
123 pub fn execute(
124 &self,
125 ctx: &HookContext<'_>,
126 data_override: Option<&[u8]>,
127 engine_access: &dyn EngineAccess,
128 now: f64,
129 provider_events_enabled: bool,
130 ) -> Result<ExecuteResult, HookError> {
131 let call = BuiltinHookCall {
132 ctx,
133 data_override,
134 engine_access,
135 now,
136 provider_events_enabled,
137 };
138 let mut host = BuiltinHookHost::default();
139 let result = self.hook.call(call, &mut host)?;
140 if Verdict::from_u32(result.verdict).is_none() {
141 return Err(HookError::InvalidResult(format!(
142 "invalid verdict value: {}",
143 result.verdict
144 )));
145 }
146 Ok(host.finish(result))
147 }
148}
149
150static BUILTIN_HOOKS: OnceLock<RwLock<HashMap<String, Arc<dyn BuiltinHook>>>> = OnceLock::new();
151
152fn registry() -> &'static RwLock<HashMap<String, Arc<dyn BuiltinHook>>> {
153 BUILTIN_HOOKS.get_or_init(|| RwLock::new(HashMap::new()))
154}
155
156pub fn register_builtin_hook(
164 id: impl Into<String>,
165 hook: impl BuiltinHook,
166) -> Result<(), HookError> {
167 let id = id.into();
168 let mut hooks = registry()
169 .write()
170 .map_err(|_| HookError::CompileError("built-in hook registry poisoned".into()))?;
171 if hooks.contains_key(&id) {
172 return Err(HookError::CompileError(format!(
173 "built-in hook '{}' is already registered",
174 id
175 )));
176 }
177 hooks.insert(id, Arc::new(hook));
178 Ok(())
179}
180
181pub fn resolve_builtin_hook(id: &str) -> Option<Arc<dyn BuiltinHook>> {
182 registry().read().ok()?.get(id).cloned()
183}
184
185#[cfg(test)]
186pub fn clear_builtin_hooks_for_tests() {
187 if let Ok(mut hooks) = registry().write() {
188 hooks.clear();
189 }
190}