Skip to content

Latest commit

Β 

History

51 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🚁 AeroSwarm

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.

🎬 Live Demo

AeroSwarm real-time SDL3 simulation with live telemetry

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.


✨ Features

Simulation

  • 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

Search Strategy

Drone movement is not purely random.

The current exploration policy:

  1. discovers currently available neighboring cells,
  2. immediately prioritizes the target when it is adjacent,
  3. evaluates candidate cells using information gain,
  4. keeps candidates with the highest information gain,
  5. randomly selects between equally ranked candidates.

This gives the drones a lightweight exploration heuristic while preserving some stochastic behavior.

Concurrency

The parallel implementation demonstrates several C++ synchronization primitives:

  • std::thread
  • std::atomic
  • std::mutex
  • std::shared_mutex
  • std::lock_guard
  • std::shared_lock
  • std::unique_lock

Shared terrain and drone state are protected explicitly, while atomic state is used for lightweight cross-thread signalling.

Live Monitoring

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.


πŸ–₯️ Live Visualization

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.


🧠 Exploration Algorithm

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.


🧭 Eight-Direction Movement

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.


🧡 Concurrency Model

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>

Why shared_mutex for drones?

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_);

πŸ“Έ Thread-Safe Snapshots

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.


⚑ Live Execution Model

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.


🌍 Scenario Generation

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.


🚁 Four-Drone Deployment

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.


πŸ—οΈ Project Architecture

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.


▢️ Build

Requirements

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.

macOS

SDL3 can be installed with Homebrew:

brew install sdl3

Then configure:

cmake -S . -B build

Build:

cmake --build build

πŸš€ Running AeroSwarm

AeroSwarm provides several execution modes.

Sequential

./build/AeroSwarm sequential

Runs the sequential reference implementation.


Parallel

./build/AeroSwarm parallel

Runs the multi-threaded implementation without live visualization.


Parallel Live Console

./build/AeroSwarm parallel-live

Runs the parallel simulation while periodically consuming thread-safe snapshots from a console monitor.

Conceptually:

Parallel simulation
       ↓
SimulationSnapshot
       ↓
Terminal monitor

Parallel SDL

./build/AeroSwarm parallel-sdl

Runs 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.


πŸ§ͺ Testing

Build the project:

cmake -S . -B build
cmake --build build

Run the complete test suite:

ctest --test-dir build --output-on-failure

The 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.

🧡 ThreadSanitizer

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-tsan

Run tests:

ctest --test-dir build-tsan --output-on-failure

ThreadSanitizer complements the unit tests:

Unit tests
    ↓
Is the behavior correct?

ThreadSanitizer
    ↓
Are concurrent memory accesses safe?

Both questions matter.


πŸ” Continuous Integration

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.


🎯 Design Principles

AeroSwarm currently follows several deliberate design principles.

1. Separate simulation from presentation

The simulation does not know whether its state is being displayed in:

  • a terminal,
  • SDL,
  • or potentially another consumer in the future.

2. Prefer explicit synchronization

Shared state should have a clear synchronization owner and strategy.

3. Keep the sequential implementation

The sequential implementation provides a simpler behavioral reference against which the parallel implementation can be reasoned about and tested.

4. Deterministic scenarios, concurrent execution

Seeded scenario generation makes world construction reproducible.

Thread scheduling in the parallel simulation, however, is inherently affected by runtime scheduling.

5. Render snapshots, not mutable internals

Visualization consumes copied state rather than reaching directly into actively mutating worker data.

6. Correctness before optimization

The current implementation favors understandable synchronization and testability over premature fine-grained optimization.


⚠️ Current Limitations

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.


πŸ›£οΈ Possible Future Work

Potential extensions include:

Simulation

  • guaranteed-reachable random maps,
  • configurable drone counts,
  • larger terrains,
  • richer search policies,
  • A* / Dijkstra comparison,
  • frontier-based exploration,
  • configurable movement models.

Concurrency

  • finer-grained terrain synchronization,
  • contention measurement,
  • worker-pool experiments,
  • lock-free telemetry channels,
  • scalability benchmarks.

Visualization

  • sprite-based drones,
  • rock/mountain obstacle textures,
  • target flag texture,
  • trails and exploration heatmaps,
  • drone identifiers,
  • FPS / tick-rate overlays,
  • runtime statistics.

Telemetry

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.


πŸ“Š What AeroSwarm Demonstrates

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.

🧰 Technology

Component Technology
Language C++17
Build system CMake
Concurrency C++ Standard Library
Visualization SDL3
Testing Catch2
Race detection ThreadSanitizer
CI GitHub Actions

πŸ“œ Legacy Implementation

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/

πŸ“Œ Project Status

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.


🚁 AeroSwarm

Build the world. Run the workers. Observe the swarm.

About

A high-performance C++ simulator for autonomous drone swarms with parallel pathfinding.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages