NimbyScript: Difference between revisions

From NIMBY Rails Wiki
Jump to navigation Jump to search
(Created page with "<span id="purpose"></span> = Purpose = NimbyScript is an imperative, statically typed, AOT native compiled language for scripting gameplay extensions for NIMBY Rails. It compiles down to native machine code, just like C. It has zero calling and data marshalling overhead with C/C++: a NimbyScript function is a C function. NimbyScript data types are C types. This capability makes its compiled code much faster than the usual scripting languages used im game development, bo...")
(No difference)

Revision as of 17:42, 1 November 2025

Purpose

NimbyScript is an imperative, statically typed, AOT native compiled language for scripting gameplay extensions for NIMBY Rails. It compiles down to native machine code, just like C. It has zero calling and data marshalling overhead with C/C++: a NimbyScript function is a C function. NimbyScript data types are C types. This capability makes its compiled code much faster than the usual scripting languages used im game development, both interpreted or JIT compiled. NimbyScript is AOT compiled, but the compiler is bundled as part of NIMBY Rails, and scripts are automatically compiled on (re)load from their source text, making the development experience as seamless as interpreted scripts.

NimbyScript syntax and semantics are inspired by Rust, C and C++. But NimbyScript is not designed to be a stand alone language, and as such its bare bones standard library lacks most features found in general purpose languages and runtimes, like file I/O, networking or threads. It is an explicit goal of the language and its runtime environment to crash as little as possible. Crashing and hanging should only happen due to bugs in the NimbyScript compiler, its runtime or NIMBY Rails C++ code, never due to errors in NimbyScript code. To accomplish this the language itself lacks some common features like general purpose iteration or memory allocation, and most data exposed to scripts is read-only, strictly enforced by the language.

This documents assumes some familiarity with compiled, native code languages like C++ or Rust. Experience with other languages can also apply, since the basic building blocks like functions and structs/objects are similar to popular languages like Javascript or Python, but other concepts like references, pointers and values will be less clear. For this reason I won’t really explain what a function or a struct is, and instead I will focus on what makes NimbyScript different from C++/Rust in concepts like references.

Syntax

The description of each syntax category starts with a PEG (Parsing Expression Grammar) description, which is copy pasted directly from the source code of the NimbyScript compiler. Its syntax is recognized by the cpp-peglib library.

Comments are introduced by // and end at the end of the current line. Whitespace is not relevant (like C++ or Rust or Javascript, unlike Python).

Top level

TopLevel    <- (TopMeta / ConstDecl / FnDecl / StructDef / EnumDef)*

The top level of a NimbyScript source file is composed of a sequence of script meta, const definitions, function definitions, struct definitions and enum definitions.

Example:

script meta {
    lang: nimbyscript.v1,
    api: nimbyrails.v1,
}
struct SpeedTrap extend Signal {
    max_speed: f64,
}
const ms_to_kmh: f64 = 3.6;
pub fn SpeedTrap::signal_check(db: &DB, extrapolator: &Extrapolator, motion: &Motion, signal: &Signal): SignalCheck {
    let sc &= signal.view_SpeedTrap() else { return SignalCheck::Pass; }
    if let presence &= motion.presence.get() {
        if presence.speed * ms_to_kmh > sc.max_speed {
            log("speedy @!", motion.train_id);
            return SignalCheck::Stop;
        }
    }
    return SignalCheck::Pass;
}

script meta

TopMeta     <- 'script' Meta

The top level script meta declares some meta information for the scripting runtime about your script. In the current scripting runtime it must declare the language version as lang: nimbyscript.v1,, and the API version as api: nimbyrails.v1,. This meta declaration is mandatory and exactly one should be specified per source file.

Example:

script meta {
    lang: nimbyscript.v1,
    api: nimbyrails.v1,
}

const

ConstDecl   <- 'const' NAME ':' Type '=' Constant ';'

const definitions associate a name with a (typed) numeric constant. They are similar to Rust const or C++ constexpr, but much more limited in capabilities. They are not equivalent to C #define since they are typed. Constants are not variables: they cannot be assigned to, and their address cannot be referenced. They can only be used in place of a literal numeric constant.

Example:

const pi: f64 = 3.14159265358979323846;
const ms_to_kmh: f64 = 3.6;

struct

StructDef   <- 'struct' NAME StructExtend? '{' (!'}' (StructField / (Meta ',')) )* '}'
StructExtend <- 'extend' NAME
StructField <- NAME ':' Type Meta? ','

struct definitions introduce new data types within the script. Structs are a data type which contains a sequence of fields, each with a name and a type. When expressions access a value or reference of the type of the struct, it can access the individual fields with a struct.field notation.

Structs can extend one of the following game types: - Signal - Station - Train - Line - Tag When a struct extends one of these types, and its script is in use in the game session, it will be offered as an extension in the game editor UI, for each corresponding editor. If the player enables the extension for a particular object of a game type, a new object of the script struct is created, and the player is then able to give values to the fields from the game UI. This is the only way to create objects of extension structs. Scripts cannot extend game objects by themselves. Only one object of a given script struct type is allowed per game object.

Given a reference to a game object, it is possible to access its struct extension object with an auto generated method that looks it up, named GameObject::view_ScriptStruct() returning a pointer to the object of the struct, which can then be checked for validity.

Example:

struct Example extend Signal {
    v: i64,
}
// fn Signal::view_Example(): *Example has been automatically generated by the previous struct definition
fn f(signal: &Signal) {
    let config &= signal.view_Example() else { return; }
}

In the current language version it is not possible to create new instances of structs from scratch, even for structs which do not extend any game object. This might change in future versions of the language. Some game API functions might return new instances of structs for specific APIs when it is safe to do so, like Signal::forward(): Pos, since Pos does not allocate memory.

Field types

Only a subset of field types are accepted with proper editing experience in the current language version: - Boolean values: bool - Integer values: i64 - Floating point values: f64 - Enum values: any script-defined enum (but not game defined enums) - Some values of the game type ID<Object>: ID<Station>, ID<Line>, ID<Train>, ID<Schedule>, ID<Signal>, ID<Tag>

All of these types display the proper UI for editing them, including the ID<Object> types.

struct meta

struct definitions can contain a struct-level meta:

struct ProbeCheck extend Signal {
    meta {
        label: "Probe a track position",
    },
    // ...
}

The label meta field will replace the name of the struct in UI for its content.

It is also possible to define a meta for each field:

struct ProbeCheck extend Signal {
    probe: ID<Signal> meta {
        label: "Probe",
    },
}
  • The label meta field is accepted for all field types.
  • The default meta field is accepted for bool, i64, f64 and enum field types.
  • The min and max meta field is accepted for i64 and f64 field types.

enum

EnumDef     <- 'enum' NAME '{' (!'}' EnumOption)* '}'
EnumOption  <- NAME Meta? ','

Enums are a scoped set of names, with no semantics or values associated to them (unlike C enums). It is possible to pass and create values of enum types, and to check if these types match one of the enum options, but the values themselves have no meaning and cannot be expressed as any other type. Enums as a type can be referred by their name, and each option can be referred like EnumName::OptionName.

Enums are most often used to represent abstract states or settings. It is also a good idea to use them in place of booleans, since having to write out the full name of the enum and the option makes the code self-documenting.

Example:

enum Color { Red, Green, Blue, }
fn pick_color(v: i64, c: Color): Color {
    if v < 100 {
        return Color::Red;
    }
    if c == Color::Green {
        return Color::Blue;
    }
    return Color::Blue;
}

When using enums in structs, they will be displayed as a drop down menu in the game UI, and you can modify the option labels with meta:

enum Speed {
    Slow meta { label: "Allow slow trains" },
    Fast meta { label: "Allow fast trains" },
}

fn definition

FnDecl      <- VisMod? 'fn' ColonNAME '(' FnArgs? ')' (':' Type)? BlockStmt
FnArgs      <- FnArg (',' FnArg)*
FnArg       <- NAME ':' Type
VisMod      <- 'pub'

Functions, introduced by fn, define a series of parameters and contain a sequence of executable statements. All executable code is contained in one or more functions, there is no top level statements. Functions are always invoked from the game runtime in response to certain events, and there is no way to independently execute code by scripts. The pub qualifier is required for functions which are meant to be called from the game runtime, and it can be omitted for functions which are only called from within the script.

Example:

fn add(a: i64, b: i64): i64 {
    return a + b;
}

Some functions are called “methods”, when said functions are meant to be called with a reference of a specific object type as its first parameter, or for collecting them under the namespace of a certain object. The game extension scripting interface is based on naming certain pub script functions as methods of extended structs. As an analogy, if defining structs as struct extend is used to extend game object data, defining functions as methods with a certain name is used to extend the game behavior (code).

signal_check extension method

struct Example extends Signal { ... }
pub fn Example::signal_check(
    db: &DB,
    extrapolator: &Extrapolator,
    motion: &Motion,
    signal: &Signal
): SignalCheck { ... }

signal_check script functions must be defined as a method of a struct which extends the game object Signal. They must have the exact same parameters and return type as in the previous example.

signal_check are called when the player enables their respective struct as an extension for a given signal in the track editor. From that moment the game simulation system which advances the train motion simulation (the Extrapolator) will call your signal_check function whenever said signal is checked as part of a path check, and is a path signal.

This function can be called multiple times per frame per train, redundantly. Your code must return a SignalCheck enum value of SignalCheck::Pass or SignalCheck::Stop. Your code can only make the signal more restrictive, not less. In other words, your Pass will be ignored if the default path check fails. But your Stop won’t be ignored if the patch check was successful, and it will stop the train.

signal_change_path extension method

struct Example extends Signal { ... }
pub fn Example::signal_change_path(
    db: &DB,
    extrapolator: &Extrapolator,
    motion: &Motion,
    signal: &Signal,
    check: SignalCheck,
    result: &mut SignalChangePathResult
): SignalChangePath { ... }

signal_change_path script functions must be defined as a method of a struct which extends the game object Signal. They must have the exact same parameters and return type as in the previous example.

signal_change_path are called when the player enables their respective struct as an extension for a given signal in the track editor. From that moment the game simulation system which advances the train motion simulation (the Extrapolator) will call your signal_change_path function whenever said signal is allowed to change the path of the train, and it is a path signal.

Under some circumstances path signals are given a chance to change the train path, by allowing them to introduce an intermediate path goal that it is not the currently scheduled stop goal. An example of this mechanic is the stop selecting signal behavior. Starting in 1.18 this capability has been extended to every path signal, but it can only be triggered by scripts implementing signal_change_path.

Your code must return a SignalChangePath enum value of SignalChangePath::Change or SignalChangePath::Keep. Keep keeps the current train path as-is. Change will try to change the current train path to a new path that ends at the Pos specified in result.pos (check the API reference in the bottom of this document for the full definition of SignalChangePathResult). After the train reaches that position it will automatically find a path to resume its trip to its currently scheduled stop. The train path can be altered an unlimited number of times before reaching its scheduled stop, including cutting short any previously issued path by a signal_change_path script.

meta

Meta        <- 'meta' MetaMap
MetaVal     <- MetaMap / MetaVec / MetaStr / MetaName / NumberText
MetaMap     <- '{' MetaKV? ( ',' MetaKV )* ',' '}'
MetaVec     <- '[' MetaVal? ( ',' MetaVal )* ',' ']'
MetaKV      <- MetaName ':' MetaVal
MetaStr     <- '"' [^"]* '"'
MetaName    <- < [a-zA-Z_][a-zA-Z_0-9.]* >

meta is a structured comment, which can appear associated to certain syntax elements. It has no effect in the compilation or semantics of NimbyScript source code. Its purpose is to decorate some elements with extra information for the game runtime, like UI presentation hints, which have no relevance for compiled code. Metas are similar in semantics and expressiveness to JSON. Root meta information is a map of of key-value pairs. Keys are always a name complying with the identifier rules of the language. Meta values are one of maps, vectors, strings, names (which are just strings complying with identifier rules, not requiring quotes) and numbers.

Statements

BlockStmt   <- '{' (!'}' Stmt)* '}'
Stmt        <- IfBindStmt / IfStmt / ForStmt / BindElseStmt / BorrowStmt / BindStmt / AssignStmt / BlockStmt / RetStmt / BreakStmt / ContinueStmt / LogStmt / ExpStmt

The body of a function is composed of a sequence of statements. Statements are the executable instructions of code; if types (like struct and enum) define the “shape of data”, statements define how to process that data. Instructions are executed in order, starting from the first statement in a block of statements.

Variables and assignment

BindElseStmt <- BindDef 'else' BlockStmt
BindStmt    <- BindDef ';'
BindDef     <- 'let' NAME BindTypeSep? TypePattern? '=' Exp
BindTypeSep <- ':'
AssignStmt  <- DotExp '=' Exp ';'

New variables are introduced as parameters to a function, and by using the let syntax. Some syntax elements of let are optional, in particular the type signature does not need to be fully specified (this is called type inference in other languages, but I do not claim have a formal, consistent type inference capability in NimbyScript). All of the following variable definitions are equivalent:

fn (a1: i64) {
    let a2 = 1;
    let a3: i64 = 1;
}

NimbyScript variables cannot be reassigned in the sense they can in C or Rust. This distinction is not important for simple, value-based types like i64, but it is important for struct types and references. In particular it means a reference variable has exactly one chance to take the address of an object: at initialization. All other assignment operations on references always reassign into the original object. This is identical to C++ references, and unlike Rust borrows.

let a = 1;
let r &= a; // the type of r is &mut i64 and it points to a memory address; it is an alias of a, forever
log("a: ", a); // output is "1".

let else is a special case of let which expects an expression evaluating into a pointer on the right side of the equals, but it will create a reference rather than a pointer. Its goal is to be able to transform pointers into references safely, by explicitly handling the invalid case inside the else block statement. The else statement must always return.

// Signal::view_SpeedTrap() returns *SpeedTrap, which is a pointer
// pointers can be invalid, therefore to use them as references your
// code has to prove to the compiler it is handling the invalid case
// one way of doing so is using let-else as this:
let sc &= signal.view_SpeedTrap() else { return SignalCheck::Pass; }

Assignment of variables is possible when the type of the variable is mut. The left side of an assignment is just a variable name or struct field expression, and the right side is any expression which is compatible with the left side type:

let a mut= 1;
a = 10;
log("a: ", a); // 10

Flow control

IfBindStmt  <- 'if' BindDef BlockStmt ('else' (IfStmt / IfBindStmt / BlockStmt))?
IfStmt      <- 'if' Exp BlockStmt ('else' (IfStmt / IfBindStmt / BlockStmt))?

ForStmt     <- 'for' NAME 'in' Exp BlockStmt
BreakStmt   <- 'break' ';'
ContinueStmt <- 'continue' ';'

RetStmt     <- 'return' Exp? ';'

NimbyScript supports a subset of the usual control flow statements found in C++. if statements are almost full featured, with a Rust-like syntax:

if condition() {
}
else if a != b {
}
else {
}

A special, limited case of Rust if let or C++ if (auto* v = ...) is also supported, only for expressions evaluating into pointers. It is similar and has the same motivation as let else:

// another way of handling pointers safely is by proving to the compiler
// the code using the pointer as a reference is inside an if branch
// which has checked it for validity:
if let sc &= signal.view_SpeedTrap() {
    let v = sc.max_speed;
}

for statements are very limited in the current version of NimbyScript. This is a deliberate design choice. Currently it is only possible to iterate using iterator types defined in the game host runtime. Free loops and loops with arbitrary conditions are not possible. continue and break are supported.

for hit in extrapolator.reservation_probe(pos) {
    if !hit.train.id.equals(motion.train_id) {
        return SignalCheck::Stop;
    }
}

return ends the execution of the current function and returns to the caller. Its value is required when the function returns a value.

Expressions

ExpStmt     <- Exp ';'
Exp         <- EqExp (BoolOp EqExp)?
BoolOp      <- '&&' / '||'
EqExp       <- RelExp (EqOp RelExp)?
EqOp        <- '==' / '!='
RelExp      <- AddExp (CmpOp AddExp)?
CmpOp       <- '<' / '>' / '>=' / '<='
AddExp      <- MulExp (AddOp MulExp)*
AddOp       <- '+' / '-'
MulExp      <- DotExp (MulOp DotExp)*
MulOp       <- '*' / '/' / '%'
DotExp      <- AtomExp ('.' NAME)* MethodArgsExp?
MethodArgsExp <- '(' CallArgs? ')' 
CallExp     <- NAME '(' CallArgs? ')' 
CallArgs    <- Exp (',' Exp)*
UnaryExp    <- UnaryOp Exp
UnaryOp     <- '!' / '-'
AtomExp     <- AtomPar / CallExp / Constant / UnaryExp / ColonNAME
AtomPar     <- '(' Exp ')'

Expression support is similar to classic C, with some quirks around operator precedence. You might need to use parentheses more often than in other languages to ensure the semantics of your expression, but in practice it does not happen often.

“Dot expressions” to address struct fields support chaining like variable.a.b.c, but method calls like variable.method() only support one level of chaining. This can be worked around with parentheses like (variable1.method1()).method2() but it’s usually a better idea to use some intermediate variables.

As explained elsewhere, pointer types cannot be deferenced, therefore cannot be used in dot expressions. But built-in functions is_null(v): bool and is_valid(v): bool are usable with any pointer type and value.

Type matching in expressions is very strict. You cannot subtract an i64 from an f64, for example, but you can compare them. To convert types into other types there’s a family of built-in functions called as_xxx(), where xxx is the desired return type. For example as_i64(v: f64): i64 is one of them. This is equivalent to the C++ cast of int64_t(v). The standard library also has some rounding functions for the specific case of float to integer conversion.

Integer division and remainder operators trigger a compilation error, to avoid division by 0 errors at runtime. Use the zdiv and zmod library functions to do integer division, which return 0 when the divisor is 0.

Usage of variables in expressions is regulated by a set of borrow checking rules, which are a very simplified version of the Rust borrow checking rules. The design of the game APIs has been carefully done to avoid introducing difficult borrowing situations: virtually all data offered to scripts is read-only, making borrow checking trivial and unrestricted (this also enables seamless multithreading support for scripts). For more information on the borrow checker see this post.

Types

Type        <- TypeStore? TypeMut? TypeName
TypeName    <- NAME ('<' TypeTplArgs '>')? ('::' TypeName)*
TypeTplArgs <- TypeName (',' TypeName)*
TypePattern <- TypeStore? TypeMut? TypeName?
TypeMut     <- 'mut'
TypeStore   <- '&' / '*'

A NimbyScript type, as used in parameters and variables, is composed of 3 elements: the storage type, the mutability flag and the value type.

The storage of a variable type indicates if it’s storing the full value of a type, or its address. Address types are references & and pointers *. References are very much like C++ references. Pointers are like a “unusable” reference: you cannot dereference it, but if you can prove to the compiler you are checking it for validity, it will allow you to assign it as a reference.

Value storage variables do not have any special character. You will see some types, specially types from the game host API, cannot be used as value variables, forcing you to always use pointers or references to them.

Native NimbyScript value types are very limited: integer ('i64' / 'u64' / 'i32' / 'u32' / 'i16' / 'u16' / 'i8' / 'u8'), floating point ('f64' / 'f32'), booleans (bool), and enum values. Under restricted situations it is also possible to create variables of simple C-like types from the game host, like the Pos type, or iterator types. Despite this simplicity the previous type definition grammar has support for the entire set of C++ type naming. This is because although NimbyScript cannot create new objects of these types, it can access them as struct references, therefore it needs to be able to fully express these type names.

The mutability flag is specified with mut and it indicates mutating operations can be invoked on the variable. For simple types like i64 this can mean overwriting it with any other value, like v = 2;. For structs this usually means you are allowed to call functions which take a &mut to the variable, or to directly assign values to their fields like v.f = 1;. Only a few simple struct types are allowed to be fully reassigned, like Pos.

Constants

Constant    <- Number / Time / BoolConst

Number      <- NumberText NumberType?
NumberText  <- < '-'? [0-9.]+ >
NumberType  <- 'i64' / 'u64' / 'i32' / 'u32' / 'i16' / 'u16' / 'i8' / 'u8' / 'f64' / 'f32'

Time        <- TimeNumber TimeOp (TimeNumber? TimeOp)? (TimeNumber? TimeOp)? TimeNumber?
TimeNumber  <- < [0-9]+ >
TimeOp      <- ':'

BoolConst   <- 'true' / 'false'

Constant expressions are terminal syntax tokes which represent a typed value. For integer numbers, given the very strict type matching rules, you might need to use suffix annotations, like: let v = 1i32; let w = 1i32 + v;.

Identifiers

NAME <- < [a-zA-Z_][a-zA-Z_0-9]* > ColonNAME <- < [a-zA-Z_][a-zA-Z_0-9:]* >

The names you can use for your types, functions and variables are limited to the usual plain C restrictions. An exception is made for function names, since some C++ APIs expect you implement functions as a method of a struct, so the colon character is also allowed.

Debug logs

LogStmt     <- 'log' '(' '"' LogStr '"' (',' CallArgs+)? ')' ';'
LogStr      <- [^"]*

The built-in log() generic function is used for debug logging. log() takes a text string (this is one and only allowed use of strings in NimbyScript), and then zero to N arguments. It will automatically serialize these arguments as text, and it has some smarts for some game objects, like automatically printing train names if the argument is a reference to a train. Logging has a large CPU impact and it is always disabled by default; explicit manual action in the game UI by the player is required to enable it on each game session.

Standard library

Top level

fn abs(a: f32): f32
fn abs(a: f64): f64
fn abs(a: i16): i16
fn abs(a: i32): i32
fn abs(a: i64): i64
fn abs(a: i8): i8
fn ceil(a: f32): f32
fn ceil(a: f64): f64
fn exp(a: f32): f32
fn exp(a: f64): f64
fn floor(a: f32): f32
fn floor(a: f64): f64
fn iround(a: f32): i64
fn iround(a: f64): i64
fn is_inf(a: f32): bool
fn is_inf(a: f64): bool
fn is_nan(a: f32): bool
fn is_nan(a: f64): bool
fn is_normal(a: f32): bool
fn is_normal(a: f64): bool
fn log10(a: f32): f32
fn log10(a: f64): f64
fn loge(a: f32): f32
fn loge(a: f64): f64
fn max(a: f32, b: f32): f32
fn max(a: f64, b: f64): f64
fn max(a: i16, b: i16): i16
fn max(a: i32, b: i32): i32
fn max(a: i64, b: i64): i64
fn max(a: i8, b: i8): i8
fn max(a: u16, b: u16): u16
fn max(a: u32, b: u32): u32
fn max(a: u64, b: u64): u64
fn max(a: u8, b: u8): u8
fn min(a: f32, b: f32): f32
fn min(a: f64, b: f64): f64
fn min(a: i16, b: i16): i16
fn min(a: i32, b: i32): i32
fn min(a: i64, b: i64): i64
fn min(a: i8, b: i8): i8
fn min(a: u16, b: u16): u16
fn min(a: u32, b: u32): u32
fn min(a: u64, b: u64): u64
fn min(a: u8, b: u8): u8
fn pow(a: f32, b: f32): f32
fn pow(a: f64, b: f64): f64
fn round(a: f32): f32
fn round(a: f64): f64
fn sqrt(a: f32): f32
fn sqrt(a: f64): f64
fn zdiv(a: i16, b: i16): i16
fn zdiv(a: i32, b: i32): i32
fn zdiv(a: i64, b: i64): i64
fn zdiv(a: i8, b: i8): i8
fn zdiv(a: u16, b: u16): u16
fn zdiv(a: u32, b: u32): u32
fn zdiv(a: u64, b: u64): u64
fn zdiv(a: u8, b: u8): u8
fn zmod(a: i16, b: i16): i16
fn zmod(a: i32, b: i32): i32
fn zmod(a: i64, b: i64): i64
fn zmod(a: i8, b: i8): i8
fn zmod(a: u16, b: u16): u16
fn zmod(a: u32, b: u32): u32
fn zmod(a: u64, b: u64): u64
fn zmod(a: u8, b: u8): u8

Database model

Type Building

struct Building {
    id: ID<Building>,
}

Type DB

struct DB {}

The database of all player-created objects in the game.

fn DB::view(self: &DB, id: ID<Building>): *Building
fn DB::view(self: &DB, id: ID<Line>): *Line
fn DB::view(self: &DB, id: ID<Schedule>): *Schedule
fn DB::view(self: &DB, id: ID<Signal>): *Signal
fn DB::view(self: &DB, id: ID<Station>): *Station
fn DB::view(self: &DB, id: ID<Tag>): *Tag
fn DB::view(self: &DB, id: ID<Track>): *Track
fn DB::view(self: &DB, id: ID<Train>): *Train

Type Line

struct Line {
    id: ID<Line>,
    tags: Tags,
}

Type Motion

struct Motion {
    train_id: ID<Train>,
    presence: std::optional<Motion::Presence>,
    drive: std::optional<Motion::Drive>,
    timed_stop: std::optional<Motion::TimedStop>,
    schedule_stop: std::optional<Motion::ScheduleStop>,
    schedule_station_event: std::optional<Motion::ScheduleStationEvent>,
    schedule_dispatch: std::optional<Motion::ScheduleDispatch>,
}

Simulation state of a train.

Type Motion::Drive

struct Motion::Drive {
    waiting_signal_id: ID<Signal>,
    waiting_signal_last_check: i64,
    path: Path,
    goal_trace: TapeTrace,
}

Simulation state of a train being driven along a path.

Type Motion::Presence

struct Motion::Presence {
    pos: Pos,
    speed: f64,
}

Simulation state of a train being present at a certain position on a track.

Type Motion::ScheduleDispatch

struct Motion::ScheduleDispatch {
    sched_id: ID<Schedule>,
    run: Run,
    run_epoch_start: i64,
}

Simulation state of a train currently assigned to a schedule shift.

Type Motion::ScheduleStationEvent

struct Motion::ScheduleStationEvent {}

Simulation state of a train performing scheduled stop related to pax and auxiliary station data.

Type Motion::ScheduleStop

struct Motion::ScheduleStop {
    sched_id: ID<Schedule>,
    line_id: ID<Line>,
    station_id: ID<Station>,
}

Simulation state of a train performing a stop due to its schedule.

Type Motion::TimedStop

struct Motion::TimedStop {
    departure_epoch_s: i64,
}

Simulation state of a train performing a timed stop, with a departure time.

Type Path

struct Path {
    found: bool,
    goal_has_stop_time: bool,
    force_goal_reservation: bool,
    pose_start: Pose,
    pose_goal: Pose,
    found_start: Pos,
    found_goal: Pos,
}

A complex path over tracks, represented a sequence of track segments, subsegments, branchs, merges and reversals, from a start Pos to a goal Pos.

Type Pos

struct Pos {
    track_id: ID<Track>,
}

A position on a track, with a direction.

fn Pos::flip(self: &Pos): mut Pos

Type Pose

struct Pose {
    head: Pos,
    tail: Pos,
}

Two positions on a track, with opposite orientations. Usually used as part of station stop data.

Type Run

struct Run {
    line_id: ID<Line>,
}

A projection into real time of a sequence of stops belonging to a line, with possible modified timing.

Type Schedule

struct Schedule {
    id: ID<Schedule>,
    tags: Tags,
}

Type Signal

struct Signal {
    id: ID<Signal>,
    kind: SignalKind,
    match_block_facing: bool,
    check_beyond_stops: bool,
}
fn Signal::backward(self: &Signal): mut Pos

Returns the position of a train facing backwards from the signal.

fn Signal::forward(self: &Signal): mut Pos

Returns the position of a train facing forward from the signal.

Type SignalKind

enum SignalKind {
    Path,
    Balise,
    Marker,
    OneWay,
    NoWay,
    PlatformStop,
}

Type Station

struct Station {
    id: ID<Station>,
    tags: Tags,
}

Type Tag

struct Tag {
    id: ID<Tag>,
    parent_id: ID<Tag>,
}

Type Tags

struct Tags {}

A set of tags.

fn Tags::contains(self: &Tags, id: ID<Tag>): mut bool

Returns true if the given tag ID is contained in the set.

fn Tags::contains(self: &Tags, tag: &Tag): mut bool

Returns true if the given tag is contained in the set.

Type TapeTrace

struct TapeTrace {}

A simplified path (with no branching) over tracks, which represents a footprint of track segments and subsegments.

fn TapeTrace::contains(self: &TapeTrace, other: &Pos): mut bool

Returns true if the given Pos is contained inside this TapeTrace.

fn TapeTrace::contains(self: &TapeTrace, other: &TapeTrace): mut bool

Returns true if the given TapeTrace is fully contained inside this TapeTrace.

fn TapeTrace::hits(self: &TapeTrace, other: &TapeTrace): mut bool

Returns true if the given TapeTrace hits this TapeTrace.

Type Track

struct Track {
    id: ID<Track>,
}

Type Train

struct Train {
    id: ID<Train>,
    tags: Tags,
}

Type std::optional<Motion::Drive>

struct std::optional<Motion::Drive> {}
fn std::optional<Motion::Drive>::get(self: &std::optional<Motion::Drive>): *Motion::Drive

Type std::optional<Motion::Presence>

struct std::optional<Motion::Presence> {}
fn std::optional<Motion::Presence>::get(self: &std::optional<Motion::Presence>): *Motion::Presence

Type std::optional<Motion::ScheduleDispatch>

struct std::optional<Motion::ScheduleDispatch> {}
fn std::optional<Motion::ScheduleDispatch>::get(
    self: &std::optional<Motion::ScheduleDispatch>
): *Motion::ScheduleDispatch

Type std::optional<Motion::ScheduleStationEvent>

struct std::optional<Motion::ScheduleStationEvent> {}
fn std::optional<Motion::ScheduleStationEvent>::get(
    self: &std::optional<Motion::ScheduleStationEvent>
): *Motion::ScheduleStationEvent

Type std::optional<Motion::ScheduleStop>

struct std::optional<Motion::ScheduleStop> {}
fn std::optional<Motion::ScheduleStop>::get(
    self: &std::optional<Motion::ScheduleStop>
): *Motion::ScheduleStop

Type std::optional<Motion::TimedStop>

struct std::optional<Motion::TimedStop> {}
fn std::optional<Motion::TimedStop>::get(self: &std::optional<Motion::TimedStop>): *Motion::TimedStop

Type ID<Building>

struct ID<Building> {}
fn ID<Building>::equals(self: &ID<Building>, other: &ID<Building>): mut bool

Type ID<Line>

struct ID<Line> {}
fn ID<Line>::equals(self: &ID<Line>, other: &ID<Line>): mut bool

Type ID<Schedule>

struct ID<Schedule> {}
fn ID<Schedule>::equals(self: &ID<Schedule>, other: &ID<Schedule>): mut bool
      1. Type ID

<Script>

struct ID<Script> {}
fn ID<Script>::equals(self: &ID<Script>, other: &ID<Script>): mut bool

Type ID<Signal>

struct ID<Signal> {}
fn ID<Signal>::equals(self: &ID<Signal>, other: &ID<Signal>): mut bool

Type ID<Station>

struct ID<Station> {}
fn ID<Station>::equals(self: &ID<Station>, other: &ID<Station>): mut bool

Type ID<Tag>

struct ID<Tag> {}
fn ID<Tag>::equals(self: &ID<Tag>, other: &ID<Tag>): mut bool

Type ID<Track>

struct ID<Track> {}
fn ID<Track>::equals(self: &ID<Track>, other: &ID<Track>): mut bool

Type ID<Train>

struct ID<Train> {}
fn ID<Train>::equals(self: &ID<Train>, other: &ID<Train>): mut bool

Simulation

Type Extrapolator

struct Extrapolator {}

The simulation system tasked with train motion and scheduling.

fn Extrapolator::epoch_s(self: &Extrapolator): mut i64
fn Extrapolator::reservation_probe(self: &Extrapolator, pos: &Pos): mut ScResvProbeIt
fn Extrapolator::week_monday0000_epoch_s(self: &Extrapolator): mut i64
fn Extrapolator::week_s(self: &Extrapolator): mut i64

Type ReservationBoundaryBound

enum ReservationBoundaryBound {
    Continue,
    Bound,
}

Type ReservationBoundaryCheck

enum ReservationBoundaryCheck {
    Continue,
    Bound,
}

Type ScResvProbeIt

struct ScResvProbeIt {}

Iterator to query the reservations at a certain track position.

fn ScResvProbeIt::next(self: &mut ScResvProbeIt): *ScResvProbeIt::Result

Type ScResvProbeIt::Result

struct ScResvProbeIt::Result {
    is_occ: bool,
    train: &Train,
}

Type SignalChangePath

enum SignalChangePath {
    Keep,
    Change,
}

Type SignalChangePathResult

struct SignalChangePathResult {
    pos: Pos,
    goal_has_stop_time: bool,
    force_goal_reservation: bool,
}

If a signal_change_path function is successful, it must set the pos field of this struct with the goal position of the new train path.

Type SignalCheck

enum SignalCheck {
    Pass,
    Stop,
}