Added rtsim saving, chunk resources, chunk resource depletion

This commit is contained in:
Joshua Barretto 2022-08-08 23:15:45 +01:00
parent d5e324bded
commit c168ff2f9b
26 changed files with 372 additions and 289 deletions

27
Cargo.lock generated
View File

@ -1839,6 +1839,27 @@ dependencies = [
"syn 1.0.100",
]
[[package]]
name = "enum-map"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5a56d54c8dd9b3ad34752ed197a4eb2a6601bc010808eb097a04a58ae4c43e1"
dependencies = [
"enum-map-derive",
"serde",
]
[[package]]
name = "enum-map-derive"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9045e2676cd5af83c3b167d917b0a5c90a4d8e266e2683d6631b235c457fc27"
dependencies = [
"proc-macro2 1.0.43",
"quote 1.0.21",
"syn 1.0.100",
]
[[package]]
name = "enumset"
version = "1.0.11"
@ -6655,6 +6676,8 @@ dependencies = [
"crossbeam-utils 0.8.11",
"csv",
"dot_vox",
"enum-iterator 1.1.3",
"enum-map",
"fxhash",
"hashbrown 0.12.3",
"indexmap",
@ -6888,9 +6911,11 @@ dependencies = [
name = "veloren-rtsim"
version = "0.10.0"
dependencies = [
"enum-map",
"hashbrown 0.12.3",
"ron 0.8.0",
"serde",
"vek 0.15.8",
"veloren-common",
"veloren-world",
]
@ -6907,6 +6932,7 @@ dependencies = [
"chrono-tz",
"crossbeam-channel",
"drop_guard",
"enum-map",
"enumset",
"futures-util",
"hashbrown 0.12.3",
@ -7120,6 +7146,7 @@ dependencies = [
"csv",
"deflate",
"enum-iterator 1.1.3",
"enum-map",
"fallible-iterator",
"flate2",
"fxhash",

View File

@ -1855,6 +1855,7 @@ impl Client {
true,
None,
&self.connected_server_constants,
|_, _, _, _| {},
);
// TODO: avoid emitting these in the first place
let _ = self

View File

@ -26,6 +26,8 @@ common-base = { package = "veloren-common-base", path = "base" }
serde = { version = "1.0.110", features = ["derive", "rc"] }
# Util
enum-iterator = "1.1.3"
enum-map = "2.4"
vek = { version = "0.15.8", features = ["serde"] }
cfg-if = "1.0.0"
chrono = "0.4.22"

View File

@ -8,7 +8,9 @@ use crate::{
lottery::LootSpec,
npc::{self, NPC_NAMES},
trade::SiteInformation,
rtsim,
};
use enum_map::EnumMap;
use serde::Deserialize;
use vek::*;
@ -449,6 +451,7 @@ impl EntityInfo {
#[derive(Default)]
pub struct ChunkSupplement {
pub entities: Vec<EntityInfo>,
pub rtsim_max_resources: EnumMap<rtsim::ChunkResource, usize>,
}
impl ChunkSupplement {

View File

@ -4,6 +4,7 @@
// module in `server`.
use specs::Component;
use serde::{Serialize, Deserialize};
use vek::*;
use crate::comp::dialogue::MoodState;
@ -82,3 +83,10 @@ impl RtSimController {
}
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, enum_map::Enum)]
pub enum ChunkResource {
Grass,
Flax,
Cotton,
}

View File

@ -4,6 +4,7 @@ use crate::{
consts::FRIC_GROUND,
lottery::LootSpec,
make_case_elim,
rtsim,
};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
@ -195,6 +196,26 @@ impl Block {
}
}
/// Returns the rtsim resource, if any, that this block corresponds to. If you want the scarcity of a block to change with rtsim's resource depletion tracking, you can do so by editing this function.
#[inline]
pub fn get_rtsim_resource(&self) -> Option<rtsim::ChunkResource> {
match self.get_sprite()? {
SpriteKind::LongGrass
| SpriteKind::MediumGrass
| SpriteKind::ShortGrass
| SpriteKind::LargeGrass
| SpriteKind::GrassSnow
| SpriteKind::GrassBlue
| SpriteKind::SavannaGrass
| SpriteKind::TallSavannaGrass
| SpriteKind::RedSavannaGrass
| SpriteKind::JungleRedGrass => Some(rtsim::ChunkResource::Grass),
SpriteKind::WildFlax => Some(rtsim::ChunkResource::Flax),
SpriteKind::Cotton => Some(rtsim::ChunkResource::Cotton),
_ => None,
}
}
#[inline]
pub fn get_glow(&self) -> Option<u8> {
match self.kind() {

View File

@ -524,7 +524,12 @@ impl State {
}
// Apply terrain changes
pub fn apply_terrain_changes(&self) { self.apply_terrain_changes_internal(false); }
pub fn apply_terrain_changes(
&self,
mut block_update: impl FnMut(&specs::World, Vec3<i32>, Block, Block),
) {
self.apply_terrain_changes_internal(false, block_update);
}
/// `during_tick` is true if and only if this is called from within
/// [State::tick].
@ -534,7 +539,11 @@ impl State {
/// from within both the client and the server ticks, right after
/// handling terrain messages; currently, client sets it to true and
/// server to false.
fn apply_terrain_changes_internal(&self, during_tick: bool) {
fn apply_terrain_changes_internal(
&self,
during_tick: bool,
mut block_update: impl FnMut(&specs::World, Vec3<i32>, Block, Block),
) {
span!(
_guard,
"apply_terrain_changes",
@ -575,14 +584,17 @@ impl State {
}
// Apply block modifications
// Only include in `TerrainChanges` if successful
modified_blocks.retain(|pos, block| {
let res = terrain.set(*pos, *block);
modified_blocks.retain(|pos, new_block| {
let res = terrain.map(*pos, |old_block| {
block_update(&self.ecs, *pos, old_block, *new_block);
*new_block
});
if let (&Ok(old_block), true) = (&res, during_tick) {
// NOTE: If the changes are applied during the tick, we push the *old* value as
// the modified block (since it otherwise can't be recovered after the tick).
// Otherwise, the changes will be applied after the tick, so we push the *new*
// value.
*block = old_block;
*new_block = old_block;
}
res.is_ok()
});
@ -597,6 +609,7 @@ impl State {
update_terrain_and_regions: bool,
mut metrics: Option<&mut StateTickMetrics>,
server_constants: &ServerConstants,
block_update: impl FnMut(&specs::World, Vec3<i32>, Block, Block),
) {
span!(_guard, "tick", "State::tick");
@ -643,7 +656,7 @@ impl State {
drop(guard);
if update_terrain_and_regions {
self.apply_terrain_changes_internal(true);
self.apply_terrain_changes_internal(true, block_update);
}
// Process local events

View File

@ -9,3 +9,5 @@ world = { package = "veloren-world", path = "../world" }
ron = "0.8"
serde = { version = "1.0.110", features = ["derive"] }
hashbrown = { version = "0.12", features = ["rayon", "serde", "nightly"] }
enum-map = { version = "2.4", features = ["serde"] }
vek = { version = "0.15.8", features = ["serde"] }

View File

@ -1,13 +1,16 @@
use hashbrown::HashMap;
use serde::{Serialize, Deserialize};
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActorId {
pub idx: u32,
pub gen: u32,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Actor {}
#[derive(Clone, Serialize, Deserialize)]
pub struct Actors {
pub actors: HashMap<ActorId, Actor>,
}

View File

@ -1,55 +0,0 @@
use serde::{
de::{DeserializeOwned, Error},
Deserialize, Deserializer, Serialize, Serializer,
};
#[derive(Copy, Clone, Default, PartialEq, Eq, Hash)]
pub struct V<T>(pub T);
impl<T: Serialize> Serialize for V<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
impl<'de, T: Version> Deserialize<'de> for V<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
T::try_from_value_compat(ron::Value::deserialize(deserializer)?)
.map(Self)
.map_err(|e| D::Error::custom(e))
}
}
impl<U, T: Latest<U>> Latest<U> for V<T> {
fn to_unversioned(self) -> U { self.0.to_unversioned() }
fn from_unversioned(x: &U) -> Self { Self(T::from_unversioned(x)) }
}
pub trait Latest<T> {
fn to_unversioned(self) -> T;
fn from_unversioned(x: &T) -> Self;
}
pub trait Version: Sized + DeserializeOwned {
type Prev: Version;
fn migrate(prev: Self::Prev) -> Self;
fn try_from_value_compat(value: ron::Value) -> Result<Self, ron::Error> {
value.clone().into_rust().or_else(|e| {
Ok(Self::migrate(
<Self as Version>::Prev::try_from_value_compat(value).map_err(|_| e)?,
))
})
}
}
#[derive(Deserialize)]
pub enum Bottom {}
impl Version for Bottom {
type Prev = Self;
fn migrate(prev: Self::Prev) -> Self { prev }
}

View File

@ -1,6 +1,3 @@
pub mod helper;
pub mod version;
pub mod actor;
pub mod nature;
@ -11,9 +8,10 @@ pub use self::{
use self::helper::Latest;
use ron::error::SpannedResult;
use serde::Deserialize;
use serde::{Serialize, Deserialize};
use std::io::{Read, Write};
#[derive(Clone, Serialize, Deserialize)]
pub struct Data {
pub nature: Nature,
pub actors: Actors,
@ -21,10 +19,10 @@ pub struct Data {
impl Data {
pub fn from_reader<R: Read>(reader: R) -> SpannedResult<Self> {
ron::de::from_reader(reader).map(version::LatestData::to_unversioned)
ron::de::from_reader(reader)
}
pub fn write_to<W: Write>(&self, writer: W) -> Result<(), ron::Error> {
ron::ser::to_writer(writer, &version::LatestData::from_unversioned(self))
ron::ser::to_writer(writer, self)
}
}

View File

@ -1 +1,44 @@
pub struct Nature {}
use serde::{Serialize, Deserialize};
use enum_map::EnumMap;
use common::{
grid::Grid,
rtsim::ChunkResource,
};
use world::World;
use vek::*;
#[derive(Clone, Serialize, Deserialize)]
pub struct Nature {
chunks: Grid<Chunk>,
}
impl Nature {
pub fn generate(world: &World) -> Self {
Self {
chunks: Grid::populate_from(
world.sim().get_size().map(|e| e as i32),
|pos| Chunk {
res: EnumMap::<_, f32>::default().map(|_, _| 1.0),
},
),
}
}
// TODO: Clean up this API a bit
pub fn get_chunk_resources(&self, key: Vec2<i32>) -> EnumMap<ChunkResource, f32> {
self.chunks
.get(key)
.map(|c| c.res)
.unwrap_or_default()
}
pub fn set_chunk_resources(&mut self, key: Vec2<i32>, res: EnumMap<ChunkResource, f32>) {
if let Some(chunk) = self.chunks.get_mut(key) {
chunk.res = res;
}
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Chunk {
res: EnumMap<ChunkResource, f32>,
}

View File

@ -1,85 +0,0 @@
use super::*;
use crate::data::{Actor, ActorId, Actors};
use hashbrown::HashMap;
// ActorId
impl Latest<ActorId> for ActorIdV0 {
fn to_unversioned(self) -> ActorId {
ActorId {
idx: self.idx,
gen: self.gen,
}
}
fn from_unversioned(id: &ActorId) -> Self {
Self {
idx: id.idx,
gen: id.gen,
}
}
}
#[derive(Serialize, Deserialize, Copy, Clone, Hash, PartialEq, Eq)]
pub struct ActorIdV0 {
pub idx: u32,
pub gen: u32,
}
impl Version for ActorIdV0 {
type Prev = Bottom;
fn migrate(x: Self::Prev) -> Self { match x {} }
}
// Actor
impl Latest<Actor> for ActorV0 {
fn to_unversioned(self) -> Actor { Actor {} }
fn from_unversioned(actor: &Actor) -> Self { Self {} }
}
#[derive(Serialize, Deserialize, Copy, Clone, Hash, PartialEq, Eq)]
pub struct ActorV0 {}
impl Version for ActorV0 {
type Prev = Bottom;
fn migrate(x: Self::Prev) -> Self { match x {} }
}
// Actors
impl Latest<Actors> for ActorsV0 {
fn to_unversioned(self) -> Actors {
Actors {
actors: self
.actors
.into_iter()
.map(|(k, v)| (k.to_unversioned(), v.to_unversioned()))
.collect(),
}
}
fn from_unversioned(actors: &Actors) -> Self {
Self {
actors: actors
.actors
.iter()
.map(|(k, v)| (Latest::from_unversioned(k), Latest::from_unversioned(v)))
.collect(),
}
}
}
#[derive(Serialize, Deserialize)]
pub struct ActorsV0 {
actors: HashMap<V<ActorIdV0>, V<ActorV0>>,
}
impl Version for ActorsV0 {
type Prev = Bottom;
fn migrate(x: Self::Prev) -> Self { match x {} }
}

View File

@ -1,84 +0,0 @@
// # Hey, you! Yes, you!
//
// Don't touch anything in this module, or any sub-modules. No, really. Bad
// stuff will happen.
//
// You're only an exception to this rule if you fulfil the following criteria:
//
// - You *really* understand exactly how the versioning system in `helper.rs`
// works, what assumptions it makes, and how all of this can go badly wrong.
//
// - You are creating a new version of a data structure, and *not* modifying an
// existing one.
//
// - You've thought really carefully about things and you've come to the
// conclusion that there's just no way to add the feature you want to add
// without creating a new version of the data structure in question.
//
// Please note that in *very specific* cases, it is possible to make a change to
// an existing data structure that is backward-compatible. For example, adding a
// new variant to an enum or a new field to a struct (where said field is
// annotated with `#[serde(default)]`) is generally considered to be a
// backward-compatible change.
//
// That said, here's how to make a breaking change to one of the structures in
// this module, or submodules.
//
// 1) Duplicate the latest version of the data structure and the `Version` impl
// for it (later versions should be kept at the top of each file).
//
// 2) Rename the duplicated version, incrementing the version number (i.e: V0
// becomes V1).
//
// 3) Change the `type Prev =` associated type in the new `Version` impl to the
// previous versions' type. You will need to write an implementation of
// `migrate` that migrates from the old version to the new version.
//
// 4) *Change* the existing `Latest` impl so that it uses the new version you
// have created.
//
// 5) If your data structure is contained within another data structure, you
// will need to similarly update the parent data structure too, also
// following these instructions.
//
// The *golden rule* is that, once merged to master, an old version's type must
// not be changed!
pub mod actor;
pub mod nature;
use super::{
helper::{Bottom, Latest, Version, V},
Data,
};
use serde::{Deserialize, Serialize};
pub type LatestData = DataV0;
impl Latest<Data> for LatestData {
fn to_unversioned(self) -> Data {
Data {
nature: self.nature.to_unversioned(),
actors: self.actors.to_unversioned(),
}
}
fn from_unversioned(data: &Data) -> Self {
Self {
nature: Latest::from_unversioned(&data.nature),
actors: Latest::from_unversioned(&data.actors),
}
}
}
#[derive(Serialize, Deserialize)]
pub struct DataV0 {
nature: V<nature::NatureV0>,
actors: V<actor::ActorsV0>,
}
impl Version for DataV0 {
type Prev = Bottom;
fn migrate(x: Self::Prev) -> Self { match x {} }
}

View File

@ -1,17 +0,0 @@
use super::*;
use crate::data::Nature;
impl Latest<crate::data::Nature> for NatureV0 {
fn to_unversioned(self) -> Nature { Nature {} }
fn from_unversioned(nature: &Nature) -> Self { Self {} }
}
#[derive(Serialize, Deserialize)]
pub struct NatureV0 {}
impl Version for NatureV0 {
type Prev = Bottom;
fn migrate(x: Self::Prev) -> Self { match x {} }
}

View File

@ -5,7 +5,7 @@ use world::World;
impl Data {
pub fn generate(world: &World) -> Self {
Self {
nature: Nature {},
nature: Nature::generate(world),
actors: Actors {
actors: HashMap::default(),
},

View File

@ -64,6 +64,7 @@ authc = { git = "https://gitlab.com/veloren/auth.git", rev = "fb3dcbc4962b367253
slab = "0.4"
rand_distr = "0.4.0"
enumset = "1.0.8"
enum-map = "2.4"
noise = { version = "0.7", default-features = false }
censor = "0.2"

View File

@ -1,4 +1,7 @@
use crate::metrics::ChunkGenMetrics;
use crate::{
metrics::ChunkGenMetrics,
rtsim2::RtSim,
};
#[cfg(not(feature = "worldgen"))]
use crate::test_world::{IndexOwned, World};
use common::{
@ -44,6 +47,10 @@ impl ChunkGenerator {
key: Vec2<i32>,
slowjob_pool: &SlowJobPool,
world: Arc<World>,
#[cfg(feature = "worldgen")]
rtsim: &RtSim,
#[cfg(not(feature = "worldgen"))]
rtsim: &(),
index: IndexOwned,
time: (TimeOfDay, Calendar),
) {
@ -56,10 +63,17 @@ impl ChunkGenerator {
v.insert(Arc::clone(&cancel));
let chunk_tx = self.chunk_tx.clone();
self.metrics.chunks_requested.inc();
// Get state for this chunk from rtsim
#[cfg(feature = "worldgen")]
let rtsim_resources = Some(rtsim.get_chunk_resources(key));
#[cfg(not(feature = "worldgen"))]
let rtsim_resources = None;
slowjob_pool.spawn("CHUNK_GENERATOR", move || {
let index = index.as_index_ref();
let payload = world
.generate_chunk(index, key, || cancel.load(Ordering::Relaxed), Some(time))
.generate_chunk(index, key, rtsim_resources, || cancel.load(Ordering::Relaxed), Some(time))
// FIXME: Since only the first entity who cancels a chunk is notified, we end up
// delaying chunk re-requests for up to 3 seconds for other clients, which isn't
// great. We *could* store all the other requesting clients here, but it could

View File

@ -82,7 +82,7 @@ use common::{
rtsim::RtSimEntity,
shared_server_config::ServerConstants,
slowjob::SlowJobPool,
terrain::{TerrainChunk, TerrainChunkSize},
terrain::{TerrainChunk, TerrainChunkSize, Block},
vol::RectRasterableVol,
};
use common_ecs::run_now;
@ -342,6 +342,7 @@ impl Server {
pool.configure("CHUNK_DROP", |_n| 1);
pool.configure("CHUNK_GENERATOR", |n| n / 2 + n / 4);
pool.configure("CHUNK_SERIALIZER", |n| n / 2);
pool.configure("RTSIM_SAVE", |_| 1);
}
state
.ecs_mut()
@ -700,6 +701,13 @@ impl Server {
let before_state_tick = Instant::now();
fn on_block_update(ecs: &specs::World, wpos: Vec3<i32>, old_block: Block, new_block: Block) {
// When a resource block updates, inform rtsim
if old_block.get_rtsim_resource().is_some() || new_block.get_rtsim_resource().is_some() {
ecs.write_resource::<rtsim2::RtSim>().hook_block_update(wpos, old_block, new_block);
}
}
// 4) Tick the server's LocalState.
// 5) Fetch any generated `TerrainChunk`s and insert them into the terrain.
// in sys/terrain.rs
@ -726,6 +734,7 @@ impl Server {
false,
Some(&mut state_tick_metrics),
&self.server_constants,
on_block_update,
);
let before_handle_events = Instant::now();
@ -749,7 +758,7 @@ impl Server {
self.state.update_region_map();
// NOTE: apply_terrain_changes sends the *new* value since it is not being
// synchronized during the tick.
self.state.apply_terrain_changes();
self.state.apply_terrain_changes(on_block_update);
let before_sync = Instant::now();
@ -994,6 +1003,10 @@ impl Server {
let mut chunk_generator = ecs.write_resource::<ChunkGenerator>();
let client = ecs.read_storage::<Client>();
let mut terrain = ecs.write_resource::<common::terrain::TerrainGrid>();
#[cfg(feature = "worldgen")]
let rtsim = ecs.read_resource::<rtsim2::RtSim>();
#[cfg(not(feature = "worldgen"))]
let rtsim = ();
// Cancel all pending chunks.
chunk_generator.cancel_all();
@ -1009,6 +1022,7 @@ impl Server {
pos,
&slow_jobs,
Arc::clone(world),
&rtsim,
index.clone(),
(
*ecs.read_resource::<TimeOfDay>(),
@ -1172,11 +1186,16 @@ impl Server {
pub fn generate_chunk(&mut self, entity: EcsEntity, key: Vec2<i32>) {
let ecs = self.state.ecs();
let slow_jobs = ecs.read_resource::<SlowJobPool>();
#[cfg(feature = "worldgen")]
let rtsim = ecs.read_resource::<rtsim2::RtSim>();
#[cfg(not(feature = "worldgen"))]
let rtsim = ();
ecs.write_resource::<ChunkGenerator>().generate_chunk(
Some(entity),
key,
&slow_jobs,
Arc::clone(&self.world),
&rtsim,
self.index.clone(),
(
*ecs.read_resource::<TimeOfDay>(),

View File

@ -1,17 +1,31 @@
pub mod tick;
use common::grid::Grid;
use common::{
grid::Grid,
slowjob::SlowJobPool,
rtsim::ChunkResource,
terrain::{TerrainChunk, Block},
vol::RectRasterableVol,
};
use common_ecs::{dispatch, System};
use rtsim2::{data::Data, RtState};
use specs::{DispatcherBuilder, WorldExt};
use std::{fs::File, io, path::PathBuf, sync::Arc};
use tracing::info;
use std::{
fs::{self, File},
path::PathBuf,
sync::Arc,
time::Instant,
io::{self, Write},
};
use enum_map::EnumMap;
use tracing::{error, warn, info};
use vek::*;
use world::World;
pub struct RtSim {
file_path: PathBuf,
chunk_states: Grid<bool>, // true = loaded
last_saved: Option<Instant>,
chunk_states: Grid<Option<LoadedChunkState>>,
state: RtState,
}
@ -20,20 +34,46 @@ impl RtSim {
let file_path = Self::get_file_path(data_dir);
Ok(Self {
chunk_states: Grid::populate_from(world.sim().get_size().as_(), |_| false),
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());
match File::open(&file_path) {
Ok(file) => {
info!("Rtsim state found. Attending to load...");
Data::from_reader(file)?
},
Err(e) if e.kind() == io::ErrorKind::NotFound => {
info!("No rtsim state found. Generating from initial world state...");
Data::generate(&world)
},
Err(e) => return Err(e.into()),
'load: {
match File::open(&file_path) {
Ok(file) => {
info!("Rtsim state found. Attempting to load...");
match Data::from_reader(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!("ron_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;
}
},
}
},
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
}
},
},
@ -52,17 +92,88 @@ impl RtSim {
path
}
pub fn hook_load_chunk(&mut self, key: Vec2<i32>) {
if let Some(is_loaded) = self.chunk_states.get_mut(key) {
*is_loaded = true;
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) {
*chunk_state = Some(LoadedChunkState { max_res });
}
}
pub fn hook_unload_chunk(&mut self, key: Vec2<i32>) {
if let Some(is_loaded) = self.chunk_states.get_mut(key) {
*is_loaded = false;
if let Some(chunk_state) = self.chunk_states.get_mut(key) {
*chunk_state = None;
}
}
pub fn save(&mut self, slowjob_pool: &SlowJobPool) {
info!("Beginning rtsim state save...");
let file_path = self.file_path.clone();
let data = self.state.data.clone();
info!("Starting rtsim save job...");
// TODO: Use slow job
// slowjob_pool.spawn("RTSIM_SAVE", move || {
std::thread::spawn(move || {
let tmp_file_name = "state_tmp.ron";
if let Err(e) = file_path
.parent()
.map(|dir| {
fs::create_dir_all(dir)?;
// We write to a temporary file and then rename to avoid corruption.
Ok(dir.join(tmp_file_name))
})
.unwrap_or_else(|| Ok(tmp_file_name.into()))
.and_then(|tmp_file_path| {
Ok((File::create(&tmp_file_path)?, tmp_file_path))
})
.map_err(|e: io::Error| ron::Error::from(e))
.and_then(|(mut file, tmp_file_path)| {
info!("Writing rtsim state to file...");
data.write_to(&mut file)?;
file.flush()?;
drop(file);
fs::rename(tmp_file_path, file_path)?;
info!("Rtsim state saved.");
Ok(())
})
{
error!("Saving rtsim state failed: {}", e);
}
});
self.last_saved = Some(Instant::now());
}
// 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)
}
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);
}
}
}
struct LoadedChunkState {
// The maximum possible number of each resource in this chunk
max_res: EnumMap<ChunkResource, usize>,
}
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {

View File

@ -7,10 +7,11 @@ use common::{
event::{EventBus, ServerEvent},
generation::{BodyBuilder, EntityConfig, EntityInfo},
resources::{DeltaTime, Time},
slowjob::SlowJobPool,
};
use common_ecs::{Job, Origin, Phase, System};
use specs::{Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage};
use std::sync::Arc;
use std::{sync::Arc, time::Duration};
#[derive(Default)]
pub struct Sys;
@ -22,6 +23,7 @@ impl<'a> System<'a> for Sys {
WriteExpect<'a, RtSim>,
ReadExpect<'a, Arc<world::World>>,
ReadExpect<'a, world::IndexOwned>,
ReadExpect<'a, SlowJobPool>,
);
const NAME: &'static str = "rtsim::tick";
@ -30,9 +32,14 @@ impl<'a> System<'a> for Sys {
fn run(
_job: &mut Job<Self>,
(dt, time, server_event_bus, mut rtsim, world, index): Self::SystemData,
(dt, time, server_event_bus, mut rtsim, world, index, slow_jobs): Self::SystemData,
) {
let rtsim = &mut *rtsim;
if rtsim.last_saved.map_or(true, |ls| ls.elapsed() > Duration::from_secs(60)) {
rtsim.save(&slow_jobs);
}
// rtsim.tick += 1;
// Update unloaded rtsim entities, in groups at a time

View File

@ -7,6 +7,7 @@ use crate::{
presence::{Presence, RepositionOnChunkLoad},
settings::Settings,
sys::sentinel::DeletedEntities,
rtsim2::RtSim,
wiring, BattleModeBuffer, SpawnPoint,
};
use common::{
@ -497,6 +498,10 @@ impl StateExt for State {
{
let ecs = self.ecs();
let slow_jobs = ecs.write_resource::<SlowJobPool>();
#[cfg(feature = "worldgen")]
let rtsim = ecs.read_resource::<RtSim>();
#[cfg(not(feature = "worldgen"))]
let rtsim = ();
let mut chunk_generator =
ecs.write_resource::<crate::chunk_generator::ChunkGenerator>();
let chunk_pos = self.terrain().pos_key(pos.0.map(|e| e as i32));
@ -517,7 +522,7 @@ impl StateExt for State {
#[cfg(feature = "worldgen")]
{
let time = (*ecs.read_resource::<TimeOfDay>(), (*ecs.read_resource::<Calendar>()).clone());
chunk_generator.generate_chunk(None, chunk_key, &slow_jobs, Arc::clone(world), index.clone(), time);
chunk_generator.generate_chunk(None, chunk_key, &slow_jobs, Arc::clone(world), &rtsim, index.clone(), time);
}
});
}

View File

@ -112,7 +112,7 @@ impl<'a> System<'a> for Sys {
mut terrain_changes,
mut chunk_requests,
//mut rtsim,
mut rtsim2,
mut rtsim,
mut _terrain_persistence,
mut positions,
presences,
@ -137,6 +137,7 @@ impl<'a> System<'a> for Sys {
request.key,
&slow_jobs,
Arc::clone(&world),
&rtsim,
index.clone(),
(*time_of_day, calendar.clone()),
)
@ -182,7 +183,7 @@ impl<'a> System<'a> for Sys {
} else {
terrain_changes.new_chunks.insert(key);
#[cfg(feature = "worldgen")]
rtsim2.hook_load_chunk(key);
rtsim.hook_load_chunk(key, supplement.rtsim_max_resources);
}
// Handle chunk supplement
@ -387,7 +388,7 @@ impl<'a> System<'a> for Sys {
terrain.remove(key).map(|chunk| {
terrain_changes.removed_chunks.insert(key);
#[cfg(feature = "worldgen")]
rtsim2.hook_unload_chunk(key);
rtsim.hook_unload_chunk(key);
chunk
})
})

View File

@ -21,6 +21,7 @@ common-dynlib = {package = "veloren-common-dynlib", path = "../common/dynlib", o
bincode = "1.3.1"
bitvec = "1.0.1"
enum-iterator = "1.1.3"
enum-map = "2.4"
fxhash = "0.2.1"
image = { version = "0.24", default-features = false, features = ["png"] }
itertools = "0.10"

View File

@ -141,6 +141,7 @@ pub struct Canvas<'a> {
pub(crate) info: CanvasInfo<'a>,
pub(crate) chunk: &'a mut TerrainChunk,
pub(crate) entities: Vec<EntityInfo>,
pub(crate) rtsim_resource_blocks: Vec<Vec3<i32>>,
}
impl<'a> Canvas<'a> {
@ -159,11 +160,20 @@ impl<'a> Canvas<'a> {
}
pub fn set(&mut self, pos: Vec3<i32>, block: Block) {
if block.get_rtsim_resource().is_some() {
self.rtsim_resource_blocks.push(pos);
}
let _ = self.chunk.set(pos - self.wpos(), block);
}
pub fn map(&mut self, pos: Vec3<i32>, f: impl FnOnce(Block) -> Block) {
let _ = self.chunk.map(pos - self.wpos(), f);
let _ = self.chunk.map(pos - self.wpos(), |b| {
let new_block = f(b);
if new_block.get_rtsim_resource().is_some() {
self.rtsim_resource_blocks.push(pos);
}
new_block
});
}
pub fn set_sprite_cfg(&mut self, pos: Vec3<i32>, sprite_cfg: SpriteCfg) {

View File

@ -53,8 +53,10 @@ use common::{
Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize, TerrainGrid,
},
vol::{ReadVol, RectVolSize, WriteVol},
rtsim::ChunkResource,
};
use common_net::msg::{world_msg, WorldMapMsg};
use enum_map::EnumMap;
use rand::{prelude::*, Rng};
use rand_chacha::ChaCha8Rng;
use rayon::iter::ParallelIterator;
@ -235,7 +237,7 @@ impl World {
// Unwrapping because generate_chunk only returns err when should_continue evals
// to true
let (tc, _cs) = self
.generate_chunk(index, chunk_pos, || false, None)
.generate_chunk(index, chunk_pos, None, || false, None)
.unwrap();
tc.find_accessible_pos(spawn_wpos, ascending)
@ -246,6 +248,7 @@ impl World {
&self,
index: IndexRef,
chunk_pos: Vec2<i32>,
rtsim_resources: Option<EnumMap<ChunkResource, f32>>,
// TODO: misleading name
mut should_continue: impl FnMut() -> bool,
time: Option<(TimeOfDay, Calendar)>,
@ -377,6 +380,7 @@ impl World {
},
chunk: &mut chunk,
entities: Vec::new(),
rtsim_resource_blocks: Vec::new(),
};
if index.features.train_tracks {
@ -416,9 +420,12 @@ impl World {
.iter()
.for_each(|site| index.sites[*site].apply_to(&mut canvas, &mut dynamic_rng));
let mut rtsim_resource_blocks = std::mem::take(&mut canvas.rtsim_resource_blocks);
let mut supplement = ChunkSupplement {
entities: canvas.entities,
entities: std::mem::take(&mut canvas.entities),
rtsim_max_resources: Default::default(),
};
drop(canvas);
let gen_entity_pos = |dynamic_rng: &mut ChaCha8Rng| {
let lpos2d = TerrainChunkSize::RECT_SIZE
@ -485,6 +492,33 @@ impl World {
// Finally, defragment to minimize space consumption.
chunk.defragment();
// Before we finish, we check candidate rtsim resource blocks, deduplicating positions and only keeping those
// that actually do have resources. Although this looks potentially very expensive, only blocks that are rtsim
// resources (i.e: a relatively small number of sprites) are processed here.
if let Some(rtsim_resources) = rtsim_resources {
rtsim_resource_blocks.sort_unstable_by_key(|pos| pos.into_array());
rtsim_resource_blocks.dedup();
for wpos in rtsim_resource_blocks {
chunk.map(
wpos - chunk_wpos2d.with_z(0),
|block| if let Some(res) = block.get_rtsim_resource() {
// Note: this represents the upper limit, not the actual number spanwed, so we increment this before deciding whether we're going to spawn the resource.
supplement.rtsim_max_resources[res] += 1;
// Throw a dice to determine whether this resource should actually spawn
// TODO: Don't throw a dice, try to generate the *exact* correct number
if dynamic_rng.gen_bool(rtsim_resources[res] as f64) {
block
} else {
block.into_vacant()
}
} else {
block
},
);
}
}
Ok((chunk, supplement))
}