Skip to main content

traaains/sim/track/segment/commands/
remove.rs

1use bevy::{
2    ecs::{entity::Entity, system::Command, world::World},
3    log::tracing,
4    state::state::{NextState, State},
5};
6
7use crate::{
8    sim::{
9        logical_graph::LogicalBlock,
10        track::{
11            node::{RemoveConnectionFor, TrackNode},
12            segment::{TrackSegment, signals_manager::SignalsManager},
13        },
14    },
15    ui::UiState,
16};
17
18/// Command which removes a track segment along with taking care of removing node and logical connections.
19pub struct RemoveSegment {
20    /// Target segment to remove
21    target: Entity,
22    /// Whether to despawn nodes. This should almost always be `true` unless one is splitting segments.
23    despawn_nodes: bool,
24    /// Whether to despawn block. This should almost always be `true` unless one is splitting segments.
25    despawn_block: bool,
26    /// Whether to despawn associated signals. This should almost always be `true` unless one is splitting segments.
27    despawn_signals: bool,
28}
29
30impl RemoveSegment {
31    pub fn new(target: Entity) -> Self {
32        Self {
33            target,
34            despawn_nodes: true,
35            despawn_block: true,
36            despawn_signals: true,
37        }
38    }
39
40    pub fn with_despawn_nodes(mut self, despawn_nodes: bool) -> Self {
41        self.despawn_nodes = despawn_nodes;
42        self
43    }
44
45    pub fn with_despawn_block(mut self, despawn_block: bool) -> Self {
46        self.despawn_block = despawn_block;
47        self
48    }
49
50    pub fn with_despawn_signals(mut self, despawn_signals: bool) -> Self {
51        self.despawn_signals = despawn_signals;
52        self
53    }
54
55    /// Decreases number of segments for the given block and despawns it if the number is zero.
56    /// Always returns false if RemoveSegment's despawn_block is set to false.
57    fn decrease_block_segments(world: &mut World, block: Entity, despawn: bool) -> bool {
58        let mut lb = world
59            .query::<&mut LogicalBlock>()
60            .get_mut(world, block)
61            .unwrap();
62
63        lb.number_of_segments -= 1;
64
65        if despawn && lb.number_of_segments == 0 {
66            tracing::debug!(
67                "Despawning block {} due to having no attached segments",
68                block
69            );
70            world.despawn(block);
71            true
72        } else {
73            false
74        }
75    }
76}
77
78impl Command for RemoveSegment {
79    type Out = Result<TrackSegment, &'static str>;
80
81    fn apply(self, world: &mut World) -> Self::Out {
82        // despawn UI if it belongs to our segment
83        let ui_state = world.resource::<State<UiState>>();
84        if let UiState::SegmentWindow { target } = ui_state.get()
85            && *target == self.target
86        {
87            world
88                .resource_mut::<NextState<UiState>>()
89                .set(UiState::Default);
90        }
91
92        let Some(ts) = world.entity_mut(self.target).take::<TrackSegment>() else {
93            panic!("RemoveSegment applied on a non-track segment entity!");
94        };
95
96        let block_despawned =
97            RemoveSegment::decrease_block_segments(world, ts.block, self.despawn_block);
98
99        for node in [ts.from_node, ts.to_node] {
100            if RemoveConnectionFor::new(node, self.target)
101                .with_despawn(self.despawn_nodes)
102                .apply(world)
103                == Ok(true)
104            {
105                continue;
106            }
107
108            let Ok(tn) = world.query::<&mut TrackNode>().get(world, node) else {
109                panic!("Applied RemoveSegment on a non-track node entity!")
110            };
111
112            let local_segments: Vec<_> = tn.connections.iter().map(|c| c.segment).collect();
113            // true if this node contains more segments from the same block
114            let same_local_segments = local_segments
115                .into_iter()
116                .filter(|s| *s != self.target)
117                .map(|s| world.query::<&TrackSegment>().get(world, s).unwrap().block)
118                .any(|b| b == ts.block);
119
120            if !block_despawned && !same_local_segments {
121                let mut lb = world
122                    .query::<&mut LogicalBlock>()
123                    .get_mut(world, ts.block)
124                    .unwrap();
125
126                // connections are ordered by node first and entities are just u64
127                // get all connections from our block through `from_node` and delete them
128                let node_logical_conns: Vec<_> = lb
129                    .connections
130                    .range_mut(
131                        &(node, Entity::from_bits(1)).into()
132                            ..&(node, Entity::from_bits(u64::MAX)).into(),
133                    )
134                    .map(|(k, _)| k.clone())
135                    .collect();
136
137                for key in node_logical_conns {
138                    lb.connections.remove(&key);
139                }
140            }
141
142            // get logical blocks on either nodes and try to remove their connections keyed by (node, self.target)
143            let node_segments: Vec<_> = world
144                .query::<&TrackNode>()
145                .get(world, node)
146                .unwrap()
147                .connections
148                .iter()
149                .map(|c| c.segment)
150                .collect();
151
152            let node_blocks: Vec<_> = node_segments
153                .into_iter()
154                .map(|segment_id| {
155                    world
156                        .query::<&TrackSegment>()
157                        .get(world, segment_id)
158                        .unwrap()
159                        .block
160                })
161                .collect();
162
163            for block in node_blocks {
164                let mut block = world
165                    .query::<&mut LogicalBlock>()
166                    .get_mut(world, block)
167                    .unwrap();
168
169                block.connections.remove(&(node, self.target).into());
170            }
171        }
172
173        if self.despawn_signals {
174            let signals_manager = world
175                .entity_mut(self.target)
176                .take::<SignalsManager>()
177                .unwrap();
178
179            for (_, signal) in signals_manager.inner {
180                world.despawn(signal);
181            }
182        }
183
184        // finally, despawn the target segment
185        world.despawn(self.target);
186
187        Ok(ts)
188    }
189}