From 708eea7b4365f0a450ee110e07f18a3205efe51f Mon Sep 17 00:00:00 2001 From: osmarks Date: Wed, 4 Mar 2026 18:48:11 +0000 Subject: [PATCH] fixes --- src/main.rs | 47 +++++++++++++++++++++++++++++++++++++++++------ src/map.rs | 2 +- src/plant.rs | 2 +- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index e0f1816..8a5895c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ use tokio::net::{TcpListener, TcpStream}; use tokio_tungstenite::tungstenite::protocol::Message; use tokio::sync::{mpsc, Mutex}; use anyhow::{Result, Context, anyhow}; +use argh::FromArgs; use std::{convert::TryFrom, hash::{Hash, Hasher}, net::SocketAddr, ops::DerefMut, sync::Arc, time::Duration}; use slab::Slab; use serde::{Serialize, Deserialize}; @@ -20,6 +21,17 @@ pub mod world_serde; use map::*; +#[derive(FromArgs)] +/// Run the game server. +struct Args { + /// websocket listen address + #[argh(option, default = "String::from(\"0.0.0.0:8011\")")] + listen_addr: String, + /// simulation tick interval in milliseconds + #[argh(option, default = "56")] + tick_interval_ms: u64, +} + async fn handle_connection(raw_stream: TcpStream, addr: SocketAddr, mut frames_rx: mpsc::Receiver, inputs_tx: mpsc::Sender) -> Result<()> { let ws_stream = tokio_tungstenite::accept_async(raw_stream).await.context("websocket handshake failure")?; let (mut outgoing, incoming) = ws_stream.split(); @@ -87,6 +99,19 @@ struct Client { entity: Entity } +#[derive(Debug, Serialize, Deserialize)] +struct GameMetrics { + plants_died: u64, + plants_reproduced: u64, + enemies_spawned: u64 +} + +impl GameMetrics { + fn new() -> Self { + GameMetrics { plants_died: 0, plants_reproduced: 0, enemies_spawned: 0 } + } +} + struct GameState { world: World, clients: Slab, @@ -99,7 +124,8 @@ struct GameState { baseline_temperature: Map, dynamic_soil_nutrients: Map, dynamic_groundwater: Map, - positions: PositionIndex + positions: PositionIndex, + metrics: GameMetrics } impl GameState { @@ -496,6 +522,7 @@ impl SavedGame { dynamic_soil_nutrients: self.dynamic_soil_nutrients, dynamic_groundwater: self.dynamic_groundwater, positions, + metrics: GameMetrics::new() }) } } @@ -524,7 +551,7 @@ const SOIL_NUTRIENT_CONSUMPTION_RATE: f32 = 0.04; const SOIL_NUTRIENT_FIXATION_RATE: f32 = 0.0002; const WATER_CONSUMPTION_RATE: f32 = 0.02; const PLANT_IDLE_WATER_CONSUMPTION_OFFSET: f32 = 0.05; -const PLANT_DIEOFF_THRESHOLD: f32 = 0.01; +const PLANT_DIEOFF_THRESHOLD: f32 = 0.3; const PLANT_DIEOFF_RATE: f32 = 0.2; const SAVE_FILE: &str = "save.bin"; const AUTOSAVE_INTERVAL_TICKS: u64 = 1024; @@ -648,6 +675,7 @@ async fn game_tick(state: &mut GameState) -> Result<()> { )) }; *spawn_count += 1; + state.metrics.enemies_spawned += 1; } } } @@ -672,6 +700,7 @@ async fn game_tick(state: &mut GameState) -> Result<()> { // TODO: this is inelegant and should be shared with the other death code // also, it might break the position tracker kill(&mut buffer, &mut despawn_buffer, &state, &mut rng, entity, None); + state.metrics.plants_died += 1; } } } @@ -700,6 +729,8 @@ async fn game_tick(state: &mut GameState) -> Result<()> { state.dynamic_groundwater[pos] -= water_consumed; plant.water_consumed += water_consumed; + println!("water={} nutrient={} diff={} consume={}", water, soil_nutrients, difference, water_consumed); + if difference > 0.0 { plant.growth_ticks += 1; if let Ok(mut health) = state.world.get::<&mut Health>(entity) { @@ -755,11 +786,11 @@ async fn game_tick(state: &mut GameState) -> Result<()> { Plant::new(hybrid_genome.clone()), NewlyAdded )); - println!("plant reproduced {:?} {:?} {} {}.", plant.genome, other_plant.genome, plant.current_size, other_plant.current_size); plant.children += 1; other_plant.children += 1; plant.current_size *= plant.genome.reproductive_size_fraction(); // TODO: explicit cooldown? other_plant.current_size *= other_plant.genome.reproductive_size_fraction(); // TODO: vary this by plant component gender? + state.metrics.plants_reproduced += 1; break; } } @@ -1140,7 +1171,9 @@ const INITIAL_PLANTS: usize = 262144; #[tokio::main] async fn main() -> Result<()> { - let addr = std::env::args().nth(1).unwrap_or_else(|| "0.0.0.0:8011".to_string()); + let args: Args = argh::from_env(); + let addr = args.listen_addr; + let tick_interval_ms = args.tick_interval_ms; let mut loaded_save = false; let state = if let Some(saved) = load_saved_game().await? { println!("Loaded game state from {}", SAVE_FILE); @@ -1176,7 +1209,8 @@ async fn main() -> Result<()> { baseline_salt, baseline_temperature, dynamic_soil_nutrients, - dynamic_groundwater + dynamic_groundwater, + metrics: GameMetrics::new() })) }; @@ -1207,7 +1241,7 @@ async fn main() -> Result<()> { let state_ = state.clone(); tokio::spawn(async move { let state = state_.clone(); - let mut interval = tokio::time::interval(Duration::from_millis(56)); + let mut interval = tokio::time::interval(Duration::from_millis(tick_interval_ms)); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); loop { let mut state = state.lock().await; @@ -1222,6 +1256,7 @@ async fn main() -> Result<()> { } let tick_elapsed = time.elapsed(); println!("Tick time: {:?}", tick_elapsed); + println!("{:?}", state.metrics); interval.tick().await; } }); diff --git a/src/map.rs b/src/map.rs index 2ebdbb8..9e426c4 100644 --- a/src/map.rs +++ b/src/map.rs @@ -218,7 +218,7 @@ pub fn smooth(map: &Map, radius: i32) -> Map { // TODO: this is still really slow! //let result = if radius < 3 { - let result = ConvExt::conv_par(&data, &kernel, ConvMode::Same, PaddingMode::Replicate).unwrap(); + let result = ConvExt::conv(&data, &kernel, ConvMode::Same, PaddingMode::Replicate).unwrap(); //} else { // ConvFFTExt::conv_fft(&data, &kernel, ConvMode::Same, PaddingMode::Replicate).unwrap() //}; diff --git a/src/plant.rs b/src/plant.rs index f07d287..0fc7a3a 100644 --- a/src/plant.rs +++ b/src/plant.rs @@ -60,7 +60,7 @@ impl Genome { - salt_excess * 1.5 - self.water_tolerance * 0.2 - self.temperature_tolerance * 0.2 - - self.salt_tolerance * 0.2; + - self.salt_tolerance * 0.15; (base * (-nutrients.min(0.0)).exp()).max(0.0) }