Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Setting Up

This page describes how to set up Dies for development.

Requirements

To compile the project all you need a recent version of stable Rust. We recommend using rustup to manage your Rust installation.

For development, you should make sure that rustfmt and clippy are installed. You can install them with rustup component add rustfmt clippy.

To use the ER-Force simulator, you will also need to install a recent version of Docker and ensure that the Docker daemon is running.

Building and Running

Dies uses cargo workspaces to manage its crates. The dies-cli crate contains the Dies CLI, which can be used to run matches.

To run tests for all crates, run cargo test in the root directory. You can run specific tests by specifying the crate name, eg. cargo test -p dies-core.

To run the CLI from the workspace root, use cargo run.

TODO: describe web UI.

Developer Guide

This document provides some guidelines for developing Dies.

Using Git

The main branch should remain stable at all times. All new features and fixes should be developed in feature branches, and merged into main only after they are complete and have been tested. Feature branches should be named <name of author>/<feature name>, eg. balint/fix-ersim-env-win.

The CI will run tests and formatting checks on all branches, but you should run these locally before pushing your changes.

Commit names should adhere to a lite version of the Conventional Commits spec, ie. they should follow the format: <commit type>: <short description>. Scopes are not needed. The commit type should be one of the following:

  • feat: new feature
  • fix: bug fix
  • refactor: refactoring
  • docs: documentation
  • test: adding tests
  • chore: other changes (eg. CI, formatting, etc.)
  • wip: work in progress
  • misc: other changes

Code Style

Dies uses the rustfmt formatter. It is recommended to use the rust-analyzer extension for VS Code, and configure it to automatically format the code on save.

We also use clippy for linting. The VS Code extension should also automatically run clippy on save.

In general, the Rust code should be idiomatic, and should attempt to follow the Rust API Guidelines.

Generics should be used with consideration. They are powerful, but they make the code more complex and significantly increase compile times. As a rule of thumb, remember YAGNI (you ain't gonna need it) -- don't overengineer, you can always add generics later if you need them.

Crate Structure

Dies is split into multiple crates. We use cargo workspaces to manage these. Having a lot of crates is not necessarily a bad thing -- crates are the unit of compilation in Rust, so more granular crates help caching and parallel compilation.

In general, the dependency graph between our crates should resemble a diamond:

     +-  feature crates  -+
    /                      \
dies-core ------------> dies-cli
    \                      /
     +-  feature crates  -+

There should be a few root crates (dies-core) which will rarely change, a number of specialized feature crates (dies-basestation-client, dies-executor, etc.), and a few small leaf crates which just glue things together (dies-cli).

For an up-to-date list of crates, see the README.

Error Handling

Errors should be handled according to Rust idioms. In general, we should avoid panicking, and instead propagate Result to the very top, where it can be handled by the executor.

Throughout the codebase, the anyhow crate should be used for error handling. Anwhow provides a convenient dynamic wrapper type for errors, so we do not have to define our own error types. anyhow::Result should be the return type of all functions which can fail.

Logging

Logging is really important for Dies, as it is the only way to debug issues that occured during a match. Logs should contain all the information needed to replay matches, including all the data we receive from the vision system, the world state, and the commands we send to the robots.

Dies uses the log crate for logging. The following log levels should be used:

  • error: should be used only for fatal errors, which cause the executor to stop, ie. errors should only be logged in the executor
  • warn: should be used for unexpected events or recoverable errors, which do not cause the executor to stop and do not get propagated up
  • info: should be used rarely, only for information that the user needs to know while running Dies - for example when long operations start and finish, or when error-prone operations succeed
  • debug: should be used most of the time: everything from the data we receive from the vision system, commands we send to the robots, to all minor events should be logged at this level

We do not use trace.

println! and all other forms of direct output should be avoided, as they are not captured by the logger.

Testing

Reliability is very important for Dies, so we aim for high test coverage. All new features should come with corresponding unit tests. Unit tests should test the code located in the module they are in, and should avoid relying on other modules.

Property-based testing should be explored for testing the core logic of Dies. See the proptest crate for more details on this technique.

Documentation

This section is WIP.

Dies Architecture

Dies is Delft Mercurians' open source framework for building AIs, specifically the central team controller, for RoboCup Small Size League.

Dies runs on a central server, and communicates with the tournament software (ssl-vision/simulator, game controller), and with the robots on the field. It is responsible for running the AI, and for sending commands to the robots.

graph TD
    GameController[/Game Controller/] <--> Dies
    SSLVision[/SSL Vision/] --> Dies
    Robots[/Robots/] <--> Dies

Framework Component Relationships

Legend
  • A `*--` B: Ownership (A can only exist as part of B)
  • A `o..` B: Aggregation (A owns B, but B can exist independently)
  • A `<..` B: Dependency (A does not store B, but makes use of it)
classDiagram
    %%  Cli is the main entry point
    Cli o.. WebUi
    Cli o.. TestMainLoop

    %% WebUi owns the main loops
    WebUi o.. InteractiveMainLoop
    WebUi o.. PlaybackMainLoop

    %% Main components owned by TestMainLoop
    TestMainLoop <.. Simulator
    TestMainLoop <.. TeamMap

    %% Main components owned by InteractiveMainLoop
    InteractiveMainLoop <.. Simulator
    InteractiveMainLoop <.. SslClient
    InteractiveMainLoop <.. BsClient
    InteractiveMainLoop <.. TeamMap

    PlaybackMainLoop *-- Logplayer

    %% TeamMap owns Teams
    TeamMap *-- Team

    %% Team owns core components
    Team *-- Tracker
    Team *-- TeamController
    Team *-- StrategyServer

    %% Tracker hierarchy
    Tracker *-- PlayerTracker
    Tracker *-- BallTracker
    Tracker *-- GameStateTracker

    %% Controller hierarchy
    TeamController *-- PlayerController
    PlayerController *-- RobotController
    PlayerController *-- Skills

    %% Skills collection
    Skills o-- GoTo
    Skills o-- Face
    Skills o-- FetchBall
    Skills o-- OrbitBall
    Skills o-- Pass
    Skills o-- Receive

    %% Strategy client/server relationship
    StrategyServer ..> StrategyApi : IPC
    StrategyApi <-- StrategyClient

    class Cli {
        main()
    }

    class Skills {
        <<collection>>
    }
    class StrategyServer {
        <<IPC Server>>
    }
    class StrategyApi {

    }

Data Flow

Interactive Mode (simulation or live)

flowchart TB
    %% Define all components first
    UI[Web UI]
    WS[Web Server]
    ML[Main Loop]
    TR[Tracker]
    CT[Controller]
    SS[Strategy Server]
    SA[Strategy API]
    ST[Strategy Process]
    SIM[Simulator]
    SSL[SSL/BS Client]
    TM[Team]
    CLI[CLI]

    %% Group the UI layer
    subgraph UserInterface[User Interface]
        UI
        WS
        CLI
    end

    %% Group the core system components
    subgraph CoreSystem[Core]
        ML
        SIM
        SSL
    end

    %% Group the team components
    subgraph TeamSystem[Team]
        TM
        TR
        CT
        SS
    end

    %% Group the strategy components
    subgraph StrategySystem[Strategy]
        SA
        ST
    end

    %% Now define all the connections
    CLI -->|Config| WS
    ML -->|Debug data & Status| WS
    WS -->|Player Overrides| TM
    WS <-->|Start/Stop| ML
    UI <-->|UiMessages & UiData| WS
    ML -->|Simulator Cmds| SIM

    SSL -->|Vision/GC Updates| TR
    SIM -->|Vision/GC Updates| TR

    TR -->|Tracker Data| TM
    TM -->|Tracker Data| CT
    TM -->|Tracker Data| SS

    SS -->|Team Cmds| CT
    SS <--->|Team Cmds & Tracker Data| SA
    SA <-->|IPC| ST

    SSL <-->|Robot Cmds| CT

    TR -->|World Frame| WS

Test Mode (automated)

flowchart TB
    %% Main components - note the reduced set
    TML[Test Main Loop]
    TR[Tracker]
    CT[Controller]
    SS[Strategy Server]
    SA[Strategy API]
    ST[Strategy Process]
    SIM[Simulator]
    TM[Team]

    %% Test configuration and control
    TML -->|Test Scenario Commands| SIM

    %% Simulated vision flow
    SIM -->|Simulated Vision/GC Updates| TR

    %% Tracker data flow - through team
    TR -->|Tracker Data| TM
    TM -->|Tracker Data| CT
    TM -->|Tracker Data| SS

    %% Strategy flow remains the same
    SS -->|Team Cmds| CT
    SS <-->|Team Cmds & Tracker Data| SA
    SA <-->|IPC| ST

    %% Robot control now goes directly to simulator
    CT -->|Robot Commands| SIM

    %% Add subgraph for test components
    subgraph TestFramework[Test Framework]
        TML
    end

    %% Add subgraph for team components
    subgraph Team
        TM
        TR
        CT
        SS
    end