Technical information about the inner workings of Transport Fever entity component system (ecs).

Willkommen in der Transport Fever Community

Wir begrüßen euch in der Fan-Community zu den Spielen Transport Fever und Train Fever, den Wirtschaftssimulatoren von Urban Games. Die Community steht euch kostenlos zur Verfügung damit ihr euch über das Spiel austauschen und informieren könnt. Wir pflegen hier einen freundlichen und sachlichen Umgang untereinander und unser Team steht euch in allen Fragen gerne beiseite.

 

Die Registrierung und Nutzung ist selbstverständlich kostenlos.

 

Wir wünschen euch viel Spaß und hoffen auf rege Beteiligung.

Das Team der Transport-Fever Community


  • Technical information about the inner workings of Transport Fever entity component system (ecs).


    Note: This pretty much my limited view on it, and even may change in the future!


    For curious people like me, a lot of technical infos, please simple ignore this post if you don't want to look into game internals.



    Entities

    All game relevant stuff in TPF2 are handled in entities.

    Each Entity has an numeric id. Since the api update, you have access to entity components via api.engine.getComponent.


    ecs::components are structs, not classes. However they need to be copy able, so pretty much the act class like.


    ecs::components are mostly PODs, so they don’t have actually code.



    TPF(2) has relevant Systems to manipulate the necessary data.

    If a tram moves around, there is a Move System handling that kind of entity. A line system for stop management and so on.


    You can ask a lot of Systems to give information. See api.engine.*System



    ecs::Engine and ecs::ComponentManager

    The ecs::Engine and ecs::ComponentManager hold the game data,

    if TPF2 changes something or need data it looks into the components of Entity, this is handled by these.


    Most code assumes that the necessary data to be present, if not a it != it->components.end exception is a result.


    When loading or saving, your game save data is handled by the component manager and it does game version difference handling. Upgrade savegames when loading.

    As the structure needs to be known it's not easy to write an external savegame editor.


    Flow of Savegame System is as this:


    For each ecs::component:

    SaveComponentData<struct ecs::component>Write()
    Write each component data field via std io functions to savegame. There is no header, only the count of data in vectors are written.
    The load functions my transform the data or link entity components in ways the game needs.


    After loading, there are some fixups done, if a earlier game did something bad, or a savegame version fundamental changed something.


    Game States


    Each Game has at least two currently active GameStates with a own copy of the entity data. An own instance of ecs::Engine,

    GameRes is shared across states, and shouldn't actively change. (That's a reason you can't write to api.res from UGs point of view)


    There is a time frame where each System may actively allow to change the data.

    Each entity component will be flagged if changed.


    Every game state flip changes are copied, or you do a savegame as example, you simply use a GameState for saving, the game can continue running.


    You can actively follow this, as game_scripts get there relevant load and save events.

    If you mess with the lua state of you game script, you get an error, because TPF2 can't transfer your lua variable state from one GameState to the next.


    If copying takes to long, or a gamestate is still is used by an industry developer or street expansion, you get micro stops in the game.

    This system was actively changed in the performance updates and got several changes.


    Technical you can't write to entities via lua api. You are allow to send api.cmds or influence decision by commands, if the command does a gamescript callback.


    ecs::Engine internal workings:

    Internally each component data is hold into an vector<struct ecs::component::something)

    and the ecs::Engine, ecs::ComponentManager is responsible to manage lifetime.


    A basic c++ code (that doesn't support high level revision system) straight from CommonAPI.

    The typeidptr is actually a c++ std::type_index of a ecs::component::something



    Depending on compiler decision the entity iterator code is inlined. Means the compiler thinks for performance reasons it's worth to not call a function but use a copy of the code in place.


    UI::CSelector


    UI::CSelector is the execution root of UI user input, if something in the deeps of the game crashes, a catch Exception block will print the error to stdout.txt


    You can think about the actually flow of the game like this:

    UI::CSelector -> UI and GameScript running -> LUA -> your function maybe -> LUA -> api.* function -> c++ function throws exception,