1use std::collections::BTreeSet;
5
6use crate::pass::{Given, OptionSpec, Options, Pass, PassLog, PassSpec};
7use crate::{Component, Entity, Instance, JsonError, Mesh, MeshFile, Transform, World};
8use serde::Serialize;
9use serde_json::Value;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
17pub struct Priority(pub u8);
18
19impl Component for Priority {
20 const NAME: &'static str = "core.priority";
21 const GEOMETRY: crate::Geometry = crate::Geometry::None;
22 fn apply_linear(&self, _l: &crate::Linear) -> crate::world::Linearity<Self> {
24 crate::world::Linearity::Invariant
25 }
26
27 fn to_json(&self) -> Result<Option<Value>, JsonError> {
28 crate::json::to_value(self).map(Some)
29 }
30
31 fn verify(&self, entity: Entity, world: &World) -> Result<(), String> {
32 if world.has::<Instance>(entity)
33 || (world.has::<Transform>(entity)
34 && (world.has::<Mesh>(entity) || world.has::<MeshFile>(entity)))
35 {
36 Ok(())
37 } else {
38 Err("priority on an entity that is not placed".into())
39 }
40 }
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub struct DropOptional {
49 pub max: u8,
50}
51
52impl Default for DropOptional {
53 fn default() -> Self {
54 DropOptional { max: u8::MAX }
55 }
56}
57
58pub const DROP_OPTIONAL: PassSpec = PassSpec {
60 name: "drop-optional",
61 options: &[OptionSpec::optional(
62 "max",
63 "3",
64 "a priority 0-255: placements above it are dropped",
65 "255: nothing is dropped",
66 )],
67 make: drop_optional,
68};
69
70fn drop_optional(g: &Given) -> Result<Box<dyn Pass>, String> {
71 let mut pass = DropOptional::default();
72 if let Some(v) = g.optional("max") {
73 pass.max = v
74 .parse()
75 .map_err(|_| format!("max={v} is not a priority 0-255"))?;
76 }
77 Ok(Box::new(pass))
78}
79
80impl Pass for DropOptional {
81 fn name(&self) -> &'static str {
82 "drop-optional"
83 }
84 fn options(&self) -> Options {
85 if self.max == u8::MAX {
86 Options::new()
87 } else {
88 Options::from([("max".to_owned(), self.max.to_string())])
89 }
90 }
91 fn despawns_entities(&self) -> bool {
92 true
93 }
94 fn run(&self, w: &mut World, log: &mut PassLog) -> Result<(), String> {
95 let dropped: Vec<(Entity, u8)> = w
96 .iter::<Priority>()
97 .filter(|(_, p)| p.0 > self.max)
98 .map(|(e, p)| (e, p.0))
99 .collect();
100 let gone: BTreeSet<Entity> = dropped.iter().map(|(e, _)| *e).collect();
101 if let Some((name, holder, target)) = w
102 .references_to(&gone)
103 .into_iter()
104 .find(|(_, holder, _)| !gone.contains(holder))
105 {
106 return Err(format!(
107 "{name} on {holder:?} refers to {target:?}, a placement above priority {}",
108 self.max
109 ));
110 }
111 for (e, p) in dropped {
112 crate::ledger::despawn(w, e, log, &format!("placements at priority {p} left out"));
113 }
114 Ok(())
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::Name;
122 use crate::pass::{PassManager, PassRegistry, RunReport};
123
124 #[test]
125 fn priority_preserves_full_range_and_requires_a_placement() {
126 let mut world = World::new();
127 let entity = world.spawn();
128 world.insert(entity, Priority(255));
129 assert_eq!(world.verify_components().len(), 1);
130 world.insert(entity, Mesh::default());
131 assert_eq!(world.verify_components().len(), 1);
132 world.insert(entity, Transform::IDENTITY);
133 assert!(world.verify_components().is_empty());
134 for value in [0, 15, 16, 255] {
135 assert_eq!(Priority(value).to_json().unwrap(), Some(Value::from(value)));
136 }
137 }
138
139 fn placed_world() -> (World, Entity, Vec<Entity>) {
141 let mut w = World::new();
142 let proto = w.spawn();
143 w.insert(proto, Mesh::default());
144 let mut placed = Vec::new();
145 for p in [None, Some(0), Some(2), Some(3)] {
146 let e = w.spawn();
147 w.insert(
148 e,
149 Instance {
150 prototype: proto,
151 variant: 0,
152 },
153 );
154 w.insert(e, crate::Transform::IDENTITY);
155 if let Some(p) = p {
156 w.insert(e, Priority(p));
157 }
158 if p == Some(3) {
159 w.insert(e, Name("optional".into()));
160 }
161 placed.push(e);
162 }
163 (w, proto, placed)
164 }
165
166 #[test]
167 fn drop_optional_leaves_out_placements_above_the_maximum_through_the_manager() {
168 let (mut w, _, placed) = placed_world();
169 let mut registry = PassRegistry::new();
170 registry.add(&DROP_OPTIONAL);
171 let m = PassManager::new(registry);
172 let pipeline = m.registry.parse("drop-optional{max=2}").unwrap();
173 let mut report = RunReport::default();
174 m.run(&mut w, &pipeline, &mut report).unwrap();
175 let step = &report.steps[0];
176 assert_eq!(step.pass, "drop-optional{max=2}");
177 assert_eq!(
178 step.removed,
179 ["core.name"],
180 "only the dropped placement had a name"
181 );
182 assert!(step.log.counts.is_empty());
183 assert_eq!(
184 step.log.drops,
185 [(
186 (
187 crate::ledger::Measure::Placements,
188 "placements at priority 3 left out".to_owned()
189 ),
190 1
191 )]
192 .into()
193 );
194 assert_eq!(
195 placed.iter().map(|e| w.is_alive(*e)).collect::<Vec<_>>(),
196 [true, true, true, false]
197 );
198 let (mut w, _, _) = placed_world();
200 let all = w.entity_count();
201 let pipeline = m.registry.parse("drop-optional").unwrap();
202 m.run(&mut w, &pipeline, &mut RunReport::default()).unwrap();
203 assert_eq!(w.entity_count(), all);
204 assert!(
205 DROP_OPTIONAL
206 .instance(&Options::from([("max".into(), "256".into())]))
207 .is_err()
208 );
209 assert!(
210 DROP_OPTIONAL
211 .instance(&Options::from([("level".into(), "2".into())]))
212 .is_err()
213 );
214 }
215
216 #[test]
217 fn a_dropped_placement_something_kept_refers_to_is_refused() {
218 let (mut w, proto, placed) = placed_world();
219 w.insert(proto, Transform::IDENTITY);
221 w.insert(proto, Priority(5));
222 let err = DropOptional { max: 2 }
223 .run(&mut w, &mut PassLog::default())
224 .unwrap_err();
225 assert!(
226 err.contains("core.instance") && err.contains("above priority 2"),
227 "{err}"
228 );
229 assert!(
230 w.is_alive(proto) && placed.iter().all(|e| w.is_alive(*e)),
231 "nothing dropped"
232 );
233 }
234}