Skip to main content

unlit3dx/
handle.rs

1//! The handle every spawn hands back.
2//!
3//! A spawned world — a sub-world on the host thread, a threaded world on its
4//! own — is reached the same way: a bounded command channel that never
5//! blocks. [`crate::handle::WorldHandle`] is that uniform handle. The spawn
6//! functions return it instead of a bare entity, so a caller queues work
7//! without a second lookup for the proxy component on the host.
8
9use unlit_ecs::Entity;
10
11use crate::channel::SnapshotReader;
12use crate::job::JobSender;
13use crate::thread::{ThreadStatus, WorldState};
14
15/// A cloneable handle to a spawned world.
16///
17/// It carries the host entity the world's proxy component lives on, a
18/// cloneable command sender, and the state the world publishes about itself.
19/// It can be handed to the world that queues work without the host's `World`
20/// in hand. Cloning it does not spawn anything: the world lives on until the
21/// host drops the proxy entity.
22#[derive(Clone)]
23pub struct WorldHandle {
24    /// The host entity the world's proxy component lives on.
25    pub entity: Entity,
26    /// The command queue. Queueing through [`send`](JobSender::send) never
27    /// blocks: a full or stopped queue reports
28    /// [`SendError`](crate::job::SendError) and hands the job back.
29    pub sender: JobSender,
30    /// What the world is doing, and how it ended.
31    ///
32    /// A world publishes its state, so a reader that arrives after the end
33    /// still learns how the world ended, and reading never takes the answer
34    /// away from another reader.
35    pub state: SnapshotReader<WorldState>,
36    status: ThreadStatus,
37}
38
39impl WorldHandle {
40    /// Bind a handle to the entity carrying the world's proxy.
41    ///
42    /// A runner of the caller's own builds its handle this way: the world it
43    /// drives shares its thread, so it stays running until that runner ends
44    /// it.
45    #[must_use]
46    pub fn new(entity: Entity, sender: JobSender) -> Self {
47        Self::with_status(entity, sender, ThreadStatus::detached())
48    }
49
50    /// Bind a handle to a world the spawn functions drive themselves.
51    #[must_use]
52    pub(crate) fn with_status(entity: Entity, sender: JobSender, status: ThreadStatus) -> Self {
53        Self {
54            entity,
55            sender,
56            state: status.state(),
57            status,
58        }
59    }
60
61    /// Whether the world has stopped or died.
62    ///
63    /// It reads the same state the world publishes, so a world of any kind
64    /// answers it: a threaded world when its thread ends, a stepped world
65    /// when its tick ends or its owner despawns it, and a sub-world when its
66    /// host stops it or drops it.
67    #[must_use]
68    pub fn is_finished(&self) -> bool {
69        self.status.is_finished()
70    }
71
72    /// Ask the world to stop, without waiting for it.
73    ///
74    /// The world ends on its driver's next turn — the thread's next loop, the
75    /// host's next [`step_stepped_world`](crate::world::step_stepped_world) or
76    /// [`drain_sub_world`](crate::host::drain_sub_world) — and only then does
77    /// its queue close. A job sent in between is accepted and then dropped,
78    /// so a caller that must know the job ran reads [`state`](Self::state)
79    /// rather than trusting the send.
80    pub fn stop(&self) {
81        self.status.stop();
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use unlit_ecs::World;
88
89    /// A handle queues jobs into the world it was spawned for.
90    #[test]
91    fn a_handle_queues_into_its_world() {
92        let mut host = World::new();
93        let handle = crate::host::spawn_sub_world(&mut host, "nested", 8);
94        let ran = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
95        let seen = std::sync::Arc::clone(&ran);
96        handle
97            .sender
98            .send(Box::new(move |_: &mut World| {
99                seen.store(true, std::sync::atomic::Ordering::SeqCst);
100            }))
101            .expect("the queue has room");
102        let mut world_row = host
103            .root_scope()
104            .query_one::<&mut World>(handle.entity)
105            .expect("the nested world");
106
107        let nested = world_row.get();
108        crate::host::drain_sub_world(nested);
109        assert!(ran.load(std::sync::atomic::Ordering::SeqCst));
110    }
111}