This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
dev:proposals:xatc [2019-10-29 08:15] 141.76.180.151 fix bullet lists [orwell] |
dev:proposals:xatc [2022-06-26 18:08] (current) 56independent ↷ Page moved from dev:lines:xatc to dev:proposals:xatc |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== xATC ====== | + | ====== xATC Proposal |
xATC is a proposed system to supersede conventional ATC, by incorporating and taking advantage of new features, like: | xATC is a proposed system to supersede conventional ATC, by incorporating and taking advantage of new features, like: | ||
* Approach callbacks | * Approach callbacks | ||
Line 6: | Line 6: | ||
* programmatically splitting and joining trains | * programmatically splitting and joining trains | ||
- | This is a roadmap. Suggestions, | + | This is a **Very WIP, experimental draft** which is nowhere near final. Suggestions, |
+ | |||
+ | ===== Motivation ===== | ||
+ | |||
+ | At the moment, there exist two systems for controlling trains automatedly. They somewhat work hand-in-hand, | ||
+ | |||
+ | ==== Simple ATC ==== | ||
+ | This is the first ATC system that ever existed in advtrains, and has not changed much since then. | ||
+ | * Commands are purely assigned to the train. The train is responsible for parsing and executing the commands. | ||
+ | * This means that there' | ||
+ | * The syntax relies on 1-letter command codes and simple RegEx matching. It is not extensible. | ||
+ | * In particular, it is impossible to integrate more advanced commands like setting in/outside text or setting routes | ||
+ | * Commands have to be one-liners | ||
+ | |||
+ | ==== LuaATC ==== | ||
+ | This second automation system is powerful, but not very thought through. It uses plain Lua scripts in a slightly sandboxed environment. | ||
+ | * Commands are purely assigned to rails, and can not affect trains that are not standing on the rail (except for approach callbacks which use an ugly workaround) | ||
+ | * Insufficient sandboxing (Multiple DoS attacks possible) | ||
===== Constraints ===== | ===== Constraints ===== | ||
Line 12: | Line 29: | ||
* SHOULD support everything LuaATC can do | * SHOULD support everything LuaATC can do | ||
* Should be easy to use for newcomers (not pure Lua) | * Should be easy to use for newcomers (not pure Lua) | ||
+ | * Setups should be copyable, and there should be a way to modularize setups (" | ||
+ | * Commands are given out by stationary components, but are associated and functional even when the train is no longer at the commanding location (like LuaATC currently enforces). In a later stage, it should also be possible to assign xATC programs purely to trains (see H#124) | ||
The following things should be doable through xATC: | The following things should be doable through xATC: | ||
Line 22: | Line 41: | ||
* Interface to external components (e.g. digilines) | * Interface to external components (e.g. digilines) | ||
* Be extensible to new features introduced over time | * Be extensible to new features introduced over time | ||
+ | |||
+ | ===== Draft 2 ===== | ||
+ | |||
+ | Half a year of rethinking xATC has brought some new approaches: | ||
+ | |||
+ | ==== Inspiration ==== | ||
+ | |||
+ | xATC can draw inspiration from other " | ||
+ | * **Scratch** ([[https:// | ||
+ | * **Python asyncio:** the " | ||
+ | |||
+ | ==== Event blocks ==== | ||
+ | |||
+ | xATC code is organized in "Event Blocks" | ||
+ | |||
+ | An Event Block consists of an event identifier and a set of code lines. There are built-in identifiers that depend on the context, and the user can define and call own event identifiers. | ||
+ | |||
+ | ==== Contexts ==== | ||
+ | |||
+ | Each xATC snippet is run in a context. | ||
+ | |||
+ | A new context is instantiated when a context does not exist for a particular combination of involved entities. For example, when a train approaches an xATC rail, a new context for this combination of train and xATC rail is created, unless one already exists. | ||
+ | |||
+ | A context is destroyed when all event blocks in the context are completed and no future events are scheduled for it. | ||
+ | |||
+ | For the first implementation, | ||
+ | |||
+ | A context is not created at all when an event is fired on a not-yet-existing context, but the associated xATC snippet has no handling event block for this event. | ||
+ | |||
+ | ==== Operations ==== | ||
+ | |||
+ | The following high-level operations exist (analogous to Scratch " | ||
+ | * Property/ | ||
+ | * Used for example for train inside/ | ||
+ | * Local variable creation/ | ||
+ | * Local vars are only valid in this event block | ||
+ | * Wait for a Future to complete ('' | ||
+ | * this can be waiting for a certain time, waiting for a signal to turn green etc | ||
+ | * Wait for an event to occur in the current context ('' | ||
+ | * Fire an event in the current context ('' | ||
+ | * Fire an event in a foreign context ('' | ||
+ | * Call another built-in/ | ||
+ | * Control structures: If/Else, for loop? | ||
+ | * Exit this event block ('' | ||
+ | * Destroy the context, killing this and all other event blocks in it ('' | ||
+ | |||
+ | This concept does no longer allow for directly " | ||
+ | |||
+ | '' | ||
+ | |||
+ | ==== Concurrency ==== | ||
+ | |||
+ | Event blocks are executed concurrent, but not multithreaded. Since event blocks will not perform intensive calculations but instead yield very often (via await), every time event blocks (within a context) become ready to continue execution, they are executed consecutively until the next yield. If we ever support loops, they would also yield implicitly every iteration (but instantly become ready again). | ||
+ | |||
+ | ==== Interpreter ==== | ||
+ | |||
+ | The code written in xATC is untrusted, since it is possibly written by untrusted users. We therefore build a somewhat custom Lua interpreter. A plan for this looks as follows: | ||
+ | * Every line is considered an instruction or a block start/end (we won't support ; to separate multiple commands per line for now) | ||
+ | * The global instruction structure is parsed by custom code | ||
+ | * Individual instructions are left as-is and are compiled to Lua directly. | ||
+ | * A special xATC-to-Lua compiler will find out where yield points are, split the xatc instructions into consecutive instructions without yield, and compile these somehow as individual Lua functions. This must be done in the way that the yield point the code currently is at can be saved persistently. We assume that execution does not halt between yield points. | ||
+ | * Instructions are only allowed to be in " | ||
+ | |||
+ | ==== Built-in events and variables ==== | ||
+ | |||
+ | As for the xATC rail, it will emit the following events (in typical order of occurence) | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | |||
+ | Special variables bound are " | ||
+ | |||
+ | ==== Examples ==== | ||
+ | |||
+ | This could be a simple stop rail: | ||
+ | < | ||
+ | on approach: | ||
+ | if train.line == " | ||
+ | set_point_speed_limit(2) -- this is a context function since it depends on both train and rail | ||
+ | train.disable_ars = true | ||
+ | end | ||
+ | -- this code does not yield, it directly terminates | ||
+ | -- At this point, no event block is active anymore and the context is destroyed | ||
+ | |||
+ | on enter: | ||
+ | if train.line ~= " | ||
+ | return | ||
+ | end | ||
+ | await train.brake(0) -- instruct the train to brake to speed 0, and wait for it | ||
+ | await train.open_doors_right() -- open the doors and wait for it | ||
+ | await delay(20) -- wait at least 20 seconds before continuing | ||
+ | train.disable_ars = false | ||
+ | await train.next_signal.wait_for_proceed() -- wait until the signal ahead shows a non-danger aspect | ||
+ | await train.close_doors() | ||
+ | train.speed(15) -- depart from the station | ||
+ | </ | ||
+ | |||
+ | More sophisticated: | ||
+ | < | ||
+ | on approach: | ||
+ | set_point_speed_limit(2) -- this is a context function since it depends on both train and rail | ||
+ | train.disable_ars = true | ||
+ | |||
+ | on enter: | ||
+ | await train.brake(0) | ||
+ | await train.open_doors_right() | ||
+ | | ||
+ | local other_track = get_xatc_rail(" | ||
+ | | ||
+ | rail.data[" | ||
+ | -- if nothing yields (no await and no loops) we have no preemption, so race conditions can not occur. | ||
+ | if other_track.data[" | ||
+ | -- the train is already present on the other track and | ||
+ | -- is waiting to receive our arrival event | ||
+ | other_track.fire_all(" | ||
+ | else | ||
+ | -- the other train has not arrived yet. | ||
+ | -- wait for the " | ||
+ | await event(" | ||
+ | -- note that we don't have a dedicated event block for that event, we just wait for it in here | ||
+ | end | ||
+ | -- when we get here, both trains have arrived. | ||
+ | rail.data[" | ||
+ | | ||
+ | await delay(20) -- wait at least 20 seconds before continuing | ||
+ | train.disable_ars = false | ||
+ | await train.next_signal.wait_for_proceed() -- wait until the signal ahead shows a non-danger aspect | ||
+ | await train.close_doors() | ||
+ | train.speed(15) -- depart from the station | ||
+ | </ | ||
+ | |||
+ | An alternative way to write the last example: | ||
+ | < | ||
+ | on approach: | ||
+ | set_point_speed_limit(2) -- this is a context function since it depends on both train and rail | ||
+ | train.disable_ars = true | ||
+ | |||
+ | on enter: | ||
+ | await train.brake(0) | ||
+ | await train.open_doors_right() | ||
+ | | ||
+ | rail.data[" | ||
+ | | ||
+ | local other_track = get_xatc_rail(" | ||
+ | if other_track.data[" | ||
+ | -- the train is already present on the other track and | ||
+ | -- is waiting to receive our arrival event | ||
+ | other_track.fire_all(" | ||
+ | fire(" | ||
+ | end | ||
+ | |||
+ | -- this event gets always fired by the train arriving second | ||
+ | on other_train_arrived: | ||
+ | rail.data[" | ||
+ | | ||
+ | await delay(20) -- wait at least 20 seconds before continuing | ||
+ | train.disable_ars = false | ||
+ | await train.next_signal.wait_for_proceed() -- wait until the signal ahead shows a non-danger aspect | ||
+ | await train.close_doors() | ||
+ | train.speed(15) -- depart from the station | ||
+ | </ | ||
+ | |||
+ | Note that this whole example can probably benefit from a semaphore concept. This is TBD. | ||
+ | |||
+ | ===== Draft 1 (older) ===== | ||
+ | |||
+ | xATC will use a subset of Lua to express actions. The following things are restricted: | ||
+ | * " | ||
+ | * " | ||
+ | * The global environment is not writable. A number of standard variables and functions exists to be used. | ||
+ | |||
+ | xATC code is organized in so-called " | ||
+ | |||
+ | 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, | ||
+ | train.ars = false | ||
+ | train.stop() | ||
+ | rail.digiline_send(" | ||
+ | |||
+ | On Stop: | ||
+ | train.reverse() | ||
+ | train.text_inside = "Euler Street" | ||
+ | train.doors(" | ||
+ | schedule(" | ||
+ | rail.digiline_send(" | ||
+ | |||
+ | On Depart-Ready: | ||
+ | signal.ars().wait(" | ||
+ | |||
+ | On Depart: | ||
+ | train.doors(" | ||
+ | train.text_inside = " | ||
+ | train.ars = true | ||
+ | train.speed() | ||
+ | rail.digiline_send(" | ||
+ | |||
+ | </ | ||
+ | |||
+ | Notable things: | ||
+ | * Events are denoted by "On Eventname:" | ||
+ | * The pre-defined entities " | ||
+ | * " | ||
+ | * Actions (like doors(), speed(), ars()) are asynchronous in first term. You can make them explicitly synchronous by calling wait() without arguments on them. This only works on some actions (e.g. not on ars(), because that might never return) | ||
+ | * Alternatively, | ||
+ | |||
+ | ==== Asynchronous execution (added 2020-10-28) ==== | ||
+ | I imagine that the asynchronous code execution (wait etc.) can draw much inspiration from python' | ||
+ | [[https:// | ||
+ | |||
+ | The above code using an " | ||
+ | < | ||
+ | On Approach: | ||
+ | train.text_outside = "E3 - Trisiston\nvia Tanh Cliffs" | ||
+ | train.text_inside = "Next Stop: Euler Street\nTerminus, | ||
+ | train.ars = false | ||
+ | local stop_await = train: | ||
+ | -- makes train stop at the rail, returns an ' | ||
+ | stop_await: | ||
+ | -- when stopping the train is finished, throw the " | ||
+ | rail.digiline_send(" | ||
+ | -- scope of "local stop_await" | ||
+ | |||
+ | On Stop: -- user-defined event (fired by stop_at_rail() above) | ||
+ | train.reverse() | ||
+ | train.text_inside = "Euler Street" | ||
+ | train.doors(" | ||
+ | schedule(" | ||
+ | rail.digiline_send(" | ||
+ | |||
+ | On Depart-Ready: | ||
+ | signal: | ||
+ | |||
+ | On Depart: | ||
+ | await train.doors(" | ||
+ | train.text_inside = " | ||
+ | train.ars = true | ||
+ | train.speed() | ||
+ | rail.digiline_send(" | ||
+ | |||
+ | On ARSFail: | ||
+ | schedule(" | ||
+ | |||
+ | </ | ||
+ | |||
+ | All awaitables can either complete successfully or fail. If an awaitable succeeds, and '' | ||
+ | |||
+ | '' | ||
+ | |||
+ | ==== Lua Interpreter Implementation details ==== | ||
+ | |||
+ | 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: | ||
+ | * Every line is considered an instruction or a block start/end (we won't support ; to separate multiple commands per line for now) | ||
+ | * The global instruction structure is parsed by custom code | ||
+ | * Individual instructions are left as-is. Custom code assembles the instructions into Lua code safe for procesing. | ||
+ | * Instructions are only allowed to be in " | ||
+ | |||
+ | Perhaps the exact syntax and semantics of " | ||
+ | |||
+ | A possible ' | ||
+ | < | ||
+ | On Approach: | ||
+ | schedule("/ | ||
+ | train.stop() -- Stop the train | ||
+ | |||
+ | On Stop: | ||
+ | wait_for(" | ||
+ | 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 " | ||
+ | On Stop: | ||
+ | wait_for(" | ||
+ | schedule(" | ||
+ | |||
+ | -- Train 2 | ||
+ | On Stop: | ||
+ | rail.dispatch_event_to(" | ||
+ | -- the semantics of this are not yet specified | ||
+ | </ | ||
+ |