Skip to content

Commit

Permalink
Merge pull request #156 from twof/Documentation
Browse files Browse the repository at this point in the history
Commenting the main flows
  • Loading branch information
rlane authored Dec 22, 2024
2 parents c616909 + e328275 commit d0900f8
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 21 deletions.
20 changes: 20 additions & 0 deletions frontend/app/src/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,10 +349,14 @@ impl Component for Game {
if matches!(self.overlay, Some(Overlay::Compiling)) {
self.overlay = None;
}

// TODO: Smarter cache eviction policy
if self.compilation_cache.len() > 10 {
self.compilation_cache.clear();
}
let mut teams_with_errors = vec![];

// Display compiler errors or cache compilation results
for (team, result) in results.iter().enumerate() {
match result {
Ok(code) => {
Expand All @@ -375,13 +379,15 @@ impl Component for Game {
.cloned()
.collect();
if errors.is_empty() {
// If no errors, start running simulation
services::send_telemetry(Telemetry::StartScenario {
scenario_name: context.props().scenario.clone(),
code: code_to_string(&self.player_team().running_source_code),
});
self.run(context, execution_mode);
self.focus_simulation();
} else {
// Populate compiler output with compilation errors and focus compiler output tab
self.compiler_errors = Some(errors.join("\n"));
self.focus_editor(teams_with_errors[0]);
js::golden_layout::select_tab("compiler_output");
Expand Down Expand Up @@ -1050,11 +1056,13 @@ impl Game {
.link()
.callback(move |results| Msg::CompileFinished(results, execution_mode));

/// Sends code to the compiler service, returns a compiled WASM binary
async fn compile(text: String) -> Result<Code, String> {
if text.trim().is_empty() {
return Ok(Code::None);
}

// Compilation time will be logged
let start_time = instant::Instant::now();

let url = format!("{}/compile", services::compiler_url());
Expand Down Expand Up @@ -1091,10 +1099,12 @@ impl Game {
} else if team.running_source_code == team.initial_source_code
&& team.initial_compiled_code != Code::None
{
// Avoid recompilation if using the initial source code
team.initial_compiled_code.clone()
} else if let Some(compiled_code) =
self.compilation_cache.get(&team.running_source_code)
{
// Avoid recompilation if current code has already been compilde and cached
compiled_code.clone()
} else {
team.running_source_code.clone()
Expand All @@ -1104,6 +1114,8 @@ impl Game {

wasm_bindgen_futures::spawn_local(async move {
let mut results = vec![];

// All uncompiled code is compiled, and the callback defined above is called on completion
for source_code in source_codes {
let result = match source_code {
Code::Rust(text) => compile(text).await,
Expand All @@ -1116,15 +1128,21 @@ impl Game {
});
}

/// Kicks off simulation using running compiled code from each team
/// NOTE: Assumes no compiler errors
pub fn run(&mut self, context: &Context<Self>, execution_mode: ExecutionMode) {
self.compiler_errors = None;

// Collect compiled code from each team
let codes: Vec<_> = self
.teams
.iter()
.map(|x| x.running_compiled_code.clone())
.collect();
let rand_seed = rand::thread_rng().gen();

// If replaying, reuse previous seed if it exists
// instead of using a newly generated seed
let seed = match execution_mode {
ExecutionMode::Initial | ExecutionMode::Run => {
self.configured_seed(context).unwrap_or(rand_seed)
Expand All @@ -1134,6 +1152,8 @@ impl Game {
.unwrap_or(self.previous_seed.unwrap_or(rand_seed)),
};
let start_paused = matches!(execution_mode, ExecutionMode::Replay { paused: true });

// Cache seed for replays
self.previous_seed = Some(seed);
self.execution_mode = execution_mode;

Expand Down
1 change: 1 addition & 0 deletions frontend/simulation_worker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ impl yew_agent::Worker for SimAgent {
nonce,
} => {
self.sim = Some(Simulation::new(&scenario_name, seed, &codes));
// Snapshot of the starting state of the simulation
let snapshot = self.sim().snapshot(nonce);
self.errored = !snapshot.errors.is_empty();
self.link.respond(who, Response::Snapshot { snapshot });
Expand Down
3 changes: 3 additions & 0 deletions shared/compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ impl Compiler {
tmp_path.join("ai/src/lib.rs"),
include_bytes!("../../ai/src/lib.rs"),
)?;
// This is the file that exposes the global `tick` function referened in `shared/simulator/src/vm/mod.rs`
std::fs::write(
tmp_path.join("ai/src/tick.rs"),
include_bytes!("../../ai/src/tick.rs"),
Expand All @@ -103,6 +104,8 @@ impl Compiler {

let disallowed_environment_variables = ["RUSTC_WORKSPACE_WRAPPER", "RUSTC_WRAPPER"];

// TODO: If `cargo` crate was imported, we could call this directly instead of going through
// the command line
match std::process::Command::new("cargo")
.args([
"build",
Expand Down
38 changes: 29 additions & 9 deletions shared/simulator/src/bullet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,23 +85,30 @@ pub fn destroy(sim: &mut Simulation, handle: BulletHandle) {
);
}

/// Process movement and collisions for each bullet in the simulation
///
/// NOTE: As a performance optimization, colliders are only added to
/// bullets after a collision has been detected since most bullets
/// spend most of their lifetime far away from any ships they could
/// hit.
pub fn tick(sim: &mut Simulation) {
let dt = PHYSICS_TICK_LENGTH;
// Indices for ships
let (indices_by_team, coarse_grids_by_team) = build_indices(sim, dt);
let mut stack = Vec::new();
let shape = rapier2d_f64::geometry::Ball { radius: 1.0 };
let bullets: Vec<BulletHandle> = sim.bullets.iter().cloned().collect();
for handle in bullets {
let team = {
let data = data_mut(sim, handle);
data.ttl -= dt as f32;
if data.ttl <= 0.0 {
destroy(sim, handle);
continue;
}
data.team
};
let data = data_mut(sim, handle);

// If a bullet's lifetime has been exceeded, destroy it and move on to the next bullet
data.ttl -= dt as f32;
if data.ttl <= 0.0 {
destroy(sim, handle);
continue;
}

let team = data.team;
let has_collider;
let coarse_grid_hit;
let mut needs_collider = false;
Expand All @@ -110,6 +117,7 @@ pub fn tick(sim: &mut Simulation) {
let body = sim.bodies.get_mut(RigidBodyHandle(handle.index())).unwrap();
has_collider = !body.colliders().is_empty();

// If bullet is outside world, destroy it
let position = *body.translation();
if position.x < -world_size / 2.0
|| position.x > world_size / 2.0
Expand All @@ -120,6 +128,7 @@ pub fn tick(sim: &mut Simulation) {
continue;
}

// If bullet maybe hits a ship belonging to another team
coarse_grid_hit = coarse_grids_by_team
.iter()
.any(|(other_team, grid)| *other_team != team && grid.lookup(position));
Expand All @@ -129,8 +138,10 @@ pub fn tick(sim: &mut Simulation) {
&body.predict_position_using_velocity_and_forces(dt),
);

// Check index for hit
for (other_team, index) in indices_by_team.iter() {
if team != *other_team {
// The bullet needs a collider if there was a hit
needs_collider = needs_collider
|| index
.query_iter_with_stack(
Expand All @@ -142,6 +153,8 @@ pub fn tick(sim: &mut Simulation) {
)
.next()
.is_some();
// TODO: Could we break here if the index query returns something
// rather than continuing to check if a bullet has hit multiple teams?
}
}
}
Expand All @@ -153,6 +166,7 @@ pub fn tick(sim: &mut Simulation) {
remove_collider(sim, handle);
}

// Debugging helper
if COLOR_COLLIDERS {
if needs_collider {
data_mut(sim, handle).color = 0x00ff00ff;
Expand All @@ -165,6 +179,9 @@ pub fn tick(sim: &mut Simulation) {
}
}

/// Builds ship position indices for fast lookups
///
/// Returns (indicies_by_team, coarse_grids_by_team)
fn build_indices(
sim: &Simulation,
dt: f64,
Expand Down Expand Up @@ -261,10 +278,13 @@ impl CoarseGrid {
pub fn insert(&mut self, mut aabb: Aabb) {
aabb.mins -= vector![Self::CELL_SIZE, Self::CELL_SIZE];
aabb.maxs += vector![Self::CELL_SIZE, Self::CELL_SIZE];

// If the aabb intersects with the world, work with the intersection.
if let Some(aabb) = aabb.intersection(&Aabb::from_half_extents(
point![0.0, 0.0],
vector![MAX_WORLD_SIZE / 2.0, MAX_WORLD_SIZE / 2.0],
)) {
// Set the cells occupied by the aabb to true
let w = ((aabb.maxs.x - aabb.mins.x) * Self::RECIP_CELL_SIZE).ceil() as i32;
let h = ((aabb.maxs.y - aabb.mins.y) * Self::RECIP_CELL_SIZE).ceil() as i32;
let min_index = Self::to_cell(aabb.mins.coords);
Expand Down
1 change: 1 addition & 0 deletions shared/simulator/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub struct Line {
pub color: Vector4<f32>,
}

/// Display gun orientation, acceleration vector, and radar radius for each ship
pub fn emit_ship(sim: &mut Simulation, handle: ShipHandle) {
let mut lines = Vec::with_capacity(2 + sim.ship(handle).data().guns.len());
let body = sim.ship(handle).body();
Expand Down
8 changes: 8 additions & 0 deletions shared/simulator/src/radio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ pub fn tick(sim: &mut Simulation) {
let mut receivers: BTreeMap<usize, Vec<RadioReceiver>> = BTreeMap::new();
let mut senders: BTreeMap<usize, Vec<RadioSender>> = BTreeMap::new();

// Create a cache of radio senders and recievers for each channel
for handle in handle_snapshot.iter().cloned() {
let ship = sim.ship(handle);
let ship_data = ship.data();
Expand All @@ -82,6 +83,8 @@ pub fn tick(sim: &mut Simulation) {
}

for channel in 0..NUM_CHANNELS {
// For each reciever, look for the sender on the same channel with the highest
// signal strength, and give that message to the reciever
for rx in receivers.get(&channel).unwrap_or(&Vec::new()) {
let mut best_msg = None;
let mut best_rssi = rx.min_rssi;
Expand All @@ -100,13 +103,18 @@ pub fn tick(sim: &mut Simulation) {
}
}

// Reset sent messages
for handle in handle_snapshot.iter().cloned() {
for radio in sim.ship_mut(handle).data_mut().radios.iter_mut() {
radio.sent = None;
}
}
}

/// Computes signal strength between a sender and reciever based on
/// their distance from each other and the sender's power.
///
/// Reference: https://en.wikipedia.org/wiki/Received_signal_strength_indicator
fn compute_rssi(sender: &RadioSender, receiver: &RadioReceiver) -> f64 {
let r_sq = nalgebra::distance_squared(&sender.position, &receiver.position);
sender.power * receiver.rx_cross_section / (TAU * r_sq)
Expand Down
24 changes: 13 additions & 11 deletions shared/simulator/src/ship.rs
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,7 @@ impl<'a: 'b, 'b> ShipAccessorMut<'a> {
let team = ship_data.team;
let gun = {
let gun = &mut ship_data.guns[index as usize];
// Exit if gun is still reloading
if gun.reload_ticks_remaining > 0 {
return;
}
Expand Down Expand Up @@ -932,13 +933,10 @@ impl<'a: 'b, 'b> ShipAccessorMut<'a> {
}

pub fn tick(&mut self) {
// Weapons.
// Weapons
// Handle reload timers
{
let ship_data = self
.simulation
.ship_data
.get_mut(self.handle.index())
.unwrap();
let ship_data = self.data_mut();
for gun in ship_data.guns.iter_mut() {
if gun.reload_ticks_remaining > 0 {
gun.reload_ticks_remaining -= 1;
Expand All @@ -952,7 +950,8 @@ impl<'a: 'b, 'b> ShipAccessorMut<'a> {
}
}

// Acceleration.
// Movement
// Apply boosts, consume fuel
{
let mut acceleration = self.data().acceleration;
if self.readonly().is_ability_active(Ability::Boost) {
Expand All @@ -976,7 +975,7 @@ impl<'a: 'b, 'b> ShipAccessorMut<'a> {
self.data_mut().acceleration = vector![0.0, 0.0];
}

// Torque.
// Torque
{
let inertia_sqrt = 1.0
/ self
Expand All @@ -991,6 +990,7 @@ impl<'a: 'b, 'b> ShipAccessorMut<'a> {
}

// TTL
// Destroy ship if it exceeds TTL
{
if let Some(ttl) = self.data_mut().ttl {
self.data_mut().ttl = Some(ttl - 1);
Expand All @@ -1000,7 +1000,8 @@ impl<'a: 'b, 'b> ShipAccessorMut<'a> {
}
}

// Special abilities.
// Special abilities
// Handle reload and remaining time
{
for ship_ability in self.data_mut().abilities.iter_mut() {
ship_ability.active_time_remaining =
Expand All @@ -1010,7 +1011,8 @@ impl<'a: 'b, 'b> ShipAccessorMut<'a> {
}
}

// Destruction.
// Destruction
// If a ship has been destroyed, remove it from the simulation
if self.data().destroyed {
if let Some(team_ctrl) = self.simulation.get_team_controller(self.data().team) {
team_ctrl.borrow_mut().remove_ship(self.handle);
Expand All @@ -1022,7 +1024,7 @@ impl<'a: 'b, 'b> ShipAccessorMut<'a> {
&mut self.simulation.colliders,
&mut self.simulation.impulse_joints,
&mut self.simulation.multibody_joints,
/*remove_attached_colliders=*/ true,
true,
);
self.simulation
.ship_data
Expand Down
Loading

0 comments on commit d0900f8

Please sign in to comment.