Merge branch 'isse/optimize-weather' into 'master'

Optimize weather

See merge request veloren/veloren!4270
This commit is contained in:
Isse 2024-01-27 17:20:53 +00:00
commit 2023786f5e
13 changed files with 335 additions and 116 deletions

View File

@ -49,7 +49,7 @@ use common::{
trade::{PendingTrade, SitePrices, TradeAction, TradeId, TradeResult}, trade::{PendingTrade, SitePrices, TradeAction, TradeId, TradeResult},
uid::{IdMaps, Uid}, uid::{IdMaps, Uid},
vol::RectVolSize, vol::RectVolSize,
weather::{Weather, WeatherGrid}, weather::{CompressedWeather, SharedWeatherGrid, Weather, WeatherGrid},
}; };
#[cfg(feature = "tracy")] use common_base::plot; #[cfg(feature = "tracy")] use common_base::plot;
use common_base::{prof_span, span}; use common_base::{prof_span, span};
@ -178,12 +178,32 @@ pub struct SiteInfoRich {
} }
struct WeatherLerp { struct WeatherLerp {
old: (WeatherGrid, Instant), old: (SharedWeatherGrid, Instant),
new: (WeatherGrid, Instant), new: (SharedWeatherGrid, Instant),
old_local_wind: (Vec2<f32>, Instant),
new_local_wind: (Vec2<f32>, Instant),
local_wind: Vec2<f32>,
} }
impl WeatherLerp { impl WeatherLerp {
fn weather_update(&mut self, weather: WeatherGrid) { fn local_wind_update(&mut self, wind: Vec2<f32>) {
self.old_local_wind = mem::replace(&mut self.new_local_wind, (wind, Instant::now()));
}
fn update_local_wind(&mut self) {
// Assumes updates are regular
let t = (self.new_local_wind.1.elapsed().as_secs_f32()
/ self
.new_local_wind
.1
.duration_since(self.old_local_wind.1)
.as_secs_f32())
.clamp(0.0, 1.0);
self.local_wind = Vec2::lerp_unclamped(self.old_local_wind.0, self.new_local_wind.0, t);
}
fn weather_update(&mut self, weather: SharedWeatherGrid) {
self.old = mem::replace(&mut self.new, (weather, Instant::now())); self.old = mem::replace(&mut self.new, (weather, Instant::now()));
} }
@ -191,13 +211,14 @@ impl WeatherLerp {
// that updates come at regular intervals. // that updates come at regular intervals.
fn update(&mut self, to_update: &mut WeatherGrid) { fn update(&mut self, to_update: &mut WeatherGrid) {
prof_span!("WeatherLerp::update"); prof_span!("WeatherLerp::update");
self.update_local_wind();
let old = &self.old.0; let old = &self.old.0;
let new = &self.new.0; let new = &self.new.0;
if new.size() == Vec2::zero() { if new.size() == Vec2::zero() {
return; return;
} }
if to_update.size() != new.size() { if to_update.size() != new.size() {
*to_update = new.clone(); *to_update = WeatherGrid::from(new);
} }
if old.size() == new.size() { if old.size() == new.size() {
// Assumes updates are regular // Assumes updates are regular
@ -209,7 +230,7 @@ impl WeatherLerp {
.iter_mut() .iter_mut()
.zip(old.iter().zip(new.iter())) .zip(old.iter().zip(new.iter()))
.for_each(|((_, current), ((_, old), (_, new)))| { .for_each(|((_, current), ((_, old), (_, new)))| {
*current = Weather::lerp_unclamped(old, new, t); *current = CompressedWeather::lerp_unclamped(old, new, t);
}); });
} }
} }
@ -217,9 +238,14 @@ impl WeatherLerp {
impl Default for WeatherLerp { impl Default for WeatherLerp {
fn default() -> Self { fn default() -> Self {
let old = Instant::now();
let new = Instant::now();
Self { Self {
old: (WeatherGrid::new(Vec2::zero()), Instant::now()), old: (SharedWeatherGrid::new(Vec2::zero()), old),
new: (WeatherGrid::new(Vec2::zero()), Instant::now()), new: (SharedWeatherGrid::new(Vec2::zero()), new),
old_local_wind: (Vec2::zero(), old),
new_local_wind: (Vec2::zero(), new),
local_wind: Vec2::zero(),
} }
} }
} }
@ -1716,7 +1742,11 @@ impl Client {
/// Returns Weather::default if no player position exists. /// Returns Weather::default if no player position exists.
pub fn weather_at_player(&self) -> Weather { pub fn weather_at_player(&self) -> Weather {
self.position() self.position()
.map(|wpos| self.state.weather_at(wpos.xy())) .map(|p| {
let mut weather = self.state.weather_at(p.xy());
weather.wind = self.weather.local_wind;
weather
})
.unwrap_or_default() .unwrap_or_default()
} }
@ -2539,6 +2569,9 @@ impl Client {
ServerGeneral::WeatherUpdate(weather) => { ServerGeneral::WeatherUpdate(weather) => {
self.weather.weather_update(weather); self.weather.weather_update(weather);
}, },
ServerGeneral::LocalWindUpdate(wind) => {
self.weather.local_wind_update(wind);
},
ServerGeneral::SpectatePosition(pos) => { ServerGeneral::SpectatePosition(pos) => {
frontend_events.push(Event::SpectatePosition(pos)); frontend_events.push(Event::SpectatePosition(pos));
}, },

View File

@ -17,7 +17,7 @@ use common::{
trade::{PendingTrade, SitePrices, TradeId, TradeResult}, trade::{PendingTrade, SitePrices, TradeId, TradeResult},
uid::Uid, uid::Uid,
uuid::Uuid, uuid::Uuid,
weather::WeatherGrid, weather::SharedWeatherGrid,
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -214,7 +214,8 @@ pub enum ServerGeneral {
/// Economic information about sites /// Economic information about sites
SiteEconomy(EconomyInfo), SiteEconomy(EconomyInfo),
MapMarker(comp::MapMarkerUpdate), MapMarker(comp::MapMarkerUpdate),
WeatherUpdate(WeatherGrid), WeatherUpdate(SharedWeatherGrid),
LocalWindUpdate(Vec2<f32>),
/// Suggest the client to spectate a position. Called after client has /// Suggest the client to spectate a position. Called after client has
/// requested teleport etc. /// requested teleport etc.
SpectatePosition(Vec3<f32>), SpectatePosition(Vec3<f32>),
@ -339,6 +340,7 @@ impl ServerMsg {
| ServerGeneral::SiteEconomy(_) | ServerGeneral::SiteEconomy(_)
| ServerGeneral::MapMarker(_) | ServerGeneral::MapMarker(_)
| ServerGeneral::WeatherUpdate(_) | ServerGeneral::WeatherUpdate(_)
| ServerGeneral::LocalWindUpdate(_)
| ServerGeneral::SpectatePosition(_) => { | ServerGeneral::SpectatePosition(_) => {
c_type == ClientType::Game && presence.is_some() c_type == ClientType::Game && presence.is_some()
}, },

View File

@ -32,11 +32,11 @@ impl Weather {
} }
} }
pub fn lerp_unclamped(from: &Self, to: &Self, t: f32) -> Self { pub fn lerp_unclamped(&self, to: &Self, t: f32) -> Self {
Self { Self {
cloud: f32::lerp_unclamped(from.cloud, to.cloud, t), cloud: f32::lerp_unclamped(self.cloud, to.cloud, t),
rain: f32::lerp_unclamped(from.rain, to.rain, t), rain: f32::lerp_unclamped(self.rain, to.rain, t),
wind: Vec2::<f32>::lerp_unclamped(from.wind, to.wind, t), wind: Vec2::<f32>::lerp_unclamped(self.wind, to.wind, t),
} }
} }
@ -75,11 +75,105 @@ pub const CHUNKS_PER_CELL: u32 = 16;
pub const CELL_SIZE: u32 = CHUNKS_PER_CELL * TerrainChunkSize::RECT_SIZE.x; pub const CELL_SIZE: u32 = CHUNKS_PER_CELL * TerrainChunkSize::RECT_SIZE.x;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone)]
pub struct WeatherGrid { pub struct WeatherGrid {
weather: Grid<Weather>, weather: Grid<Weather>,
} }
/// Weather that's compressed in order to send it to the client.
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
pub struct CompressedWeather {
cloud: u8,
rain: u8,
}
impl CompressedWeather {
pub fn lerp_unclamped(&self, to: &CompressedWeather, t: f32) -> Weather {
Weather {
cloud: f32::lerp_unclamped(self.cloud as f32, to.cloud as f32, t) / 255.0,
rain: f32::lerp_unclamped(self.rain as f32, to.rain as f32, t) / 255.0,
wind: Vec2::zero(),
}
}
}
impl From<Weather> for CompressedWeather {
fn from(weather: Weather) -> Self {
Self {
cloud: (weather.cloud * 255.0).round() as u8,
rain: (weather.rain * 255.0).round() as u8,
}
}
}
impl From<CompressedWeather> for Weather {
fn from(weather: CompressedWeather) -> Self {
Self {
cloud: weather.cloud as f32 / 255.0,
rain: weather.rain as f32 / 255.0,
wind: Vec2::zero(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SharedWeatherGrid {
weather: Grid<CompressedWeather>,
}
impl From<&WeatherGrid> for SharedWeatherGrid {
fn from(value: &WeatherGrid) -> Self {
Self {
weather: Grid::from_raw(
value.weather.size(),
value
.weather
.raw()
.iter()
.copied()
.map(CompressedWeather::from)
.collect::<Vec<_>>(),
),
}
}
}
impl From<&SharedWeatherGrid> for WeatherGrid {
fn from(value: &SharedWeatherGrid) -> Self {
Self {
weather: Grid::from_raw(
value.weather.size(),
value
.weather
.raw()
.iter()
.copied()
.map(Weather::from)
.collect::<Vec<_>>(),
),
}
}
}
impl SharedWeatherGrid {
pub fn new(size: Vec2<u32>) -> Self {
size.map(|e| debug_assert!(i32::try_from(e).is_ok()));
Self {
weather: Grid::new(size.as_(), CompressedWeather::default()),
}
}
pub fn iter(&self) -> impl Iterator<Item = (Vec2<i32>, &CompressedWeather)> {
self.weather.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (Vec2<i32>, &mut CompressedWeather)> {
self.weather.iter_mut()
}
pub fn size(&self) -> Vec2<u32> { self.weather.size().as_() }
}
/// Transforms a world position to cell coordinates. Where (0.0, 0.0) in cell /// Transforms a world position to cell coordinates. Where (0.0, 0.0) in cell
/// coordinates is the center of the weather cell located at (0, 0) in the grid. /// coordinates is the center of the weather cell located at (0, 0) in the grid.
fn to_cell_pos(wpos: Vec2<f32>) -> Vec2<f32> { wpos / CELL_SIZE as f32 - 0.5 } fn to_cell_pos(wpos: Vec2<f32>) -> Vec2<f32> { wpos / CELL_SIZE as f32 - 0.5 }
@ -113,6 +207,13 @@ impl WeatherGrid {
pub fn size(&self) -> Vec2<u32> { self.weather.size().as_() } pub fn size(&self) -> Vec2<u32> { self.weather.size().as_() }
pub fn get(&self, cell_pos: Vec2<u32>) -> Weather {
self.weather
.get(cell_pos.as_())
.copied()
.unwrap_or_default()
}
/// Get the weather at a given world position by doing bilinear /// Get the weather at a given world position by doing bilinear
/// interpolation between four cells. /// interpolation between four cells.
pub fn get_interpolated(&self, wpos: Vec2<f32>) -> Weather { pub fn get_interpolated(&self, wpos: Vec2<f32>) -> Weather {

View File

@ -192,6 +192,7 @@ impl Client {
| ServerGeneral::FinishedTrade(_) | ServerGeneral::FinishedTrade(_)
| ServerGeneral::MapMarker(_) | ServerGeneral::MapMarker(_)
| ServerGeneral::WeatherUpdate(_) | ServerGeneral::WeatherUpdate(_)
| ServerGeneral::LocalWindUpdate(_)
| ServerGeneral::SpectatePosition(_) => { | ServerGeneral::SpectatePosition(_) => {
PreparedMsg::new(2, &g, &self.in_game_stream_params) PreparedMsg::new(2, &g, &self.in_game_stream_params)
}, },

View File

@ -10,7 +10,7 @@ use crate::{
SettingError, WhitelistInfo, WhitelistRecord, SettingError, WhitelistInfo, WhitelistRecord,
}, },
sys::terrain::NpcData, sys::terrain::NpcData,
weather::WeatherSim, weather::WeatherJob,
wiring, wiring,
wiring::OutputFormula, wiring::OutputFormula,
Server, Settings, StateExt, Server, Settings, StateExt,
@ -4474,11 +4474,14 @@ fn handle_weather_zone(
let mut add_zone = |weather: weather::Weather| { let mut add_zone = |weather: weather::Weather| {
if let Ok(pos) = position(server, client, "player") { if let Ok(pos) = position(server, client, "player") {
let pos = pos.0.xy() / weather::CELL_SIZE as f32; let pos = pos.0.xy() / weather::CELL_SIZE as f32;
server if let Some(weather_job) = server
.state .state
.ecs_mut() .ecs_mut()
.write_resource::<WeatherSim>() .write_resource::<Option<WeatherJob>>()
.add_zone(weather, pos, radius, time); .as_mut()
{
weather_job.queue_zone(weather, pos, radius, time);
}
} }
}; };
match name.as_str() { match name.as_str() {

View File

@ -371,6 +371,7 @@ impl Server {
pool.configure("CHUNK_GENERATOR", |n| n / 2 + n / 4); pool.configure("CHUNK_GENERATOR", |n| n / 2 + n / 4);
pool.configure("CHUNK_SERIALIZER", |n| n / 2); pool.configure("CHUNK_SERIALIZER", |n| n / 2);
pool.configure("RTSIM_SAVE", |_| 1); pool.configure("RTSIM_SAVE", |_| 1);
pool.configure("WEATHER", |_| 1);
} }
state state
.ecs_mut() .ecs_mut()
@ -588,7 +589,7 @@ impl Server {
return Err(Error::RtsimError(err)); return Err(Error::RtsimError(err));
}, },
} }
weather::init(&mut state, &world); weather::init(&mut state);
} }
let server_constants = ServerConstants { let server_constants = ServerConstants {

View File

@ -1,41 +1,23 @@
use common::weather::CHUNKS_PER_CELL; use common_ecs::dispatch;
use common_ecs::{dispatch, System};
use common_state::State; use common_state::State;
use specs::DispatcherBuilder; use specs::DispatcherBuilder;
use std::time::Duration;
use crate::sys::SysScheduler;
mod sim; mod sim;
mod sync;
mod tick; mod tick;
pub use sim::WeatherSim; pub use tick::WeatherJob;
/// How often the weather is updated, in seconds /// How often the weather is updated, in seconds
const WEATHER_DT: f32 = 5.0; const WEATHER_DT: f32 = 5.0;
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) { pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
dispatch::<tick::Sys>(dispatch_builder, &[]); dispatch::<tick::Sys>(dispatch_builder, &[]);
dispatch::<sync::Sys>(dispatch_builder, &[&tick::Sys::sys_name()]);
} }
#[cfg(feature = "worldgen")] #[cfg(feature = "worldgen")]
pub fn init(state: &mut State, world: &world::World) { pub fn init(state: &mut State) {
let weather_size = world.sim().get_size() / CHUNKS_PER_CELL; use crate::weather::sim::LightningCells;
let sim = WeatherSim::new(weather_size, world);
state.ecs_mut().insert(sim);
// NOTE: If weather computations get too heavy, this should not block the main state.ecs_mut().insert(None::<WeatherJob>);
// thread. state.ecs_mut().insert(LightningCells::default());
state
.ecs_mut()
.insert(SysScheduler::<tick::Sys>::every(Duration::from_secs_f32(
WEATHER_DT,
)));
state
.ecs_mut()
.insert(SysScheduler::<sync::Sys>::every(Duration::from_secs_f32(
WEATHER_DT,
)));
} }

View File

@ -1,12 +1,9 @@
use common::{ use common::{
event::EventBus,
grid::Grid, grid::Grid,
outcome::Outcome,
resources::TimeOfDay, resources::TimeOfDay,
weather::{Weather, WeatherGrid, CELL_SIZE, CHUNKS_PER_CELL}, weather::{Weather, WeatherGrid, CELL_SIZE, CHUNKS_PER_CELL},
}; };
use noise::{NoiseFn, SuperSimplex, Turbulence}; use noise::{NoiseFn, SuperSimplex, Turbulence};
use rand::prelude::*;
use vek::*; use vek::*;
use world::World; use world::World;
@ -31,6 +28,12 @@ pub struct WeatherSim {
zones: Grid<Option<WeatherZone>>, zones: Grid<Option<WeatherZone>>,
} }
/// A list of weather cells where lightning has a chance to strike.
#[derive(Default)]
pub struct LightningCells {
pub cells: Vec<Vec2<i32>>,
}
impl WeatherSim { impl WeatherSim {
pub fn new(size: Vec2<u32>, world: &World) -> Self { pub fn new(size: Vec2<u32>, world: &World) -> Self {
Self { Self {
@ -85,14 +88,8 @@ impl WeatherSim {
} }
} }
// Time step is cell size / maximum wind speed // Time step is cell size / maximum wind speed.
pub fn tick( pub fn tick(&mut self, time_of_day: TimeOfDay, out: &mut WeatherGrid) -> LightningCells {
&mut self,
time_of_day: &TimeOfDay,
outcomes: &EventBus<Outcome>,
out: &mut WeatherGrid,
world: &World,
) {
let time = time_of_day.0; let time = time_of_day.0;
let base_nz = Turbulence::new( let base_nz = Turbulence::new(
@ -105,6 +102,7 @@ impl WeatherSim {
let rain_nz = SuperSimplex::new(); let rain_nz = SuperSimplex::new();
let mut lightning_cells = Vec::new();
for (point, cell) in out.iter_mut() { for (point, cell) in out.iter_mut() {
if let Some(zone) = &mut self.zones[point] { if let Some(zone) = &mut self.zones[point] {
*cell = zone.weather; *cell = zone.weather;
@ -147,16 +145,14 @@ impl WeatherSim {
rain_nz.get((spos + 1.0).into_array()).powi(3) as f32, rain_nz.get((spos + 1.0).into_array()).powi(3) as f32,
) * 200.0 ) * 200.0
* (1.0 - pressure); * (1.0 - pressure);
if cell.rain > 0.2 && cell.cloud > 0.15 && thread_rng().gen_bool(0.01) {
let wpos = wpos.map(|e| {
e as f32 + thread_rng().gen_range(-1.0..1.0) * CELL_SIZE as f32 * 0.5
});
outcomes.emit_now(Outcome::Lightning {
pos: wpos.with_z(world.sim().get_alt_approx(wpos.as_()).unwrap_or(0.0)),
});
}
} }
if cell.rain > 0.2 && cell.cloud > 0.15 {
lightning_cells.push(point);
}
}
LightningCells {
cells: lightning_cells,
} }
} }

View File

@ -1,37 +0,0 @@
use common::weather::WeatherGrid;
use common_ecs::{Origin, Phase, System};
use common_net::msg::ServerGeneral;
use specs::{Join, ReadExpect, ReadStorage, Write};
use crate::{client::Client, sys::SysScheduler};
#[derive(Default)]
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
ReadExpect<'a, WeatherGrid>,
Write<'a, SysScheduler<Self>>,
ReadStorage<'a, Client>,
);
const NAME: &'static str = "weather::sync";
const ORIGIN: Origin = Origin::Server;
const PHASE: Phase = Phase::Create;
fn run(
_job: &mut common_ecs::Job<Self>,
(weather_grid, mut scheduler, clients): Self::SystemData,
) {
if scheduler.should_run() {
let mut lazy_msg = None;
for client in clients.join() {
if lazy_msg.is_none() {
lazy_msg =
Some(client.prepare(ServerGeneral::WeatherUpdate(weather_grid.clone())));
}
lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
}
}
}
}

View File

@ -1,24 +1,64 @@
use common::{event::EventBus, outcome::Outcome, resources::TimeOfDay, weather::WeatherGrid}; use common::{
comp,
event::EventBus,
outcome::Outcome,
resources::{DeltaTime, ProgramTime, TimeOfDay},
slowjob::{SlowJob, SlowJobPool},
weather::{SharedWeatherGrid, Weather, WeatherGrid},
};
use common_ecs::{Origin, Phase, System}; use common_ecs::{Origin, Phase, System};
use specs::{Read, ReadExpect, Write, WriteExpect}; use common_net::msg::ServerGeneral;
use std::sync::Arc; use rand::{seq::SliceRandom, thread_rng, Rng};
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, Write, WriteExpect};
use std::{mem, sync::Arc};
use vek::Vec2;
use world::World; use world::World;
use crate::sys::SysScheduler; use crate::{client::Client, Tick};
use super::sim::WeatherSim; use super::{
sim::{LightningCells, WeatherSim},
WEATHER_DT,
};
enum WeatherJobState {
Working(SlowJob),
Idle(WeatherSim),
None,
}
pub struct WeatherJob {
last_update: ProgramTime,
weather_tx: crossbeam_channel::Sender<(WeatherGrid, LightningCells, WeatherSim)>,
weather_rx: crossbeam_channel::Receiver<(WeatherGrid, LightningCells, WeatherSim)>,
state: WeatherJobState,
qeued_zones: Vec<(Weather, Vec2<f32>, f32, f32)>,
}
impl WeatherJob {
pub fn queue_zone(&mut self, weather: Weather, pos: Vec2<f32>, radius: f32, time: f32) {
self.qeued_zones.push((weather, pos, radius, time))
}
}
#[derive(Default)] #[derive(Default)]
pub struct Sys; pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
Entities<'a>,
Read<'a, TimeOfDay>, Read<'a, TimeOfDay>,
WriteExpect<'a, WeatherSim>, Read<'a, ProgramTime>,
Read<'a, Tick>,
Read<'a, DeltaTime>,
Write<'a, LightningCells>,
Write<'a, Option<WeatherJob>>,
WriteExpect<'a, WeatherGrid>, WriteExpect<'a, WeatherGrid>,
Write<'a, SysScheduler<Self>>, WriteExpect<'a, SlowJobPool>,
ReadExpect<'a, EventBus<Outcome>>, ReadExpect<'a, EventBus<Outcome>>,
ReadExpect<'a, Arc<World>>, ReadExpect<'a, Arc<World>>,
ReadStorage<'a, Client>,
ReadStorage<'a, comp::Pos>,
); );
const NAME: &'static str = "weather::tick"; const NAME: &'static str = "weather::tick";
@ -27,13 +67,110 @@ impl<'a> System<'a> for Sys {
fn run( fn run(
_job: &mut common_ecs::Job<Self>, _job: &mut common_ecs::Job<Self>,
(game_time, mut sim, mut grid, mut scheduler, outcomes, world): Self::SystemData, (
entities,
game_time,
program_time,
tick,
delta_time,
mut lightning_cells,
mut weather_job,
mut grid,
slow_job_pool,
outcomes,
world,
clients,
positions,
): Self::SystemData,
) { ) {
if scheduler.should_run() { let to_update = match &mut *weather_job {
if grid.size() != sim.size() { Some(weather_job) => (program_time.0 - weather_job.last_update.0 >= WEATHER_DT as f64)
.then_some(weather_job),
None => {
let (weather_tx, weather_rx) = crossbeam_channel::bounded(1);
let weather_size = world.sim().get_size() / common::weather::CHUNKS_PER_CELL;
let mut sim = WeatherSim::new(weather_size, &world);
*grid = WeatherGrid::new(sim.size()); *grid = WeatherGrid::new(sim.size());
*lightning_cells = sim.tick(*game_time, &mut grid);
*weather_job = Some(WeatherJob {
last_update: *program_time,
weather_tx,
weather_rx,
state: WeatherJobState::Idle(sim),
qeued_zones: Vec::new(),
});
None
},
};
if let Some(weather_job) = to_update {
if matches!(weather_job.state, WeatherJobState::Working(_))
&& let Ok((new_grid, new_lightning_cells, sim)) = weather_job.weather_rx.try_recv() {
*grid = new_grid;
*lightning_cells = new_lightning_cells;
let mut lazy_msg = None;
for client in clients.join() {
if lazy_msg.is_none() {
lazy_msg = Some(client.prepare(ServerGeneral::WeatherUpdate(
SharedWeatherGrid::from(&*grid),
)));
}
lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
}
weather_job.state = WeatherJobState::Idle(sim);
}
if matches!(weather_job.state, WeatherJobState::Idle(_)) {
weather_job.last_update = *program_time;
let old_state = mem::replace(&mut weather_job.state, WeatherJobState::None);
let WeatherJobState::Idle(mut sim) = old_state else {
unreachable!()
};
let weather_tx = weather_job.weather_tx.clone();
let game_time = *game_time;
for (weather, pos, radius, time) in weather_job.qeued_zones.drain(..) {
sim.add_zone(weather, pos, radius, time)
}
let job = slow_job_pool.spawn("WEATHER", move || {
let mut grid = WeatherGrid::new(sim.size());
let lightning_cells = sim.tick(game_time, &mut grid);
let _ = weather_tx.send((grid, lightning_cells, sim));
});
weather_job.state = WeatherJobState::Working(job);
}
}
// Chance to emit lightning every frame from one or more of the cells that
// currently has the correct weather conditions.
let mut outcome_emitter = outcomes.emitter();
let mut rng = thread_rng();
let num_cells = lightning_cells.cells.len() as f64 * 0.0015 * delta_time.0 as f64;
let num_cells = num_cells.floor() as u32 + rng.gen_bool(num_cells.fract()) as u32;
for _ in 0..num_cells {
let cell_pos = lightning_cells.cells.choose(&mut rng).expect(
"This is non-empty, since we multiply with its len for the chance to do a \
lightning strike.",
);
let wpos = cell_pos
.map(|e| (e as f32 + rng.gen_range(0.0..1.0)) * common::weather::CELL_SIZE as f32);
outcome_emitter.emit(Outcome::Lightning {
pos: wpos.with_z(world.sim().get_alt_approx(wpos.as_()).unwrap_or(0.0)),
});
}
for (entity, client, pos) in (&entities, &clients, &positions).join() {
if entity.id() as u64 % 30 == tick.0 % 30 {
let weather = grid.get_interpolated(pos.0.xy());
client.send_fallible(ServerGeneral::LocalWindUpdate(weather.wind));
} }
sim.tick(&game_time, &outcomes, &mut grid, &world);
} }
} }
} }

View File

@ -780,7 +780,7 @@ impl FigureMgr {
// Are shadows enabled at all? // Are shadows enabled at all?
let can_shadow_sun = renderer.pipeline_modes().shadow.is_map() && is_daylight; let can_shadow_sun = renderer.pipeline_modes().shadow.is_map() && is_daylight;
let weather = scene_data.state.weather_at(cam_pos.xy()); let weather = scene_data.client.weather_at_player();
let cam_pos = math::Vec3::from(cam_pos); let cam_pos = math::Vec3::from(cam_pos);

View File

@ -1198,7 +1198,7 @@ impl Scene {
.max_weather_near(focus_off.xy() + cam_pos.xy()); .max_weather_near(focus_off.xy() + cam_pos.xy());
self.wind_vel = weather.wind_vel(); self.wind_vel = weather.wind_vel();
if weather.rain > RAIN_THRESHOLD { if weather.rain > RAIN_THRESHOLD {
let weather = client.state().weather_at(focus_off.xy() + cam_pos.xy()); let weather = client.weather_at_player();
let rain_vel = weather.rain_vel(); let rain_vel = weather.rain_vel();
let rain_view_mat = math::Mat4::look_at_rh(look_at, look_at + rain_vel, up); let rain_view_mat = math::Mat4::look_at_rh(look_at, look_at + rain_vel, up);

View File

@ -1580,7 +1580,7 @@ impl<V: RectRasterableVol> Terrain<V> {
min: visible_bounding_box.min.as_::<f64>(), min: visible_bounding_box.min.as_::<f64>(),
max: visible_bounding_box.max.as_::<f64>(), max: visible_bounding_box.max.as_::<f64>(),
}; };
let weather = scene_data.state.weather_at(focus_off.xy() + cam_pos.xy()); let weather = scene_data.client.weather_at_player();
let ray_direction = math::Vec3::<f32>::from(weather.rain_vel().normalized()); let ray_direction = math::Vec3::<f32>::from(weather.rain_vel().normalized());
// NOTE: We use proj_mat_treeculler here because // NOTE: We use proj_mat_treeculler here because