This is an old revision of the document!
xATC is a proposed system to supersede conventional ATC, by incorporating and taking advantage of new features, like:
This is a Very WIP, experimental draft which is nowhere near final. Suggestions, ideas and improvements are welcome
At the moment, there exist two systems for controlling trains automatedly. They somewhat work hand-in-hand, but have individual design flaws:
This is the first ATC system that ever existed in advtrains, and has not changed much since then.
This second automation system is powerful, but not very thought trough. It uses plain Lua scripts in a slightly sandboxed environment.
The following things should be doable through xATC:
xATC will use a subset of Lua to express actions. The following things are restricted:
xATC code is organized in so-called “events”. An event can be a builtin event or a user-defined event. The RWT scheduler, which has a builtin DOS protection, will serve as event queue.
An xATC code snippet would, for example, look like this:
On Approach: train.text_outside = "E3 - Trisiston\nvia Tanh Cliffs" train.text_inside = "Next Stop: Euler Street\nTerminus, please get off." train.ars = false train.stop() -- makes train stop at the rail and emits "Stop" afterwards rail.digiline_send("DFI-controller", "Arr:E3W") On Stop: train.reverse() train.text_inside = "Euler Street" train.doors("R") schedule("+02;00", "Depart-Ready") -- Schedule departure in 2 minutes rail.digiline_send("DFI-controller", "Stp:E3W") On Depart-Ready: signal.ars().wait("Depart") -- Tell the next signal to do ARS and emit "Depart" when route is set On Depart: train.doors("C").wait() -- Wait in-place (without another event) for doors to close train.text_inside = "Welcome on board! Next stop: Mountain South" train.ars = true train.speed() rail.digiline_send("DFI-controller", "Dep:E3W")
Notable things:
I imagine that the asynchronous code execution (wait etc.) can draw much inspiration from python's asyncio
. Possibly we will also use an “await” keyword to wait for asynchronous tasks.
https://docs.python.org/3/library/asyncio-task.html
The above code using an “await” keyword:
On Approach: train.text_outside = "E3 - Trisiston\nvia Tanh Cliffs" train.text_inside = "Next Stop: Euler Street\nTerminus, please get off." train.ars = false local stop_await = train:stop_at_rail() -- makes train stop at the rail, returns an 'awaitable' stop_await:then("Stop") -- when stopping the train is finished, throw the "Stop" event. rail.digiline_send("DFI-controller", "Arr:E3W") -- scope of "local stop_await" ends here On Stop: -- user-defined event (fired by stop_at_rail() above) train.reverse() train.text_inside = "Euler Street" train.doors("R") schedule("+02;00", "Depart-Ready") -- Schedule departure in 2 minutes rail.digiline_send("DFI-controller", "Stp:E3W") On Depart-Ready: signal:ars():then("Depart"):else("ARSFail") -- Tell the next signal to do ARS and emit "Depart" when route is set On Depart: await train.doors("C") -- Wait in-place (without another event) for doors to close train.text_inside = "Welcome on board! Next stop: Mountain South" train.ars = true train.speed() rail.digiline_send("DFI-controller", "Dep:E3W") On ARSFail: schedule("+00;10", "Depart-Ready") -- try again later
All awaitables can either complete successfully or fail. If an awaitable succeeds, and then(“eventname”)
had been called on it, the specified event is fired. If the await
keyword has been used, execution continues. However, if an awaitable fails (this can happen e.g. for the “stop_at_rail” event if a player takes over control during stopping, or for “ars” if the route is cancelled manually by a player), the coroutine that await
s it is cancelled. It is possible to call awaitable.else(“eventname”)
to catch the error case in a separate event, however.
then()
and else()
are chainable, that means they return the awaitable they've been called on.
The code written in xATC is untrusted, since it is possibly written by untrusted users. We therefore build a somewhat custom Lua interpreter.
How this code is parsed and executed is not yet decided. This is a matter of where to lay the boundary between custom parsing and Lua compiling.
A first suggestion looks as follows:
Perhaps the exact syntax and semantics of “wait” functions will be changed in this draft, this is yet unclear. There needs to be a way to continue a halted “thread” from saved data, which effectively prevents us from using coroutines. The simplest way would be to abandon wait() altogether, but this violates the “simplicity” constraint.
A possible 'wait_for(“Event”)' function could allow for quasi-procedural operation, doing things like
On Approach: schedule("/01;00+00;00", "Depart") -- When the train approaches, emit a "Depart" event at the next full minute train.stop() -- Stop the train On Stop: wait_for("Depart") -- When the train stopped, wait until the "Depart" event arrived (if it already arrived before, returns immediately; this works here because there's no "On Depart" that handled this, it remained in queue) train.speed()
Eventually, a further extension can implement event sending from external entities, such as a train waiting for another connecting train.
-- Train 1 (running in context of some xATC rail called "Trisiston:1") On Stop: wait_for("connecting_train_arrived") schedule("+00;20", "Depart") -- Train 2 On Stop: rail.dispatch_event_to("Trisiston:1", "connecting_train_arrived") -- the semantics of this are not yet specified