Concurrent Multi-Drone Search Simulation with Real-Time SDL3 Visualization
AeroSwarm is a modern C++ simulation project for exploring concurrent agents, shared-state synchronization, search heuristics, deterministic scenario generation, and real-time visualization.
Multiple autonomous drones explore a shared 2D environment containing obstacles and a target. The project provides both a sequential reference implementation and a parallel multi-threaded implementation, together with a live console monitor and an SDL3 graphical renderer.
Parallel drone simulation with thread-safe snapshots and live SDL3 telemetry.
The current architecture deliberately separates:
- simulation logic,
- concurrency and synchronization,
- scenario generation,
- application runners,
- telemetry snapshots,
- and visualization.
This makes AeroSwarm useful not only as a drone-search simulation, but also as a compact example of the architecture behind real-time systems where a high-frequency producer generates state that is consumed independently by monitoring or visualization components.
- Sequential reference simulation
- Parallel multi-drone simulation
- Shared 2D terrain
- Obstacles
- Target detection
- Four-drone corner deployment
- Eight-direction movement
- Deterministic seeded scenarios
- Random target generation
- Random obstacle generation
- Global visited-cell tracking
Drone movement is not purely random.
The current exploration policy:
- discovers currently available neighboring cells,
- immediately prioritizes the target when it is adjacent,
- evaluates candidate cells using information gain,
- keeps candidates with the highest information gain,
- randomly selects between equally ranked candidates.
This gives the drones a lightweight exploration heuristic while preserving some stochastic behavior.
The parallel implementation demonstrates several C++ synchronization primitives:
std::threadstd::atomicstd::mutexstd::shared_mutexstd::lock_guardstd::shared_lockstd::unique_lock
Shared terrain and drone state are protected explicitly, while atomic state is used for lightweight cross-thread signalling.
AeroSwarm currently supports two live consumers:
- terminal-based live monitoring,
- SDL3 graphical visualization.
The simulation and renderer intentionally run at different frequencies:
Simulation workers β 100 Hz
Renderer β 60 FPS
The renderer does not directly inspect mutable worker state.
Instead, it consumes a thread-safe SimulationSnapshot.
The SDL3 mode displays the simulation while the parallel workers are running.
The current visualization includes:
- terrain grid,
- drone positions,
- visited cells,
- obstacles,
- target location,
- live movement,
- final simulation state.
The visualization layer is intentionally independent from the simulation engine.
βββββββββββββββββββββββββββββββββββββββββββββ
β SDL3 Renderer β
β ~60 FPS β
βββββββββββββββββββββββ²ββββββββββββββββββββββ
β
β SimulationSnapshot
β
βββββββββββββββββββββββ΄ββββββββββββββββββββββ
β Parallel Simulation β
β β
β Drone 1 Drone 2 Drone 3 Drone 4 β
β β β β β β
β βββββββββββ΄βββββ¬βββββ΄ββββββββββ β
β β β
β Shared Terrain β
β β β
β synchronized access β
βββββββββββββββββββββββββββββββββββββββββββββ
A simulation update therefore does not imply a rendered frame.
For example:
time (ms) 0 10 20 30 40 50 60
simulation S S S S S S S
βββββββββββββ ~100 Hz βββββββββββββββ
renderer R R R R
βββββββββββββ ~60 FPS ββββββββββββββ
Some intermediate simulation states may never be rendered.
That is intentional.
The renderer only needs the latest consistent state when producing the next frame.
AeroSwarm uses a simple information-gain heuristic to reduce the probability that a drone immediately explores itself into a dead end.
For a candidate position:
information_gain(position)
=
number of currently available neighboring cells
A simplified decision flow is:
Current Position
β
βΌ
Find available neighbors
β
βΌ
Is target immediately
reachable?
/ \
yes no
β β
βΌ βΌ
choose target calculate
information gain
β
βΌ
highest-gain cells
β
βΌ
random tie-break
β
βΌ
claim cell
β
βββββββββββ΄ββββββββββ
β β
success failed
β another drone
βΌ claimed it
move drone β
βΌ
retry
The final try_claim_cell() operation remains authoritative.
This matters in the parallel implementation because another worker may modify the terrain between:
candidate discovery
β
candidate scoring
β
cell claiming
A candidate that looked available a moment earlier may therefore no longer be available.
Drones can currently move in eight directions:
β β β
\ | /
β β D β β
/ | \
β β β
Conceptually:
{
{ 1, 0},
{-1, 0},
{ 0, 1},
{ 0, -1},
{ 1, 1},
{ 1, -1},
{-1, 1},
{-1, -1}
}For a center cell in an unobstructed grid, this provides up to eight candidate neighbors.
Boundary cells, obstacles, and already claimed cells reduce that number.
The parallel implementation is built around multiple drone workers operating on shared simulation state.
ParallelSimulation
β
ββββββββββββββββΌβββββββββββββββ
β β β
βΌ βΌ βΌ
Worker 1 Worker 2 Worker N
β β β
ββββββββββββββββΌβββββββββββββββ
β
βΌ
ParallelTerrain
β
synchronized
access
Different pieces of state have different synchronization requirements.
| State | Synchronization |
|---|---|
| Terrain / cell claiming | Mutex-protected |
| Drone positions | std::shared_mutex |
| Target-found flag | std::atomic<bool> |
| Simulation tick | std::atomic<std::size_t> |
| Winning drone | Mutex-protected |
| Live completion flag | std::atomic<bool> |
Snapshots frequently read drone positions while simulation workers occasionally write them.
That allows multiple readers:
snapshot reader βββ
snapshot reader βββΌββ shared access
snapshot reader βββ
but a movement update requires exclusive access:
writer
β
βΌ
βββββββββββββββββββ
β exclusive accessβ
βββββββββββββββββββ
In C++:
std::shared_lock<std::shared_mutex> read_lock(drones_mutex_);versus:
std::unique_lock<std::shared_mutex> write_lock(drones_mutex_);The visualization layer does not directly access mutable simulation internals.
Instead:
ParallelSimulation
β
β snapshot()
βΌ
SimulationSnapshot
β
ββββββΊ Console monitor
β
ββββββΊ SDL3 renderer
A snapshot contains a consistent view of the information required by a consumer, such as:
tick
drone positions
visited cells
obstacles
target
target-found state
winning drone
This provides a clean boundary between:
simulation / producer
and:
visualization / consumer
That separation is one of the central architectural ideas in AeroSwarm.
In live mode, the parallel simulation runs on a background thread while the main thread handles monitoring or SDL rendering.
MAIN / RENDER THREAD SIMULATION THREAD
create simulation
β
ββββββββββββββββββββββββββββββββΊ simulation.run()
β β
βΌ βΌ
process SDL events drone workers
β β
snapshot() update terrain
β β
render update drones
β β
sleep ~16 ms sleep ~10 ms
β β
snapshot() ...
β β
render β
β simulation finishes
β β
β simulation_finished = true
β β
βΌ βΌ
render final state
β
β
user closes window
β
βΌ
join simulation thread
β
βΌ
exit
The simulation therefore remains independent of rendering performance.
A slow frame does not redefine the simulation's update model.
AeroSwarm supports deterministic random scenario generation.
A scenario contains:
grid dimensions
target
obstacles
drone starting positions
random seed
The current random scenario factory:
- creates four drones,
- places them at the four corners,
- generates a target,
- generates unique obstacles,
- prevents obstacles from occupying drone starting cells,
- prevents obstacles from occupying the target,
- uses a deterministic seed.
For example:
auto scenario = make_random_scenario(
30,
30,
80,
42
);Using the same seed produces the same generated scenario:
seed 42
β
same target
same obstacles
same initial configuration
This is particularly useful for debugging and comparing implementations.
The default generated scenario deploys drones from all four corners:
Drone 1 Drone 2
β β
D . . . . . . . . . . . . . . . . . . D
. .
. obstacles .
. .
. π© .
. .
. .
D . . . . . . . . . . . . . . . . . . . D
β β
Drone 3 Drone 4
All workers operate against the same shared terrain.
A successful cell claim prevents another drone from subsequently claiming the same cell.
The active codebase is organized around clear responsibilities:
AeroSwarm/
β
βββ include/aeroswarm/
β β
β βββ app/
β β βββ scenario.hpp
β β βββ scenario_factory.hpp
β β βββ scenario_validation.hpp
β β βββ sequential_runner.hpp
β β βββ parallel_runner.hpp
β β βββ parallel_live_runner.hpp
β β βββ parallel_sdl_runner.hpp
β β
β βββ live/
β β βββ simulation_snapshot.hpp
β β βββ sdl_renderer.hpp
β β
β βββ sequential/
β β βββ terrain.hpp
β β βββ simulation.hpp
β β
β βββ parallel/
β β βββ terrain.hpp
β β βββ simulation.hpp
β β
β βββ drone.hpp
β βββ types.hpp
β
βββ src/
β βββ main.cpp
β βββ sequential_simulation.cpp
β βββ parallel_simulation.cpp
β βββ sequential_runner.cpp
β βββ parallel_runner.cpp
β βββ parallel_live_runner.cpp
β βββ parallel_sdl_runner.cpp
β βββ scenario_factory.cpp
β βββ scenario_validation.cpp
β βββ sdl_renderer.cpp
β
βββ tests/
β βββ ...
β
βββ legacy/
β βββ ...
β
βββ CMakeLists.txt
βββ README.md
legacy/contains earlier experimental implementations retained for historical/reference purposes. It is not part of the active AeroSwarm architecture.
AeroSwarm currently requires:
- C++17-compatible compiler
- CMake
- SDL3
- Git
Catch2 is used for testing.
SDL3 can be discovered from the host system or obtained by the CMake configuration when required.
SDL3 can be installed with Homebrew:
brew install sdl3Then configure:
cmake -S . -B buildBuild:
cmake --build buildAeroSwarm provides several execution modes.
./build/AeroSwarm sequentialRuns the sequential reference implementation.
./build/AeroSwarm parallelRuns the multi-threaded implementation without live visualization.
./build/AeroSwarm parallel-liveRuns the parallel simulation while periodically consuming thread-safe snapshots from a console monitor.
Conceptually:
Parallel simulation
β
SimulationSnapshot
β
Terminal monitor
./build/AeroSwarm parallel-sdlRuns the parallel simulation with real-time SDL3 visualization.
Parallel simulation (~100 Hz)
β
thread-safe snapshot
β
SDL3 renderer (~60 FPS)
The final simulation state remains visible until the SDL window is closed.
Build the project:
cmake -S . -B build
cmake --build buildRun the complete test suite:
ctest --test-dir build --output-on-failureThe test suite covers areas including:
- terrain behavior,
- obstacle handling,
- target handling,
- cell claiming,
- concurrent cell claiming,
- shared drone starting positions,
- sequential simulation behavior,
- parallel simulation behavior,
- scenario validation,
- deterministic scenario generation,
- sequential/parallel comparison,
- information gain,
- eight-direction neighborhood behavior.
Concurrency correctness is important to this project.
A separate ThreadSanitizer build can be used to detect potential data races.
A typical configuration is:
cmake -S . -B build-tsan \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_FLAGS="-fsanitize=thread -g" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=thread"Build:
cmake --build build-tsanRun tests:
ctest --test-dir build-tsan --output-on-failureThreadSanitizer complements the unit tests:
Unit tests
β
Is the behavior correct?
ThreadSanitizer
β
Are concurrent memory accesses safe?
Both questions matter.
The repository uses CI to build and test changes targeting main.
The CI pipeline performs:
checkout
β
install build dependencies
β
configure CMake
β
build
β
run tests
SDL3 Linux development dependencies are installed in the CI environment so that the graphical components can be compiled even though the CI runner itself does not launch the interactive SDL monitor.
AeroSwarm currently follows several deliberate design principles.
The simulation does not know whether its state is being displayed in:
- a terminal,
- SDL,
- or potentially another consumer in the future.
Shared state should have a clear synchronization owner and strategy.
The sequential implementation provides a simpler behavioral reference against which the parallel implementation can be reasoned about and tested.
Seeded scenario generation makes world construction reproducible.
Thread scheduling in the parallel simulation, however, is inherently affected by runtime scheduling.
Visualization consumes copied state rather than reaching directly into actively mutating worker data.
The current implementation favors understandable synchronization and testability over premature fine-grained optimization.
AeroSwarm is an evolving simulation project and intentionally does not claim production-scale swarm autonomy.
Current limitations include:
- information gain is a heuristic, not globally optimal pathfinding,
- cell claiming is intentionally conservative,
- parallel execution can vary because of thread scheduling,
- the current terrain synchronization strategy is relatively coarse-grained,
- the SDL renderer is intentionally lightweight and 2D,
- generated obstacle layouts are not yet guaranteed to produce a reachable target,
- no formal large-scale performance or scalability claims are currently made,
- simulation physics are abstract rather than real drone dynamics.
These constraints are useful because they define concrete directions for future engineering work.
Potential extensions include:
- guaranteed-reachable random maps,
- configurable drone counts,
- larger terrains,
- richer search policies,
- A* / Dijkstra comparison,
- frontier-based exploration,
- configurable movement models.
- finer-grained terrain synchronization,
- contention measurement,
- worker-pool experiments,
- lock-free telemetry channels,
- scalability benchmarks.
- sprite-based drones,
- rock/mountain obstacle textures,
- target flag texture,
- trails and exploration heatmaps,
- drone identifiers,
- FPS / tick-rate overlays,
- runtime statistics.
A particularly natural extension is to decouple monitoring further:
Simulation
β
Telemetry producer
β
transport / queue
β
consumer
β
visualization / analytics
This would allow the renderer to become only one of several possible consumers.
Although the simulated domain is autonomous drones, the underlying engineering concepts are more general.
AeroSwarm demonstrates:
multi-threaded producers
β
shared synchronized state
β
safe snapshots
β
independent consumer
β
real-time visualization
The same architectural ideas appear in systems such as:
- live telemetry,
- monitoring dashboards,
- multiplayer simulations,
- robotics,
- sensor processing,
- real-time analytics,
- live event processing.
| Component | Technology |
|---|---|
| Language | C++17 |
| Build system | CMake |
| Concurrency | C++ Standard Library |
| Visualization | SDL3 |
| Testing | Catch2 |
| Race detection | ThreadSanitizer |
| CI | GitHub Actions |
Earlier AeroSwarm experiments are retained under:
legacy/
They contain previous implementations and algorithm experiments that helped inform the current architecture.
They are preserved as reference material but are not part of the active application build.
The active implementation lives under:
include/aeroswarm/
src/
tests/
AeroSwarm currently provides a functioning end-to-end path:
Random Scenario
β
Scenario Validation
β
Parallel Multi-Drone Simulation
β
Concurrent Exploration
β
Thread-Safe State
β
SimulationSnapshot
β
Real-Time SDL3 Visualization
The current focus is on clean architecture, concurrency correctness, reproducibility, and observable real-time execution rather than artificial scalability claims.
Build the world. Run the workers. Observe the swarm.
