mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Make resource depletion an rtsim rule
This commit is contained in:
parent
0b06eaec6f
commit
9d3dadfaba
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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<Chunk>,
|
||||
@ -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<ChunkResource, f32>,
|
||||
}
|
||||
|
8
rtsim/src/event.rs
Normal file
8
rtsim/src/event.rs
Normal 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 {}
|
109
rtsim/src/lib.rs
109
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<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
21
rtsim/src/rule.rs
Normal 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
19
rtsim/src/rule/example.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -7,7 +7,8 @@
|
||||
let_chains,
|
||||
never_type,
|
||||
option_zip,
|
||||
unwrap_infallible
|
||||
unwrap_infallible,
|
||||
explicit_generic_args_with_impl_trait
|
||||
)]
|
||||
#![feature(hash_drain_filter)]
|
||||
|
||||
|
12
server/src/rtsim2/event.rs
Normal file
12
server/src/rtsim2/event.rs
Normal 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 {}
|
@ -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<Instant>,
|
||||
chunk_states: Grid<Option<LoadedChunkState>>,
|
||||
state: RtState,
|
||||
}
|
||||
|
||||
@ -34,52 +39,54 @@ impl RtSim {
|
||||
pub fn new(world: &World, data_dir: PathBuf) -> Result<Self, ron::Error> {
|
||||
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<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 });
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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::<dyn Error>)
|
||||
.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<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) {
|
||||
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<i32>, old: Block, new: Block) {
|
||||
self.state.emit(event::OnBlockChange { wpos, old, new });
|
||||
}
|
||||
}
|
||||
|
||||
struct ChunkStates(pub Grid<Option<LoadedChunkState>>);
|
||||
|
||||
struct LoadedChunkState {
|
||||
// The maximum possible number of each resource in this chunk
|
||||
max_res: EnumMap<ChunkResource, usize>,
|
||||
|
9
server/src/rtsim2/rule.rs
Normal file
9
server/src/rtsim2/rule.rs
Normal 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>();
|
||||
}
|
47
server/src/rtsim2/rule/deplete_resources.rs
Normal file
47
server/src/rtsim2/rule/deplete_resources.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user