sim_lib_skill/
registry.rs1use std::{
2 collections::BTreeMap,
3 sync::{Arc, Mutex},
4};
5
6use sim_citizen_derive::non_citizen;
7use sim_kernel::{Cx, Error, Object, ObjectCompat, Result, Symbol, Value};
8
9#[cfg(any(feature = "cache", feature = "cassette"))]
10use crate::record::SkillAuditEntry;
11use crate::{SkillCallable, SkillCard, SkillTransport, SkillTransportValue};
12
13#[derive(Clone, Default)]
21#[non_citizen(
22 reason = "live skill registry handle; cards use skill/Card descriptor",
23 kind = "handle"
24)]
25pub struct SkillRegistry {
26 state: Arc<Mutex<SkillRegistryState>>,
27}
28
29#[derive(Default)]
30struct SkillRegistryState {
31 transports: BTreeMap<String, Arc<dyn SkillTransport>>,
32 cards: BTreeMap<String, SkillCard>,
33 #[cfg(any(feature = "cache", feature = "cassette"))]
34 audit: Vec<SkillAuditEntry>,
35}
36
37impl SkillRegistry {
38 pub fn install_transport(&self, transport: Arc<dyn SkillTransport>) -> Result<()> {
41 self.state
42 .lock()
43 .map_err(|_| Error::PoisonedLock("skill registry"))?
44 .transports
45 .insert(transport.id().to_owned(), transport);
46 Ok(())
47 }
48
49 pub fn bind_card(&self, cx: &mut Cx, card: SkillCard) -> Result<Value> {
56 let transport = {
57 let state = self
58 .state
59 .lock()
60 .map_err(|_| Error::PoisonedLock("skill registry"))?;
61 state
62 .transports
63 .get(&card.transport_id)
64 .cloned()
65 .ok_or_else(|| {
66 Error::Eval(format!("missing skill transport {}", card.transport_id))
67 })?
68 };
69 #[cfg(any(feature = "cache", feature = "cassette"))]
70 let skill_callable = SkillCallable::new_bound(card.clone(), transport, self.clone());
71 #[cfg(not(any(feature = "cache", feature = "cassette")))]
72 let skill_callable = SkillCallable::new(card.clone(), transport);
73 let callable = cx.factory().opaque(Arc::new(skill_callable))?;
74 cx.registry_mut()
75 .register_function_value(card.symbol.clone(), callable.clone())?;
76 crate::browse::publish_card_claims(cx, &card)?;
77 self.state
78 .lock()
79 .map_err(|_| Error::PoisonedLock("skill registry"))?
80 .cards
81 .insert(card.id.clone(), card);
82 Ok(callable)
83 }
84
85 pub fn cards(&self) -> Result<Vec<SkillCard>> {
87 Ok(self
88 .state
89 .lock()
90 .map_err(|_| Error::PoisonedLock("skill registry"))?
91 .cards
92 .values()
93 .cloned()
94 .collect())
95 }
96
97 pub fn card_by_id(&self, id: &str) -> Result<Option<SkillCard>> {
99 Ok(self
100 .state
101 .lock()
102 .map_err(|_| Error::PoisonedLock("skill registry"))?
103 .cards
104 .get(id)
105 .cloned())
106 }
107
108 pub fn card_by_symbol(&self, symbol: &Symbol) -> Result<Option<SkillCard>> {
110 Ok(self
111 .state
112 .lock()
113 .map_err(|_| Error::PoisonedLock("skill registry"))?
114 .cards
115 .values()
116 .find(|card| &card.symbol == symbol)
117 .cloned())
118 }
119
120 #[cfg(any(feature = "cache", feature = "cassette"))]
121 pub(crate) fn record_audit(&self, entry: SkillAuditEntry) -> Result<()> {
122 self.state
123 .lock()
124 .map_err(|_| Error::PoisonedLock("skill registry"))?
125 .audit
126 .push(entry);
127 Ok(())
128 }
129
130 #[cfg(any(feature = "cache", feature = "cassette"))]
131 pub(crate) fn audit_values(&self, cx: &mut Cx) -> Result<Value> {
132 let entries = self
133 .state
134 .lock()
135 .map_err(|_| Error::PoisonedLock("skill registry"))?
136 .audit
137 .clone();
138 let values = entries
139 .iter()
140 .map(|entry| entry.value(cx))
141 .collect::<Result<Vec<_>>>()?;
142 cx.factory().list(values)
143 }
144}
145
146impl Object for SkillRegistry {
147 fn display(&self, _cx: &mut Cx) -> Result<String> {
148 Ok("#<skill-registry>".to_owned())
149 }
150
151 fn as_any(&self) -> &dyn std::any::Any {
152 self
153 }
154}
155
156impl ObjectCompat for SkillRegistry {
157 fn as_table(&self, cx: &mut Cx) -> Result<Value> {
158 cx.factory().table(vec![
159 (
160 Symbol::new("kind"),
161 cx.factory().symbol(Symbol::new("skill/registry"))?,
162 ),
163 (
164 Symbol::new("cards"),
165 cx.factory().string(self.cards()?.len().to_string())?,
166 ),
167 ])
168 }
169}
170
171pub fn skill_registry_symbol() -> Symbol {
173 Symbol::qualified("skill", "registry")
174}
175
176pub fn skill_registry(cx: &mut Cx) -> Result<SkillRegistry> {
180 let value = cx.resolve_value(&skill_registry_symbol())?;
181 value
182 .object()
183 .downcast_ref::<SkillRegistry>()
184 .cloned()
185 .ok_or(Error::TypeMismatch {
186 expected: "skill registry",
187 found: "non-registry",
188 })
189}
190
191pub(crate) fn transport_from_value(value: &Value) -> Result<Arc<dyn SkillTransport>> {
192 value
193 .object()
194 .downcast_ref::<SkillTransportValue>()
195 .map(SkillTransportValue::transport)
196 .ok_or(Error::TypeMismatch {
197 expected: "skill transport",
198 found: "non-transport",
199 })
200}
201
202pub(crate) fn card_from_value(cx: &mut Cx, value: &Value) -> Result<SkillCard> {
203 if let Some(card) = value.object().downcast_ref::<SkillCard>() {
204 return Ok(card.clone());
205 }
206 let expr = value.object().as_expr(cx)?;
207 SkillCard::from_expr(&expr)
208}