|
RTE RBC 2oo2 Generic Application 0.1.0 build 308eda7
IL/CTC route and train logic for the RBC_GP reference RBC
|
A/B's own route handling against GP's own runtime table (via AB/GP/ga_interface/ga_interface.h - see that file's own doc: this module never #includes any GP submodule header directly) and site/'s static track layout - today's slice: verify a route reported by ILSim against the layout, and add/remove/set-status/assign-train/unassign-train against GP's own route table. More...
#include <stdbool.h>#include <stdint.h>#include "rte/utils/status/rte_status.h"#include "rbc_wire_types.h"#include "site_config.h"#include "ga_interface.h"#include "ab_siteGA.h"Go to the source code of this file.
Functions | |
| void | ab_ga_il_on_route_context_changed (ab_gp_train_session_t *sessions, ab_gp_db_t *db, const ab_gp_route_context_t *ctx, ab_gp_route_update_type_t update_type) |
| GA's single dispatch module (safeAPIRBC2oo2GA/src/AB/main/ ab_ga_dispatch.c) is the ONLY thing that registers with GP now (interface-simplification pass) - it owns the ONE route-context subscription and fans AB_ROUTE_CONNECTED/_DISCONNECTED here. AB_ROUTE_ADDED/_STATUS_CHANGED are ignored (IL never cared about those). | |
| bool | ab_ga_il_site_has_route (uint16_t route_id, const ab_site_ga_route_layout_t **out_layout) |
Pure site-layout lookup - does route_id exist in this site's own static track layout at all (ab_site_ga_track_layout, ab_site.h)? | |
| bool | ab_ga_il_site_has_route_by_signals (uint16_t start_signal, uint16_t end_signal, const ab_site_ga_route_layout_t **out_layout) |
Pure site-layout lookup by route IDENTITY - does a route with this exact (start_signal, end_signal) pair exist in this site's own static track layout? Same shape as ab_ga_il_site_has_route() above, but by signal pair rather than synthetic id - the wire's own route-identity model (rbc_wire_types.h's own doc) - used to resolve an inbound ROUTE_ADD/ROUTE_RELEASE's own start_signal/end_signal fields to this site's internal route_id before touching db->routes[]. | |
| bool | ab_ga_il_route_verify (uint16_t route_id, uint16_t start_signal, uint16_t end_signal) |
| Verifies a route as reported by ILSim is OK: it must both exist in this site's own static layout AND report the same start/end signals the layout itself has for that route id - ILSim is external, untrusted input (same REQ-RBC-002 "reject a
malformed frame, don't index blind" posture already applied to wire input elsewhere in this project), so its own claimed signals are cross-checked against the site's own authoritative layout, not taken on faith. | |
| bool | ab_ga_il_route_add (ab_gp_db_t *db, uint16_t route_id, uint16_t start_signal, uint16_t end_signal) |
| Adds a new route to GP's own route table, after verifying it via ab_ga_il_route_verify() above - thin wrapper over ga_interface.h's own ab_gp_db_route_add(). | |
| bool | ab_ga_il_route_remove (ab_gp_db_t *db, uint16_t route_id) |
| Removes a tracked route from GP's own route table, freeing its slot - thin wrapper over ga_interface.h's own ab_gp_db_route_remove(). | |
| bool | ab_ga_il_route_set_status (ab_gp_db_t *db, uint16_t route_id, ab_gp_db_route_il_status_t status) |
| Updates a tracked route's own il_status - IL's last-reported status for it - thin wrapper over ga_interface.h's own ab_gp_db_route_set_status(). Does not touch occupied (a separate, non-IL-sourced concept). | |
| bool | ab_ga_il_route_set_type (ab_gp_db_t *db, uint16_t route_id, uint32_t route_type) |
| Updates a tracked route's own route_type - IL's last-reported supervision mode (rbc_route_type_t: RBC_ROUTE_TYPE_FS/_OS/_SH, rbc_wire_types.h) for it - thin wrapper over ga_interface.h's own ab_gp_db_route_set_type(). | |
| bool | ab_ga_il_route_assign_train (ab_gp_db_t *db, uint16_t route_id, uint8_t train_id) |
| Assigns a route to a train - thin wrapper over ga_interface.h's own ab_gp_db_route_connect_train() (which fires GP's own route-connected notify on success). | |
| bool | ab_ga_il_route_unassign_train (ab_gp_db_t *db, uint16_t route_id) |
| Reverses ab_ga_il_route_assign_train() - thin wrapper over ga_interface.h's own ab_gp_db_route_disconnect_train(). | |
| bool | ab_ga_il_handle_route_add (ab_gp_db_t *db, uint8_t train_id) |
| Handles the MA-length-bookkeeping half of an inbound ROUTE_ADD - marks this train as now holding a valid MA (ab_gp_train_set_has_ma()). Deliberately unaware of route identity/type - that half (resolving/creating the real ab_gp_db_route_t) is handle_route_add()'s own job (ab_ga_il.c, file-static) - this function stays the same narrow "train now has
an MA" bookkeeping step it always was, kept public since ab_ga_il_apply_envelope()'s own ROUTE_ADD path still calls it alongside the newer route-identity handling. | |
| bool | ab_ga_il_apply_envelope (const void *context, ab_gp_train_session_t *sessions, ab_gp_db_t *db, const uint8_t env_bytes[RBC_ENVELOPE_WIRE_SIZE], uint32_t cycle, rte_example_site_t site, const char *role_tag) |
THE handler this module registers with GP (ab_gp_register_il_envelope_handler(), ga_interface.h, via AB/main/ab_ga_dispatch.c's own ab_ga_dispatch_init()) for every raw envelope popped off its dedicated IL relay channel (ROUTE_ADD/ROUTE_RELEASE - see site_config.h's own rte_example_relay_kind_t doc) - "pure GA" now (later session, on direct request): GP no longer calls this by bare name, only ever through the registered function pointer, so context (unused here - matches ab_gp_il_envelope_handler_fn's own signature purely so this function can be registered directly, no trampoline needed) is always NULL. Wire-decodes the envelope (this module's own il_wire_decode(), ab_ga_il.c) and dispatches by kind to this module's own file-static handle_route_add()/handle_route_release(). | |
| uint8_t | ab_ga_il_build_outputs (ab_gp_il_pending_output_t out[AB_GP_IL_MAX_PENDING_OUTPUTS]) |
Drains this module's own TrainPositionInRoute event queue (populated by this module's own route-connected/-disconnected notify subscribers, registered from ab_ga_il_init() - see ga_interface.h's own ab_gp_db_register_route_connected_notify()/ _disconnected_notify()) and wire-encodes each into out. Deliberately takes no sessions/db parameters, unlike AB/GP/train/ab_gp_train.h's/AB/GA/ctc/ab_ga_ctc.h's own build_outputs() - everything this needs was already captured into the internal event queue by the notify callbacks at the moment each route-connect/-disconnect actually happened, not re-derived here from current table state. Called from AB/GP/main/post_execute.c's own ab_gp_channel_post_execute() (the real forward-to-C logic, step 3 of that function), same "GP orchestrates, GA builds" pattern CTC/Train already use. | |
| void | ab_ga_il_confirm_outputs_sent (void) |
| Clears the pending-route-event queue ab_ga_il_build_outputs() just built from - call ONLY once those outputs are confirmed actually forwarded to C this same cycle (post_execute.c's own should_forward_to_c). Nothing can enqueue a new event between a build()/confirm() pair in the same cycle (both run back to back in post_execute.c, nothing else executes in between), so this needs no count/identity parameter - it simply drops exactly what the immediately-preceding build() call drained. | |
A/B's own route handling against GP's own runtime table (via AB/GP/ga_interface/ga_interface.h - see that file's own doc: this module never #includes any GP submodule header directly) and site/'s static track layout - today's slice: verify a route reported by ILSim against the layout, and add/remove/set-status/assign-train/unassign-train against GP's own route table.
ab_ga_il_handle_route_add() below is this module's own inbound envelope handler: com/'s ab_gp_channel.c now forwards the raw ROUTE_ADD/ ROUTE_RELEASE wire bytes here rather than decoding them itself - see ab_ga_il_apply_envelope()'s own doc. IL<->RBC route-identity pass: the wire now carries a real route identity (start_signal, end_signal - see rbc_wire_types.h's own doc), so ROUTE_ADD genuinely creates/looks up a real ab_gp_db_route_t now (via ab_ga_il_site_has_route_by_signals() below), and ROUTE_RELEASE (new) lets IL explicitly tell RBC a route is no longer needed. Reporting a route's own occupancy status back to ILSim (the "send back info to sim whether train is inside route or not" half of this module's original scope) IS now implemented - ab_ga_il_build_outputs() below, driven by GP's own route-connected/ -disconnected notify hooks (ga_interface.h), the same way AB/GA/ctc/ab_ga_ctc.h's own ab_ga_ctc_build_outputs() already works for CTC.
Every function below that takes ab_gp_db_t * explicitly does so as the caller-owned, GP-opaque parameter (same "caller-owned storage, no hidden state" convention as ab_gp_channel_context_t) - this module holds no state of its own beyond its own small pending-output queue (ab_ga_il_build_outputs() below, which drains AND clears it - see that function's own doc for why it accumulates rather than being reset on a fixed cycle boundary), and never dereferences db itself, only passes it through to ga_interface.h's own getter/setter functions.
Definition in file ab_ga_il.h.
| bool ab_ga_il_apply_envelope | ( | const void * | context, |
| ab_gp_train_session_t * | sessions, | ||
| ab_gp_db_t * | db, | ||
| const uint8_t | env_bytes[RBC_ENVELOPE_WIRE_SIZE], | ||
| uint32_t | cycle, | ||
| rte_example_site_t | site, | ||
| const char * | role_tag ) |
THE handler this module registers with GP (ab_gp_register_il_envelope_handler(), ga_interface.h, via AB/main/ab_ga_dispatch.c's own ab_ga_dispatch_init()) for every raw envelope popped off its dedicated IL relay channel (ROUTE_ADD/ROUTE_RELEASE - see site_config.h's own rte_example_relay_kind_t doc) - "pure GA" now (later session, on direct request): GP no longer calls this by bare name, only ever through the registered function pointer, so context (unused here - matches ab_gp_il_envelope_handler_fn's own signature purely so this function can be registered directly, no trampoline needed) is always NULL. Wire-decodes the envelope (this module's own il_wire_decode(), ab_ga_il.c) and dispatches by kind to this module's own file-static handle_route_add()/handle_route_release().
| sessions | com's own ab_gp_train_session_t table (opaque to this module - see this file's own module doc) (RTE_EXAMPLE_MAX_TRAINS entries) - mutated in place via ga_interface.h's own setters. |
| db | AB's own Train/Route database (opaque) - mutated in place. |
| env_bytes | Raw wire bytes, exactly RBC_ENVELOPE_WIRE_SIZE - not yet decoded. |
| cycle | This AB cycle's own number (logging only). |
| site | This channel's own site (logging only). |
| role_tag | This channel's own "A/WEST" etc. tag (logging only). |
Definition at line 163 of file ab_ga_il.c.
| uint8_t ab_ga_il_build_outputs | ( | ab_gp_il_pending_output_t | out[AB_GP_IL_MAX_PENDING_OUTPUTS] | ) |
Drains this module's own TrainPositionInRoute event queue (populated by this module's own route-connected/-disconnected notify subscribers, registered from ab_ga_il_init() - see ga_interface.h's own ab_gp_db_register_route_connected_notify()/ _disconnected_notify()) and wire-encodes each into out. Deliberately takes no sessions/db parameters, unlike AB/GP/train/ab_gp_train.h's/AB/GA/ctc/ab_ga_ctc.h's own build_outputs() - everything this needs was already captured into the internal event queue by the notify callbacks at the moment each route-connect/-disconnect actually happened, not re-derived here from current table state. Called from AB/GP/main/post_execute.c's own ab_gp_channel_post_execute() (the real forward-to-C logic, step 3 of that function), same "GP orchestrates, GA builds" pattern CTC/Train already use.
The pending-output element type/capacity this module fills (ab_gp_il_pending_output_t/AB_GP_IL_MAX_PENDING_OUTPUTS) are GP-owned now, declared in ga_interface.h - the "consumer owns the shape" posture ab_gp_route_context_t/ab_gp_train_context_t already take (see that header's own doc).
Deliberately an ACCUMULATING queue, not reset at the top of every cycle - found live: an earlier version of this module did reset it every cycle (before ab_gp_logic_connect_routes_and_trains() could enqueue anything new), reasoning that the non-forwarding channel's own copy needed clearing to avoid filling up in steady state. That reasoning was wrong on two counts: (1) ab_gp_db_route_connect_train()/_disconnect_train() only fire their own notify ONCE, at the genuine state transition - ab_gp_logic_connect_route_for_train()'s own idempotent already_assigned_to_this_train() check means a route already connected is a silent no-op on every later cycle, so nothing re-enqueues repeatedly; (2) the reset was unconditional on EVERY channel, including the currently-forwarding one - an event enqueued on a cycle where should_forward_to_c happened to be false that same cycle (e.g. ab_gp_channel_crosscompare.c's own routine per-train sync-skip window) was silently destroyed by the very next cycle's reset before this function ever got a chance to drain and send it - confirmed live via a real Docker run where "IL notified: route N connected" logged but the IL sim never received the matching TrainPositionInRoute. Instead, this queue simply accumulates (bounded by AB_GP_IL_MAX_PENDING_OUTPUTS' own drop-and-log ceiling below) until a cycle where this function actually runs and drains it - on the non-forwarding channel that may never happen while it stays non-forwarding, which is fine: those events would only become relevant if this channel is later promoted, at which point sending its accumulated history (in order) on first forwarding is correct, not stale.
Revised again, 2026-09-15: the fix above moved the clear from "top of every cycle" to "end of this function" - correct progress, but this function is itself called (via ab_gp_drain_il_outputs()) EVERY cycle regardless of whether THIS cycle's build ends up forwarded, and it used to clear the queue unconditionally as part of building - the exact same class of bug the 2026-09 fix above already diagnosed, just one level deeper: an event built into out on a cycle where should_forward_to_c (post_execute.c) turns out false was still silently discarded, and since the queue was already emptied by THIS function, no later cycle could rebuild/resend it - confirmed live via IL Route Command Toggles Route State Without A Train intermittently getting stuck on a stale route_fsm_state. This function no longer clears the queue at all - see ab_ga_il_confirm_outputs_sent() below, which post_execute.c now calls ONLY on the cycle it actually forwards what this function built.
| out | Destination array, AB_GP_IL_MAX_PENDING_OUTPUTS entries. Must not be NULL. |
out (0..AB_GP_IL_MAX_PENDING_OUTPUTS). Definition at line 233 of file ab_ga_il.c.
| void ab_ga_il_confirm_outputs_sent | ( | void | ) |
Clears the pending-route-event queue ab_ga_il_build_outputs() just built from - call ONLY once those outputs are confirmed actually forwarded to C this same cycle (post_execute.c's own should_forward_to_c). Nothing can enqueue a new event between a build()/confirm() pair in the same cycle (both run back to back in post_execute.c, nothing else executes in between), so this needs no count/identity parameter - it simply drops exactly what the immediately-preceding build() call drained.
Definition at line 274 of file ab_ga_il.c.
| bool ab_ga_il_handle_route_add | ( | ab_gp_db_t * | db, |
| uint8_t | train_id ) |
Handles the MA-length-bookkeeping half of an inbound ROUTE_ADD - marks this train as now holding a valid MA (ab_gp_train_set_has_ma()). Deliberately unaware of route identity/type - that half (resolving/creating the real ab_gp_db_route_t) is handle_route_add()'s own job (ab_ga_il.c, file-static) - this function stays the same narrow "train now has an MA" bookkeeping step it always was, kept public since ab_ga_il_apply_envelope()'s own ROUTE_ADD path still calls it alongside the newer route-identity handling.
train_id is not a currently-connected train; otherwise true. Definition at line 158 of file ab_ga_il.c.
| void ab_ga_il_on_route_context_changed | ( | ab_gp_train_session_t * | sessions, |
| ab_gp_db_t * | db, | ||
| const ab_gp_route_context_t * | ctx, | ||
| ab_gp_route_update_type_t | update_type ) |
GA's single dispatch module (safeAPIRBC2oo2GA/src/AB/main/ ab_ga_dispatch.c) is the ONLY thing that registers with GP now (interface-simplification pass) - it owns the ONE route-context subscription and fans AB_ROUTE_CONNECTED/_DISCONNECTED here. AB_ROUTE_ADDED/_STATUS_CHANGED are ignored (IL never cared about those).
GA's single dispatch module (ab_ga_dispatch.c) fans every route change here for AB_ROUTE_CONNECTED/AB_ROUTE_DISCONNECTED - the only two IL cares about (see this file's own module doc). Was two separate GP-registered callbacks (il_on_route_connected/ _disconnected) - collapsed into this one dispatch-driven function on direct request, same rationale as ab_ga_ctc_on_route_context_changed()'s own doc.
Movement Authority granting/shrinking lives HERE now (later session, on direct request: "route<->train connection is GP/GA's own
decision, based on train position - IL's own ROUTE_ADD/ROUTE_RELEASE
wire messages must not dictate which train a route grant applies to"). Previously handle_route_add()/handle_route_release() (this file's own file-static handlers) read env->train_id straight off the wire and granted/shrank MA directly, entirely bypassing GP's own position-based connect gate (ab_gp_logic_connect_routes_and_trains(), AB/GP/ma_logic/ab_gp_logic.c, which already ran every cycle independently, but used to only produce a cosmetic TrainPositionInRoute indication, never an actual MA change) and the registered route-connection VALIDATOR (validate_route_assignment(), AB/ma_logic_adaptation/ab_ga_ma_logic_adaptation.c) that gate is supposed to gate through. Now: handle_route_add() only tracks route existence (adds it to GP's own route table); handle_route_release() only untracks it. The actual MA grant/shrink happens HERE, in reaction to GP's own AB_ROUTE_CONNECTED/_DISCONNECTED notification - i.e. only once GP's position-based sweep has ACTUALLY connected (or disconnected) this route to a specific train, using ctx->train_id (GP's own real, tracked association - db->routes[].train_id, never anything IL supplied on the wire) and layout->length_m (this route's own known length, resolved by ctx->id - never a wire- supplied length either, on the same "IL doesn't dictate this" basis).
| sessions | This channel's own long-lived Train session table - needed now to grant/shrink MA on AB_ROUTE_CONNECTED/_DISCONNECTED (was previously only threaded through the route-connection validator, a different call path). |
| db | This channel's own Train/Route database (opaque) - forwarded to ab_gp_train_notify_context_changed()/ ab_ga_il_handle_route_add() below. |
| ctx | The route's own current-state snapshot - see that struct's own doc, ga_interface.h. |
| update_type | Which kind of change this is - only AB_ROUTE_CONNECTED/_DISCONNECTED are acted on. |
GA's single dispatch module (safeAPIRBC2oo2GA/src/AB/main/ ab_ga_dispatch.c) is the ONLY thing that registers with GP now (interface-simplification pass) - it owns the ONE route-context subscription and fans AB_ROUTE_CONNECTED/_DISCONNECTED here. AB_ROUTE_ADDED/_STATUS_CHANGED are ignored (IL never cared about those).
Definition at line 711 of file ab_ga_il.c.
| bool ab_ga_il_route_add | ( | ab_gp_db_t * | db, |
| uint16_t | route_id, | ||
| uint16_t | start_signal, | ||
| uint16_t | end_signal ) |
Adds a new route to GP's own route table, after verifying it via ab_ga_il_route_verify() above - thin wrapper over ga_interface.h's own ab_gp_db_route_add().
Definition at line 124 of file ab_ga_il.c.
| bool ab_ga_il_route_assign_train | ( | ab_gp_db_t * | db, |
| uint16_t | route_id, | ||
| uint8_t | train_id ) |
Assigns a route to a train - thin wrapper over ga_interface.h's own ab_gp_db_route_connect_train() (which fires GP's own route-connected notify on success).
| train_id | 1..RTE_EXAMPLE_MAX_TRAINS (same 1-based convention as ab_gp_train_session_t.train_id). |
route_id is not tracked, train_id is out of range, the route is already used_in_ma (call ab_ga_il_route_unassign_train() first - never silently reassigned), it is already in that same train's route_ids[], or that train's route_ids[] (RTE_EXAMPLE_MAX_ROUTES_PER_TRAIN) is full. Definition at line 148 of file ab_ga_il.c.
| bool ab_ga_il_route_remove | ( | ab_gp_db_t * | db, |
| uint16_t | route_id ) |
Removes a tracked route from GP's own route table, freeing its slot - thin wrapper over ga_interface.h's own ab_gp_db_route_remove().
route_id is not tracked, or it is still used_in_ma (call ab_ga_il_route_unassign_train() first - a route actively backing a train's Movement Authority is never silently dropped). Definition at line 133 of file ab_ga_il.c.
| bool ab_ga_il_route_set_status | ( | ab_gp_db_t * | db, |
| uint16_t | route_id, | ||
| ab_gp_db_route_il_status_t | status ) |
Updates a tracked route's own il_status - IL's last-reported status for it - thin wrapper over ga_interface.h's own ab_gp_db_route_set_status(). Does not touch occupied (a separate, non-IL-sourced concept).
route_id is not tracked. Definition at line 138 of file ab_ga_il.c.
| bool ab_ga_il_route_set_type | ( | ab_gp_db_t * | db, |
| uint16_t | route_id, | ||
| uint32_t | route_type ) |
Updates a tracked route's own route_type - IL's last-reported supervision mode (rbc_route_type_t: RBC_ROUTE_TYPE_FS/_OS/_SH, rbc_wire_types.h) for it - thin wrapper over ga_interface.h's own ab_gp_db_route_set_type().
route_id is not tracked. Definition at line 143 of file ab_ga_il.c.
| bool ab_ga_il_route_unassign_train | ( | ab_gp_db_t * | db, |
| uint16_t | route_id ) |
Reverses ab_ga_il_route_assign_train() - thin wrapper over ga_interface.h's own ab_gp_db_route_disconnect_train().
route_id is not tracked, or it is not currently used_in_ma (nothing to unassign). Definition at line 153 of file ab_ga_il.c.
| bool ab_ga_il_route_verify | ( | uint16_t | route_id, |
| uint16_t | start_signal, | ||
| uint16_t | end_signal ) |
Verifies a route as reported by ILSim is OK: it must both exist in this site's own static layout AND report the same start/end signals the layout itself has for that route id - ILSim is external, untrusted input (same REQ-RBC-002 "reject a malformed frame, don't index blind" posture already applied to wire input elsewhere in this project), so its own claimed signals are cross-checked against the site's own authoritative layout, not taken on faith.
Definition at line 113 of file ab_ga_il.c.
| bool ab_ga_il_site_has_route | ( | uint16_t | route_id, |
| const ab_site_ga_route_layout_t ** | out_layout ) |
Pure site-layout lookup - does route_id exist in this site's own static track layout at all (ab_site_ga_track_layout, ab_site.h)?
| route_id | Route identity to look up. 0 always returns false (ab_gp_db.h/ab_site.h's own "no route" sentinel, never a real route id). |
| out_layout | If non-NULL and route_id is found, receives a pointer to that route's own static layout entry (owned by ab_site_ga_track_layout[], not the caller - never free()d). Left unmodified if not found. |
route_id is a real entry in this site's layout. Definition at line 71 of file ab_ga_il.c.
| bool ab_ga_il_site_has_route_by_signals | ( | uint16_t | start_signal, |
| uint16_t | end_signal, | ||
| const ab_site_ga_route_layout_t ** | out_layout ) |
Pure site-layout lookup by route IDENTITY - does a route with this exact (start_signal, end_signal) pair exist in this site's own static track layout? Same shape as ab_ga_il_site_has_route() above, but by signal pair rather than synthetic id - the wire's own route-identity model (rbc_wire_types.h's own doc) - used to resolve an inbound ROUTE_ADD/ROUTE_RELEASE's own start_signal/end_signal fields to this site's internal route_id before touching db->routes[].
| out_layout | Same contract as ab_ga_il_site_has_route()'s own. |
Definition at line 93 of file ab_ga_il.c.