crux_core/bridge/
registry.rs1use std::collections::HashMap;
2use std::sync::Mutex;
3
4use facet::Facet;
5use serde::{Deserialize, Serialize};
6
7use super::{BridgeError, FfiFormat, Request};
8use crate::bridge::request_serde::ResolveSerialized;
9use crate::{EffectFFI, ResolveError};
10
11#[allow(clippy::unsafe_derive_deserialize)]
18#[derive(Facet, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(transparent)]
20#[facet(transparent)]
21pub struct EffectId(pub u32);
22
23pub struct ResolveRegistry<T: FfiFormat>(Mutex<Outstanding<T>>);
24
25struct Outstanding<T: FfiFormat> {
27 entries: HashMap<u32, ResolveSerialized<T>>,
28 next_id: u32,
29}
30
31impl<T: FfiFormat> Outstanding<T> {
32 fn issue_id(&mut self) -> u32 {
40 loop {
41 let id = self.next_id;
42 self.next_id = self.next_id.wrapping_add(1);
43
44 if !self.entries.contains_key(&id) {
45 return id;
46 }
47 }
48 }
49}
50
51impl<T: FfiFormat> Default for ResolveRegistry<T> {
52 fn default() -> Self {
53 Self(Mutex::new(Outstanding {
54 entries: HashMap::new(),
55 next_id: 0,
56 }))
57 }
58}
59
60impl<T: FfiFormat> ResolveRegistry<T> {
61 pub fn register<Eff>(&self, effect: Eff) -> Request<Eff::Ffi>
72 where
73 Eff: EffectFFI,
74 {
75 let (effect, resolve) = effect.serialize();
76
77 let id = {
78 let mut outstanding = self.0.lock().expect("Registry Mutex poisoned.");
79 let id = outstanding.issue_id();
80
81 if !matches!(resolve, ResolveSerialized::Never) {
85 outstanding.entries.insert(id, resolve);
86 }
87
88 id
89 };
90
91 Request {
92 id: EffectId(id),
93 effect,
94 }
95 }
96 pub fn resume(&self, id: EffectId, response: &[u8]) -> Result<(), BridgeError<T>> {
112 let mut outstanding = self.0.lock().expect("Registry Mutex poisoned");
113
114 let Some(entry) = outstanding.entries.get_mut(&id.0) else {
115 return Err(BridgeError::ProcessResponse(ResolveError::NotFound(
116 id.0.into(),
117 )));
118 };
119
120 let resolved = entry.resolve(response);
121
122 if matches!(entry, ResolveSerialized::Never) {
125 outstanding.entries.remove(&id.0);
126 }
127
128 resolved
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::{Outstanding, ResolveSerialized};
135 use crate::bridge::JsonFfiFormat;
136 use std::collections::HashMap;
137
138 fn outstanding(next_id: u32) -> Outstanding<JsonFfiFormat> {
139 Outstanding {
140 entries: HashMap::new(),
141 next_id,
142 }
143 }
144
145 #[test]
146 fn ids_ascend_and_are_never_reused() {
147 let mut outstanding = outstanding(0);
148
149 let ids: Vec<_> = (0..4).map(|_| outstanding.issue_id()).collect();
150 assert_eq!(ids, vec![0, 1, 2, 3]);
151
152 outstanding.entries.remove(&1);
154
155 assert_eq!(outstanding.issue_id(), 4);
156 }
157
158 #[test]
159 fn wrapping_steps_over_outstanding_ids() {
160 let mut outstanding = outstanding(u32::MAX);
161
162 outstanding.entries.insert(0, ResolveSerialized::Never);
164 outstanding.entries.insert(1, ResolveSerialized::Never);
165
166 assert_eq!(outstanding.issue_id(), u32::MAX);
167 assert_eq!(
168 outstanding.issue_id(),
169 2,
170 "wrapping displaced a request that was still outstanding"
171 );
172 }
173}