Skip to main content

examples/systems/
system_immediate.rs

1use crate::z_ignore_test_common::*;
2
3use flecs_ecs::prelude::*;
4
5// When an application calls world.progress(), the world is put in readonly mode.
6// This ensures that systems (on multiple threads) can safely iterate
7// components, without having to worry about components moving around while
8// they're being read. This has as side effect that any operations (like adding
9// or removing components) are not visible until the end of the frame (see the
10// sync_point example for more details).
11// Sometimes this is not what you want, and you need a change to be visible
12// immediately. For these use cases, applications can use a no_readonly system.
13// This temporarily takes the world out of readonly mode, so a system can make
14// changes that are directly visible.
15// Because they mutate the world directly, no_readonly systems are never ran on
16// more than one thread, and no other systems are ran at the same time.
17
18#[derive(Component)]
19struct Waiter;
20
21#[derive(Component)]
22struct Plate;
23
24fn main() {
25    let world = World::new();
26
27    // Create query to find all waiters without a plate
28    let mut q_waiter = world
29        .query::<()>()
30        .with(Waiter)
31        .without((Plate, id::<flecs::Wildcard>()))
32        .build();
33
34    // System that assigns plates to waiter. By making this system no_readonly
35    // plate assignments are assigned directly (not deferred) to waiters, which
36    // ensures that we won't assign plates to the same waiter more than once.
37    world
38        .system_named::<()>("AssignPlate")
39        .with(Plate)
40        .without((Waiter, id::<flecs::Wildcard>()))
41        .immediate(true)
42        .each_iter(move |mut it, index, plate| {
43            let world = it.world();
44            let plate = it.entity(index);
45
46            // Find an available waiter
47            if let Some(waiter) = q_waiter.try_first_entity() {
48                // An available waiter was found, assign a plate to it so
49                // that the next plate will no longer find it.
50                // The defer_suspend function temporarily suspends deferring
51                // operations, which ensures that our plate is assigned
52                // immediately. Even though this is a no_readonly system,
53                // deferring is still enabled by default as adding/removing
54                // components to the entities being iterated would interfere
55                // with the system iterator.
56                it.world().defer_suspend();
57                waiter.add((Plate, plate));
58                it.world().defer_resume();
59
60                // Now that deferring is resumed, we can safely also add the
61                // waiter to the plate. We can't do this while deferring is
62                // suspended, because the plate is the entity we're
63                // currently iterating, and we don't want to move it to a
64                // different table while we're iterating it.
65
66                plate.add((Waiter, waiter));
67
68                println!("Assigned {} to {}!", waiter.name(), plate.name());
69            }
70        });
71
72    let waiter_1 = world.entity_named("waiter_1").add(Waiter);
73    world.entity_named("waiter_2").add(Waiter);
74    world.entity_named("waiter_3").add(Waiter);
75
76    world.entity_named("plate_1").add(Plate);
77    let plate_2 = world.entity_named("plate_2").add(Plate);
78    world.entity_named("plate_3").add(Plate);
79
80    waiter_1.add((Plate, plate_2));
81    plate_2.add((Waiter, waiter_1));
82
83    // run systems
84    world.progress();
85
86    // Output:
87    //  Assigned waiter_3 to plate_1!
88    //  Assigned waiter_2 to plate_3!
89}
90
91#[cfg(feature = "flecs_nightly_tests")]
92#[test]
93#[ignore = "todo fix"]
94fn test() {
95    let output_capture = OutputCapture::capture().unwrap();
96    main();
97    output_capture.test("system_no_readonly".to_string());
98}