Make resource depletion an rtsim rule

This commit is contained in:
Joshua Barretto 2022-08-10 00:02:56 +01:00
parent 0b06eaec6f
commit 9d3dadfaba
13 changed files with 305 additions and 86 deletions

9
Cargo.lock generated
View File

@ -145,6 +145,12 @@ version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
[[package]]
name = "anymap2"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c"
[[package]] [[package]]
name = "app_dirs2" name = "app_dirs2"
version = "2.5.4" version = "2.5.4"
@ -6939,11 +6945,14 @@ dependencies = [
name = "veloren-rtsim" name = "veloren-rtsim"
version = "0.10.0" version = "0.10.0"
dependencies = [ dependencies = [
"anymap2",
"atomic_refcell",
"enum-map", "enum-map",
"hashbrown 0.12.3", "hashbrown 0.12.3",
"rmp-serde", "rmp-serde",
"ron 0.8.0", "ron 0.8.0",
"serde", "serde",
"tracing",
"vek 0.15.8", "vek 0.15.8",
"veloren-common", "veloren-common",
"veloren-world", "veloren-world",

View File

@ -12,3 +12,6 @@ hashbrown = { version = "0.12", features = ["rayon", "serde", "nightly"] }
enum-map = { version = "2.4", features = ["serde"] } enum-map = { version = "2.4", features = ["serde"] }
vek = { version = "0.15.8", features = ["serde"] } vek = { version = "0.15.8", features = ["serde"] }
rmp-serde = "1.1.0" rmp-serde = "1.1.0"
anymap2 = "0.13"
tracing = "0.1"
atomic_refcell = "0.1"

View File

@ -7,6 +7,8 @@ use common::{
use world::World; use world::World;
use vek::*; use vek::*;
/// Represents the state of 'natural' elements of the world such as plant/animal/resource populations, weather systems,
/// etc.
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct Nature { pub struct Nature {
chunks: Grid<Chunk>, chunks: Grid<Chunk>,
@ -40,5 +42,6 @@ impl Nature {
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct Chunk { pub struct Chunk {
/// Represent the 'naturally occurring' resource proportion that exists in this chunk.
res: EnumMap<ChunkResource, f32>, res: EnumMap<ChunkResource, f32>,
} }

8
rtsim/src/event.rs Normal file
View File

@ -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 {}

View File

@ -1,10 +1,107 @@
pub mod data; #![feature(explicit_generic_args_with_impl_trait)]
pub mod gen;
use self::data::Data; pub mod data;
use std::sync::Arc; pub mod event;
use world::World; 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 struct RtState {
pub data: Data, resources: SendSyncAnyMap,
rules: SendSyncAnyMap,
event_handlers: SendSyncAnyMap,
}
type RuleState<R> = AtomicRefCell<R>;
type EventHandlersOf<E> = Vec<Box<dyn Fn(&RtState, E) + Send + Sync + 'static>>;
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<R: Send + Sync + 'static>(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::<rule::example::RuleState>();
}
pub fn start_rule<R: Rule>(&mut self) {
info!("Initiating '{}' rule...", type_name::<R>());
match R::start(self) {
Ok(rule) => { self.rules.insert::<RuleState<R>>(AtomicRefCell::new(rule)); },
Err(e) => error!("Error when initiating '{}' rule: {}", type_name::<R>(), e),
}
}
fn rule_mut<R: Rule>(&self) -> impl DerefMut<Target = R> + '_ {
self.rules
.get::<RuleState<R>>()
.unwrap_or_else(|| panic!("Tried to access rule '{}' but it does not exist", type_name::<R>()))
.borrow_mut()
}
pub fn bind<R: Rule, E: Event>(&mut self, mut f: impl FnMut(&mut R, &RtState, E) + Send + Sync + 'static) {
let f = AtomicRefCell::new(f);
self.event_handlers
.entry::<EventHandlersOf<E>>()
.or_default()
.push(Box::new(move |rtstate, event| {
(f.borrow_mut())(&mut rtstate.rule_mut(), rtstate, event)
}));
}
pub fn data(&self) -> impl Deref<Target = Data> + '_ { self.resource() }
pub fn data_mut(&self) -> impl DerefMut<Target = Data> + '_ { self.resource_mut() }
pub fn resource<R: Send + Sync + 'static>(&self) -> impl Deref<Target = R> + '_ {
self.resources
.get::<AtomicRefCell<R>>()
.unwrap_or_else(|| panic!("Tried to access resource '{}' but it does not exist", type_name::<R>()))
.borrow()
}
pub fn resource_mut<R: Send + Sync + 'static>(&self) -> impl DerefMut<Target = R> + '_ {
self.resources
.get::<AtomicRefCell<R>>()
.unwrap_or_else(|| panic!("Tried to access resource '{}' but it does not exist", type_name::<R>()))
.borrow_mut()
}
pub fn emit<E: Event>(&mut self, e: E) {
self.event_handlers
.get::<EventHandlersOf<E>>()
.map(|handlers| handlers
.iter()
.for_each(|f| f(self, e.clone())));
}
pub fn tick(&mut self, dt: f32) {
self.emit(OnTick { dt });
}
} }

21
rtsim/src/rule.rs Normal file
View File

@ -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<Self, RuleError>;
}

19
rtsim/src/rule/example.rs Normal file
View File

@ -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<Self, RuleError> {
info!("Hello from example rule!");
rtstate.bind::<Self, OnTick>(|this, rtstate, event| {
// println!("Tick!");
});
Ok(Self)
}
}

View File

@ -7,7 +7,8 @@
let_chains, let_chains,
never_type, never_type,
option_zip, option_zip,
unwrap_infallible unwrap_infallible,
explicit_generic_args_with_impl_trait
)] )]
#![feature(hash_drain_filter)] #![feature(hash_drain_filter)]

View File

@ -0,0 +1,12 @@
use rtsim2::Event;
use common::terrain::Block;
use vek::*;
#[derive(Clone)]
pub struct OnBlockChange {
pub wpos: Vec3<i32>,
pub old: Block,
pub new: Block,
}
impl Event for OnBlockChange {}

View File

@ -1,3 +1,5 @@
pub mod event;
pub mod rule;
pub mod tick; pub mod tick;
use common::{ use common::{
@ -8,7 +10,11 @@ use common::{
vol::RectRasterableVol, vol::RectRasterableVol,
}; };
use common_ecs::{dispatch, System}; use common_ecs::{dispatch, System};
use rtsim2::{data::{Data, ReadError}, RtState}; use rtsim2::{
data::{Data, ReadError},
rule::Rule,
RtState,
};
use specs::{DispatcherBuilder, WorldExt}; use specs::{DispatcherBuilder, WorldExt};
use std::{ use std::{
fs::{self, File}, fs::{self, File},
@ -19,14 +25,13 @@ use std::{
error::Error, error::Error,
}; };
use enum_map::EnumMap; use enum_map::EnumMap;
use tracing::{error, warn, info}; use tracing::{error, warn, info, debug};
use vek::*; use vek::*;
use world::World; use world::World;
pub struct RtSim { pub struct RtSim {
file_path: PathBuf, file_path: PathBuf,
last_saved: Option<Instant>, last_saved: Option<Instant>,
chunk_states: Grid<Option<LoadedChunkState>>,
state: RtState, state: RtState,
} }
@ -34,52 +39,54 @@ impl RtSim {
pub fn new(world: &World, data_dir: PathBuf) -> Result<Self, ron::Error> { pub fn new(world: &World, data_dir: PathBuf) -> Result<Self, ron::Error> {
let file_path = Self::get_file_path(data_dir); let file_path = Self::get_file_path(data_dir);
Ok(Self { info!("Looking for rtsim data in {}...", file_path.display());
chunk_states: Grid::populate_from(world.sim().get_size().as_(), |_| None), let data = 'load: {
last_saved: None, match File::open(&file_path) {
state: RtState { Ok(file) => {
data: { info!("Rtsim data found. Attempting to load...");
info!("Looking for rtsim state in {}...", file_path.display()); match Data::from_reader(io::BufReader::new(file)) {
'load: { Ok(data) => { info!("Rtsim data loaded."); break 'load data },
match File::open(&file_path) { Err(e) => {
Ok(file) => { error!("Rtsim data failed to load: {}", e);
info!("Rtsim state found. Attempting to load..."); let mut i = 0;
match Data::from_reader(io::BufReader::new(file)) { loop {
Ok(data) => { info!("Rtsim state loaded."); break 'load data }, let mut backup_path = file_path.clone();
Err(e) => { backup_path.set_extension(if i == 0 {
error!("Rtsim state failed to load: {}", e); format!("backup_{}", i)
let mut i = 0; } else {
loop { "ron_backup".to_string()
let mut backup_path = file_path.clone(); });
backup_path.set_extension(if i == 0 { if !backup_path.exists() {
format!("backup_{}", i) fs::rename(&file_path, &backup_path)?;
} else { warn!("Failed rtsim data was moved to {}", backup_path.display());
"ron_backup".to_string() info!("A fresh rtsim data will now be generated.");
}); break;
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;
}
},
} }
}, i += 1;
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
} }
}, },
}, 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, file_path,
}) };
rule::start_rules(&mut this.state);
Ok(this)
} }
fn get_file_path(mut data_dir: PathBuf) -> PathBuf { fn get_file_path(mut data_dir: PathBuf) -> PathBuf {
@ -89,31 +96,31 @@ impl RtSim {
data_dir.push("rtsim"); data_dir.push("rtsim");
data_dir data_dir
}); });
path.push("state.dat"); path.push("data.dat");
path path
} }
pub fn hook_load_chunk(&mut self, key: Vec2<i32>, max_res: EnumMap<ChunkResource, usize>) { pub fn hook_load_chunk(&mut self, key: Vec2<i32>, max_res: EnumMap<ChunkResource, usize>) {
if let Some(chunk_state) = self.chunk_states.get_mut(key) { if let Some(chunk_state) = self.state.resource_mut::<ChunkStates>().0.get_mut(key) {
*chunk_state = Some(LoadedChunkState { max_res }); *chunk_state = Some(LoadedChunkState { max_res });
} }
} }
pub fn hook_unload_chunk(&mut self, key: Vec2<i32>) { pub fn hook_unload_chunk(&mut self, key: Vec2<i32>) {
if let Some(chunk_state) = self.chunk_states.get_mut(key) { if let Some(chunk_state) = self.state.resource_mut::<ChunkStates>().0.get_mut(key) {
*chunk_state = None; *chunk_state = None;
} }
} }
pub fn save(&mut self, slowjob_pool: &SlowJobPool) { 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 file_path = self.file_path.clone();
let data = self.state.data.clone(); let data = self.state.data().clone();
info!("Starting rtsim save job..."); debug!("Starting rtsim data save job...");
// TODO: Use slow job // TODO: Use slow job
// slowjob_pool.spawn("RTSIM_SAVE", move || { // slowjob_pool.spawn("RTSIM_SAVE", move || {
std::thread::spawn(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 if let Err(e) = file_path
.parent() .parent()
.map(|dir| { .map(|dir| {
@ -127,16 +134,16 @@ impl RtSim {
}) })
.map_err(|e: io::Error| Box::new(e) as Box::<dyn Error>) .map_err(|e: io::Error| Box::new(e) as Box::<dyn Error>)
.and_then(|(mut file, tmp_file_path)| { .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))?; data.write_to(io::BufWriter::new(&mut file))?;
file.flush()?; file.flush()?;
drop(file); drop(file);
fs::rename(tmp_file_path, file_path)?; fs::rename(tmp_file_path, file_path)?;
info!("Rtsim state saved."); debug!("Rtsim data saved.");
Ok(()) Ok(())
}) })
{ {
error!("Saving rtsim state failed: {}", e); error!("Saving rtsim data failed: {}", e);
} }
}); });
self.last_saved = Some(Instant::now()); self.last_saved = Some(Instant::now());
@ -144,34 +151,15 @@ impl RtSim {
// TODO: Clean up this API a bit // TODO: Clean up this API a bit
pub fn get_chunk_resources(&self, key: Vec2<i32>) -> EnumMap<ChunkResource, f32> { pub fn get_chunk_resources(&self, key: Vec2<i32>) -> EnumMap<ChunkResource, f32> {
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<i32>, old_block: Block, new_block: Block) { pub fn hook_block_update(&mut self, wpos: Vec3<i32>, old: Block, new: Block) {
let key = wpos self.state.emit(event::OnBlockChange { wpos, old, new });
.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);
}
} }
} }
struct ChunkStates(pub Grid<Option<LoadedChunkState>>);
struct LoadedChunkState { struct LoadedChunkState {
// The maximum possible number of each resource in this chunk // The maximum possible number of each resource in this chunk
max_res: EnumMap<ChunkResource, usize>, max_res: EnumMap<ChunkResource, usize>,

View File

@ -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::<deplete_resources::State>();
}

View File

@ -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<Self, RuleError> {
info!("Hello from the resource depletion rule!");
rtstate.bind::<Self, OnBlockChange>(|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::<ChunkStates>().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)
}
}

View File

@ -36,6 +36,8 @@ impl<'a> System<'a> for Sys {
) { ) {
let rtsim = &mut *rtsim; let rtsim = &mut *rtsim;
rtsim.state.tick(dt.0);
if rtsim.last_saved.map_or(true, |ls| ls.elapsed() > Duration::from_secs(60)) { if rtsim.last_saved.map_or(true, |ls| ls.elapsed() > Duration::from_secs(60)) {
rtsim.save(&slow_jobs); rtsim.save(&slow_jobs);
} }