Skip to content

Conversation

@rozgo
Copy link
Contributor

@rozgo rozgo commented Jul 2, 2025

Adds spatial indexing capabilities for grid-based Monte Carlo simulations.

New Features

SpatialGridPlugin

  • HashMap-based spatial indexing for O(1) neighbor lookups
  • Distance queries within Manhattan distance radius
  • Automatic entity tracking with ECS integration
  • Optional grid boundaries with bounds validation

SimulationBuilder Integration

let simulation = SimulationBuilder::new()
    .add_spatial_grid(bounds)
    .add_systems(movement_system)
    .build();

Bevy Math Integration

  • GridPosition wraps IVec2
  • GridBounds wraps IRect with grid cell semantics
  • Deref/DerefMut access to underlying Bevy types

Examples

Forest Fire Simulation

  • Cellular automaton with probabilistic fire spread
  • Moore/Von Neumann neighborhood calculations
  • Fire statistics time series collection

Pandemic Simulation

  • Spatial infection modeling with configurable radius
  • Contact tracing and quarantine systems
  • Social distancing behavior modeling

Implementation Details

  • O(1) spatial queries using HashMap indexing
  • No allocations for neighbor calculations
  • Built on Bevy's types for ecosystem compatibility
  • 10 test cases covering core functionality

Copy link
Owner

@haath haath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great stuff, thanks a lot!

Only one point I'd like to discuss: what do you think about making the dimensionality generic so that it works both for 2D and 3D grids?
It seems most of the code would be the same, and we would get the extra dimension practically for free.

The way I'm thinking it SpatialGrid would be generic over a sealed trait which we would implement only for IVec2 and IVec3.

Then we would provide shorthand types for the user like this:

type SpatialGrid2D = SpatialGrid<IVec2>;
type SpatialGrid3D = SpatialGrid<IVec3>;

Let me know if you'd like to take on this effort yourself.
No worries if you'd rather not spend more time on this, I could also do it in a follow-up change.

@rozgo rozgo marked this pull request as draft July 2, 2025 18:40
@rozgo
Copy link
Contributor Author

rozgo commented Jul 3, 2025

Ok, lots of feedback, appreciate you taking the time to go through this. I think I covered all of them. Was trying this repo out of curiosity, but really liking how you structured this and contained the bevy complexities while making good use of bevys strength.

Let me know if I missed anything. There are many projects I want to do with a system like this, so its in my best interest to help out with anything I can.

@rozgo rozgo marked this pull request as ready for review July 3, 2025 03:35
@rozgo
Copy link
Contributor Author

rozgo commented Jul 3, 2025

Added a multi grid example:

    let mut simulation = SimulationBuilder::new()
        // Create separate spatial grids for each entity type
        .add_spatial_grid::<IVec2, Prey>(bounds)
        .add_spatial_grid::<IVec2, Predator>(bounds)
        .add_spatial_grid::<IVec2, Vegetation>(bounds)
        .add_entity_spawner(spawn_ecosystem)
        .add_systems((
            prey_behavior,
            predator_hunting.after(prey_behavior),
            vegetation_dynamics.after(predator_hunting),
            prey_feeding.after(vegetation_dynamics),
            lifecycle_system.after(prey_feeding),
            display_ecosystem_stats.after(lifecycle_system),
        ))
        .build();

@rozgo
Copy link
Contributor Author

rozgo commented Jul 4, 2025

Tests and examples are updated and clippy fixed. Ecosystem removed.

@rozgo rozgo force-pushed the feature/spatial-grid branch from 5f8d2ac to 8fe1a67 Compare July 4, 2025 21:05
@rozgo
Copy link
Contributor Author

rozgo commented Jul 4, 2025

rebased on main

@rozgo
Copy link
Contributor Author

rozgo commented Jul 4, 2025

Also, do not feel pressured to accept this PR. Already getting a good learning experience out of the process.

Copy link
Owner

@haath haath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some last points and it should be good to go!

src/lib.rs Outdated

mod error;
mod plugins;
pub mod plugins;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't expose the plugins module.
Instead we do pub use only for the types the user might need to reference (GridPosition, SpatialGrid etc).

{
for (entity, position) in &query
{
spatial_grid.remove(entity);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry my bad, I didn't see that insert() calls remove() on its own.
Can remove this line.

/// Returns the position where the entity was located, if it was found.
pub(crate) fn remove(&mut self, entity: Entity) -> Option<GridPosition<T>>
{
if let Some(position) = self.entity_to_position.remove(&entity)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's handle the error case as well.

let position = self.entity_to_position.remove(&entity)?;

let Some(entities_at_position) = self.position_to_entities.get_mut(&position)
else
{
    panic!("entity found in one hashmap but not the other?");
};

entities_at_position.remove(&entity);
if entities_at_position.is_empty()
{
    self.position_to_entities.remove(&position);
}
Some(position)

}

/// Clear all entities from the spatial index.
pub fn clear(&mut self)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be private.
Same for insert and remove actually, I think pub(crate) is not necessary.

{
// only get new samples once every 'sample_interval' steps
if step_counter.is_multiple_of(time_series.sample_interval)
if (**step_counter).is_multiple_of(time_series.sample_interval)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explicit deref with ** is not needed here.

@haath haath merged commit adcad19 into haath:main Jul 7, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants