Skip to main content

traaains/sim/track/node/
mod.rs

1//! A node in the physical track graph.
2
3use bevy::{
4    asset::Handle,
5    ecs::{
6        component::Component,
7        entity::Entity,
8        event::EntityEvent,
9        observer::{ObserverSystemExt, On},
10        system::{Commands, ResMut},
11    },
12    mesh::{Mesh, Mesh2d},
13    picking::events::PointerPress,
14    sprite_render::{ColorMaterial, MeshMaterial2d},
15    state::{condition::in_state, state::NextState},
16    transform::components::Transform,
17};
18use smallvec::SmallVec;
19
20use crate::{editor::EditorState, ui::UiState, utils::alloc_map::AllocMap};
21
22mod commands;
23mod connection;
24mod manager;
25
26pub use commands::RemoveConnectionFor;
27pub use connection::{Connection, ConnectionLocation};
28
29const MAX_CONNECTIONS: usize = 32;
30const STATIC_CONNECTIONS: usize = 3;
31
32// I do have some doubts about this. Point was to have really quick lookup times for other sides of active connections, which currently it is O(1).
33// Inserting a node is at most times O(log n), however sometimes it is required to reallocate the inner smallvec, which makes the operation more
34// expensive. Finding a specific connection for a given segment is O(n), not just respective to the number of tracked connections, but to the size
35// of the inner smallvec.
36// TODO: Implement resizing of the inner smallvec
37//
38// Of course, it would be easy to just keep the connections in some version of a BST keyed by the connection's segment Entity.
39// This version has the added benefit that most nodes, having only two to three connections, fit on the stack.
40///
41/// Keeps track of node connections. Stores them in a vector, with `alloc_map` keeping track of which spaces in the vector
42/// are allocated and which are not. Empty spaces are marked with [Option::None] in the inner vector.
43#[derive(Clone, Default)]
44pub struct NodeConnections {
45    inner: SmallVec<[Option<Connection>; STATIC_CONNECTIONS]>,
46    alloc_map: AllocMap<MAX_CONNECTIONS>,
47    count: usize,
48
49    /// Indices of connections which make up an active connection.
50    /// Stored in order, meaning a\[0\] < a\[1\] (a\[0\] != a\[1\], an active connection cannot be between itself).
51    active: Option<(usize, usize)>,
52}
53
54impl NodeConnections {
55    fn find_first_two(&self) -> Option<[usize; 2]> {
56        // find the allocated connections and activate them
57        let mut conns = self
58            .inner
59            .iter()
60            .enumerate()
61            .filter_map(|(i, c)| if c.is_some() { Some(i) } else { None });
62
63        Some([conns.next()?, conns.next()?])
64    }
65
66    /// Tries to add the connection. If the allocation succeded, returns the connections ID.
67    /// If there is no space left, returns the connection wrapped in an error.
68    pub fn add(&mut self, c: Connection) -> Result<usize, Connection> {
69        match self.alloc_map.allocate() {
70            Some(index) => {
71                // if the index is larger then the inner smallvec's length, then we want to push it, otherwise set it to index
72                // index > self.inner.len() can never happen, because the allocation map always returns minimum
73                if index == self.inner.len() {
74                    self.inner.push(Some(c));
75                } else {
76                    self.inner[index] = Some(c);
77                }
78
79                self.count += 1;
80
81                if self.count == 2 {
82                    let [c0, c1] = self.find_first_two().unwrap();
83                    self.activate(c0, c1);
84                }
85
86                Ok(index)
87            }
88            None => Err(c),
89        }
90    }
91
92    /// Attempts to remove the given connection.
93    /// If no such connection exists, returns false, otherwise returns true.
94    pub fn remove(&mut self, index: usize) -> bool {
95        if !self.alloc_map.is_allocated(index) {
96            return false;
97        }
98
99        self.inner[index] = None;
100        self.alloc_map.deallocate(index);
101        self.count -= 1;
102
103        if let Some(ac) = self.active
104            && (ac.0 == index || ac.1 == index)
105        {
106            self.deactivate();
107        }
108
109        true
110    }
111
112    /// Attempts to remove a connection with the given segment
113    /// If no such connection exists, returns false, otherwise returns true.
114    pub fn remove_for(&mut self, segment: Entity) -> bool {
115        for (i, c) in self.inner.iter().enumerate() {
116            if let Some(conn) = c
117                && conn.segment == segment
118            {
119                return self.remove(i);
120            }
121        }
122
123        false
124    }
125
126    /// Whether the backing store has any more free spaces.
127    pub fn has_free(&self) -> bool {
128        self.alloc_map.has_free()
129    }
130
131    /// Returns an iterator over the inner connections
132    pub fn iter(&self) -> impl Iterator<Item = &Connection> {
133        self.inner.iter().filter_map(|c| c.as_ref())
134    }
135
136    /// Returns the number of connections
137    pub fn len(&self) -> usize {
138        self.count
139    }
140
141    /// Returns whether there are no connections, similar to [Vec::is_empty()]
142    pub fn is_empty(&self) -> bool {
143        self.len() == 0
144    }
145
146    /// Sets connections with indices `c0` and `c1` as active.
147    pub fn activate(&mut self, mut c0: usize, mut c1: usize) {
148        assert!(c0 != c1);
149
150        if c0 > c1 {
151            (c0, c1) = (c1, c0);
152        }
153
154        self.active = Some((c0, c1));
155    }
156
157    /// Removes the active connection.
158    pub fn deactivate(&mut self) {
159        self.active = None;
160    }
161
162    /// Returns the active connection.
163    pub fn active(&self) -> Option<(&Connection, &Connection)> {
164        Some((
165            self.inner[self.active?.0].as_ref().unwrap(),
166            self.inner[self.active?.1].as_ref().unwrap(),
167        ))
168    }
169
170    fn advance(c0: &mut usize, c1: &mut usize, len: usize) {
171        if *c1 + 1 < len {
172            *c1 += 1;
173        } else {
174            if *c0 + 2 < len {
175                *c0 += 1;
176            } else {
177                *c0 = 0;
178            }
179            *c1 = *c0 + 1;
180        }
181    }
182
183    /// Does nothing if there aren't enough connections present (n < 2).
184    pub fn activate_next(&mut self) {
185        match self.active {
186            Some((sc0, sc1)) => {
187                let len = self.inner.len();
188                let (mut c0, mut c1) = (sc0, sc1);
189                NodeConnections::advance(&mut c0, &mut c1, len);
190
191                loop {
192                    if self.inner[c1].is_some() {
193                        self.activate(c0, c1);
194                        return;
195                    }
196
197                    NodeConnections::advance(&mut c0, &mut c1, len);
198
199                    if (c0, c1) == (sc0, sc1) {
200                        return;
201                    }
202                }
203            }
204            None => {
205                if let Some([c0, c1]) = self.find_first_two() {
206                    self.activate(c0, c1);
207                }
208            }
209        }
210    }
211
212    /// Returns the other side of the active connection for the `segment`.
213    /// None if `segment` isn't part of the active connection.
214    pub fn get_active_for(&self, segment: Entity) -> Option<&Connection> {
215        if self.inner[self.active?.0].as_ref().unwrap().segment == segment {
216            return self.inner[self.active?.1].as_ref();
217        }
218
219        if self.inner[self.active?.1].as_ref().unwrap().segment == segment {
220            return self.inner[self.active?.0].as_ref();
221        }
222
223        None
224    }
225}
226
227/// A node in the physical track graph.
228#[derive(Clone, Default, Component)]
229pub struct TrackNode {
230    pub connections: NodeConnections,
231}
232
233impl TrackNode {
234    pub fn spawn(
235        cmd: &mut Commands,
236        transform: Transform,
237        mesh: Handle<Mesh>,
238        mat: Handle<ColorMaterial>,
239    ) -> Entity {
240        cmd.spawn((
241            TrackNode::default(),
242            transform,
243            Mesh2d(mesh),
244            MeshMaterial2d(mat),
245        ))
246        .observe(on_node_press_window.run_if(in_state(EditorState::Viewing)))
247        .id()
248    }
249}
250
251/// Observer that listens for pointer presses on nodes and opens editing windows
252pub fn on_node_press_window(e: On<PointerPress>, mut next_state: ResMut<NextState<UiState>>) {
253    next_state.set(UiState::NodeWindow {
254        target: e.event_target(),
255    });
256}