1use std::collections::BTreeMap;
18use std::sync::Arc;
19
20use sim_kernel::{CapabilityName, Cx, Error, Expr, Result, Symbol};
21
22use crate::codec::SurfaceCodec;
23use crate::contract::{Lens, LensKind};
24
25pub struct DispatchContext<'a> {
28 pub explicit: Option<Symbol>,
30 pub preference: Option<Symbol>,
32 pub active_mode: Option<Symbol>,
34 pub value_class: Option<Symbol>,
36 pub granted: &'a dyn Fn(&CapabilityName) -> bool,
38}
39
40impl<'a> DispatchContext<'a> {
41 pub fn permissive(grant_all: &'a dyn Fn(&CapabilityName) -> bool) -> Self {
43 Self {
44 explicit: None,
45 preference: None,
46 active_mode: None,
47 value_class: None,
48 granted: grant_all,
49 }
50 }
51}
52
53#[derive(Clone, Debug, PartialEq, Eq)]
55pub enum DispatchReason {
56 Explicit,
58 Preference,
60 ShapeMatch(i32),
62 ClassMatch,
64 UniversalDefault,
66}
67
68#[derive(Clone, Debug, PartialEq, Eq)]
70pub struct DispatchOutcome {
71 pub lens_id: Symbol,
73 pub reason: DispatchReason,
75}
76
77#[derive(Default)]
79pub struct LensRegistry {
80 lenses: Vec<Lens>,
81 surface_codecs: BTreeMap<Symbol, Arc<dyn SurfaceCodec>>,
82}
83
84impl LensRegistry {
85 pub fn new() -> Self {
87 Self::default()
88 }
89
90 pub fn register(&mut self, lens: Lens) {
92 self.lenses.push(lens);
93 }
94
95 pub fn register_surface_codec(&mut self, id: Symbol, codec: Arc<dyn SurfaceCodec>) {
97 self.surface_codecs.insert(id, codec);
98 }
99
100 pub fn surface_codec(&self, id: &Symbol) -> Option<Arc<dyn SurfaceCodec>> {
102 self.surface_codecs.get(id).cloned()
103 }
104
105 pub fn get(&self, id: &Symbol) -> Option<&Lens> {
107 self.lenses.iter().find(|lens| &lens.meta.id == id)
108 }
109
110 pub fn lenses(&self) -> &[Lens] {
112 &self.lenses
113 }
114
115 pub fn dispatch_view(
117 &self,
118 cx: &mut Cx,
119 target: &Expr,
120 ctx: &DispatchContext,
121 ) -> Result<DispatchOutcome> {
122 self.dispatch(cx, LensKind::View, target, ctx)
123 }
124
125 pub fn dispatch_editor(
127 &self,
128 cx: &mut Cx,
129 target: &Expr,
130 ctx: &DispatchContext,
131 ) -> Result<DispatchOutcome> {
132 self.dispatch(cx, LensKind::Editor, target, ctx)
133 }
134
135 pub fn dispatch(
137 &self,
138 cx: &mut Cx,
139 kind: LensKind,
140 target: &Expr,
141 ctx: &DispatchContext,
142 ) -> Result<DispatchOutcome> {
143 if let Some(outcome) = self.pick_named(&ctx.explicit, kind, ctx, DispatchReason::Explicit) {
145 return Ok(outcome);
146 }
147 if let Some(outcome) =
149 self.pick_named(&ctx.preference, kind, ctx, DispatchReason::Preference)
150 {
151 return Ok(outcome);
152 }
153 if let Some(outcome) = self.pick_shape_match(cx, kind, target, ctx)? {
155 return Ok(outcome);
156 }
157 if let Some(outcome) = self.pick_class_match(kind, ctx) {
159 return Ok(outcome);
160 }
161 if let Some(lens) = self.lenses.iter().find(|lens| {
163 lens.meta.kind == kind && lens.meta.universal_default && self.allowed(lens, ctx)
164 }) {
165 return Ok(DispatchOutcome {
166 lens_id: lens.meta.id.clone(),
167 reason: DispatchReason::UniversalDefault,
168 });
169 }
170 Err(Error::HostError(format!(
171 "no {kind:?} lens available for the value (not even a universal default)"
172 )))
173 }
174
175 pub(crate) fn allowed(&self, lens: &Lens, ctx: &DispatchContext) -> bool {
176 lens.meta
177 .required_capabilities
178 .iter()
179 .all(|capability| (ctx.granted)(capability))
180 }
181
182 fn pick_named(
183 &self,
184 id: &Option<Symbol>,
185 kind: LensKind,
186 ctx: &DispatchContext,
187 reason: DispatchReason,
188 ) -> Option<DispatchOutcome> {
189 let id = id.as_ref()?;
190 let lens = self.get(id)?;
191 if lens.meta.kind == kind && self.allowed(lens, ctx) {
192 Some(DispatchOutcome {
193 lens_id: id.clone(),
194 reason,
195 })
196 } else {
197 None
198 }
199 }
200
201 fn pick_shape_match(
202 &self,
203 cx: &mut Cx,
204 kind: LensKind,
205 target: &Expr,
206 ctx: &DispatchContext,
207 ) -> Result<Option<DispatchOutcome>> {
208 let mut best: Option<(i32, i32, i32, Symbol)> = None;
209 for lens in &self.lenses {
210 if lens.meta.kind != kind || lens.meta.universal_default || !self.allowed(lens, ctx) {
211 continue;
212 }
213 let Some(score) = best_shape_score(cx, lens, target)? else {
214 continue;
215 };
216 let candidate = (score, lens.meta.quality, -lens.meta.cost);
218 let better = match &best {
219 Some((bs, bq, bc, _)) => candidate > (*bs, *bq, *bc),
220 None => true,
221 };
222 if better {
223 best = Some((candidate.0, candidate.1, candidate.2, lens.meta.id.clone()));
224 }
225 }
226 Ok(best.map(|(score, _, _, lens_id)| DispatchOutcome {
227 lens_id,
228 reason: DispatchReason::ShapeMatch(score),
229 }))
230 }
231
232 fn pick_class_match(&self, kind: LensKind, ctx: &DispatchContext) -> Option<DispatchOutcome> {
233 let class = ctx.value_class.as_ref()?;
234 let mut best: Option<(i32, i32, Symbol)> = None;
235 for lens in &self.lenses {
236 if lens.meta.kind != kind
237 || lens.meta.universal_default
238 || !self.allowed(lens, ctx)
239 || !lens.meta.claimed_classes.contains(class)
240 {
241 continue;
242 }
243 let candidate = (lens.meta.quality, -lens.meta.cost);
244 let better = match &best {
245 Some((bq, bc, _)) => candidate > (*bq, *bc),
246 None => true,
247 };
248 if better {
249 best = Some((candidate.0, candidate.1, lens.meta.id.clone()));
250 }
251 }
252 best.map(|(_, _, lens_id)| DispatchOutcome {
253 lens_id,
254 reason: DispatchReason::ClassMatch,
255 })
256 }
257}
258
259pub(crate) fn best_shape_score(cx: &mut Cx, lens: &Lens, target: &Expr) -> Result<Option<i32>> {
261 let mut best: Option<i32> = None;
262 for shape_value in &lens.meta.claimed_shapes {
263 let Some(shape) = shape_value.object().as_shape() else {
264 continue;
265 };
266 let matched = shape.check_expr(cx, target)?;
267 if matched.accepted {
268 let score = matched.score.value();
269 best = Some(best.map_or(score, |current| current.max(score)));
270 }
271 }
272 Ok(best)
273}