Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Commenting the main flows #156

Merged
merged 3 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
};
Comment on lines -95 to -103
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was having a little trouble parsing this when I was looking at it. It was unclear to me why mutations were being done inside the constructor for the team variable, so I restructured the code to make it a little easier to read.

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();
Comment on lines -937 to -941
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this line was the same as data_mut, so I replaced it with data_mut

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
Loading