diff --git a/Cargo.toml b/Cargo.toml index 83dc44d..ca0c338 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,10 @@ authors = ["Adrian Heine "] [dependencies] tui = "0.15" termion = "1.5" + +[dev-dependencies] +criterion = "0.3" + +[[bench]] +name = "benchmark" +harness = false diff --git a/README.md b/README.md index 74a701a..8d3cd2f 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,23 @@ cargo run ``` To exit the simulation, press q, ESC or Ctrl+c. + +## Tests + +A few tests can be run with: + +```sh +cargo test +``` + +## Benchmarks + +`cargo bench` runs benchmarks. Their results are available under `target/criterion/report/index.html`. + +## Simulation + +* The world is a simple two-dimensional rectangle with integer positions +* Agents can occupy the same space +* Agents can make one move per simulation step, either by trying to move by at most one unit in both directions or by trying to tag another actor +* Agents need to be at most one unit away in both directions from others in order to tag them +* Agents act at the same time and their actions are validated against the previous state diff --git a/benches/benchmark.rs b/benches/benchmark.rs new file mode 100644 index 0000000..cc2ed77 --- /dev/null +++ b/benches/benchmark.rs @@ -0,0 +1,48 @@ +use criterion::{BenchmarkId, measurement::Measurement}; +use criterion::{criterion_group, criterion_main, Criterion, Throughput, BenchmarkGroup}; +use gntag::agent::{Agent, SimpleAgent}; +use gntag::world::ActualWorld; + +fn world(mut group: BenchmarkGroup, validate: bool) { + let width: isize = 1000; + for spacing in (50..100).step_by(10) { + group.throughput(Throughput::Elements((width / spacing).pow(2) as u64)); + group.bench_with_input( + BenchmarkId::from_parameter(spacing), + &spacing, + |b, &spacing| { + let mut agents: Vec<(_, Box)> = vec![]; + for x in (0..width).step_by(spacing as usize) { + for y in (0..width).step_by(spacing as usize) { + agents.push(((x, y).into(), Box::new(SimpleAgent))); + } + } + let mut world = ActualWorld::new((width, width).into(), agents, validate); + b.iter(|| world.do_step()); + }, + ); + } + group.finish(); +} + +fn world_validating(c: &mut Criterion) { + let group = c.benchmark_group("world_validating"); + world(group, true) +} + +fn world_nonvalidating(c: &mut Criterion) { + let group = c.benchmark_group("world_nonvalidating"); + world(group, false) +} + +criterion_group!(benches, world_validating, world_nonvalidating); +criterion_main!(benches); + +// 5000/1000 -> 9,993 +// 5/01 -> 23,252 +// 50/10 -> 14,000 +// 100/20 -> 14,000 +// 100/10 -> 96,000 +// 100/05 -> 917,767 +// 200/10 -> 916,172 +// 1000/10 -> 443,482,526 diff --git a/src/main.rs b/src/main.rs index 518c107..8cb1afe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,7 +42,7 @@ fn run_simulation(view: &Arc>>>) { agents.push(((x, y).into(), Box::new(SimpleAgent))); } } - let mut world = ActualWorld::new((width, height).into(), agents); + let mut world = ActualWorld::new((width, height).into(), agents, true); let mut gen = 0; loop { diff --git a/src/world.rs b/src/world.rs index 3122c05..9eb2c49 100644 --- a/src/world.rs +++ b/src/world.rs @@ -5,6 +5,7 @@ pub struct ActualWorld { size: Position, pub agents: HashMap>, pub state: WorldState, + validating: bool, } pub struct WorldState { @@ -16,7 +17,11 @@ pub struct WorldState { impl ActualWorld { /// Agents receive incrementing ids starting with 0 /// The last agent is 'It' - pub fn new(size: Position, input_agents: Vec<(Position, Box)>) -> Self { + pub fn new( + size: Position, + input_agents: Vec<(Position, Box)>, + validating: bool, + ) -> Self { let agent_count = input_agents.len(); assert!(agent_count > 0); let mut id = 0; @@ -36,6 +41,7 @@ impl ActualWorld { size, agents, state, + validating, } } @@ -77,8 +83,9 @@ impl ActualWorld { tagged_by: self.state.tagged_by, }; for (id, mv) in moves { - self.check_move(id, &mv); - //println!("{} {:?}", id, mv); + if self.validating { + self.check_move(id, &mv); + } let mut new_pos = None; match mv { Move::Noop => {} @@ -102,7 +109,10 @@ impl ActualWorld { match mv { Move::Noop => {} Move::TryTag(other_id) => { - // FIXME check distance + let my_pos = self.state.agent_positions.get(&id).unwrap(); + let other_pos = self.state.agent_positions.get(&other_id).unwrap(); + assert!((my_pos.x - other_pos.x).abs() <= 1); + assert!((my_pos.y - other_pos.y).abs() <= 1); assert_eq!(self.state.tagged, id); assert_ne!(self.state.tagged_by, Some(*other_id)); assert!( @@ -111,7 +121,8 @@ impl ActualWorld { ); } Move::TryMove(dir) => { - // FIXME check speed + assert!(dir.x.abs() <= 1); + assert!(dir.y.abs() <= 1); let pos = self.state.agent_positions.get(&id).unwrap(); let size = &self.size; assert!(pos.x + dir.x > 0); @@ -131,7 +142,7 @@ mod test { #[test] #[should_panic] fn empty() { - ActualWorld::new(Position { x: 0, y: 0 }, vec![]); + ActualWorld::new(Position { x: 0, y: 0 }, vec![], true); } #[test] @@ -139,6 +150,7 @@ mod test { let mut world = ActualWorld::new( Position { x: 0, y: 0 }, vec![(Position { x: 0, y: 0 }, Box::new(NullAgent))], + true, ); world.do_step(); assert_eq!(world.state.tagged, 0); @@ -159,6 +171,7 @@ mod test { Box::new(ScriptedAgent::new(vec![Move::TryTag(0)])), ), ], + true, ); world.do_step(); assert_eq!(world.state.tagged, 0); @@ -174,6 +187,7 @@ mod test { Position { x: 0, y: 0 }, Box::new(ScriptedAgent::new(vec![Move::TryMove((-1, -1).into())])), )], + true, ); world.do_step(); }