1use std::fmt;
2
3use crate::bridge::Bridge;
4use crate::domain::{
5 ChartCommand, ChartPose, DomainError, DomainHandle, DomainId, DomainSpace, Domains, Field,
6 Instance, Pose,
7};
8use crate::entity::{Entities, EntitiesSnapshot, Entity, SceneId};
9use crate::relation::Relation;
10use crate::session::{
11 Assets, Material, MaterialId, PaletteId, PreparedGeometry, PreparedId, RestoreError,
12};
13use crate::store::{Owner, StoreError};
14use crate::stores::{HasRelation, HasStore, Stores};
15use crate::view::Views;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
18pub struct RequestId(u64);
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum Outcome {
22 Done,
23 Spawned(Entity),
24}
25
26impl fmt::Display for Outcome {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 match self {
29 Self::Done => f.write_str("done"),
30 Self::Spawned(entity) => write!(f, "spawned {entity}"),
31 }
32 }
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum Rejection {
37 Reserved(Entity),
38 Capacity,
39 Domain(DomainError),
40 Store(StoreError),
41 Restore(RestoreError),
42 Cancelled,
43 Unsupported(&'static str),
44 #[cfg(feature = "physics")]
46 Edit(loam_physics::EditError),
47}
48
49impl fmt::Display for Rejection {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 match self {
52 Self::Reserved(entity) => write!(f, "{entity} is still reserved"),
53 Self::Capacity => f.write_str("the queue is full"),
54 Self::Domain(error) => fmt::Display::fmt(error, f),
55 Self::Store(error) => fmt::Display::fmt(error, f),
56 Self::Restore(error) => fmt::Display::fmt(error, f),
57 Self::Cancelled => f.write_str("cancelled by a reset"),
58 Self::Unsupported(what) => write!(f, "unsupported: {what}"),
59 #[cfg(feature = "physics")]
60 Self::Edit(error) => fmt::Display::fmt(error, f),
61 }
62 }
63}
64
65impl From<DomainError> for Rejection {
66 fn from(error: DomainError) -> Self {
67 Self::Domain(error)
68 }
69}
70
71impl From<StoreError> for Rejection {
72 fn from(error: StoreError) -> Self {
73 Self::Store(error)
74 }
75}
76
77#[cfg(feature = "physics")]
78impl From<loam_physics::EditError> for Rejection {
79 fn from(error: loam_physics::EditError) -> Self {
80 Self::Edit(error)
81 }
82}
83
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85pub struct CommandResult {
86 pub request: RequestId,
87 pub name: &'static str,
88 pub outcome: Result<Outcome, Rejection>,
89}
90
91#[derive(Clone, Copy, Debug, PartialEq, Eq)]
93pub struct Reservation {
94 pub request: RequestId,
95 pub entity: Entity,
96}
97
98pub trait AppCommand<A>: Send + 'static {
99 fn name(&self) -> &'static str;
100
101 fn apply(&mut self, dispatch: &mut Dispatch<'_, A>) -> Result<Outcome, Rejection>;
102}
103
104pub enum Command<A> {
105 Spawn(SpawnBundle<A>),
106 Despawn(Entity),
107 Chart(DomainId, ChartCommand),
108 App(Box<dyn AppCommand<A>>),
109 Reset,
110}
111
112impl<A: 'static> Command<A> {
113 pub fn name(&self) -> &'static str {
114 match self {
115 Self::Spawn(_) => "spawn",
116 Self::Despawn(_) => "despawn",
117 Self::Chart(_, _) => "chart",
118 Self::App(command) => command.name(),
119 Self::Reset => "reset",
120 }
121 }
122}
123
124impl<A> Command<A> {
125 pub fn try_app_fn(
126 name: &'static str,
127 apply: impl FnMut(&mut Dispatch<'_, A>) -> Result<Outcome, Rejection> + Send + 'static,
128 ) -> Self {
129 Self::App(Box::new(FnCommand { name, apply }))
130 }
131}
132
133pub struct Request<A> {
134 pub id: RequestId,
135 pub command: Command<A>,
136}
137
138pub struct Commands<A> {
140 queue: Vec<Request<A>>,
141 entities: Entities,
142 next: u64,
143}
144
145impl<A: Stores> Commands<A> {
146 pub(crate) fn new(scene: SceneId) -> Self {
147 Self {
148 queue: Vec::new(),
149 entities: Entities::new(scene),
150 next: 0,
151 }
152 }
153
154 pub(crate) fn entities(&self) -> &Entities {
155 &self.entities
156 }
157
158 pub(crate) fn entities_mut(&mut self) -> &mut Entities {
159 &mut self.entities
160 }
161
162 pub(crate) fn drain_into(&mut self, into: &mut Vec<Request<A>>) {
163 into.append(&mut self.queue);
164 }
165
166 pub(crate) fn cancel_into(&mut self, results: &mut Vec<CommandResult>) {
167 for request in self.queue.drain(..) {
168 if let Command::Spawn(SpawnBundle {
169 reserved: Some(entity),
170 ..
171 }) = &request.command
172 {
173 self.entities.release(*entity);
174 }
175 results.push(CommandResult {
176 request: request.id,
177 name: request.command.name(),
178 outcome: Err(Rejection::Cancelled),
179 });
180 }
181 }
182
183 pub(crate) fn next_request(&self) -> RequestId {
184 RequestId(self.next)
185 }
186
187 pub(crate) fn restore(&mut self, entities: &EntitiesSnapshot, next: RequestId) {
188 self.entities.restore(entities);
189 self.next = self.next.max(next.0);
190 }
191
192 pub fn submit(&mut self, command: Command<A>) -> RequestId {
193 let id = RequestId(self.next);
194 self.next += 1;
195 self.queue.push(Request { id, command });
196 id
197 }
198
199 pub fn app(&mut self, command: impl AppCommand<A>) -> RequestId {
200 self.submit(Command::App(Box::new(command)))
201 }
202
203 pub fn try_app_fn(
204 &mut self,
205 name: &'static str,
206 apply: impl FnMut(&mut Dispatch<'_, A>) -> Result<Outcome, Rejection> + Send + 'static,
207 ) -> RequestId {
208 self.submit(Command::try_app_fn(name, apply))
209 }
210
211 pub fn spawn(&mut self, mut bundle: SpawnBundle<A>) -> Result<Reservation, Rejection> {
212 let entity = self.entities.reserve();
213 bundle.reserved = Some(entity);
214 let request = self.submit(Command::Spawn(bundle));
215 Ok(Reservation { request, entity })
216 }
217
218 pub fn pending(&self) -> &[Request<A>] {
219 &self.queue
220 }
221
222 pub fn len(&self) -> usize {
223 self.queue.len()
224 }
225
226 pub fn is_empty(&self) -> bool {
227 self.queue.is_empty()
228 }
229}
230
231struct FnCommand<F> {
232 name: &'static str,
233 apply: F,
234}
235
236impl<A, F> AppCommand<A> for FnCommand<F>
237where
238 F: FnMut(&mut Dispatch<'_, A>) -> Result<Outcome, Rejection> + Send + 'static,
239{
240 fn name(&self) -> &'static str {
241 self.name
242 }
243
244 fn apply(&mut self, dispatch: &mut Dispatch<'_, A>) -> Result<Outcome, Rejection> {
245 (self.apply)(dispatch)
246 }
247}
248
249trait Place: Send {
250 fn place(
251 &self,
252 domains: &mut Domains,
253 entity: Entity,
254 instance: Option<Instance>,
255 ) -> Result<DomainId, Rejection>;
256}
257
258struct TypedPlace<S: DomainSpace> {
259 domain: DomainHandle<S>,
260 pose: Pose<S>,
261}
262
263impl<S: DomainSpace> Place for TypedPlace<S> {
264 fn place(
265 &self,
266 domains: &mut Domains,
267 entity: Entity,
268 instance: Option<Instance>,
269 ) -> Result<DomainId, Rejection> {
270 let domain = domains.typed(self.domain)?;
271 domain.attach_pose(entity, self.pose)?;
272 if let Some(instance) = instance {
273 domain.attach_instance(entity, instance)?;
274 }
275 Ok(self.domain.id())
276 }
277}
278
279struct ChartPlace {
280 domain: DomainId,
281 pose: ChartPose,
282}
283
284impl Place for ChartPlace {
285 fn place(
286 &self,
287 domains: &mut Domains,
288 entity: Entity,
289 instance: Option<Instance>,
290 ) -> Result<DomainId, Rejection> {
291 let domain = domains
292 .facade(self.domain)
293 .ok_or(DomainError::UnknownDomain(self.domain))?;
294 domain.apply(&ChartCommand::Place {
295 entity,
296 pose: self.pose,
297 })?;
298 if let Some(instance) = instance {
299 domain.apply(&ChartCommand::Attach { entity, instance })?;
300 }
301 Ok(self.domain)
302 }
303}
304
305trait Attach<A>: Send {
306 fn attach(self: Box<Self>, app: &mut A, entity: Entity) -> Result<(), StoreError>;
307}
308
309struct Row<T>(T);
310
311impl<A: HasStore<T>, T: Send + 'static> Attach<A> for Row<T> {
312 fn attach(self: Box<Self>, app: &mut A, entity: Entity) -> Result<(), StoreError> {
313 app.store_mut().insert_raw(entity, self.0)
314 }
315}
316
317pub struct SpawnBundle<A> {
319 placement: Option<Box<dyn Place>>,
320 instance: Option<Instance>,
321 rows: Vec<Box<dyn Attach<A>>>,
322 reserved: Option<Entity>,
323}
324
325impl<A: Stores> Default for SpawnBundle<A> {
326 fn default() -> Self {
327 Self::new()
328 }
329}
330
331impl<A: Stores> SpawnBundle<A> {
332 pub fn new() -> Self {
333 Self {
334 placement: None,
335 instance: None,
336 rows: Vec::new(),
337 reserved: None,
338 }
339 }
340
341 pub fn at<S: DomainSpace>(mut self, domain: DomainHandle<S>, pose: Pose<S>) -> Self {
342 self.placement = Some(Box::new(TypedPlace { domain, pose }));
343 self
344 }
345
346 pub fn at_chart(mut self, domain: DomainId, pose: ChartPose) -> Self {
347 self.placement = Some(Box::new(ChartPlace { domain, pose }));
348 self
349 }
350
351 pub fn instance(mut self, instance: Instance) -> Self {
352 self.instance = Some(instance);
353 self
354 }
355
356 pub fn row<T: Send + 'static>(mut self, row: T) -> Self
357 where
358 A: HasStore<T>,
359 {
360 self.rows.push(Box::new(Row(row)));
361 self
362 }
363}
364
365pub struct Dispatch<'a, A> {
367 pub app: &'a mut A,
368 pub domains: &'a mut Domains,
369 pub views: &'a mut Views,
370 entities: &'a mut Entities,
371 bridges: &'a mut Relation<Bridge>,
372 assets: &'a mut Assets,
373}
374
375impl<'a, A: Stores> Dispatch<'a, A> {
376 pub(crate) fn new(
377 app: &'a mut A,
378 domains: &'a mut Domains,
379 views: &'a mut Views,
380 entities: &'a mut Entities,
381 bridges: &'a mut Relation<Bridge>,
382 assets: &'a mut Assets,
383 ) -> Self {
384 Self {
385 app,
386 domains,
387 views,
388 entities,
389 bridges,
390 assets,
391 }
392 }
393
394 pub fn prepare(&mut self, geometry: PreparedGeometry) -> PreparedId {
395 self.assets.prepare(geometry)
396 }
397
398 pub fn prepared(&self, id: PreparedId) -> Option<&PreparedGeometry> {
399 self.assets.prepared(id)
400 }
401
402 pub fn add_palette(&mut self, colors: Vec<[f32; 4]>) -> PaletteId {
403 self.assets.add_palette(colors)
404 }
405
406 pub fn palette(&self, id: PaletteId) -> Option<&[[f32; 4]]> {
407 self.assets.palette(id)
408 }
409
410 pub fn add_material(&mut self, material: Material) -> MaterialId {
411 self.assets.add_material(material)
412 }
413
414 pub fn material(&self, id: MaterialId) -> Option<&Material> {
415 self.assets.material(id)
416 }
417
418 pub fn spawn(&mut self, bundle: SpawnBundle<A>) -> Result<Entity, Rejection> {
419 let entity = match bundle.reserved {
420 Some(entity) if self.entities.is_reserved(entity) => entity,
421 Some(entity) => return Err(Rejection::Domain(DomainError::Stale(entity))),
422 None => self.entities.reserve(),
423 };
424 match self.attach_bundle(entity, bundle) {
425 Ok(()) => {
426 self.entities.commit(entity);
427 Ok(entity)
428 }
429 Err(rejection) => {
430 self.detach(entity);
431 self.entities.release(entity);
432 Err(rejection)
433 }
434 }
435 }
436
437 pub fn attach<T: Send + 'static>(&mut self, entity: Entity, row: T) -> Result<(), Rejection>
438 where
439 A: HasStore<T>,
440 {
441 self.require_live(entity)?;
442 self.app.store_mut().insert_raw(entity, row)?;
443 Ok(())
444 }
445
446 pub fn link<T: Send + 'static>(
447 &mut self,
448 from: Entity,
449 to: Entity,
450 data: T,
451 ) -> Result<crate::relation::LinkId, Rejection>
452 where
453 A: HasRelation<T>,
454 {
455 self.require_live(from)?;
456 self.require_live(to)?;
457 Ok(self.app.relation_mut().link_raw(from, to, data)?)
458 }
459
460 pub fn attach_instance<S: DomainSpace>(
461 &mut self,
462 domain: DomainHandle<S>,
463 entity: Entity,
464 instance: Instance,
465 ) -> Result<(), Rejection> {
466 self.require_live(entity)?;
467 self.domains
468 .typed(domain)?
469 .attach_instance(entity, instance)?;
470 Ok(())
471 }
472
473 pub fn attach_field<S: DomainSpace>(
474 &mut self,
475 domain: DomainHandle<S>,
476 entity: Entity,
477 field: Field,
478 ) -> Result<(), Rejection> {
479 self.require_live(entity)?;
480 self.domains.typed(domain)?.attach_field(entity, field)?;
481 Ok(())
482 }
483
484 fn attach_bundle(&mut self, entity: Entity, bundle: SpawnBundle<A>) -> Result<(), Rejection> {
485 if let Some(placement) = &bundle.placement {
486 placement.place(self.domains, entity, bundle.instance)?;
487 }
488 for row in bundle.rows {
489 row.attach(self.app, entity)?;
490 }
491 Ok(())
492 }
493
494 fn detach(&mut self, entity: Entity) {
495 self.app.release(entity, Owner::new());
496 for domain in self.domains.iter_mut() {
497 domain.release(entity);
498 }
499 while let Some(id) = self
500 .bridges
501 .outgoing(entity)
502 .next()
503 .or_else(|| self.bridges.incoming(entity).next())
504 {
505 let Ok(link) = self.bridges.unlink(id) else {
506 break;
507 };
508 self.views.unplace(link.data.image);
509 }
510 }
511
512 pub fn despawn(&mut self, entity: Entity) -> Result<(), Rejection> {
513 if self.entities.is_reserved(entity) {
514 return Err(Rejection::Reserved(entity));
515 }
516 if self.entities.resolve(entity).is_none() {
517 return Err(Rejection::Domain(DomainError::Stale(entity)));
518 }
519 self.detach(entity);
520 self.entities.despawn(entity)?;
521 Ok(())
522 }
523
524 fn require_live(&self, entity: Entity) -> Result<(), Rejection> {
525 self.entities
526 .resolve(entity)
527 .map(|_| ())
528 .ok_or(Rejection::Domain(DomainError::Stale(entity)))
529 }
530
531 pub fn apply(&mut self, command: Command<A>) -> Result<Outcome, Rejection> {
532 match command {
533 Command::Spawn(bundle) => self.spawn(bundle).map(Outcome::Spawned),
534 Command::Despawn(entity) => self.despawn(entity).map(|()| Outcome::Done),
535 Command::Chart(domain, command) => {
536 let entity = command.entity();
537 if self.entities.resolve(entity).is_none() {
538 return Err(Rejection::Domain(DomainError::Stale(entity)));
539 }
540 self.domains
541 .facade(domain)
542 .ok_or(DomainError::UnknownDomain(domain))?
543 .apply(&command)
544 }
545 Command::App(mut command) => command.apply(self),
546 Command::Reset => Err(Rejection::Unsupported(
547 "reset applies at a session boundary",
548 )),
549 }
550 }
551
552 pub fn entities(&self) -> &Entities {
553 self.entities
554 }
555}