1use std::collections::BTreeMap;
24
25use crate::World;
26use crate::world::Resource;
27
28pub trait Summary: Resource + PartialEq {
31 fn merge(&mut self, other: Self) -> Result<(), String>;
34 fn retained_bytes(&self) -> usize;
38}
39
40#[derive(Clone, Copy)]
42struct Kind {
43 merge: fn(into: &mut World, from: &World) -> Result<(), String>,
45 equal: fn(a: &World, b: &World) -> bool,
47 copy: fn(from: &World, to: &mut World),
49 bytes: fn(w: &World) -> usize,
51 summary: bool,
53}
54
55impl std::fmt::Debug for Kind {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 f.write_str(if self.summary { "summary" } else { "decision" })
58 }
59}
60
61fn merge_typed<S: Summary>(into: &mut World, from: &World) -> Result<(), String> {
62 let Some(s) = from.resource::<S>().cloned() else {
63 return Ok(());
64 };
65 match into.resource_mut::<S>() {
66 Some(m) => m.merge(s),
67 None => {
68 into.insert_resource(s);
69 Ok(())
70 }
71 }
72}
73
74fn bytes_typed<S: Summary>(w: &World) -> usize {
75 w.resource::<S>().map_or(0, Summary::retained_bytes)
76}
77
78fn no_bytes(_: &World) -> usize {
79 0
80}
81
82fn equal_typed<S: Summary>(a: &World, b: &World) -> bool {
83 a.resource::<S>() == b.resource::<S>()
84}
85
86fn copy_typed<R: Resource>(from: &World, to: &mut World) {
87 if let Some(r) = from.resource::<R>() {
88 to.insert_resource(r.clone());
89 }
90}
91
92fn never_merged<R: Resource>(_: &mut World, _: &World) -> Result<(), String> {
93 Err(format!("{} is a decision, made once: it is not merged", R::NAME))
94}
95
96fn never_compared(_: &World, _: &World) -> bool {
98 true
99}
100
101#[derive(Debug, Default)]
103pub struct SummaryRegistry {
104 kinds: BTreeMap<&'static str, Kind>,
105}
106
107impl SummaryRegistry {
108 pub fn new() -> Self {
109 SummaryRegistry::default()
110 }
111
112 pub fn summary<S: Summary>(&mut self) {
114 self.kinds.insert(
115 S::NAME,
116 Kind {
117 merge: merge_typed::<S>,
118 equal: equal_typed::<S>,
119 copy: copy_typed::<S>,
120 bytes: bytes_typed::<S>,
121 summary: true,
122 },
123 );
124 }
125
126 pub fn decision<D: Resource>(&mut self) {
128 self.kinds.insert(
129 D::NAME,
130 Kind {
131 merge: never_merged::<D>,
132 equal: never_compared,
133 copy: copy_typed::<D>,
134 bytes: no_bytes,
135 summary: false,
136 },
137 );
138 }
139
140 pub fn summaries(&self) -> impl Iterator<Item = &'static str> + '_ {
142 self.kinds.iter().filter(|(_, k)| k.summary).map(|(n, _)| *n)
143 }
144
145 pub fn decisions(&self) -> impl Iterator<Item = &'static str> + '_ {
147 self.kinds.iter().filter(|(_, k)| !k.summary).map(|(n, _)| *n)
148 }
149
150 pub fn retained(&self, w: &World) -> usize {
152 self.kinds.values().map(|k| (k.bytes)(w)).sum()
153 }
154
155 pub fn take_summaries(&self, part: &World) -> World {
157 let mut out = World::new();
158 for k in self.kinds.values().filter(|k| k.summary) {
159 (k.copy)(part, &mut out);
160 }
161 out
162 }
163
164 pub fn merge_order_free(&self, parts: &[World]) -> Result<World, String> {
168 let forward = self.merge_in(&mut parts.iter())?;
169 let backward = self.merge_in(&mut parts.iter().rev())?;
170 for (name, k) in self.kinds.iter().filter(|(_, k)| k.summary) {
171 if !(k.equal)(&forward, &backward) {
172 return Err(format!(
173 "{name}: its merge depends on the order the parts arrive in (the merge of {} parts differs \
174 forward and backward)",
175 parts.len()
176 ));
177 }
178 }
179 Ok(forward)
180 }
181
182 pub fn merge(&self, parts: &[World]) -> Result<World, String> {
185 self.merge_in(&mut parts.iter())
186 }
187
188 fn merge_in(&self, order: &mut dyn Iterator<Item = &World>) -> Result<World, String> {
189 let mut out = World::new();
190 for p in order {
191 for (name, k) in self.kinds.iter().filter(|(_, k)| k.summary) {
192 (k.merge)(&mut out, p).map_err(|e| format!("{name}: {e}"))?;
193 }
194 }
195 Ok(out)
196 }
197
198 pub fn hand_on(&self, from: &World, to: &mut World) {
201 for k in self.kinds.values() {
202 (k.copy)(from, to);
203 }
204 }
205
206 pub fn take_summaries_made(&self, w: &World, handed: &World) -> World {
209 let given: std::collections::BTreeSet<&str> = handed.resource_names().into_iter().collect();
210 let mut out = World::new();
211 for (name, k) in self.kinds.iter().filter(|(_, k)| k.summary) {
212 if !given.contains(name) {
213 (k.copy)(w, &mut out);
214 }
215 }
216 out
217 }
218
219 pub fn carry_decisions(&self, from: &World, to: &mut World) {
221 for k in self.kinds.values().filter(|k| !k.summary) {
222 (k.copy)(from, to);
223 }
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[derive(Clone, Debug, PartialEq)]
233 struct Lowest(f64);
234 impl Resource for Lowest {
235 const NAME: &'static str = "toy.lowest";
236 }
237 impl Summary for Lowest {
238 fn merge(&mut self, other: Self) -> Result<(), String> {
239 self.0 = self.0.min(other.0);
240 Ok(())
241 }
242 fn retained_bytes(&self) -> usize {
243 8
244 }
245 }
246
247 #[derive(Clone, Debug, PartialEq)]
249 struct Arrivals(Vec<u32>);
250 impl Resource for Arrivals {
251 const NAME: &'static str = "toy.arrivals";
252 }
253 impl Summary for Arrivals {
254 fn merge(&mut self, other: Self) -> Result<(), String> {
255 self.0.extend(other.0);
256 Ok(())
257 }
258 fn retained_bytes(&self) -> usize {
259 4 * self.0.len()
260 }
261 }
262
263 #[derive(Clone, Debug, PartialEq)]
264 struct Floor(f64);
265 impl Resource for Floor {
266 const NAME: &'static str = "toy.floor";
267 }
268
269 fn part(lowest: f64, arrival: Option<u32>) -> World {
270 let mut w = World::new();
271 w.insert_resource(Lowest(lowest));
272 if let Some(a) = arrival {
273 w.insert_resource(Arrivals(vec![a]));
274 }
275 w
276 }
277
278 #[test]
279 fn an_order_free_summary_merges_and_an_order_bound_one_is_refused() {
280 let mut r = SummaryRegistry::new();
281 r.summary::<Lowest>();
282 r.decision::<Floor>();
283 let parts: Vec<World> = [3.0, -2.0, 7.0]
284 .into_iter()
285 .map(|l| r.take_summaries(&part(l, Some(1))))
286 .collect();
287 assert!(parts[0].resource::<Arrivals>().is_none());
289 let merged = r.merge_order_free(&parts).unwrap();
290 assert_eq!(merged.resource::<Lowest>(), Some(&Lowest(-2.0)));
291 let mut decided = merged;
293 decided.insert_resource(Floor(-2.0));
294 let mut p = World::new();
295 r.hand_on(&decided, &mut p);
296 assert_eq!(
297 (p.resource::<Lowest>(), p.resource::<Floor>()),
298 (Some(&Lowest(-2.0)), Some(&Floor(-2.0)))
299 );
300
301 r.summary::<Arrivals>();
302 let parts: Vec<World> = [1, 2, 3]
303 .into_iter()
304 .map(|a| r.take_summaries(&part(0.0, Some(a))))
305 .collect();
306 let e = r.merge_order_free(&parts).unwrap_err();
307 assert!(
308 e.starts_with("toy.arrivals: its merge depends on the order"),
309 "{e}"
310 );
311 assert!(r.merge_order_free(&parts[..1]).is_ok());
313 let without: Vec<World> = (0..2).map(|_| r.take_summaries(&part(1.0, None))).collect();
314 assert!(r.merge_order_free(&without).is_ok());
315 }
316
317 #[test]
318 fn a_decision_is_not_merged() {
319 let mut r = SummaryRegistry::new();
320 r.decision::<Floor>();
321 let mut into = World::new();
322 let mut from = World::new();
323 from.insert_resource(Floor(1.0));
324 let k = r.kinds[Floor::NAME];
325 assert!((k.merge)(&mut into, &from).unwrap_err().contains("a decision"));
326 assert_eq!(r.decisions().collect::<Vec<_>>(), [Floor::NAME]);
327 assert_eq!(r.summaries().count(), 0);
328 }
329}