From 9d3dadfabab0396beb63198b9c4bb1e524c33b2b Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 10 Aug 2022 00:02:56 +0100 Subject: [PATCH] Make resource depletion an rtsim rule --- Cargo.lock | 9 ++ rtsim/Cargo.toml | 3 + rtsim/src/data/nature.rs | 3 + rtsim/src/event.rs | 8 ++ rtsim/src/lib.rs | 109 ++++++++++++++- rtsim/src/rule.rs | 21 +++ rtsim/src/rule/example.rs | 19 +++ server/src/lib.rs | 3 +- server/src/rtsim2/event.rs | 12 ++ server/src/rtsim2/mod.rs | 146 +++++++++----------- server/src/rtsim2/rule.rs | 9 ++ server/src/rtsim2/rule/deplete_resources.rs | 47 +++++++ server/src/rtsim2/tick.rs | 2 + 13 files changed, 305 insertions(+), 86 deletions(-) create mode 100644 rtsim/src/event.rs create mode 100644 rtsim/src/rule.rs create mode 100644 rtsim/src/rule/example.rs create mode 100644 server/src/rtsim2/event.rs create mode 100644 server/src/rtsim2/rule.rs create mode 100644 server/src/rtsim2/rule/deplete_resources.rs diff --git a/Cargo.lock b/Cargo.lock index 4915092e2c..efd30ed9ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,6 +145,12 @@ version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" +[[package]] +name = "anymap2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" + [[package]] name = "app_dirs2" version = "2.5.4" @@ -6939,11 +6945,14 @@ dependencies = [ name = "veloren-rtsim" version = "0.10.0" dependencies = [ + "anymap2", + "atomic_refcell", "enum-map", "hashbrown 0.12.3", "rmp-serde", "ron 0.8.0", "serde", + "tracing", "vek 0.15.8", "veloren-common", "veloren-world", diff --git a/rtsim/Cargo.toml b/rtsim/Cargo.toml index 96fccf251e..1ffcb48f0d 100644 --- a/rtsim/Cargo.toml +++ b/rtsim/Cargo.toml @@ -12,3 +12,6 @@ hashbrown = { version = "0.12", features = ["rayon", "serde", "nightly"] } enum-map = { version = "2.4", features = ["serde"] } vek = { version = "0.15.8", features = ["serde"] } rmp-serde = "1.1.0" +anymap2 = "0.13" +tracing = "0.1" +atomic_refcell = "0.1" diff --git a/rtsim/src/data/nature.rs b/rtsim/src/data/nature.rs index 9fccf7ad2a..5cb9bea7ef 100644 --- a/rtsim/src/data/nature.rs +++ b/rtsim/src/data/nature.rs @@ -7,6 +7,8 @@ use common::{ use world::World; use vek::*; +/// Represents the state of 'natural' elements of the world such as plant/animal/resource populations, weather systems, +/// etc. #[derive(Clone, Serialize, Deserialize)] pub struct Nature { chunks: Grid, @@ -40,5 +42,6 @@ impl Nature { #[derive(Clone, Serialize, Deserialize)] pub struct Chunk { + /// Represent the 'naturally occurring' resource proportion that exists in this chunk. res: EnumMap, } diff --git a/rtsim/src/event.rs b/rtsim/src/event.rs new file mode 100644 index 0000000000..deb35cc68c --- /dev/null +++ b/rtsim/src/event.rs @@ -0,0 +1,8 @@ +use common::resources::Time; + +pub trait Event: Clone + 'static {} + +#[derive(Clone)] +pub struct OnTick { pub dt: f32 } + +impl Event for OnTick {} diff --git a/rtsim/src/lib.rs b/rtsim/src/lib.rs index 975ea54412..1397d99dd8 100644 --- a/rtsim/src/lib.rs +++ b/rtsim/src/lib.rs @@ -1,10 +1,107 @@ -pub mod data; -pub mod gen; +#![feature(explicit_generic_args_with_impl_trait)] -use self::data::Data; -use std::sync::Arc; -use world::World; +pub mod data; +pub mod event; +pub mod gen; +pub mod rule; + +pub use self::{ + data::Data, + event::{Event, OnTick}, + rule::{Rule, RuleError}, +}; +use anymap2::SendSyncAnyMap; +use tracing::{info, error}; +use atomic_refcell::AtomicRefCell; +use std::{ + any::type_name, + ops::{Deref, DerefMut}, +}; pub struct RtState { - pub data: Data, + resources: SendSyncAnyMap, + rules: SendSyncAnyMap, + event_handlers: SendSyncAnyMap, +} + +type RuleState = AtomicRefCell; +type EventHandlersOf = Vec>; + +impl RtState { + pub fn new(data: Data) -> Self { + let mut this = Self { + resources: SendSyncAnyMap::new(), + rules: SendSyncAnyMap::new(), + event_handlers: SendSyncAnyMap::new(), + } + .with_resource(data); + + this.start_default_rules(); + + this + } + + pub fn with_resource(mut self, r: R) -> Self { + self.resources.insert(AtomicRefCell::new(r)); + self + } + + fn start_default_rules(&mut self) { + info!("Starting default rtsim rules..."); + self.start_rule::(); + } + + pub fn start_rule(&mut self) { + info!("Initiating '{}' rule...", type_name::()); + match R::start(self) { + Ok(rule) => { self.rules.insert::>(AtomicRefCell::new(rule)); }, + Err(e) => error!("Error when initiating '{}' rule: {}", type_name::(), e), + } + } + + fn rule_mut(&self) -> impl DerefMut + '_ { + self.rules + .get::>() + .unwrap_or_else(|| panic!("Tried to access rule '{}' but it does not exist", type_name::())) + .borrow_mut() + } + + pub fn bind(&mut self, mut f: impl FnMut(&mut R, &RtState, E) + Send + Sync + 'static) { + let f = AtomicRefCell::new(f); + self.event_handlers + .entry::>() + .or_default() + .push(Box::new(move |rtstate, event| { + (f.borrow_mut())(&mut rtstate.rule_mut(), rtstate, event) + })); + } + + pub fn data(&self) -> impl Deref + '_ { self.resource() } + pub fn data_mut(&self) -> impl DerefMut + '_ { self.resource_mut() } + + pub fn resource(&self) -> impl Deref + '_ { + self.resources + .get::>() + .unwrap_or_else(|| panic!("Tried to access resource '{}' but it does not exist", type_name::())) + .borrow() + } + + pub fn resource_mut(&self) -> impl DerefMut + '_ { + self.resources + .get::>() + .unwrap_or_else(|| panic!("Tried to access resource '{}' but it does not exist", type_name::())) + .borrow_mut() + } + + pub fn emit(&mut self, e: E) { + self.event_handlers + .get::>() + .map(|handlers| handlers + .iter() + .for_each(|f| f(self, e.clone()))); + } + + pub fn tick(&mut self, dt: f32) { + self.emit(OnTick { dt }); + } } diff --git a/rtsim/src/rule.rs b/rtsim/src/rule.rs new file mode 100644 index 0000000000..6f6b701740 --- /dev/null +++ b/rtsim/src/rule.rs @@ -0,0 +1,21 @@ +pub mod example; + +use std::fmt; +use super::RtState; + +#[derive(Debug)] +pub enum RuleError { + NoSuchRule(&'static str), +} + +impl fmt::Display for RuleError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::NoSuchRule(r) => write!(f, "tried to fetch rule state '{}' but it does not exist", r), + } + } +} + +pub trait Rule: Sized + Send + Sync + 'static { + fn start(rtstate: &mut RtState) -> Result; +} diff --git a/rtsim/src/rule/example.rs b/rtsim/src/rule/example.rs new file mode 100644 index 0000000000..d4cda69920 --- /dev/null +++ b/rtsim/src/rule/example.rs @@ -0,0 +1,19 @@ +use tracing::info; +use crate::{ + event::OnTick, + RtState, Rule, RuleError, +}; + +pub struct RuleState; + +impl Rule for RuleState { + fn start(rtstate: &mut RtState) -> Result { + info!("Hello from example rule!"); + + rtstate.bind::(|this, rtstate, event| { + // println!("Tick!"); + }); + + Ok(Self) + } +} diff --git a/server/src/lib.rs b/server/src/lib.rs index df1dca17e4..ec39cab251 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -7,7 +7,8 @@ let_chains, never_type, option_zip, - unwrap_infallible + unwrap_infallible, + explicit_generic_args_with_impl_trait )] #![feature(hash_drain_filter)] diff --git a/server/src/rtsim2/event.rs b/server/src/rtsim2/event.rs new file mode 100644 index 0000000000..e576c42fcb --- /dev/null +++ b/server/src/rtsim2/event.rs @@ -0,0 +1,12 @@ +use rtsim2::Event; +use common::terrain::Block; +use vek::*; + +#[derive(Clone)] +pub struct OnBlockChange { + pub wpos: Vec3, + pub old: Block, + pub new: Block, +} + +impl Event for OnBlockChange {} diff --git a/server/src/rtsim2/mod.rs b/server/src/rtsim2/mod.rs index 2cf6b2b337..f71555afaa 100644 --- a/server/src/rtsim2/mod.rs +++ b/server/src/rtsim2/mod.rs @@ -1,3 +1,5 @@ +pub mod event; +pub mod rule; pub mod tick; use common::{ @@ -8,7 +10,11 @@ use common::{ vol::RectRasterableVol, }; use common_ecs::{dispatch, System}; -use rtsim2::{data::{Data, ReadError}, RtState}; +use rtsim2::{ + data::{Data, ReadError}, + rule::Rule, + RtState, +}; use specs::{DispatcherBuilder, WorldExt}; use std::{ fs::{self, File}, @@ -19,14 +25,13 @@ use std::{ error::Error, }; use enum_map::EnumMap; -use tracing::{error, warn, info}; +use tracing::{error, warn, info, debug}; use vek::*; use world::World; pub struct RtSim { file_path: PathBuf, last_saved: Option, - chunk_states: Grid>, state: RtState, } @@ -34,52 +39,54 @@ impl RtSim { pub fn new(world: &World, data_dir: PathBuf) -> Result { let file_path = Self::get_file_path(data_dir); - Ok(Self { - chunk_states: Grid::populate_from(world.sim().get_size().as_(), |_| None), - last_saved: None, - state: RtState { - data: { - info!("Looking for rtsim state in {}...", file_path.display()); - 'load: { - match File::open(&file_path) { - Ok(file) => { - info!("Rtsim state found. Attempting to load..."); - match Data::from_reader(io::BufReader::new(file)) { - Ok(data) => { info!("Rtsim state loaded."); break 'load data }, - Err(e) => { - error!("Rtsim state failed to load: {}", e); - let mut i = 0; - loop { - let mut backup_path = file_path.clone(); - backup_path.set_extension(if i == 0 { - format!("backup_{}", i) - } else { - "ron_backup".to_string() - }); - if !backup_path.exists() { - fs::rename(&file_path, &backup_path)?; - warn!("Failed rtsim state was moved to {}", backup_path.display()); - info!("A fresh rtsim state will now be generated."); - break; - } - i += 1; - } - }, + info!("Looking for rtsim data in {}...", file_path.display()); + let data = 'load: { + match File::open(&file_path) { + Ok(file) => { + info!("Rtsim data found. Attempting to load..."); + match Data::from_reader(io::BufReader::new(file)) { + Ok(data) => { info!("Rtsim data loaded."); break 'load data }, + Err(e) => { + error!("Rtsim data failed to load: {}", e); + let mut i = 0; + loop { + let mut backup_path = file_path.clone(); + backup_path.set_extension(if i == 0 { + format!("backup_{}", i) + } else { + "ron_backup".to_string() + }); + if !backup_path.exists() { + fs::rename(&file_path, &backup_path)?; + warn!("Failed rtsim data was moved to {}", backup_path.display()); + info!("A fresh rtsim data will now be generated."); + break; } - }, - Err(e) if e.kind() == io::ErrorKind::NotFound => - info!("No rtsim state found. Generating from initial world state..."), - Err(e) => return Err(e.into()), - } - - let data = Data::generate(&world); - info!("Rtsim state generated."); - data + i += 1; + } + }, } }, - }, + Err(e) if e.kind() == io::ErrorKind::NotFound => + info!("No rtsim data found. Generating from world..."), + Err(e) => return Err(e.into()), + } + + let data = Data::generate(&world); + info!("Rtsim data generated."); + data + }; + + let mut this = Self { + last_saved: None, + state: RtState::new(data) + .with_resource(ChunkStates(Grid::populate_from(world.sim().get_size().as_(), |_| None))), file_path, - }) + }; + + rule::start_rules(&mut this.state); + + Ok(this) } fn get_file_path(mut data_dir: PathBuf) -> PathBuf { @@ -89,31 +96,31 @@ impl RtSim { data_dir.push("rtsim"); data_dir }); - path.push("state.dat"); + path.push("data.dat"); path } pub fn hook_load_chunk(&mut self, key: Vec2, max_res: EnumMap) { - if let Some(chunk_state) = self.chunk_states.get_mut(key) { + if let Some(chunk_state) = self.state.resource_mut::().0.get_mut(key) { *chunk_state = Some(LoadedChunkState { max_res }); } } pub fn hook_unload_chunk(&mut self, key: Vec2) { - if let Some(chunk_state) = self.chunk_states.get_mut(key) { + if let Some(chunk_state) = self.state.resource_mut::().0.get_mut(key) { *chunk_state = None; } } pub fn save(&mut self, slowjob_pool: &SlowJobPool) { - info!("Beginning rtsim state save..."); + info!("Saving rtsim data..."); let file_path = self.file_path.clone(); - let data = self.state.data.clone(); - info!("Starting rtsim save job..."); + let data = self.state.data().clone(); + debug!("Starting rtsim data save job..."); // TODO: Use slow job // slowjob_pool.spawn("RTSIM_SAVE", move || { std::thread::spawn(move || { - let tmp_file_name = "state_tmp.dat"; + let tmp_file_name = "data_tmp.dat"; if let Err(e) = file_path .parent() .map(|dir| { @@ -127,16 +134,16 @@ impl RtSim { }) .map_err(|e: io::Error| Box::new(e) as Box::) .and_then(|(mut file, tmp_file_path)| { - info!("Writing rtsim state to file..."); + debug!("Writing rtsim data to file..."); data.write_to(io::BufWriter::new(&mut file))?; file.flush()?; drop(file); fs::rename(tmp_file_path, file_path)?; - info!("Rtsim state saved."); + debug!("Rtsim data saved."); Ok(()) }) { - error!("Saving rtsim state failed: {}", e); + error!("Saving rtsim data failed: {}", e); } }); self.last_saved = Some(Instant::now()); @@ -144,34 +151,15 @@ impl RtSim { // TODO: Clean up this API a bit pub fn get_chunk_resources(&self, key: Vec2) -> EnumMap { - self.state.data.nature.get_chunk_resources(key) + self.state.data().nature.get_chunk_resources(key) } - pub fn hook_block_update(&mut self, wpos: Vec3, old_block: Block, new_block: Block) { - let key = wpos - .xy() - .map2(TerrainChunk::RECT_SIZE, |e, sz| e.div_euclid(sz as i32)); - if let Some(Some(chunk_state)) = self.chunk_states.get(key) { - let mut chunk_res = self.get_chunk_resources(key); - // Remove resources - if let Some(res) = old_block.get_rtsim_resource() { - if chunk_state.max_res[res] > 0 { - chunk_res[res] = (chunk_res[res] - 1.0 / chunk_state.max_res[res] as f32).max(0.0); - println!("Subbing {} to resources", 1.0 / chunk_state.max_res[res] as f32); - } - } - // Add resources - if let Some(res) = new_block.get_rtsim_resource() { - if chunk_state.max_res[res] > 0 { - chunk_res[res] = (chunk_res[res] + 1.0 / chunk_state.max_res[res] as f32).min(1.0); - println!("Added {} to resources", 1.0 / chunk_state.max_res[res] as f32); - } - } - println!("Chunk resources are {:?}", chunk_res); - self.state.data.nature.set_chunk_resources(key, chunk_res); - } + pub fn hook_block_update(&mut self, wpos: Vec3, old: Block, new: Block) { + self.state.emit(event::OnBlockChange { wpos, old, new }); } } +struct ChunkStates(pub Grid>); + struct LoadedChunkState { // The maximum possible number of each resource in this chunk max_res: EnumMap, diff --git a/server/src/rtsim2/rule.rs b/server/src/rtsim2/rule.rs new file mode 100644 index 0000000000..41dd191507 --- /dev/null +++ b/server/src/rtsim2/rule.rs @@ -0,0 +1,9 @@ +pub mod deplete_resources; + +use tracing::info; +use rtsim2::RtState; + +pub fn start_rules(rtstate: &mut RtState) { + info!("Starting server rtsim rules..."); + rtstate.start_rule::(); +} diff --git a/server/src/rtsim2/rule/deplete_resources.rs b/server/src/rtsim2/rule/deplete_resources.rs new file mode 100644 index 0000000000..fe13a2d190 --- /dev/null +++ b/server/src/rtsim2/rule/deplete_resources.rs @@ -0,0 +1,47 @@ +use tracing::info; +use rtsim2::{RtState, Rule, RuleError}; +use crate::rtsim2::{ + event::OnBlockChange, + ChunkStates, +}; +use common::{ + terrain::TerrainChunk, + vol::RectRasterableVol, +}; + +pub struct State; + +impl Rule for State { + fn start(rtstate: &mut RtState) -> Result { + info!("Hello from the resource depletion rule!"); + + rtstate.bind::(|this, rtstate, event| { + let key = event.wpos + .xy() + .map2(TerrainChunk::RECT_SIZE, |e, sz| e.div_euclid(sz as i32)); + if let Some(Some(chunk_state)) = rtstate.resource_mut::().0.get(key) { + let mut chunk_res = rtstate.data().nature.get_chunk_resources(key); + // Remove resources + if let Some(res) = event.old.get_rtsim_resource() { + if chunk_state.max_res[res] > 0 { + chunk_res[res] = (chunk_res[res] * chunk_state.max_res[res] as f32 - 1.0) + .round() + .max(0.0) / chunk_state.max_res[res] as f32; + } + } + // Add resources + if let Some(res) = event.new.get_rtsim_resource() { + if chunk_state.max_res[res] > 0 { + chunk_res[res] = (chunk_res[res] * chunk_state.max_res[res] as f32 + 1.0) + .round() + .max(0.0) / chunk_state.max_res[res] as f32; + } + } + println!("Chunk resources = {:?}", chunk_res); + rtstate.data_mut().nature.set_chunk_resources(key, chunk_res); + } + }); + + Ok(Self) + } +} diff --git a/server/src/rtsim2/tick.rs b/server/src/rtsim2/tick.rs index 4e3e2b1e13..bf07399e7c 100644 --- a/server/src/rtsim2/tick.rs +++ b/server/src/rtsim2/tick.rs @@ -36,6 +36,8 @@ impl<'a> System<'a> for Sys { ) { let rtsim = &mut *rtsim; + rtsim.state.tick(dt.0); + if rtsim.last_saved.map_or(true, |ls| ls.elapsed() > Duration::from_secs(60)) { rtsim.save(&slow_jobs); }