Skip to content

Enhancement: Architecture adjustment for separation of concerns #18

@M4X1K02

Description

@M4X1K02

Also coming from a previous PR that was about refactoring, I have noticed some code was not completely separated by their concerns. That is okay, I do this all the time too :)
But seeing how this project might grow, it may be worthwhile to reconsider the current architecture. Therefore I've asked gemini and this is what it proposes. Tbh I don't understand every detail, but I figured you would know whether this makes sense and this is something we should pursue.
I have tried to display everything as concise and understandable as possible.


This plan outlines the refactoring of the SimApi project to decouple the Data Provider (the daemon that maps game data) from the Client SDK (the library used by dashboards and HUDs).


1. The Core Benefit: Separation of Concerns

Currently, any application that wants to read telemetry must link against a monolithic library that contains code for every supported racing game.

Why this matters

  • Maintenance – Adding support for a new game shouldn’t require recompiling every HUD and dashboard.
  • Dependency hygiene – Clients (HUDs) shouldn’t need libuv or libconfig; only the provider (simd) needs those for process and configuration management.
  • Ease of use – Switch from manual memory mapping to a clean simapi_connect() function.

2. Architecture Diagrams

Before: The Monolithic Architecture

The simapi library is a leaky abstraction containing both the public interface and the heavy mapping machinery.

graph TD
    subgraph libsimapi.so [Target: simapi - The Monolith]
        SDK[Public: simdata.h]
        Engine[Internal: simmapper.c + mapping/]
        Games[Game Headers: ac.h, rf2.h, etc.]
        Engine --> SDK
        Engine --> Games
    end

    subgraph simd_app [Target: simd]
        Daemon[simd.c]
        Daemon -- "Links & Includes" --> libsimapi.so
    end

    subgraph consumers [Consumers]
        VT[view_telemetry.c]
        VT -- "Manual SHM Read" --> SHM[("/dev/shm/SIMAPI.DAT")]
        VT -- "Includes" --> SDK
    end

    Daemon -- "Writes" --> SHM
Loading

AFTER: The Layered Architecture

The mapping logic is encapsulated. The public SDK is tiny and dependency-free.

graph TD
    subgraph ClientSDK [Target: libsimapi - Public SDK]
        SDK[simdata.h / simapi.h]
        API[simapi_client.c - NEW]
        API --> SDK
    end

    subgraph MappingEngine [Target: libsimmapper - Internal]
        Engine[simmapper.c + mapping/]
        GHeaders[ac.h, rf2.h, etc.]
        Engine --> GHeaders
    end

    subgraph Daemon [Target: simd - The Service]
        Main[simd.c]
        Main -- "Links" --> MappingEngine
        Main -- "Links" --> ClientSDK
    end

    subgraph Apps [Client Applications]
        HUD[view_telemetry / HUDs]
        HUD -- "Links" --> ClientSDK
    end

    Main -- "Writes" --> SHM[("/dev/shm/SIMAPI.DAT")]
    ClientSDK -- "Reads" --> SHM
Loading

3. Comparison Contrast

Feature Current (Monolith) New (Layered)
Public API Manual shm_open + mmap simapi_connect(), simapi_read()
Client Dependencies libuv, libconfig, game headers None (standard C library only)
Namespace Hygiene ac.h, rf2.h exposed to clients Game structures hidden in libsimmapper
Build Impact Adding a game = rebuild everything Adding a game = rebuild simd only
Library Size Large (contains translation logic) Tiny (<20KB SDK)

4. Implementation Checklist: What Changes?

1. Structural Changes

  • Move simapi/mapping/ and simapi/simmapper.c to a new internal directory (e.g., simapi/internal/ or simd/mapper/).
  • Move ac.h, rf2.h, pcars2.h, etc., to the internal directory.

2. New Files

  • simapi/simapi.c: Implements the new client-facing API:
    • SimData* simapi_connect() — handles shm_open and mmap.
    • void simapi_disconnect() — handles munmap.
  • simapi/simapi.h: Cleaned up to only export the two functions above and the SimData struct.

3. Build System (CMakeLists.txt)

  • Split the simapi target:
    • simapi: A public SHARED library (the SDK).
    • simmapper: A private STATIC library (the Engine).
  • Update simd: Link it against both simmapper and simapi.
  • Update view_telemetry: Update code to use simapi_connect().

5. Impact on monocoque

This refactoring is highly beneficial for the Monocoque project (and other consumers like simmonitor). Since Monocoque is "sim-agnostic" by design, it currently pays a "tax" for the monolithic architecture that this refactor eliminates.

Here is exactly how it affects Monocoque:

1. Zero Breaking Changes (Data Compatibility)

  • The most important point: The SimData structure remains 100% identical.
  • Because the binary layout of the shared memory (SIMAPI.DAT) does not change, existing versions of Monocoque will continue to work without any modifications.
  • It will still find the data exactly where it expects it in /dev/shm/.

2. Radical Dependency Reduction

  • Currently, if you want to compile Monocoque, you effectively have to include the logic for every single simulator simapi supports.
  • Before: Monocoque (a client) has a transitive dependency on libuv, libconfig, and all the simulator-specific mapping code (Assetto Corsa, rFactor 2, etc.).
  • After: Monocoque only links against the new, tiny libsimapi. It becomes a pure "Consumer" with zero knowledge of the "Provider" complexities.

3. Simplified Integration (The SDK)

  • Code Cleanup: You can delete the manual shm_open, ftruncate, and mmap boilerplate code in Monocoque.
  • New API: Instead of managing file descriptors and memory pointers, Monocoque would simply do:
SimData* telemetry = simapi_connect();
// ... use telemetry->speed, etc ...
simapi_disconnect();

4. Improved Portability

Because the new libsimapi will have zero external dependencies (only standard C headers), it becomes much easier to port Monocoque to different distributions or environments. You no longer need to ensure that the client environment has all the libraries required by the mapping engine.

Summary of Impact for Monocoque

Aspect Impact
Binary Compatibility Perfect. No change required to run.
Source Code Simplified. Can remove ~50-100 lines of SHM boilerplate.
Build System Streamlined. No longer needs to link against provider-specific libs.
Maintenance Better. Monocoque doesn't need a re-build when simapi adds a new game.

In short: Monocoque goes from being "tied to the engine" to "talking to an interface."

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions