Development:Structure: Difference between revisions

From librae wiki
No edit summary
 
(5 intermediate revisions by the same user not shown)
Line 1: Line 1:
This article describes the structure of the code of Librae. [The code itself is available on GitLab https://gitlab.com/librae/librae].
This article describes the structure of the code of Librae. [https://gitlab.com/librae/librae The code itself is available on GitLab].


Part of the game engine is called <code>rustorion</code>. It's the older name for the project, and intended to be the name of the engine, when it is fully separated from game logic.
Part of the game engine is called <code>rustorion</code>. It's the older name for the project, and intended to be the name of the engine, when it is fully separated from game logic.
Line 15: Line 15:
==Game state==
==Game state==


The game is structured to support trustless (except the server), simultaneous multiplayer. The game state is stored as a single '''universe''' structure (<code>rustorion::Universe</code>). Client receives a '''view''' which contains only the information the client is supposed to know. Client sends '''action'''s to the server that contain the information about what the client wants to do on the current turn. When all the players are ready (submitted their actions or it's a timeout), the server processes all the actions (they are designed to be executed independently of each other, so there are no race conditions), performs various updating on the '''universe''' and notifies clients that they can download new '''view'''s.
The game is structured to support trustless (except the server), simultaneous multiplayer. The game state is stored as a single '''universe''' structure (<code>rustorion::Universe</code>). Client receives a '''view''' which contains only the information the client is supposed to know. Views are not intended to be modified. Client sends '''action'''s to the server that contain the information about what the client wants to do on the current turn. When all the players are ready (submitted their actions or it's a timeout), the server processes all the actions (they are designed to be executed independently of each other, so there are no race conditions), performs various updating on the '''universe''' and notifies clients that they can download new '''view'''s.


In the future, clients might be able to download previous views to browse them at will or use for gathering statistics.
In the future, clients might be able to download previous views to browse them at will or use for gathering statistics.
Line 30: Line 30:


However, there are simplified interfaces for '''Universe''' and '''Universeview''' called <code>rustorion::universe::interface::Universe</code> and <code>rustorion::universeview::interface::Universeview</code>, respectively. These allow for object-oriented, safe examination of data and contain many useful routines. The intention is to use these to prepare the modifications, and later execute them on the storage type after dropping the interface.
However, there are simplified interfaces for '''Universe''' and '''Universeview''' called <code>rustorion::universe::interface::Universe</code> and <code>rustorion::universeview::interface::Universeview</code>, respectively. These allow for object-oriented, safe examination of data and contain many useful routines. The intention is to use these to prepare the modifications, and later execute them on the storage type after dropping the interface.

==RPC==

Communication with client is done using [https://gitlab.com/librae/cborpc/ CBOR-serialized RPC]. It's rather haphazardly done using Tokio's example-like length-delimited codecs, and needs to be improved or replaced with something less monstrous than gRPC. Light macro usage might be in order.

==Authentication==

Clients authenticate themselves with client TLS certificates to the server. Currently BLAKE3 hashes of the client certs are used, this seems to work but is probably not the best idea.

== State Machines ==

Interaction between parts of the game, like network client, server, GUI or CLI is based on state machines. A state machine in that context is an entity which, after being started, manages itself and interacts with others using messages. For example, if you want to use the 'client' machine:

# Start the machine (this launches it in another 'thread', in case of the client, it's a Tokio green thread)
# Subscribe to its events (start listening on message channels connected to it)
# Send the 'connect' message
# Wait for the other messages, like 'connected', and change your own state accordingly.

State machines are implemented using the [https://gitlab.com/librae/state-machine state_machine] library. The library supports the Tokio and 'futures' crate primitives, the latter are also used by GTK4.

Latest revision as of 15:04, 6 January 2025

This article describes the structure of the code of Librae. The code itself is available on GitLab.

Part of the game engine is called rustorion. It's the older name for the project, and intended to be the name of the engine, when it is fully separated from game logic.

Language

The game is written in Rust. It tries to be pure Rust, but some unique foreign components are still used, most prominently GTK4 for the user interface.

Main parts

  • rustorion — game logic library
  • librae-gui — gtk-based UI
  • librae-cli — CLI tools

Game state

The game is structured to support trustless (except the server), simultaneous multiplayer. The game state is stored as a single universe structure (rustorion::Universe). Client receives a view which contains only the information the client is supposed to know. Views are not intended to be modified. Client sends actions to the server that contain the information about what the client wants to do on the current turn. When all the players are ready (submitted their actions or it's a timeout), the server processes all the actions (they are designed to be executed independently of each other, so there are no race conditions), performs various updating on the universe and notifies clients that they can download new views.

In the future, clients might be able to download previous views to browse them at will or use for gathering statistics.

Entities and IDs

Most things, entities within the universe, or anything else that implements EntityStored have an ID. It is an unique identifier of an entity within the storing structure. Using EntityStored::get() and other similar methods you can obtain a reference to the entity by its ID. IDs are used to store links between entities through plain inclusion on a structure, or using types from rustorion::storage::links for more complex relations like many-to-many.

IDs are issued sequentially, but then the number is encrypted with Blowfish to ensure there is no leaking information about number of entities in an universe. Because the encryption key is stored in Universe, only Universe can issue new IDs.

Working with state

To modify the state, the only option is to work with the storage type (e.g. Universe) directly. However, this is not easy, you have to obtain IDs, use them to extract data, etc., manually. This appears to be the only way to keep the structure mutable and avoid using some kind of GC.

However, there are simplified interfaces for Universe and Universeview called rustorion::universe::interface::Universe and rustorion::universeview::interface::Universeview, respectively. These allow for object-oriented, safe examination of data and contain many useful routines. The intention is to use these to prepare the modifications, and later execute them on the storage type after dropping the interface.

RPC

Communication with client is done using CBOR-serialized RPC. It's rather haphazardly done using Tokio's example-like length-delimited codecs, and needs to be improved or replaced with something less monstrous than gRPC. Light macro usage might be in order.

Authentication

Clients authenticate themselves with client TLS certificates to the server. Currently BLAKE3 hashes of the client certs are used, this seems to work but is probably not the best idea.

State Machines

Interaction between parts of the game, like network client, server, GUI or CLI is based on state machines. A state machine in that context is an entity which, after being started, manages itself and interacts with others using messages. For example, if you want to use the 'client' machine:

  1. Start the machine (this launches it in another 'thread', in case of the client, it's a Tokio green thread)
  2. Subscribe to its events (start listening on message channels connected to it)
  3. Send the 'connect' message
  4. Wait for the other messages, like 'connected', and change your own state accordingly.

State machines are implemented using the state_machine library. The library supports the Tokio and 'futures' crate primitives, the latter are also used by GTK4.