Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,31 @@ FROM ghcr.io/bounverif/esmini:latest-devel
# Create non-root user (base image is AlmaLinux 8 without an 'ubuntu' user)
RUN (getent group 1000 || groupadd -g 1000 ubuntu) \
&& (getent passwd 1000 || useradd -m -s /bin/bash -u 1000 -g 1000 ubuntu)

# Install vcpkg prerequisites and dev libraries for full build
# - zip: required by vcpkg
# - lz4-devel, libzstd-devel: required by asam-osi-utilities (Phase A)
# - libX11-devel, libXrandr-devel, libXinerama-devel, mesa-libGL-devel,
# fontconfig-devel: required for OSG/ImPlot linking (Phase B)
RUN dnf install -y -q \
zip \
lz4-devel libzstd-devel \
libX11-devel libXrandr-devel libXinerama-devel \
mesa-libGL-devel fontconfig-devel \
&& dnf clean all

# Bootstrap vcpkg for OSI two-phase build
RUN git clone --depth 1 https://github.com/microsoft/vcpkg.git /opt/vcpkg \
&& /opt/vcpkg/bootstrap-vcpkg.sh -disableMetrics \
&& ln -s /opt/vcpkg/vcpkg /usr/local/bin/vcpkg

ENV VCPKG_ROOT=/opt/vcpkg

# Note: The base image ships protobuf 3.21.12 at /opt/bazalt/.
# Use the "base" preset (not "vcpkg") for Phase A inside this container
# to avoid version conflicts with vcpkg's newer protobuf.
# Example:
# cmake -S externals/asam-osi-utilities -B build-deps --preset base \
# -DCMAKE_PREFIX_PATH=/opt/bazalt \
# -DProtobuf_PROTOC_EXECUTABLE=/opt/bazalt/bin/protoc \
# -DCMAKE_CXX_FLAGS="-fpermissive"
5 changes: 5 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
"build": {
"dockerfile": "Dockerfile"
},
"containerEnv": {
"HTTP_PROXY": "${localEnv:HTTP_PROXY}",
"HTTPS_PROXY": "${localEnv:HTTPS_PROXY}",
"NO_PROXY": "${localEnv:NO_PROXY}"
},
"customizations": {
"vscode": {
"extensions": [
Expand Down
144 changes: 144 additions & 0 deletions .github/actions/setup_osi_utilities/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
name: Setup OSI Utilities
description: >
Two-phase build of asam-osi-utilities via vcpkg.
Phase A: configure, build, and install asam-osi-utilities into a prefix directory.
Sets output variables for Phase B (esmini configure).

inputs:
triplet:
description: 'vcpkg triplet (e.g. x64-linux, x64-windows-static-md, universal-osx)'
required: true
configuration:
description: 'Build configuration for Phase A'
required: false
default: 'Release'
vcpkg-commit:
description: 'vcpkg git commit to pin. If empty, derived from externals/asam-osi-utilities/vcpkg-configuration.json baseline'
required: false
default: ''
overlay-triplets:
description: 'Path to overlay triplets directory (for custom triplets like universal-osx)'
required: false
default: ''
cmake-generator:
description: 'CMake generator args (e.g. -G "Visual Studio 17 2022" -T v142 -A x64)'
required: false
default: ''

outputs:
osi-prefix:
description: 'Install prefix for OSI_UTILITIES_PREFIX'
value: ${{ steps.paths.outputs.osi-prefix }}
cmake-prefix-path:
description: 'CMAKE_PREFIX_PATH for Phase B'
value: ${{ steps.paths.outputs.cmake-prefix-path }}
protoc-executable:
description: 'Path to protoc executable'
value: ${{ steps.paths.outputs.protoc-executable }}

runs:
using: 'composite'
steps:
- name: Restore Phase A cache
id: cache-osi
uses: actions/cache/restore@v4
with:
path: |
${{ github.workspace }}/osi-deps
${{ github.workspace }}/build-osi-deps/vcpkg_installed
key: osi-utilities-${{ runner.os }}-${{ inputs.triplet }}-${{ inputs.configuration }}-${{ hashFiles('externals/asam-osi-utilities/**', '!externals/asam-osi-utilities/build*', 'support/vcpkg-triplets/**') }}
restore-keys: |
osi-utilities-${{ runner.os }}-${{ inputs.triplet }}-${{ inputs.configuration }}-
osi-utilities-${{ runner.os }}-${{ inputs.triplet }}-

- name: Derive vcpkg baseline from submodule
id: vcpkg-baseline
if: steps.cache-osi.outputs.cache-hit != 'true'
shell: bash
run: |
BASELINE=$(jq -r '.["default-registry"]["baseline"]' externals/asam-osi-utilities/vcpkg-configuration.json)
echo "commit=${BASELINE}" >> "$GITHUB_OUTPUT"
echo "Using vcpkg baseline: $BASELINE"

- name: Export GitHub Actions cache environment
if: steps.cache-osi.outputs.cache-hit != 'true'
uses: actions/github-script@v7
with:
script: |
core.exportVariable('ACTIONS_CACHE_URL', process.env['ACTIONS_CACHE_URL'] || '');
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN'] || '');
core.exportVariable('VCPKG_BINARY_SOURCES', 'clear;x-gha,readwrite');

- name: Setup vcpkg
if: steps.cache-osi.outputs.cache-hit != 'true'
uses: lukka/run-vcpkg@v11
with:
vcpkgDirectory: ${{ runner.temp }}/vcpkg
vcpkgGitCommitId: ${{ inputs.vcpkg-commit || steps.vcpkg-baseline.outputs.commit }}

- name: Phase A - Configure
if: steps.cache-osi.outputs.cache-hit != 'true'
shell: bash
run: |
VCPKG_TC="${{ runner.temp }}/vcpkg/scripts/buildsystems/vcpkg.cmake"
VCPKG_TC="${VCPKG_TC//\\//}"
OVERLAY_ARG=""
if [ -n "${{ inputs.overlay-triplets }}" ]; then
OVERLAY_ARG="-DVCPKG_OVERLAY_TRIPLETS=${{ inputs.overlay-triplets }}"
fi
OSX_ARCH_ARGS=()
if [ "$RUNNER_OS" = "macOS" ]; then
OSX_ARCH_ARGS=("-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64")
fi
cmake \
-S externals/asam-osi-utilities \
-B build-osi-deps \
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_TC" \
-DVCPKG_TARGET_TRIPLET=${{ inputs.triplet }} \
$OVERLAY_ARG \
"${OSX_ARCH_ARGS[@]}" \
${{ inputs.cmake-generator }} \
-DCMAKE_BUILD_TYPE=${{ inputs.configuration }} \
-DBUILD_TESTING=OFF \
-DBUILD_EXAMPLES=OFF

- name: Phase A - Build
if: steps.cache-osi.outputs.cache-hit != 'true'
shell: bash
run: cmake --build build-osi-deps --config ${{ inputs.configuration }} -j 2

- name: Phase A - Install
if: steps.cache-osi.outputs.cache-hit != 'true'
shell: bash
run: cmake --install build-osi-deps --config ${{ inputs.configuration }} --prefix osi-deps

- name: Save Phase A cache
if: steps.cache-osi.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
${{ github.workspace }}/osi-deps
${{ github.workspace }}/build-osi-deps/vcpkg_installed
key: osi-utilities-${{ runner.os }}-${{ inputs.triplet }}-${{ inputs.configuration }}-${{ hashFiles('externals/asam-osi-utilities/**', '!externals/asam-osi-utilities/build*', 'support/vcpkg-triplets/**') }}

- name: Set output paths
id: paths
shell: bash
run: |
PROTOC_EXT=""
if [ "$RUNNER_OS" = "Windows" ]; then
PROTOC_EXT=".exe"
fi
OSI_PREFIX="${{ github.workspace }}/osi-deps"
CMAKE_PFX="${{ github.workspace }}/osi-deps;${{ github.workspace }}/build-osi-deps/vcpkg_installed/${{ inputs.triplet }}"
PROTOC="${{ github.workspace }}/build-osi-deps/vcpkg_installed/${{ inputs.triplet }}/tools/protobuf/protoc${PROTOC_EXT}"

# Step outputs (for workflows that reference steps.osi.outputs.*)
echo "osi-prefix=${OSI_PREFIX}" >> "$GITHUB_OUTPUT"
echo "cmake-prefix-path=${CMAKE_PFX}" >> "$GITHUB_OUTPUT"
echo "protoc-executable=${PROTOC}" >> "$GITHUB_OUTPUT"

# Environment variables (for CMake presets using $env{})
echo "ESMINI_OSI_PREFIX=${OSI_PREFIX}" >> "$GITHUB_ENV"
echo "ESMINI_CMAKE_PREFIX_PATH=${CMAKE_PFX}" >> "$GITHUB_ENV"
echo "ESMINI_PROTOC=${PROTOC}" >> "$GITHUB_ENV"
7 changes: 7 additions & 0 deletions .github/actions/setup_tools_shared/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ runs:
- run: pip install -r support/python/requirements.txt
shell: bash

- name: Install OSI Python bindings (osi3) and utilities
if: hashFiles('externals/asam-osi-utilities/submodules/osi-python/pyproject.toml') != ''
shell: bash
run: |
pip install externals/asam-osi-utilities/submodules/osi-python
pip install externals/asam-osi-utilities/python

- name: Install OSG dependencies - Ubuntu
if: runner.os == 'Linux'
shell: bash
Expand Down
134 changes: 134 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Copilot Instructions for esmini

esmini is an OpenSCENARIO XML player (v1.0–v1.3) with OpenDRIVE road network support, OSI ground truth output, and optional 3D visualization. Licensed MPL 2.0.

## Build

```bash
# Full build (downloads externals automatically)
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release -j 4

# Minimal build (no OSG viewer, no SUMO, no OSI)
cmake -B build -DUSE_OSG=OFF -DUSE_SUMO=OFF -DUSE_OSI=OFF
cmake --build build -j 4

# Install to bin/ directory
cmake --build build --config Release --target install
```

Key CMake options: `USE_OSG`, `USE_OSI`, `USE_SUMO`, `USE_GTEST`, `USE_IMPLOT`, `BUILD_EXAMPLES`, `BUILD_REPLAYER`, `BUILD_ODRPLOT`, `ENABLE_SANITIZERS`, `ENABLE_COVERAGE`, `ENABLE_WARNINGS_AS_ERRORS`.

## Test

```bash
# All tests (unit + smoke)
./scripts/run_tests.sh

# Unit tests only (Google Test)
./build/EnvironmentSimulator/Unittest/esmini_test

# Single Google Test by filter
./build/EnvironmentSimulator/Unittest/esmini_test --gtest_filter="TestSuiteName.TestName"

# Smoke tests (Python, black-box)
pytest test/smoke_test.py

# Single smoke test
pytest test/smoke_test.py -k "test_name"

# ALKS / NCAP regression suites
python test/alks_suite.py
python test/ncap_suite.py

# Memory leak tests (Linux, requires valgrind)
./scripts/run_memory_leak_tests.sh
```

Smoke tests launch esmini as a subprocess, capture log/CSV/dat output, and assert on values. They need a built `bin/esmini` from `--target install`.

## Lint & Format

```bash
# Pre-commit runs all checks (clang-format, cmake-format, black, cppcheck)
pre-commit run --all-files

# Individual tools
clang-format -i <file> # C/C++ (Google-based, Allman braces, 150 col)
cmake-format -i <file> # CMake files (150 col)
black <file> # Python
cppcheck --enable=all <file> # Static analysis
```

All PRs must pass formatting checks. Install hooks: `pre-commit install`.

## Architecture

```
EnvironmentSimulator/
├── Modules/ # Core internal modules (dependency order):
│ ├── CommonMini/ # Utilities, logging, UDP, config parsing
│ ├── RoadManager/ # OpenDRIVE parsing, lane queries, routing
│ ├── Controllers/ # 15+ vehicle controllers (ACC, ALKS, interactive, SUMO, etc.)
│ ├── ScenarioEngine/# OpenSCENARIO parsing + execution, OSI reporter
│ ├── PlayerBase/ # Simulation loop, server, plotting
│ └── ViewerBase/ # 3D visualization (requires OSG)
├── Libraries/ # Public APIs:
│ ├── esminiLib/ # Full scenario engine API (links all modules)
│ ├── esminiRMLib/ # Standalone road network API (RoadManager only)
│ └── esminiJS/ # WebAssembly bindings
├── Applications/ # esmini, esmini-dyn, replayer, odrviewer, odrplot
├── Unittest/ # Google Test unit tests
└── code-examples/ # 17 API usage examples
```

**Module dependency chain:** CommonMini → RoadManager → Controllers → ScenarioEngine → PlayerBase → ViewerBase → esminiLib

**OSMP_FMU/** wraps esmini as an FMI 2.0 Functional Mock-up Unit for co-simulation, accepting OSI TrafficUpdate input and producing OSI SensorView output.

## Conventions

### Branching
- **`master`** — stable releases (tagged `v*.*.*`)
- **`dev`** — integration branch; **PRs target `dev`**, not master
- **`feature/**`** — feature branches

### C++ Style
- Allman braces (opening brace on new line)
- 4-space indentation, 150-character line limit
- Naming: `CamelCase` for classes/functions, `lower_case` for parameters, `lower_case_` with trailing underscore for private/protected members
- IWYU (include-what-you-use) is enforced by default

### ScenarioEngine Internals
- `ScenarioEngine/OSCTypeDefs/` — OpenSCENARIO XML type definitions (conditions, actions, positions)
- `ScenarioEngine/SourceFiles/` — Core execution (Storyboard hierarchy: Story → Act → ManeuverGroup → Maneuver → Event)
- `OSIReporter` handles all OSI ground truth generation
- Controllers are registered via a factory pattern in `Controllers/`

### Test Scenarios
- Road networks: `resources/xodr/` (18 OpenDRIVE maps)
- Scenarios: `resources/xosc/` (80+ OpenSCENARIO files)
- Test-specific scenarios: `EnvironmentSimulator/Unittest/xosc/` and `xodr/`
- Scenario run scripts: `run/esmini/*.bat`

### Python Dependencies
Install from `support/python/requirements.txt`: `pip install -r support/python/requirements.txt`

### External Dependencies
Auto-downloaded (CMake `DOWNLOAD_EXTERNALS=ON` by default): OSG, SUMO, ImPlot, GTest, vehicle/road models.

**OSI** is provided via [asam-osi-utilities](https://github.com/lichtblick-suite/asam-osi-utilities) as a git submodule in `externals/asam-osi-utilities` (recursive). The build uses a two-phase approach:
- **Phase A**: Build asam-osi-utilities with vcpkg (provides protobuf, OSI, mcap, lz4, zstd) into an install prefix
- **Phase B**: Configure esmini with `-DOSI_UTILITIES_PREFIX=<prefix> -DCMAKE_PREFIX_PATH=<prefix>;<vcpkg_installed> -DProtobuf_PROTOC_EXECUTABLE=<protoc>`

In CI, Phase A is handled by the reusable composite action `.github/actions/setup_osi_utilities/action.yml` with caching. Phase A always builds Release regardless of esmini's build config.

Bundled lightweight deps live in `externals/` (pugixml, fmt, fmi2, dirent, expr, yaml).

### Python OSI Dependencies
The `osi3` Python protobuf bindings are provided by `osi-python` (submodule at `externals/asam-osi-utilities/submodules/osi-python`). In CI, both `osi-python` and `asam-osi-utilities` Python packages are pip-installed from the submodule tree in `.github/actions/setup_tools_shared/action.yml`.

### Git Commits
- Use [Conventional Commits](https://www.conventionalcommits.org/) format
- Sign off commits with `-s` (DCO)
- Do **not** add `Co-authored-by` trailers for AI assistants
Loading
Loading