1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//local shortcuts
use crate::prelude::*;

//third-party shortcuts
use bevy::prelude::*;

//standard shortcuts


//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------

fn garbage_collect_entities(world: &mut World)
{
    while let Some(entity) = world.resource::<AutoDespawner>().try_recv()
    {
        world.despawn(entity);
    }
}

//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------

/// Executes a system command on the world.
///
/// System commands scheduled by this system will be run recursively.
///
/// Pre-existing system commands will be temporarily removed then reinserted once the internal recursion is finished.
pub(crate) fn syscommand_runner(world: &mut World, command: SystemCommand, cleanup: SystemCommandCleanup)
{
    // cleanup
    garbage_collect_entities(world);

    // On abort we perform garbage collection in case the cleanup auto-despawns entities.
    let cleanup_on_abort = |world: &mut World|
    {
        cleanup.run(world);
        garbage_collect_entities(world);
        schedule_removal_and_despawn_reactors(world);
    };

    // extract the callback
    let Some(mut entity_mut) = world.get_entity_mut(*command)
    else
    {
        (cleanup_on_abort)(world);
        return;
    };
    let Some(mut system_command) = entity_mut.get_mut::<SystemCommandStorage>()
    else
    {
        tracing::error!(?command, "system command component is missing on extract");
        (cleanup_on_abort)(world);
        return;
    };
    let Some(mut callback) = system_command.take()
    else
    {
        tracing::warn!(?command, "system command missing");
        (cleanup_on_abort)(world);
        return;
    };

    // remove existing system commands temporarily
    let preexisting_syscommands = world.resource_mut::<CobwebCommandQueue<SystemCommand>>().remove();

    // run the system command
    callback.run(world, cleanup);

    // cleanup
    // - We do this before reinserting the callback in case the callback garbage collected itself.
    garbage_collect_entities(world);

    // reinsert the callback if its target hasn't been despawned
    if let Some(mut entity_mut) = world.get_entity_mut(*command)
    {
        if let Some(mut system_command) = entity_mut.get_mut::<SystemCommandStorage>()
        {
            system_command.insert(callback);
        }
        else
        {
            std::mem::drop(callback);
            tracing::error!(?command, "system command component is missing on insert");
        }
    }
    else
    {
        std::mem::drop(callback);
    }

    // cleanup
    // - We do this again just in case dropping the callback caused entities to be garbage collected.
    garbage_collect_entities(world);

    // schedule component removal and despawn reactors
    schedule_removal_and_despawn_reactors(world);

    // recurse over new system commands
    // - Note that when we recurse, any system commands from this scope will be removed and reinserted, so this
    //   loop will only act on commands added by the system command for this scope.
    while let Some(next_command) = world.resource_mut::<CobwebCommandQueue<SystemCommand>>().pop_front()
    {
        next_command.run(world);
    }

    // reinsert previously-existing system commands
    world.resource_mut::<CobwebCommandQueue<SystemCommand>>().append(preexisting_syscommands);
}

//-------------------------------------------------------------------------------------------------------------------

/// Runs a reaction tree to completion.
///
/// This is used for running system commands, system events, and reactions that are scheduled from outside the
/// reaction tree.
pub fn reaction_tree(world: &mut World)
{
    // Set the reaction tree flag to prevent the reaction tree from being recursively scheduled.
    // - We return if we are already in a reaction tree.
    if !world.resource_mut::<ReactCache>().start_reaction_tree() { return; }

    let mut reaction_queue = world.resource_mut::<CobwebCommandQueue<ReactionCommand>>().remove();
    let mut event_queue = world.resource_mut::<CobwebCommandQueue<EventCommand>>().remove();

    // Schedule component removal and despawn reactors.
    // - We do this once at the beginning of the tree in case the scheduled command that triggered the tree
    //   fails to actually run. Even if it doesn't run, we should still handle removals and despawns.
    garbage_collect_entities(world);
    schedule_removal_and_despawn_reactors(world);

    // Run the tree.
    'r: loop
    {
        'e: loop
        {
            // run all system commands recursively
            while let Some(next_command) = world.resource_mut::<CobwebCommandQueue<SystemCommand>>().pop_front()
            {
                next_command.run(world);
            }

            // new events go to the front
            event_queue = world.resource_mut::<CobwebCommandQueue<EventCommand>>().append_and_remove(event_queue);

            // run one system event
            let Some(next_event) = event_queue.pop_front() else { break 'e; };
            next_event.run(world);
        }

        // new reactions go to the front
        reaction_queue = world.resource_mut::<CobwebCommandQueue<ReactionCommand>>().append_and_remove(reaction_queue);

        // run one reaction
        let Some(next_reaction) = reaction_queue.pop_front() else { break 'r; };
        next_reaction.run(world);
    }

    world.resource_mut::<CobwebCommandQueue<EventCommand>>().append(event_queue);
    world.resource_mut::<CobwebCommandQueue<ReactionCommand>>().append(reaction_queue);

    // Unset the reaction tree flag now that we are returning to user-land.
    world.resource_mut::<ReactCache>().end_reaction_tree();
}

//-------------------------------------------------------------------------------------------------------------------