Skip to main content

traaains/sim/
logical_graph.rs

1//! Logical graph containing the stops, etc. Unfinished.
2//!
3//! The idea is, have a logical graph containing all the different blocks (plain, stops, etc.) separate from the physical track graph.
4//! Each track segment is a member of exactly one block. A segment can be a member of either any of the neighboring blocks,
5//! extending its border, or a completely new one.
6//! At the same time, block borders aren't defined by signals. That means:
7//! - one can have a block without any signals covering it (like a station on open rail)
8//! - signals don't create a block by themselves, allowing for the existence of "distant signals" (předvěsti)
9//!
10//! Edges in the graph are directional in the logical sense. An edge exists for each physical connection that the blocks have.
11//! Each direction, however, can be "covered" by a different signal.
12//! That allows for the creation of (dopravny), where the situation can be something like this:
13//!
14//! ```text
15//!                  /-@-[track 3]-@-\
16//! ---[open rail]-x-/-@-[track 1]-@-\-x-[open rail]---
17//! ```
18//!
19//! Where `x` denotes an entry signal and `@` denote exit signals. In our current model, there is a total of 8 physical segments,
20//! whereas only four logical blocks, two for open rails on either side of the station and one block for each station track.
21//! Each open rail has two edges, connecting them to the station tracks. In the entry direction to the station tracks, the edge
22//! (or the crossing between blocks) is covered by the entry signals. Opposite direction is covered by the exit signals. Trains
23//! have a cache of nearing signals and respond to signals, not block changes. That means that even if the block borders are behind
24//! the entry signals, trains will still react in time to them.
25//!
26//! The graph is a multigraph where each connection is identified by a tuple of (block, node).
27//! Thus, we can allow stops like this:
28//!
29//! ```text
30//!                /-[stop]-\
31//! ---[open rail]-/--------\-[open rail]---
32//!```
33//!
34//! In this situation, there's only two logical blocks: one covering the open rail and another making up the stop.
35//! The open rail block needs to be connected to the stop from both sides and thus needs to have two connections to the same block.
36
37use std::collections::BTreeMap;
38
39use bevy::ecs::{component::Component, entity::Entity};
40
41/// Since the graph of logical blocks is a multigraph, each connection is identified by a tuple of (block, node).
42/// Prioritizes nodes to allow fast queries of "What are all the connections at this node?"
43#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
44pub struct LogicalConnectionKey {
45    /// The track node connecting the two segments that make up this connection.
46    pub node: Entity,
47    pub segment: Entity,
48}
49
50impl LogicalConnectionKey {
51    pub fn new(node: Entity, segment: Entity) -> Self {
52        Self { node, segment }
53    }
54}
55
56impl From<(Entity, Entity)> for LogicalConnectionKey {
57    fn from((node, segment): (Entity, Entity)) -> Self {
58        Self::new(node, segment)
59    }
60}
61
62pub struct LogicalConnection {
63    /// The block on the other side of this connection.
64    pub block: Entity,
65    /// Control device covering this connection.
66    pub cover: Option<Entity>,
67}
68
69/// Each logical block stores an adjacency list of neigbouring blocks.
70/// It also stores the number of member segments to allow for cleanup when there aren't any segments in the block.
71#[derive(Component, Default)]
72pub struct LogicalBlock {
73    pub connections: BTreeMap<LogicalConnectionKey, LogicalConnection>,
74    pub number_of_segments: usize,
75}
76
77impl LogicalBlock {
78    pub fn with_segments(mut self, n: usize) -> Self {
79        self.number_of_segments = n;
80        self
81    }
82}