mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Revert "Merge branch 'revert-b10718c5' into 'master'"
This reverts merge request !2172
This commit is contained in:
parent
880236f223
commit
1af4a04231
@ -74,6 +74,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Jump has been decreased in height but extended in length as a result of the new gravity
|
- Jump has been decreased in height but extended in length as a result of the new gravity
|
||||||
- Fall damage has been adjusted with the new gravity in mind
|
- Fall damage has been adjusted with the new gravity in mind
|
||||||
- Projectiles now generally have a different arc because they no longer have their own gravity modifier
|
- Projectiles now generally have a different arc because they no longer have their own gravity modifier
|
||||||
|
- Increased agent system target search efficiency speeding up the server
|
||||||
|
- Added more parallelization to terrain serialization and removed extra cloning speeding up the server
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -5443,7 +5443,7 @@ dependencies = [
|
|||||||
"veloren-common-frontend",
|
"veloren-common-frontend",
|
||||||
"veloren-common-net",
|
"veloren-common-net",
|
||||||
"veloren-common-state",
|
"veloren-common-state",
|
||||||
"veloren-common-sys",
|
"veloren-common-systems",
|
||||||
"veloren-network",
|
"veloren-network",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -5557,7 +5557,7 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "veloren-common-sys"
|
name = "veloren-common-systems"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
@ -5682,7 +5682,7 @@ dependencies = [
|
|||||||
"veloren-common-ecs",
|
"veloren-common-ecs",
|
||||||
"veloren-common-net",
|
"veloren-common-net",
|
||||||
"veloren-common-state",
|
"veloren-common-state",
|
||||||
"veloren-common-sys",
|
"veloren-common-systems",
|
||||||
"veloren-network",
|
"veloren-network",
|
||||||
"veloren-plugin-api",
|
"veloren-plugin-api",
|
||||||
"veloren-world",
|
"veloren-world",
|
||||||
@ -5770,7 +5770,7 @@ dependencies = [
|
|||||||
"veloren-common-frontend",
|
"veloren-common-frontend",
|
||||||
"veloren-common-net",
|
"veloren-common-net",
|
||||||
"veloren-common-state",
|
"veloren-common-state",
|
||||||
"veloren-common-sys",
|
"veloren-common-systems",
|
||||||
"veloren-server",
|
"veloren-server",
|
||||||
"veloren-voxygen-anim",
|
"veloren-voxygen-anim",
|
||||||
"veloren-world",
|
"veloren-world",
|
||||||
|
@ -7,7 +7,7 @@ members = [
|
|||||||
"common/ecs",
|
"common/ecs",
|
||||||
"common/net",
|
"common/net",
|
||||||
"common/state",
|
"common/state",
|
||||||
"common/sys",
|
"common/systems",
|
||||||
"common/frontend",
|
"common/frontend",
|
||||||
"client",
|
"client",
|
||||||
"plugin/api",
|
"plugin/api",
|
||||||
|
@ -5,7 +5,7 @@ authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
tracy = ["common/tracy", "common-base/tracy", "common-sys/tracy", "common-net/tracy", "common-frontend/tracy"]
|
tracy = ["common/tracy", "common-base/tracy", "common-state/tracy", "common-systems/tracy", "common-net/tracy", "common-frontend/tracy"]
|
||||||
simd = ["vek/platform_intrinsics"]
|
simd = ["vek/platform_intrinsics"]
|
||||||
plugins = ["common-state/plugins"]
|
plugins = ["common-state/plugins"]
|
||||||
bin_bot = ["common-ecs", "serde", "ron", "clap", "rustyline", "common-frontend", "async-channel"]
|
bin_bot = ["common-ecs", "serde", "ron", "clap", "rustyline", "common-frontend", "async-channel"]
|
||||||
@ -16,7 +16,7 @@ default = ["simd"]
|
|||||||
common = { package = "veloren-common", path = "../common", features = ["no-assets"] }
|
common = { package = "veloren-common", path = "../common", features = ["no-assets"] }
|
||||||
common-base = { package = "veloren-common-base", path = "../common/base" }
|
common-base = { package = "veloren-common-base", path = "../common/base" }
|
||||||
common-state = { package = "veloren-common-state", path = "../common/state", default-features = false }
|
common-state = { package = "veloren-common-state", path = "../common/state", default-features = false }
|
||||||
common-sys = { package = "veloren-common-sys", path = "../common/sys", default-features = false }
|
common-systems = { package = "veloren-common-systems", path = "../common/systems", default-features = false }
|
||||||
common-net = { package = "veloren-common-net", path = "../common/net" }
|
common-net = { package = "veloren-common-net", path = "../common/net" }
|
||||||
network = { package = "veloren-network", path = "../network", features = ["compression"], default-features = false }
|
network = { package = "veloren-network", path = "../network", features = ["compression"], default-features = false }
|
||||||
|
|
||||||
@ -32,6 +32,7 @@ vek = { version = "=0.14.1", features = ["serde"] }
|
|||||||
hashbrown = { version = "0.9", features = ["rayon", "serde", "nightly"] }
|
hashbrown = { version = "0.9", features = ["rayon", "serde", "nightly"] }
|
||||||
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "fb3dcbc4962b367253f8f2f92760ef44d2679c9a" }
|
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "fb3dcbc4962b367253f8f2f92760ef44d2679c9a" }
|
||||||
|
|
||||||
|
#TODO: put bot in a different crate
|
||||||
#bot only
|
#bot only
|
||||||
async-channel = { version = "1.6", optional = true }
|
async-channel = { version = "1.6", optional = true }
|
||||||
common-ecs = { package = "veloren-common-ecs", path = "../common/ecs", optional = true }
|
common-ecs = { package = "veloren-common-ecs", path = "../common/ecs", optional = true }
|
||||||
|
@ -55,7 +55,7 @@ use common_net::{
|
|||||||
sync::WorldSyncExt,
|
sync::WorldSyncExt,
|
||||||
};
|
};
|
||||||
use common_state::State;
|
use common_state::State;
|
||||||
use common_sys::add_local_systems;
|
use common_systems::add_local_systems;
|
||||||
use comp::BuffKind;
|
use comp::BuffKind;
|
||||||
use futures_util::FutureExt;
|
use futures_util::FutureExt;
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
@ -1924,7 +1924,7 @@ impl Client {
|
|||||||
match msg {
|
match msg {
|
||||||
ServerGeneral::TerrainChunkUpdate { key, chunk } => {
|
ServerGeneral::TerrainChunkUpdate { key, chunk } => {
|
||||||
if let Ok(chunk) = chunk {
|
if let Ok(chunk) = chunk {
|
||||||
self.state.insert_chunk(key, *chunk);
|
self.state.insert_chunk(key, chunk);
|
||||||
}
|
}
|
||||||
self.pending_chunks.remove(&key);
|
self.pending_chunks.remove(&key);
|
||||||
},
|
},
|
||||||
|
@ -12,7 +12,7 @@ use common::{
|
|||||||
};
|
};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::time::Duration;
|
use std::{sync::Arc, time::Duration};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
///This struct contains all messages the server might send (on different
|
///This struct contains all messages the server might send (on different
|
||||||
@ -106,7 +106,7 @@ pub enum ServerGeneral {
|
|||||||
// Ingame related AND terrain stream
|
// Ingame related AND terrain stream
|
||||||
TerrainChunkUpdate {
|
TerrainChunkUpdate {
|
||||||
key: Vec2<i32>,
|
key: Vec2<i32>,
|
||||||
chunk: Result<Box<TerrainChunk>, ()>,
|
chunk: Result<Arc<TerrainChunk>, ()>,
|
||||||
},
|
},
|
||||||
TerrainBlockUpdates(HashMap<Vec3<i32>, Block>),
|
TerrainBlockUpdates(HashMap<Vec3<i32>, Block>),
|
||||||
// Always possible
|
// Always possible
|
||||||
|
21
common/src/cached_spatial_grid.rs
Normal file
21
common/src/cached_spatial_grid.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use crate::util::SpatialGrid;
|
||||||
|
|
||||||
|
/// Cached [`SpatialGrid`] for reuse within different ecs systems during a tick.
|
||||||
|
/// This is used to accelerate queries on entities within a specific area.
|
||||||
|
/// Updated within the physics system [`crate::sys::phys::Sys`] after new entity
|
||||||
|
/// positions are calculated for the tick. So any position modifications outside
|
||||||
|
/// the physics system will not be reflected here until the next tick when the
|
||||||
|
/// physics system runs.
|
||||||
|
pub struct CachedSpatialGrid(pub SpatialGrid);
|
||||||
|
|
||||||
|
impl Default for CachedSpatialGrid {
|
||||||
|
fn default() -> Self {
|
||||||
|
let lg2_cell_size = 5; // 32
|
||||||
|
let lg2_large_cell_size = 6; // 64
|
||||||
|
let radius_cutoff = 8;
|
||||||
|
|
||||||
|
let spatial_grid = SpatialGrid::new(lg2_cell_size, lg2_large_cell_size, radius_cutoff);
|
||||||
|
|
||||||
|
Self(spatial_grid)
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,8 @@ pub use uuid;
|
|||||||
pub mod assets;
|
pub mod assets;
|
||||||
#[cfg(not(target_arch = "wasm32"))] pub mod astar;
|
#[cfg(not(target_arch = "wasm32"))] pub mod astar;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
mod cached_spatial_grid;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub mod character;
|
pub mod character;
|
||||||
#[cfg(not(target_arch = "wasm32"))] pub mod clock;
|
#[cfg(not(target_arch = "wasm32"))] pub mod clock;
|
||||||
#[cfg(not(target_arch = "wasm32"))] pub mod cmd;
|
#[cfg(not(target_arch = "wasm32"))] pub mod cmd;
|
||||||
@ -78,6 +80,8 @@ pub mod uid;
|
|||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub mod volumes;
|
pub mod volumes;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub use cached_spatial_grid::CachedSpatialGrid;
|
||||||
pub use combat::DamageSource;
|
pub use combat::DamageSource;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub use combat::{Damage, GroupTarget, Knockback, KnockbackDir};
|
pub use combat::{Damage, GroupTarget, Knockback, KnockbackDir};
|
||||||
|
@ -4,6 +4,9 @@ pub mod find_dist;
|
|||||||
mod option;
|
mod option;
|
||||||
pub mod plane;
|
pub mod plane;
|
||||||
pub mod projection;
|
pub mod projection;
|
||||||
|
/// Contains [`SpatialGrid`] which is useful for accelerating queries of nearby
|
||||||
|
/// entities
|
||||||
|
mod spatial_grid;
|
||||||
|
|
||||||
pub const GIT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/githash"));
|
pub const GIT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/githash"));
|
||||||
pub const GIT_TAG: &str = include_str!(concat!(env!("OUT_DIR"), "/gittag"));
|
pub const GIT_TAG: &str = include_str!(concat!(env!("OUT_DIR"), "/gittag"));
|
||||||
@ -28,6 +31,7 @@ lazy_static::lazy_static! {
|
|||||||
|
|
||||||
pub use color::*;
|
pub use color::*;
|
||||||
pub use dir::*;
|
pub use dir::*;
|
||||||
pub use option::*;
|
pub use option::either_with;
|
||||||
pub use plane::*;
|
pub use plane::Plane;
|
||||||
pub use projection::*;
|
pub use projection::Projection;
|
||||||
|
pub use spatial_grid::SpatialGrid;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct SpatialGrid {
|
pub struct SpatialGrid {
|
||||||
// Uses two scales of grids so that we can have a hard limit on how far to search in the
|
// Uses two scales of grids so that we can have a hard limit on how far to search in the
|
||||||
// smaller grid
|
// smaller grid
|
||||||
@ -51,8 +52,6 @@ impl SpatialGrid {
|
|||||||
/// provided axis aligned bounding region
|
/// provided axis aligned bounding region
|
||||||
/// NOTE: for best optimization of the iterator use `for_each` rather than a
|
/// NOTE: for best optimization of the iterator use `for_each` rather than a
|
||||||
/// for loop
|
/// for loop
|
||||||
// TODO: a circle would be tighter (how efficient would it be to query the cells
|
|
||||||
// intersecting a circle?)
|
|
||||||
pub fn in_aabr<'a>(&'a self, aabr: Aabr<i32>) -> impl Iterator<Item = specs::Entity> + 'a {
|
pub fn in_aabr<'a>(&'a self, aabr: Aabr<i32>) -> impl Iterator<Item = specs::Entity> + 'a {
|
||||||
let iter = |max_entity_radius, grid: &'a hashbrown::HashMap<_, _>, lg2_cell_size| {
|
let iter = |max_entity_radius, grid: &'a hashbrown::HashMap<_, _>, lg2_cell_size| {
|
||||||
// Add buffer for other entity radius
|
// Add buffer for other entity radius
|
||||||
@ -74,4 +73,36 @@ impl SpatialGrid {
|
|||||||
self.lg2_large_cell_size,
|
self.lg2_large_cell_size,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get an iterator over the entities overlapping the
|
||||||
|
/// axis aligned bounding region that contains the provided circle
|
||||||
|
/// NOTE: for best optimization of the iterator use `for_each` rather than a
|
||||||
|
/// for loop
|
||||||
|
// TODO: using the circle directly would be tighter (how efficient would it be
|
||||||
|
// to query the cells intersecting a circle?) (note: if doing this rename
|
||||||
|
// the function)
|
||||||
|
pub fn in_circle_aabr(
|
||||||
|
&self,
|
||||||
|
center: Vec2<f32>,
|
||||||
|
radius: f32,
|
||||||
|
) -> impl Iterator<Item = specs::Entity> + '_ {
|
||||||
|
let center = center.map(|e| e as i32);
|
||||||
|
let radius = radius.ceil() as i32;
|
||||||
|
// From conversion of center above
|
||||||
|
const CENTER_TRUNCATION_ERROR: i32 = 1;
|
||||||
|
let max_dist = radius + CENTER_TRUNCATION_ERROR;
|
||||||
|
|
||||||
|
let aabr = Aabr {
|
||||||
|
min: center - max_dist,
|
||||||
|
max: center + max_dist,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.in_aabr(aabr)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.grid.clear();
|
||||||
|
self.large_grid.clear();
|
||||||
|
self.largest_large_radius = self.radius_cutoff;
|
||||||
|
}
|
||||||
}
|
}
|
54
common/state/src/build_areas.rs
Normal file
54
common/state/src/build_areas.rs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
use common::depot::{Depot, Id};
|
||||||
|
use hashbrown::{hash_map, HashMap};
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
/// NOTE: Please don't add `Deserialize` without checking to make sure we
|
||||||
|
/// can guarantee the invariant that every entry in `area_names` points to a
|
||||||
|
/// valid id in `areas`.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct BuildAreas {
|
||||||
|
areas: Depot<Aabb<i32>>,
|
||||||
|
area_names: HashMap<String, Id<Aabb<i32>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum BuildAreaError {
|
||||||
|
/// This build area name is reserved by the system.
|
||||||
|
Reserved,
|
||||||
|
/// The build area name was not found.
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build area names that can only be inserted, not removed.
|
||||||
|
const RESERVED_BUILD_AREA_NAMES: &[&str] = &["world"];
|
||||||
|
|
||||||
|
impl BuildAreas {
|
||||||
|
pub fn areas(&self) -> &Depot<geom::Aabb<i32>> { &self.areas }
|
||||||
|
|
||||||
|
pub fn area_names(&self) -> &HashMap<String, Id<Aabb<i32>>> { &self.area_names }
|
||||||
|
|
||||||
|
/// If the area_name is already in the map, returns Err(area_name).
|
||||||
|
pub fn insert(&mut self, area_name: String, area: Aabb<i32>) -> Result<Id<Aabb<i32>>, String> {
|
||||||
|
let area_name_entry = match self.area_names.entry(area_name) {
|
||||||
|
hash_map::Entry::Occupied(o) => return Err(o.replace_key()),
|
||||||
|
hash_map::Entry::Vacant(v) => v,
|
||||||
|
};
|
||||||
|
let bb_id = self.areas.insert(area.made_valid());
|
||||||
|
area_name_entry.insert(bb_id);
|
||||||
|
Ok(bb_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, area_name: &str) -> Result<Aabb<i32>, BuildAreaError> {
|
||||||
|
if RESERVED_BUILD_AREA_NAMES.contains(&area_name) {
|
||||||
|
return Err(BuildAreaError::Reserved);
|
||||||
|
}
|
||||||
|
let bb_id = self
|
||||||
|
.area_names
|
||||||
|
.remove(area_name)
|
||||||
|
.ok_or(BuildAreaError::NotFound)?;
|
||||||
|
let area = self.areas.remove(bb_id).expect(
|
||||||
|
"Entries in `areas` are added before entries in `area_names` in `insert`, and that is \
|
||||||
|
the only exposed way to add elements to `area_names`.",
|
||||||
|
);
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
}
|
@ -1,589 +1,9 @@
|
|||||||
|
//! This crate contains the [`State`] and shared between
|
||||||
|
//! server (`veloren-server`) and the client (`veloren-client`)
|
||||||
|
|
||||||
|
mod build_areas;
|
||||||
#[cfg(feature = "plugins")] pub mod plugin;
|
#[cfg(feature = "plugins")] pub mod plugin;
|
||||||
|
mod state;
|
||||||
#[cfg(feature = "plugins")]
|
// TODO: breakup state module and remove glob
|
||||||
use crate::plugin::memory_manager::EcsWorld;
|
pub use build_areas::{BuildAreaError, BuildAreas};
|
||||||
#[cfg(feature = "plugins")]
|
pub use state::{BlockChange, State, TerrainChanges};
|
||||||
use crate::plugin::PluginMgr;
|
|
||||||
#[cfg(feature = "plugins")]
|
|
||||||
use common::uid::UidAllocator;
|
|
||||||
use common::{
|
|
||||||
comp,
|
|
||||||
depot::{Depot, Id},
|
|
||||||
event::{EventBus, LocalEvent, ServerEvent},
|
|
||||||
outcome::Outcome,
|
|
||||||
region::RegionMap,
|
|
||||||
resources::{DeltaTime, GameMode, PlayerEntity, PlayerPhysicsSettings, Time, TimeOfDay},
|
|
||||||
slowjob::SlowJobPool,
|
|
||||||
terrain::{Block, TerrainChunk, TerrainGrid},
|
|
||||||
time::DayPeriod,
|
|
||||||
trade::Trades,
|
|
||||||
vol::{ReadVol, WriteVol},
|
|
||||||
};
|
|
||||||
use common_base::span;
|
|
||||||
use common_ecs::{PhysicsMetrics, SysMetrics};
|
|
||||||
use common_net::sync::{interpolation as sync_interp, WorldSyncExt};
|
|
||||||
use core::{convert::identity, time::Duration};
|
|
||||||
use hashbrown::{hash_map, HashMap, HashSet};
|
|
||||||
use rayon::{ThreadPool, ThreadPoolBuilder};
|
|
||||||
use specs::{
|
|
||||||
prelude::Resource,
|
|
||||||
shred::{Fetch, FetchMut},
|
|
||||||
storage::{MaskedStorage as EcsMaskedStorage, Storage as EcsStorage},
|
|
||||||
Component, DispatcherBuilder, Entity as EcsEntity, WorldExt,
|
|
||||||
};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use vek::*;
|
|
||||||
|
|
||||||
/// How much faster should an in-game day be compared to a real day?
|
|
||||||
// TODO: Don't hard-code this.
|
|
||||||
const DAY_CYCLE_FACTOR: f64 = 24.0 * 2.0;
|
|
||||||
|
|
||||||
/// At what point should we stop speeding up physics to compensate for lag? If
|
|
||||||
/// we speed physics up too fast, we'd skip important physics events like
|
|
||||||
/// collisions. This constant determines the upper limit. If delta time exceeds
|
|
||||||
/// this value, the game's physics will begin to produce time lag. Ideally, we'd
|
|
||||||
/// avoid such a situation.
|
|
||||||
const MAX_DELTA_TIME: f32 = 1.0;
|
|
||||||
|
|
||||||
/// NOTE: Please don't add `Deserialize` without checking to make sure we
|
|
||||||
/// can guarantee the invariant that every entry in `area_names` points to a
|
|
||||||
/// valid id in `areas`.
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct BuildAreas {
|
|
||||||
areas: Depot<Aabb<i32>>,
|
|
||||||
area_names: HashMap<String, Id<Aabb<i32>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum BuildAreaError {
|
|
||||||
/// This build area name is reserved by the system.
|
|
||||||
Reserved,
|
|
||||||
/// The build area name was not found.
|
|
||||||
NotFound,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build area names that can only be inserted, not removed.
|
|
||||||
const RESERVED_BUILD_AREA_NAMES: &[&str] = &["world"];
|
|
||||||
|
|
||||||
impl BuildAreas {
|
|
||||||
pub fn areas(&self) -> &Depot<geom::Aabb<i32>> { &self.areas }
|
|
||||||
|
|
||||||
pub fn area_names(&self) -> &HashMap<String, Id<Aabb<i32>>> { &self.area_names }
|
|
||||||
|
|
||||||
/// If the area_name is already in the map, returns Err(area_name).
|
|
||||||
pub fn insert(&mut self, area_name: String, area: Aabb<i32>) -> Result<Id<Aabb<i32>>, String> {
|
|
||||||
let area_name_entry = match self.area_names.entry(area_name) {
|
|
||||||
hash_map::Entry::Occupied(o) => return Err(o.replace_key()),
|
|
||||||
hash_map::Entry::Vacant(v) => v,
|
|
||||||
};
|
|
||||||
let bb_id = self.areas.insert(area.made_valid());
|
|
||||||
area_name_entry.insert(bb_id);
|
|
||||||
Ok(bb_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(&mut self, area_name: &str) -> Result<Aabb<i32>, BuildAreaError> {
|
|
||||||
if RESERVED_BUILD_AREA_NAMES.contains(&area_name) {
|
|
||||||
return Err(BuildAreaError::Reserved);
|
|
||||||
}
|
|
||||||
let bb_id = self
|
|
||||||
.area_names
|
|
||||||
.remove(area_name)
|
|
||||||
.ok_or(BuildAreaError::NotFound)?;
|
|
||||||
let area = self.areas.remove(bb_id).expect(
|
|
||||||
"Entries in `areas` are added before entries in `area_names` in `insert`, and that is \
|
|
||||||
the only exposed way to add elements to `area_names`.",
|
|
||||||
);
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct BlockChange {
|
|
||||||
blocks: HashMap<Vec3<i32>, Block>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockChange {
|
|
||||||
pub fn set(&mut self, pos: Vec3<i32>, block: Block) { self.blocks.insert(pos, block); }
|
|
||||||
|
|
||||||
pub fn try_set(&mut self, pos: Vec3<i32>, block: Block) -> Option<()> {
|
|
||||||
if !self.blocks.contains_key(&pos) {
|
|
||||||
self.blocks.insert(pos, block);
|
|
||||||
Some(())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear(&mut self) { self.blocks.clear(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct TerrainChanges {
|
|
||||||
pub new_chunks: HashSet<Vec2<i32>>,
|
|
||||||
pub modified_chunks: HashSet<Vec2<i32>>,
|
|
||||||
pub removed_chunks: HashSet<Vec2<i32>>,
|
|
||||||
pub modified_blocks: HashMap<Vec3<i32>, Block>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TerrainChanges {
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
self.new_chunks.clear();
|
|
||||||
self.modified_chunks.clear();
|
|
||||||
self.removed_chunks.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub enum ExecMode {
|
|
||||||
Server,
|
|
||||||
Client,
|
|
||||||
Singleplayer,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A type used to represent game state stored on both the client and the
|
|
||||||
/// server. This includes things like entity components, terrain data, and
|
|
||||||
/// global states like weather, time of day, etc.
|
|
||||||
pub struct State {
|
|
||||||
ecs: specs::World,
|
|
||||||
// Avoid lifetime annotation by storing a thread pool instead of the whole dispatcher
|
|
||||||
thread_pool: Arc<ThreadPool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
/// Create a new `State` in client mode.
|
|
||||||
pub fn client() -> Self { Self::new(GameMode::Client) }
|
|
||||||
|
|
||||||
/// Create a new `State` in server mode.
|
|
||||||
pub fn server() -> Self { Self::new(GameMode::Server) }
|
|
||||||
|
|
||||||
pub fn new(game_mode: GameMode) -> Self {
|
|
||||||
let thread_name_infix = match game_mode {
|
|
||||||
GameMode::Server => "s",
|
|
||||||
GameMode::Client => "c",
|
|
||||||
GameMode::Singleplayer => "sp",
|
|
||||||
};
|
|
||||||
|
|
||||||
let thread_pool = Arc::new(
|
|
||||||
ThreadPoolBuilder::new()
|
|
||||||
.thread_name(move |i| format!("rayon-{}-{}", thread_name_infix, i))
|
|
||||||
.build()
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
Self {
|
|
||||||
ecs: Self::setup_ecs_world(game_mode, &thread_pool),
|
|
||||||
thread_pool,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates ecs world and registers all the common components and resources
|
|
||||||
// TODO: Split up registering into server and client (e.g. move
|
|
||||||
// EventBus<ServerEvent> to the server)
|
|
||||||
fn setup_ecs_world(game_mode: GameMode, thread_pool: &Arc<ThreadPool>) -> specs::World {
|
|
||||||
let mut ecs = specs::World::new();
|
|
||||||
// Uids for sync
|
|
||||||
ecs.register_sync_marker();
|
|
||||||
// Register server -> all clients synced components.
|
|
||||||
ecs.register::<comp::Body>();
|
|
||||||
ecs.register::<comp::Player>();
|
|
||||||
ecs.register::<comp::Stats>();
|
|
||||||
ecs.register::<comp::SkillSet>();
|
|
||||||
ecs.register::<comp::Buffs>();
|
|
||||||
ecs.register::<comp::Auras>();
|
|
||||||
ecs.register::<comp::Energy>();
|
|
||||||
ecs.register::<comp::Combo>();
|
|
||||||
ecs.register::<comp::Health>();
|
|
||||||
ecs.register::<comp::Poise>();
|
|
||||||
ecs.register::<comp::CanBuild>();
|
|
||||||
ecs.register::<comp::LightEmitter>();
|
|
||||||
ecs.register::<comp::Item>();
|
|
||||||
ecs.register::<comp::Scale>();
|
|
||||||
ecs.register::<comp::Mounting>();
|
|
||||||
ecs.register::<comp::MountState>();
|
|
||||||
ecs.register::<comp::Mass>();
|
|
||||||
ecs.register::<comp::Density>();
|
|
||||||
ecs.register::<comp::Collider>();
|
|
||||||
ecs.register::<comp::Sticky>();
|
|
||||||
ecs.register::<comp::CharacterState>();
|
|
||||||
ecs.register::<comp::Object>();
|
|
||||||
ecs.register::<comp::Group>();
|
|
||||||
ecs.register::<comp::Shockwave>();
|
|
||||||
ecs.register::<comp::ShockwaveHitEntities>();
|
|
||||||
ecs.register::<comp::BeamSegment>();
|
|
||||||
|
|
||||||
// Register components send from clients -> server
|
|
||||||
ecs.register::<comp::Controller>();
|
|
||||||
|
|
||||||
// Register components send directly from server -> all but one client
|
|
||||||
ecs.register::<comp::PhysicsState>();
|
|
||||||
|
|
||||||
// Register components synced from client -> server -> all other clients
|
|
||||||
ecs.register::<comp::Pos>();
|
|
||||||
ecs.register::<comp::Vel>();
|
|
||||||
ecs.register::<comp::Ori>();
|
|
||||||
ecs.register::<comp::Inventory>();
|
|
||||||
|
|
||||||
// Register common unsynced components
|
|
||||||
ecs.register::<comp::PreviousPhysCache>();
|
|
||||||
ecs.register::<comp::PosVelDefer>();
|
|
||||||
|
|
||||||
// Register client-local components
|
|
||||||
// TODO: only register on the client
|
|
||||||
ecs.register::<comp::LightAnimation>();
|
|
||||||
ecs.register::<sync_interp::InterpBuffer<comp::Pos>>();
|
|
||||||
ecs.register::<sync_interp::InterpBuffer<comp::Vel>>();
|
|
||||||
ecs.register::<sync_interp::InterpBuffer<comp::Ori>>();
|
|
||||||
|
|
||||||
// Register server-local components
|
|
||||||
// TODO: only register on the server
|
|
||||||
ecs.register::<comp::Last<comp::Pos>>();
|
|
||||||
ecs.register::<comp::Last<comp::Vel>>();
|
|
||||||
ecs.register::<comp::Last<comp::Ori>>();
|
|
||||||
ecs.register::<comp::Alignment>();
|
|
||||||
ecs.register::<comp::Agent>();
|
|
||||||
ecs.register::<comp::WaypointArea>();
|
|
||||||
ecs.register::<comp::ForceUpdate>();
|
|
||||||
ecs.register::<comp::InventoryUpdate>();
|
|
||||||
ecs.register::<comp::Admin>();
|
|
||||||
ecs.register::<comp::Waypoint>();
|
|
||||||
ecs.register::<comp::Projectile>();
|
|
||||||
ecs.register::<comp::Melee>();
|
|
||||||
ecs.register::<comp::ItemDrop>();
|
|
||||||
ecs.register::<comp::ChatMode>();
|
|
||||||
ecs.register::<comp::Faction>();
|
|
||||||
ecs.register::<comp::invite::Invite>();
|
|
||||||
ecs.register::<comp::invite::PendingInvites>();
|
|
||||||
ecs.register::<comp::Beam>();
|
|
||||||
|
|
||||||
// Register synced resources used by the ECS.
|
|
||||||
ecs.insert(TimeOfDay(0.0));
|
|
||||||
|
|
||||||
// Register unsynced resources used by the ECS.
|
|
||||||
ecs.insert(Time(0.0));
|
|
||||||
ecs.insert(DeltaTime(0.0));
|
|
||||||
ecs.insert(PlayerEntity(None));
|
|
||||||
ecs.insert(TerrainGrid::new().unwrap());
|
|
||||||
ecs.insert(BlockChange::default());
|
|
||||||
ecs.insert(BuildAreas::default());
|
|
||||||
ecs.insert(TerrainChanges::default());
|
|
||||||
ecs.insert(EventBus::<LocalEvent>::default());
|
|
||||||
ecs.insert(game_mode);
|
|
||||||
ecs.insert(Vec::<common::outcome::Outcome>::new());
|
|
||||||
|
|
||||||
let slow_limit = thread_pool.current_num_threads().max(2) as u64;
|
|
||||||
let slow_limit = slow_limit / 2 + slow_limit / 4;
|
|
||||||
tracing::trace!(?slow_limit, "Slow Thread limit");
|
|
||||||
ecs.insert(SlowJobPool::new(slow_limit, Arc::clone(&thread_pool)));
|
|
||||||
|
|
||||||
// TODO: only register on the server
|
|
||||||
ecs.insert(EventBus::<ServerEvent>::default());
|
|
||||||
ecs.insert(comp::group::GroupManager::default());
|
|
||||||
ecs.insert(RegionMap::new());
|
|
||||||
ecs.insert(SysMetrics::default());
|
|
||||||
ecs.insert(PhysicsMetrics::default());
|
|
||||||
ecs.insert(Trades::default());
|
|
||||||
ecs.insert(PlayerPhysicsSettings::default());
|
|
||||||
|
|
||||||
// Load plugins from asset directory
|
|
||||||
#[cfg(feature = "plugins")]
|
|
||||||
ecs.insert(match PluginMgr::from_assets() {
|
|
||||||
Ok(plugin_mgr) => {
|
|
||||||
let ecs_world = EcsWorld {
|
|
||||||
entities: &ecs.entities(),
|
|
||||||
health: ecs.read_component().into(),
|
|
||||||
uid: ecs.read_component().into(),
|
|
||||||
uid_allocator: &ecs.read_resource::<UidAllocator>().into(),
|
|
||||||
player: ecs.read_component().into(),
|
|
||||||
};
|
|
||||||
if let Err(e) = plugin_mgr
|
|
||||||
.execute_event(&ecs_world, &plugin_api::event::PluginLoadEvent {
|
|
||||||
game_mode,
|
|
||||||
})
|
|
||||||
{
|
|
||||||
tracing::debug!(?e, "Failed to run plugin init");
|
|
||||||
tracing::info!("Plugins disabled, enable debug logging for more information.");
|
|
||||||
PluginMgr::default()
|
|
||||||
} else {
|
|
||||||
plugin_mgr
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
tracing::debug!(?e, "Failed to read plugins from assets");
|
|
||||||
tracing::info!("Plugins disabled, enable debug logging for more information.");
|
|
||||||
PluginMgr::default()
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
ecs
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register a component with the state's ECS.
|
|
||||||
pub fn with_component<T: Component>(mut self) -> Self
|
|
||||||
where
|
|
||||||
<T as Component>::Storage: Default,
|
|
||||||
{
|
|
||||||
self.ecs.register::<T>();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write a component attributed to a particular entity, ignoring errors.
|
|
||||||
///
|
|
||||||
/// This should be used *only* when we can guarantee that the rest of the
|
|
||||||
/// code does not rely on the insert having succeeded (meaning the
|
|
||||||
/// entity is no longer alive!).
|
|
||||||
///
|
|
||||||
/// Returns None if the entity was dead or there was no previous entry for
|
|
||||||
/// this component; otherwise, returns Some(old_component).
|
|
||||||
pub fn write_component_ignore_entity_dead<C: Component>(
|
|
||||||
&mut self,
|
|
||||||
entity: EcsEntity,
|
|
||||||
comp: C,
|
|
||||||
) -> Option<C> {
|
|
||||||
self.ecs
|
|
||||||
.write_storage()
|
|
||||||
.insert(entity, comp)
|
|
||||||
.ok()
|
|
||||||
.and_then(identity)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete a component attributed to a particular entity.
|
|
||||||
pub fn delete_component<C: Component>(&mut self, entity: EcsEntity) -> Option<C> {
|
|
||||||
self.ecs.write_storage().remove(entity)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read a component attributed to a particular entity.
|
|
||||||
pub fn read_component_cloned<C: Component + Clone>(&self, entity: EcsEntity) -> Option<C> {
|
|
||||||
self.ecs.read_storage().get(entity).cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read a component attributed to a particular entity.
|
|
||||||
pub fn read_component_copied<C: Component + Copy>(&self, entity: EcsEntity) -> Option<C> {
|
|
||||||
self.ecs.read_storage().get(entity).copied()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given mutable access to the resource R, assuming the resource
|
|
||||||
/// component exists (this is already the behavior of functions like `fetch`
|
|
||||||
/// and `write_component_ignore_entity_dead`). Since all of our resources
|
|
||||||
/// are generated up front, any failure here is definitely a code bug.
|
|
||||||
pub fn mut_resource<R: Resource>(&mut self) -> &mut R {
|
|
||||||
self.ecs.get_mut::<R>().expect(
|
|
||||||
"Tried to fetch an invalid resource even though all our resources should be known at \
|
|
||||||
compile time.",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a read-only reference to the storage of a particular component type.
|
|
||||||
pub fn read_storage<C: Component>(&self) -> EcsStorage<C, Fetch<EcsMaskedStorage<C>>> {
|
|
||||||
self.ecs.read_storage::<C>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a reference to the internal ECS world.
|
|
||||||
pub fn ecs(&self) -> &specs::World { &self.ecs }
|
|
||||||
|
|
||||||
/// Get a mutable reference to the internal ECS world.
|
|
||||||
pub fn ecs_mut(&mut self) -> &mut specs::World { &mut self.ecs }
|
|
||||||
|
|
||||||
/// Get a reference to the `TerrainChanges` structure of the state. This
|
|
||||||
/// contains information about terrain state that has changed since the
|
|
||||||
/// last game tick.
|
|
||||||
pub fn terrain_changes(&self) -> Fetch<TerrainChanges> { self.ecs.read_resource() }
|
|
||||||
|
|
||||||
/// Get the current in-game time of day.
|
|
||||||
///
|
|
||||||
/// Note that this should not be used for physics, animations or other such
|
|
||||||
/// localised timings.
|
|
||||||
pub fn get_time_of_day(&self) -> f64 { self.ecs.read_resource::<TimeOfDay>().0 }
|
|
||||||
|
|
||||||
/// Get the current in-game day period (period of the day/night cycle)
|
|
||||||
/// Get the current in-game day period (period of the day/night cycle)
|
|
||||||
pub fn get_day_period(&self) -> DayPeriod { self.get_time_of_day().into() }
|
|
||||||
|
|
||||||
/// Get the current in-game time.
|
|
||||||
///
|
|
||||||
/// Note that this does not correspond to the time of day.
|
|
||||||
pub fn get_time(&self) -> f64 { self.ecs.read_resource::<Time>().0 }
|
|
||||||
|
|
||||||
/// Get the current delta time.
|
|
||||||
pub fn get_delta_time(&self) -> f32 { self.ecs.read_resource::<DeltaTime>().0 }
|
|
||||||
|
|
||||||
/// Get a reference to this state's terrain.
|
|
||||||
pub fn terrain(&self) -> Fetch<TerrainGrid> { self.ecs.read_resource() }
|
|
||||||
|
|
||||||
/// Get a reference to this state's terrain.
|
|
||||||
pub fn slow_job_pool(&self) -> Fetch<SlowJobPool> { self.ecs.read_resource() }
|
|
||||||
|
|
||||||
/// Get a writable reference to this state's terrain.
|
|
||||||
pub fn terrain_mut(&self) -> FetchMut<TerrainGrid> { self.ecs.write_resource() }
|
|
||||||
|
|
||||||
/// Get a block in this state's terrain.
|
|
||||||
pub fn get_block(&self, pos: Vec3<i32>) -> Option<Block> {
|
|
||||||
self.terrain().get(pos).ok().copied()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a block in this state's terrain.
|
|
||||||
pub fn set_block(&self, pos: Vec3<i32>, block: Block) {
|
|
||||||
self.ecs.write_resource::<BlockChange>().set(pos, block);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the block at given position `pos` has already been modified
|
|
||||||
/// this tick.
|
|
||||||
pub fn can_set_block(&self, pos: Vec3<i32>) -> bool {
|
|
||||||
!self
|
|
||||||
.ecs
|
|
||||||
.read_resource::<BlockChange>()
|
|
||||||
.blocks
|
|
||||||
.contains_key(&pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes every chunk of the terrain.
|
|
||||||
pub fn clear_terrain(&mut self) {
|
|
||||||
let removed_chunks = &mut self.ecs.write_resource::<TerrainChanges>().removed_chunks;
|
|
||||||
|
|
||||||
self.terrain_mut().drain().for_each(|(key, _)| {
|
|
||||||
removed_chunks.insert(key);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Insert the provided chunk into this state's terrain.
|
|
||||||
pub fn insert_chunk(&mut self, key: Vec2<i32>, chunk: TerrainChunk) {
|
|
||||||
if self
|
|
||||||
.ecs
|
|
||||||
.write_resource::<TerrainGrid>()
|
|
||||||
.insert(key, Arc::new(chunk))
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
self.ecs
|
|
||||||
.write_resource::<TerrainChanges>()
|
|
||||||
.modified_chunks
|
|
||||||
.insert(key);
|
|
||||||
} else {
|
|
||||||
self.ecs
|
|
||||||
.write_resource::<TerrainChanges>()
|
|
||||||
.new_chunks
|
|
||||||
.insert(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove the chunk with the given key from this state's terrain, if it
|
|
||||||
/// exists.
|
|
||||||
pub fn remove_chunk(&mut self, key: Vec2<i32>) {
|
|
||||||
if self
|
|
||||||
.ecs
|
|
||||||
.write_resource::<TerrainGrid>()
|
|
||||||
.remove(key)
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
self.ecs
|
|
||||||
.write_resource::<TerrainChanges>()
|
|
||||||
.removed_chunks
|
|
||||||
.insert(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run RegionMap tick to update entity region occupancy
|
|
||||||
pub fn update_region_map(&self) {
|
|
||||||
span!(_guard, "update_region_map", "State::update_region_map");
|
|
||||||
self.ecs.write_resource::<RegionMap>().tick(
|
|
||||||
self.ecs.read_storage::<comp::Pos>(),
|
|
||||||
self.ecs.read_storage::<comp::Vel>(),
|
|
||||||
self.ecs.entities(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply terrain changes
|
|
||||||
pub fn apply_terrain_changes(&self) {
|
|
||||||
span!(
|
|
||||||
_guard,
|
|
||||||
"apply_terrain_changes",
|
|
||||||
"State::apply_terrain_changes"
|
|
||||||
);
|
|
||||||
let mut terrain = self.ecs.write_resource::<TerrainGrid>();
|
|
||||||
let mut modified_blocks =
|
|
||||||
std::mem::take(&mut self.ecs.write_resource::<BlockChange>().blocks);
|
|
||||||
// Apply block modifications
|
|
||||||
// Only include in `TerrainChanges` if successful
|
|
||||||
modified_blocks.retain(|pos, block| terrain.set(*pos, *block).is_ok());
|
|
||||||
self.ecs.write_resource::<TerrainChanges>().modified_blocks = modified_blocks;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Execute a single tick, simulating the game state by the given duration.
|
|
||||||
pub fn tick(
|
|
||||||
&mut self,
|
|
||||||
dt: Duration,
|
|
||||||
add_systems: impl Fn(&mut DispatcherBuilder),
|
|
||||||
update_terrain_and_regions: bool,
|
|
||||||
) {
|
|
||||||
span!(_guard, "tick", "State::tick");
|
|
||||||
// Change the time accordingly.
|
|
||||||
self.ecs.write_resource::<TimeOfDay>().0 += dt.as_secs_f64() * DAY_CYCLE_FACTOR;
|
|
||||||
self.ecs.write_resource::<Time>().0 += dt.as_secs_f64();
|
|
||||||
|
|
||||||
// Update delta time.
|
|
||||||
// Beyond a delta time of MAX_DELTA_TIME, start lagging to avoid skipping
|
|
||||||
// important physics events.
|
|
||||||
self.ecs.write_resource::<DeltaTime>().0 = dt.as_secs_f32().min(MAX_DELTA_TIME);
|
|
||||||
|
|
||||||
if update_terrain_and_regions {
|
|
||||||
self.update_region_map();
|
|
||||||
}
|
|
||||||
|
|
||||||
span!(guard, "create dispatcher");
|
|
||||||
// Run systems to update the world.
|
|
||||||
// Create and run a dispatcher for ecs systems.
|
|
||||||
let mut dispatch_builder =
|
|
||||||
DispatcherBuilder::new().with_pool(Arc::clone(&self.thread_pool));
|
|
||||||
// TODO: Consider alternative ways to do this
|
|
||||||
add_systems(&mut dispatch_builder);
|
|
||||||
// This dispatches all the systems in parallel.
|
|
||||||
let mut dispatcher = dispatch_builder.build();
|
|
||||||
drop(guard);
|
|
||||||
span!(guard, "run systems");
|
|
||||||
dispatcher.dispatch(&self.ecs);
|
|
||||||
drop(guard);
|
|
||||||
|
|
||||||
span!(guard, "maintain ecs");
|
|
||||||
self.ecs.maintain();
|
|
||||||
drop(guard);
|
|
||||||
|
|
||||||
if update_terrain_and_regions {
|
|
||||||
self.apply_terrain_changes();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process local events
|
|
||||||
span!(guard, "process local events");
|
|
||||||
let events = self.ecs.read_resource::<EventBus<LocalEvent>>().recv_all();
|
|
||||||
for event in events {
|
|
||||||
let mut velocities = self.ecs.write_storage::<comp::Vel>();
|
|
||||||
let physics = self.ecs.read_storage::<comp::PhysicsState>();
|
|
||||||
match event {
|
|
||||||
LocalEvent::Jump(entity, impulse) => {
|
|
||||||
if let Some(vel) = velocities.get_mut(entity) {
|
|
||||||
vel.0.z = impulse + physics.get(entity).map_or(0.0, |ps| ps.ground_vel.z);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
LocalEvent::ApplyImpulse { entity, impulse } => {
|
|
||||||
if let Some(vel) = velocities.get_mut(entity) {
|
|
||||||
vel.0 = impulse;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
LocalEvent::Boost {
|
|
||||||
entity,
|
|
||||||
vel: extra_vel,
|
|
||||||
} => {
|
|
||||||
if let Some(vel) = velocities.get_mut(entity) {
|
|
||||||
vel.0 += extra_vel;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
LocalEvent::CreateOutcome(outcome) => {
|
|
||||||
self.ecs.write_resource::<Vec<Outcome>>().push(outcome);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
drop(guard);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clean up the state after a tick.
|
|
||||||
pub fn cleanup(&mut self) {
|
|
||||||
span!(_guard, "cleanup", "State::cleanup");
|
|
||||||
// Clean up data structures from the last tick.
|
|
||||||
self.ecs.write_resource::<TerrainChanges>().clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
530
common/state/src/state.rs
Normal file
530
common/state/src/state.rs
Normal file
@ -0,0 +1,530 @@
|
|||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
use crate::plugin::memory_manager::EcsWorld;
|
||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
use crate::plugin::PluginMgr;
|
||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
use common::uid::UidAllocator;
|
||||||
|
use common::{
|
||||||
|
comp,
|
||||||
|
event::{EventBus, LocalEvent, ServerEvent},
|
||||||
|
outcome::Outcome,
|
||||||
|
region::RegionMap,
|
||||||
|
resources::{DeltaTime, GameMode, PlayerEntity, PlayerPhysicsSettings, Time, TimeOfDay},
|
||||||
|
slowjob::SlowJobPool,
|
||||||
|
terrain::{Block, TerrainChunk, TerrainGrid},
|
||||||
|
time::DayPeriod,
|
||||||
|
trade::Trades,
|
||||||
|
vol::{ReadVol, WriteVol},
|
||||||
|
};
|
||||||
|
use common_base::span;
|
||||||
|
use common_ecs::{PhysicsMetrics, SysMetrics};
|
||||||
|
use common_net::sync::{interpolation as sync_interp, WorldSyncExt};
|
||||||
|
use core::{convert::identity, time::Duration};
|
||||||
|
use hashbrown::{HashMap, HashSet};
|
||||||
|
use rayon::{ThreadPool, ThreadPoolBuilder};
|
||||||
|
use specs::{
|
||||||
|
prelude::Resource,
|
||||||
|
shred::{Fetch, FetchMut},
|
||||||
|
storage::{MaskedStorage as EcsMaskedStorage, Storage as EcsStorage},
|
||||||
|
Component, DispatcherBuilder, Entity as EcsEntity, WorldExt,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
/// How much faster should an in-game day be compared to a real day?
|
||||||
|
// TODO: Don't hard-code this.
|
||||||
|
const DAY_CYCLE_FACTOR: f64 = 24.0 * 2.0;
|
||||||
|
|
||||||
|
/// At what point should we stop speeding up physics to compensate for lag? If
|
||||||
|
/// we speed physics up too fast, we'd skip important physics events like
|
||||||
|
/// collisions. This constant determines the upper limit. If delta time exceeds
|
||||||
|
/// this value, the game's physics will begin to produce time lag. Ideally, we'd
|
||||||
|
/// avoid such a situation.
|
||||||
|
const MAX_DELTA_TIME: f32 = 1.0;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct BlockChange {
|
||||||
|
blocks: HashMap<Vec3<i32>, Block>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockChange {
|
||||||
|
pub fn set(&mut self, pos: Vec3<i32>, block: Block) { self.blocks.insert(pos, block); }
|
||||||
|
|
||||||
|
pub fn try_set(&mut self, pos: Vec3<i32>, block: Block) -> Option<()> {
|
||||||
|
if !self.blocks.contains_key(&pos) {
|
||||||
|
self.blocks.insert(pos, block);
|
||||||
|
Some(())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) { self.blocks.clear(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct TerrainChanges {
|
||||||
|
pub new_chunks: HashSet<Vec2<i32>>,
|
||||||
|
pub modified_chunks: HashSet<Vec2<i32>>,
|
||||||
|
pub removed_chunks: HashSet<Vec2<i32>>,
|
||||||
|
pub modified_blocks: HashMap<Vec3<i32>, Block>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TerrainChanges {
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.new_chunks.clear();
|
||||||
|
self.modified_chunks.clear();
|
||||||
|
self.removed_chunks.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type used to represent game state stored on both the client and the
|
||||||
|
/// server. This includes things like entity components, terrain data, and
|
||||||
|
/// global states like weather, time of day, etc.
|
||||||
|
pub struct State {
|
||||||
|
ecs: specs::World,
|
||||||
|
// Avoid lifetime annotation by storing a thread pool instead of the whole dispatcher
|
||||||
|
thread_pool: Arc<ThreadPool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
/// Create a new `State` in client mode.
|
||||||
|
pub fn client() -> Self { Self::new(GameMode::Client) }
|
||||||
|
|
||||||
|
/// Create a new `State` in server mode.
|
||||||
|
pub fn server() -> Self { Self::new(GameMode::Server) }
|
||||||
|
|
||||||
|
pub fn new(game_mode: GameMode) -> Self {
|
||||||
|
let thread_name_infix = match game_mode {
|
||||||
|
GameMode::Server => "s",
|
||||||
|
GameMode::Client => "c",
|
||||||
|
GameMode::Singleplayer => "sp",
|
||||||
|
};
|
||||||
|
|
||||||
|
let thread_pool = Arc::new(
|
||||||
|
ThreadPoolBuilder::new()
|
||||||
|
.thread_name(move |i| format!("rayon-{}-{}", thread_name_infix, i))
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
ecs: Self::setup_ecs_world(game_mode, &thread_pool),
|
||||||
|
thread_pool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates ecs world and registers all the common components and resources
|
||||||
|
// TODO: Split up registering into server and client (e.g. move
|
||||||
|
// EventBus<ServerEvent> to the server)
|
||||||
|
fn setup_ecs_world(game_mode: GameMode, thread_pool: &Arc<ThreadPool>) -> specs::World {
|
||||||
|
let mut ecs = specs::World::new();
|
||||||
|
// Uids for sync
|
||||||
|
ecs.register_sync_marker();
|
||||||
|
// Register server -> all clients synced components.
|
||||||
|
ecs.register::<comp::Body>();
|
||||||
|
ecs.register::<comp::Player>();
|
||||||
|
ecs.register::<comp::Stats>();
|
||||||
|
ecs.register::<comp::SkillSet>();
|
||||||
|
ecs.register::<comp::Buffs>();
|
||||||
|
ecs.register::<comp::Auras>();
|
||||||
|
ecs.register::<comp::Energy>();
|
||||||
|
ecs.register::<comp::Combo>();
|
||||||
|
ecs.register::<comp::Health>();
|
||||||
|
ecs.register::<comp::Poise>();
|
||||||
|
ecs.register::<comp::CanBuild>();
|
||||||
|
ecs.register::<comp::LightEmitter>();
|
||||||
|
ecs.register::<comp::Item>();
|
||||||
|
ecs.register::<comp::Scale>();
|
||||||
|
ecs.register::<comp::Mounting>();
|
||||||
|
ecs.register::<comp::MountState>();
|
||||||
|
ecs.register::<comp::Mass>();
|
||||||
|
ecs.register::<comp::Density>();
|
||||||
|
ecs.register::<comp::Collider>();
|
||||||
|
ecs.register::<comp::Sticky>();
|
||||||
|
ecs.register::<comp::CharacterState>();
|
||||||
|
ecs.register::<comp::Object>();
|
||||||
|
ecs.register::<comp::Group>();
|
||||||
|
ecs.register::<comp::Shockwave>();
|
||||||
|
ecs.register::<comp::ShockwaveHitEntities>();
|
||||||
|
ecs.register::<comp::BeamSegment>();
|
||||||
|
|
||||||
|
// Register components send from clients -> server
|
||||||
|
ecs.register::<comp::Controller>();
|
||||||
|
|
||||||
|
// Register components send directly from server -> all but one client
|
||||||
|
ecs.register::<comp::PhysicsState>();
|
||||||
|
|
||||||
|
// Register components synced from client -> server -> all other clients
|
||||||
|
ecs.register::<comp::Pos>();
|
||||||
|
ecs.register::<comp::Vel>();
|
||||||
|
ecs.register::<comp::Ori>();
|
||||||
|
ecs.register::<comp::Inventory>();
|
||||||
|
|
||||||
|
// Register common unsynced components
|
||||||
|
ecs.register::<comp::PreviousPhysCache>();
|
||||||
|
ecs.register::<comp::PosVelDefer>();
|
||||||
|
|
||||||
|
// Register client-local components
|
||||||
|
// TODO: only register on the client
|
||||||
|
ecs.register::<comp::LightAnimation>();
|
||||||
|
ecs.register::<sync_interp::InterpBuffer<comp::Pos>>();
|
||||||
|
ecs.register::<sync_interp::InterpBuffer<comp::Vel>>();
|
||||||
|
ecs.register::<sync_interp::InterpBuffer<comp::Ori>>();
|
||||||
|
|
||||||
|
// Register server-local components
|
||||||
|
// TODO: only register on the server
|
||||||
|
ecs.register::<comp::Last<comp::Pos>>();
|
||||||
|
ecs.register::<comp::Last<comp::Vel>>();
|
||||||
|
ecs.register::<comp::Last<comp::Ori>>();
|
||||||
|
ecs.register::<comp::Alignment>();
|
||||||
|
ecs.register::<comp::Agent>();
|
||||||
|
ecs.register::<comp::WaypointArea>();
|
||||||
|
ecs.register::<comp::ForceUpdate>();
|
||||||
|
ecs.register::<comp::InventoryUpdate>();
|
||||||
|
ecs.register::<comp::Admin>();
|
||||||
|
ecs.register::<comp::Waypoint>();
|
||||||
|
ecs.register::<comp::Projectile>();
|
||||||
|
ecs.register::<comp::Melee>();
|
||||||
|
ecs.register::<comp::ItemDrop>();
|
||||||
|
ecs.register::<comp::ChatMode>();
|
||||||
|
ecs.register::<comp::Faction>();
|
||||||
|
ecs.register::<comp::invite::Invite>();
|
||||||
|
ecs.register::<comp::invite::PendingInvites>();
|
||||||
|
ecs.register::<comp::Beam>();
|
||||||
|
|
||||||
|
// Register synced resources used by the ECS.
|
||||||
|
ecs.insert(TimeOfDay(0.0));
|
||||||
|
|
||||||
|
// Register unsynced resources used by the ECS.
|
||||||
|
ecs.insert(Time(0.0));
|
||||||
|
ecs.insert(DeltaTime(0.0));
|
||||||
|
ecs.insert(PlayerEntity(None));
|
||||||
|
ecs.insert(TerrainGrid::new().unwrap());
|
||||||
|
ecs.insert(BlockChange::default());
|
||||||
|
ecs.insert(crate::build_areas::BuildAreas::default());
|
||||||
|
ecs.insert(TerrainChanges::default());
|
||||||
|
ecs.insert(EventBus::<LocalEvent>::default());
|
||||||
|
ecs.insert(game_mode);
|
||||||
|
ecs.insert(Vec::<common::outcome::Outcome>::new());
|
||||||
|
ecs.insert(common::CachedSpatialGrid::default());
|
||||||
|
|
||||||
|
let slow_limit = thread_pool.current_num_threads().max(2) as u64;
|
||||||
|
let slow_limit = slow_limit / 2 + slow_limit / 4;
|
||||||
|
tracing::trace!(?slow_limit, "Slow Thread limit");
|
||||||
|
ecs.insert(SlowJobPool::new(slow_limit, Arc::clone(&thread_pool)));
|
||||||
|
|
||||||
|
// TODO: only register on the server
|
||||||
|
ecs.insert(EventBus::<ServerEvent>::default());
|
||||||
|
ecs.insert(comp::group::GroupManager::default());
|
||||||
|
ecs.insert(RegionMap::new());
|
||||||
|
ecs.insert(SysMetrics::default());
|
||||||
|
ecs.insert(PhysicsMetrics::default());
|
||||||
|
ecs.insert(Trades::default());
|
||||||
|
ecs.insert(PlayerPhysicsSettings::default());
|
||||||
|
|
||||||
|
// Load plugins from asset directory
|
||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
ecs.insert(match PluginMgr::from_assets() {
|
||||||
|
Ok(plugin_mgr) => {
|
||||||
|
let ecs_world = EcsWorld {
|
||||||
|
entities: &ecs.entities(),
|
||||||
|
health: ecs.read_component().into(),
|
||||||
|
uid: ecs.read_component().into(),
|
||||||
|
uid_allocator: &ecs.read_resource::<UidAllocator>().into(),
|
||||||
|
player: ecs.read_component().into(),
|
||||||
|
};
|
||||||
|
if let Err(e) = plugin_mgr
|
||||||
|
.execute_event(&ecs_world, &plugin_api::event::PluginLoadEvent {
|
||||||
|
game_mode,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
tracing::debug!(?e, "Failed to run plugin init");
|
||||||
|
tracing::info!("Plugins disabled, enable debug logging for more information.");
|
||||||
|
PluginMgr::default()
|
||||||
|
} else {
|
||||||
|
plugin_mgr
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
tracing::debug!(?e, "Failed to read plugins from assets");
|
||||||
|
tracing::info!("Plugins disabled, enable debug logging for more information.");
|
||||||
|
PluginMgr::default()
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
ecs
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a component with the state's ECS.
|
||||||
|
pub fn with_component<T: Component>(mut self) -> Self
|
||||||
|
where
|
||||||
|
<T as Component>::Storage: Default,
|
||||||
|
{
|
||||||
|
self.ecs.register::<T>();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a component attributed to a particular entity, ignoring errors.
|
||||||
|
///
|
||||||
|
/// This should be used *only* when we can guarantee that the rest of the
|
||||||
|
/// code does not rely on the insert having succeeded (meaning the
|
||||||
|
/// entity is no longer alive!).
|
||||||
|
///
|
||||||
|
/// Returns None if the entity was dead or there was no previous entry for
|
||||||
|
/// this component; otherwise, returns Some(old_component).
|
||||||
|
pub fn write_component_ignore_entity_dead<C: Component>(
|
||||||
|
&mut self,
|
||||||
|
entity: EcsEntity,
|
||||||
|
comp: C,
|
||||||
|
) -> Option<C> {
|
||||||
|
self.ecs
|
||||||
|
.write_storage()
|
||||||
|
.insert(entity, comp)
|
||||||
|
.ok()
|
||||||
|
.and_then(identity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a component attributed to a particular entity.
|
||||||
|
pub fn delete_component<C: Component>(&mut self, entity: EcsEntity) -> Option<C> {
|
||||||
|
self.ecs.write_storage().remove(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a component attributed to a particular entity.
|
||||||
|
pub fn read_component_cloned<C: Component + Clone>(&self, entity: EcsEntity) -> Option<C> {
|
||||||
|
self.ecs.read_storage().get(entity).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a component attributed to a particular entity.
|
||||||
|
pub fn read_component_copied<C: Component + Copy>(&self, entity: EcsEntity) -> Option<C> {
|
||||||
|
self.ecs.read_storage().get(entity).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given mutable access to the resource R, assuming the resource
|
||||||
|
/// component exists (this is already the behavior of functions like `fetch`
|
||||||
|
/// and `write_component_ignore_entity_dead`). Since all of our resources
|
||||||
|
/// are generated up front, any failure here is definitely a code bug.
|
||||||
|
pub fn mut_resource<R: Resource>(&mut self) -> &mut R {
|
||||||
|
self.ecs.get_mut::<R>().expect(
|
||||||
|
"Tried to fetch an invalid resource even though all our resources should be known at \
|
||||||
|
compile time.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a read-only reference to the storage of a particular component type.
|
||||||
|
pub fn read_storage<C: Component>(&self) -> EcsStorage<C, Fetch<EcsMaskedStorage<C>>> {
|
||||||
|
self.ecs.read_storage::<C>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the internal ECS world.
|
||||||
|
pub fn ecs(&self) -> &specs::World { &self.ecs }
|
||||||
|
|
||||||
|
/// Get a mutable reference to the internal ECS world.
|
||||||
|
pub fn ecs_mut(&mut self) -> &mut specs::World { &mut self.ecs }
|
||||||
|
|
||||||
|
/// Get a reference to the `TerrainChanges` structure of the state. This
|
||||||
|
/// contains information about terrain state that has changed since the
|
||||||
|
/// last game tick.
|
||||||
|
pub fn terrain_changes(&self) -> Fetch<TerrainChanges> { self.ecs.read_resource() }
|
||||||
|
|
||||||
|
/// Get the current in-game time of day.
|
||||||
|
///
|
||||||
|
/// Note that this should not be used for physics, animations or other such
|
||||||
|
/// localised timings.
|
||||||
|
pub fn get_time_of_day(&self) -> f64 { self.ecs.read_resource::<TimeOfDay>().0 }
|
||||||
|
|
||||||
|
/// Get the current in-game day period (period of the day/night cycle)
|
||||||
|
/// Get the current in-game day period (period of the day/night cycle)
|
||||||
|
pub fn get_day_period(&self) -> DayPeriod { self.get_time_of_day().into() }
|
||||||
|
|
||||||
|
/// Get the current in-game time.
|
||||||
|
///
|
||||||
|
/// Note that this does not correspond to the time of day.
|
||||||
|
pub fn get_time(&self) -> f64 { self.ecs.read_resource::<Time>().0 }
|
||||||
|
|
||||||
|
/// Get the current delta time.
|
||||||
|
pub fn get_delta_time(&self) -> f32 { self.ecs.read_resource::<DeltaTime>().0 }
|
||||||
|
|
||||||
|
/// Get a reference to this state's terrain.
|
||||||
|
pub fn terrain(&self) -> Fetch<TerrainGrid> { self.ecs.read_resource() }
|
||||||
|
|
||||||
|
/// Get a reference to this state's terrain.
|
||||||
|
pub fn slow_job_pool(&self) -> Fetch<SlowJobPool> { self.ecs.read_resource() }
|
||||||
|
|
||||||
|
/// Get a writable reference to this state's terrain.
|
||||||
|
pub fn terrain_mut(&self) -> FetchMut<TerrainGrid> { self.ecs.write_resource() }
|
||||||
|
|
||||||
|
/// Get a block in this state's terrain.
|
||||||
|
pub fn get_block(&self, pos: Vec3<i32>) -> Option<Block> {
|
||||||
|
self.terrain().get(pos).ok().copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a block in this state's terrain.
|
||||||
|
pub fn set_block(&self, pos: Vec3<i32>, block: Block) {
|
||||||
|
self.ecs.write_resource::<BlockChange>().set(pos, block);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the block at given position `pos` has already been modified
|
||||||
|
/// this tick.
|
||||||
|
pub fn can_set_block(&self, pos: Vec3<i32>) -> bool {
|
||||||
|
!self
|
||||||
|
.ecs
|
||||||
|
.read_resource::<BlockChange>()
|
||||||
|
.blocks
|
||||||
|
.contains_key(&pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes every chunk of the terrain.
|
||||||
|
pub fn clear_terrain(&mut self) {
|
||||||
|
let removed_chunks = &mut self.ecs.write_resource::<TerrainChanges>().removed_chunks;
|
||||||
|
|
||||||
|
self.terrain_mut().drain().for_each(|(key, _)| {
|
||||||
|
removed_chunks.insert(key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert the provided chunk into this state's terrain.
|
||||||
|
pub fn insert_chunk(&mut self, key: Vec2<i32>, chunk: Arc<TerrainChunk>) {
|
||||||
|
if self
|
||||||
|
.ecs
|
||||||
|
.write_resource::<TerrainGrid>()
|
||||||
|
.insert(key, chunk)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
self.ecs
|
||||||
|
.write_resource::<TerrainChanges>()
|
||||||
|
.modified_chunks
|
||||||
|
.insert(key);
|
||||||
|
} else {
|
||||||
|
self.ecs
|
||||||
|
.write_resource::<TerrainChanges>()
|
||||||
|
.new_chunks
|
||||||
|
.insert(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove the chunk with the given key from this state's terrain, if it
|
||||||
|
/// exists.
|
||||||
|
pub fn remove_chunk(&mut self, key: Vec2<i32>) {
|
||||||
|
if self
|
||||||
|
.ecs
|
||||||
|
.write_resource::<TerrainGrid>()
|
||||||
|
.remove(key)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
self.ecs
|
||||||
|
.write_resource::<TerrainChanges>()
|
||||||
|
.removed_chunks
|
||||||
|
.insert(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run RegionMap tick to update entity region occupancy
|
||||||
|
pub fn update_region_map(&self) {
|
||||||
|
span!(_guard, "update_region_map", "State::update_region_map");
|
||||||
|
self.ecs.write_resource::<RegionMap>().tick(
|
||||||
|
self.ecs.read_storage::<comp::Pos>(),
|
||||||
|
self.ecs.read_storage::<comp::Vel>(),
|
||||||
|
self.ecs.entities(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply terrain changes
|
||||||
|
pub fn apply_terrain_changes(&self) {
|
||||||
|
span!(
|
||||||
|
_guard,
|
||||||
|
"apply_terrain_changes",
|
||||||
|
"State::apply_terrain_changes"
|
||||||
|
);
|
||||||
|
let mut terrain = self.ecs.write_resource::<TerrainGrid>();
|
||||||
|
let mut modified_blocks =
|
||||||
|
std::mem::take(&mut self.ecs.write_resource::<BlockChange>().blocks);
|
||||||
|
// Apply block modifications
|
||||||
|
// Only include in `TerrainChanges` if successful
|
||||||
|
modified_blocks.retain(|pos, block| terrain.set(*pos, *block).is_ok());
|
||||||
|
self.ecs.write_resource::<TerrainChanges>().modified_blocks = modified_blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute a single tick, simulating the game state by the given duration.
|
||||||
|
pub fn tick(
|
||||||
|
&mut self,
|
||||||
|
dt: Duration,
|
||||||
|
add_systems: impl Fn(&mut DispatcherBuilder),
|
||||||
|
update_terrain_and_regions: bool,
|
||||||
|
) {
|
||||||
|
span!(_guard, "tick", "State::tick");
|
||||||
|
// Change the time accordingly.
|
||||||
|
self.ecs.write_resource::<TimeOfDay>().0 += dt.as_secs_f64() * DAY_CYCLE_FACTOR;
|
||||||
|
self.ecs.write_resource::<Time>().0 += dt.as_secs_f64();
|
||||||
|
|
||||||
|
// Update delta time.
|
||||||
|
// Beyond a delta time of MAX_DELTA_TIME, start lagging to avoid skipping
|
||||||
|
// important physics events.
|
||||||
|
self.ecs.write_resource::<DeltaTime>().0 = dt.as_secs_f32().min(MAX_DELTA_TIME);
|
||||||
|
|
||||||
|
if update_terrain_and_regions {
|
||||||
|
self.update_region_map();
|
||||||
|
}
|
||||||
|
|
||||||
|
span!(guard, "create dispatcher");
|
||||||
|
// Run systems to update the world.
|
||||||
|
// Create and run a dispatcher for ecs systems.
|
||||||
|
let mut dispatch_builder =
|
||||||
|
DispatcherBuilder::new().with_pool(Arc::clone(&self.thread_pool));
|
||||||
|
// TODO: Consider alternative ways to do this
|
||||||
|
add_systems(&mut dispatch_builder);
|
||||||
|
// This dispatches all the systems in parallel.
|
||||||
|
let mut dispatcher = dispatch_builder.build();
|
||||||
|
drop(guard);
|
||||||
|
|
||||||
|
span!(guard, "run systems");
|
||||||
|
dispatcher.dispatch(&self.ecs);
|
||||||
|
drop(guard);
|
||||||
|
|
||||||
|
span!(guard, "maintain ecs");
|
||||||
|
self.ecs.maintain();
|
||||||
|
drop(guard);
|
||||||
|
|
||||||
|
if update_terrain_and_regions {
|
||||||
|
self.apply_terrain_changes();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process local events
|
||||||
|
span!(guard, "process local events");
|
||||||
|
let events = self.ecs.read_resource::<EventBus<LocalEvent>>().recv_all();
|
||||||
|
for event in events {
|
||||||
|
let mut velocities = self.ecs.write_storage::<comp::Vel>();
|
||||||
|
let physics = self.ecs.read_storage::<comp::PhysicsState>();
|
||||||
|
match event {
|
||||||
|
LocalEvent::Jump(entity, impulse) => {
|
||||||
|
if let Some(vel) = velocities.get_mut(entity) {
|
||||||
|
vel.0.z = impulse + physics.get(entity).map_or(0.0, |ps| ps.ground_vel.z);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
LocalEvent::ApplyImpulse { entity, impulse } => {
|
||||||
|
if let Some(vel) = velocities.get_mut(entity) {
|
||||||
|
vel.0 = impulse;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
LocalEvent::Boost {
|
||||||
|
entity,
|
||||||
|
vel: extra_vel,
|
||||||
|
} => {
|
||||||
|
if let Some(vel) = velocities.get_mut(entity) {
|
||||||
|
vel.0 += extra_vel;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
LocalEvent::CreateOutcome(outcome) => {
|
||||||
|
self.ecs.write_resource::<Vec<Outcome>>().push(outcome);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(guard);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clean up the state after a tick.
|
||||||
|
pub fn cleanup(&mut self) {
|
||||||
|
span!(_guard, "cleanup", "State::cleanup");
|
||||||
|
// Clean up data structures from the last tick.
|
||||||
|
self.ecs.write_resource::<TerrainChanges>().clear();
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["Marcel Märtens <marcel.cochem@googlemail.com>"]
|
authors = ["Marcel Märtens <marcel.cochem@googlemail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
name = "veloren-common-sys"
|
name = "veloren-common-systems"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
@ -6,7 +6,7 @@ mod beam;
|
|||||||
mod buff;
|
mod buff;
|
||||||
pub mod character_behavior;
|
pub mod character_behavior;
|
||||||
pub mod controller;
|
pub mod controller;
|
||||||
pub mod interpolation;
|
mod interpolation;
|
||||||
pub mod melee;
|
pub mod melee;
|
||||||
mod mount;
|
mod mount;
|
||||||
pub mod phys;
|
pub mod phys;
|
@ -1,7 +1,3 @@
|
|||||||
mod spatial_grid;
|
|
||||||
|
|
||||||
use spatial_grid::SpatialGrid;
|
|
||||||
|
|
||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
body::ship::figuredata::{VoxelCollider, VOXEL_COLLIDER_MANIFEST},
|
body::ship::figuredata::{VoxelCollider, VOXEL_COLLIDER_MANIFEST},
|
||||||
@ -15,7 +11,7 @@ use common::{
|
|||||||
resources::DeltaTime,
|
resources::DeltaTime,
|
||||||
terrain::{Block, TerrainGrid},
|
terrain::{Block, TerrainGrid},
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
util::Projection,
|
util::{Projection, SpatialGrid},
|
||||||
vol::{BaseVol, ReadVol},
|
vol::{BaseVol, ReadVol},
|
||||||
};
|
};
|
||||||
use common_base::{prof_span, span};
|
use common_base::{prof_span, span};
|
||||||
@ -132,6 +128,7 @@ pub struct PhysicsRead<'a> {
|
|||||||
#[derive(SystemData)]
|
#[derive(SystemData)]
|
||||||
pub struct PhysicsWrite<'a> {
|
pub struct PhysicsWrite<'a> {
|
||||||
physics_metrics: WriteExpect<'a, PhysicsMetrics>,
|
physics_metrics: WriteExpect<'a, PhysicsMetrics>,
|
||||||
|
cached_spatial_grid: Write<'a, common::CachedSpatialGrid>,
|
||||||
physics_states: WriteStorage<'a, PhysicsState>,
|
physics_states: WriteStorage<'a, PhysicsState>,
|
||||||
positions: WriteStorage<'a, Pos>,
|
positions: WriteStorage<'a, Pos>,
|
||||||
velocities: WriteStorage<'a, Vel>,
|
velocities: WriteStorage<'a, Vel>,
|
||||||
@ -330,27 +327,17 @@ impl<'a> PhysicsData<'a> {
|
|||||||
let mut entity_entity_collision_checks = 0;
|
let mut entity_entity_collision_checks = 0;
|
||||||
let mut entity_entity_collisions = 0;
|
let mut entity_entity_collisions = 0;
|
||||||
|
|
||||||
let aabr = {
|
let query_center = previous_cache.center.xy();
|
||||||
let center = previous_cache.center.xy().map(|e| e as i32);
|
let query_radius = previous_cache.collision_boundary;
|
||||||
let radius = previous_cache.collision_boundary.ceil() as i32;
|
|
||||||
// From conversion of center above
|
|
||||||
const CENTER_TRUNCATION_ERROR: i32 = 1;
|
|
||||||
let max_dist = radius + CENTER_TRUNCATION_ERROR;
|
|
||||||
|
|
||||||
Aabr {
|
|
||||||
min: center - max_dist,
|
|
||||||
max: center + max_dist,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
spatial_grid
|
spatial_grid
|
||||||
.in_aabr(aabr)
|
.in_circle_aabr(query_center, query_radius)
|
||||||
.filter_map(|entity| {
|
.filter_map(|entity| {
|
||||||
read.uids
|
read.uids
|
||||||
.get(entity)
|
.get(entity)
|
||||||
.zip(positions.get(entity))
|
.and_then(|l| positions.get(entity).map(|r| (l, r)))
|
||||||
.zip(previous_phys_cache.get(entity))
|
.and_then(|l| previous_phys_cache.get(entity).map(|r| (l, r)))
|
||||||
.zip(read.masses.get(entity))
|
.and_then(|l| read.masses.get(entity).map(|r| (l, r)))
|
||||||
.map(|(((uid, pos), previous_cache), mass)| {
|
.map(|(((uid, pos), previous_cache), mass)| {
|
||||||
(
|
(
|
||||||
entity,
|
entity,
|
||||||
@ -873,27 +860,17 @@ impl<'a> PhysicsData<'a> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Collide with terrain-like entities
|
// Collide with terrain-like entities
|
||||||
let aabr = {
|
let query_center = path_sphere.center.xy();
|
||||||
let center = path_sphere.center.xy().map(|e| e as i32);
|
let query_radius = path_sphere.radius;
|
||||||
let radius = path_sphere.radius.ceil() as i32;
|
|
||||||
// From conversion of center above
|
|
||||||
const CENTER_TRUNCATION_ERROR: i32 = 1;
|
|
||||||
let max_dist = radius + CENTER_TRUNCATION_ERROR;
|
|
||||||
|
|
||||||
Aabr {
|
|
||||||
min: center - max_dist,
|
|
||||||
max: center + max_dist,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
voxel_collider_spatial_grid
|
voxel_collider_spatial_grid
|
||||||
.in_aabr(aabr)
|
.in_circle_aabr(query_center, query_radius)
|
||||||
.filter_map(|entity| {
|
.filter_map(|entity| {
|
||||||
positions
|
positions
|
||||||
.get(entity)
|
.get(entity)
|
||||||
.zip(velocities.get(entity))
|
.and_then(|l| velocities.get(entity).map(|r| (l, r)))
|
||||||
.zip(previous_phys_cache.get(entity))
|
.and_then(|l| previous_phys_cache.get(entity).map(|r| (l, r)))
|
||||||
.zip(read.colliders.get(entity))
|
.and_then(|l| read.colliders.get(entity).map(|r| (l, r)))
|
||||||
.zip(orientations.get(entity))
|
.and_then(|l| orientations.get(entity).map(|r| (l, r)))
|
||||||
.map(|((((pos, vel), previous_cache), collider), ori)| {
|
.map(|((((pos, vel), previous_cache), collider), ori)| {
|
||||||
(entity, pos, vel, previous_cache, collider, ori)
|
(entity, pos, vel, previous_cache, collider, ori)
|
||||||
})
|
})
|
||||||
@ -1103,6 +1080,32 @@ impl<'a> PhysicsData<'a> {
|
|||||||
event_emitter.emit(ServerEvent::LandOnGround { entity, vel: vel.0 });
|
event_emitter.emit(ServerEvent::LandOnGround { entity, vel: vel.0 });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_cached_spatial_grid(&mut self) {
|
||||||
|
span!(_guard, "Update cached spatial grid");
|
||||||
|
let PhysicsData {
|
||||||
|
ref read,
|
||||||
|
ref mut write,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
let spatial_grid = &mut write.cached_spatial_grid.0;
|
||||||
|
spatial_grid.clear();
|
||||||
|
(
|
||||||
|
&read.entities,
|
||||||
|
&write.positions,
|
||||||
|
read.scales.maybe(),
|
||||||
|
read.colliders.maybe(),
|
||||||
|
)
|
||||||
|
.join()
|
||||||
|
.for_each(|(entity, pos, scale, collider)| {
|
||||||
|
let scale = scale.map(|s| s.0).unwrap_or(1.0);
|
||||||
|
let radius_2d =
|
||||||
|
(collider.map(|c| c.get_radius()).unwrap_or(0.5) * scale).ceil() as u32;
|
||||||
|
let pos_2d = pos.0.xy().map(|e| e as i32);
|
||||||
|
const POS_TRUNCATION_ERROR: u32 = 1;
|
||||||
|
spatial_grid.insert(pos_2d, radius_2d + POS_TRUNCATION_ERROR, entity);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> System<'a> for Sys {
|
impl<'a> System<'a> for Sys {
|
||||||
@ -1112,8 +1115,8 @@ impl<'a> System<'a> for Sys {
|
|||||||
const ORIGIN: Origin = Origin::Common;
|
const ORIGIN: Origin = Origin::Common;
|
||||||
const PHASE: Phase = Phase::Create;
|
const PHASE: Phase = Phase::Create;
|
||||||
|
|
||||||
fn run(job: &mut Job<Self>, mut psd: Self::SystemData) {
|
fn run(job: &mut Job<Self>, mut physics_data: Self::SystemData) {
|
||||||
psd.reset();
|
physics_data.reset();
|
||||||
|
|
||||||
// Apply pushback
|
// Apply pushback
|
||||||
//
|
//
|
||||||
@ -1128,13 +1131,16 @@ impl<'a> System<'a> for Sys {
|
|||||||
// terrain collision code below, although that's not trivial to do since
|
// terrain collision code below, although that's not trivial to do since
|
||||||
// it means the step needs to take into account the speeds of both
|
// it means the step needs to take into account the speeds of both
|
||||||
// entities.
|
// entities.
|
||||||
psd.maintain_pushback_cache();
|
physics_data.maintain_pushback_cache();
|
||||||
|
|
||||||
let spatial_grid = psd.construct_spatial_grid();
|
let spatial_grid = physics_data.construct_spatial_grid();
|
||||||
psd.apply_pushback(job, &spatial_grid);
|
physics_data.apply_pushback(job, &spatial_grid);
|
||||||
|
|
||||||
let voxel_collider_spatial_grid = psd.construct_voxel_collider_spatial_grid();
|
let voxel_collider_spatial_grid = physics_data.construct_voxel_collider_spatial_grid();
|
||||||
psd.handle_movement_and_terrain(job, &voxel_collider_spatial_grid);
|
physics_data.handle_movement_and_terrain(job, &voxel_collider_spatial_grid);
|
||||||
|
|
||||||
|
// Spatial grid used by other systems
|
||||||
|
physics_data.update_cached_spatial_grid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ edition = "2018"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
worldgen = []
|
worldgen = []
|
||||||
tracy = ["common/tracy", "common-base/tracy", "common-ecs/tracy", "common-sys/tracy", "common-net/tracy", "world/tracy"]
|
tracy = ["common/tracy", "common-base/tracy", "common-ecs/tracy", "common-state/tracy", "common-net/tracy", "world/tracy"]
|
||||||
simd = ["vek/platform_intrinsics"]
|
simd = ["vek/platform_intrinsics"]
|
||||||
plugins = ["common-state/plugins"]
|
plugins = ["common-state/plugins"]
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ common = { package = "veloren-common", path = "../common" }
|
|||||||
common-base = { package = "veloren-common-base", path = "../common/base" }
|
common-base = { package = "veloren-common-base", path = "../common/base" }
|
||||||
common-ecs = { package = "veloren-common-ecs", path = "../common/ecs" }
|
common-ecs = { package = "veloren-common-ecs", path = "../common/ecs" }
|
||||||
common-state = { package = "veloren-common-state", path = "../common/state" }
|
common-state = { package = "veloren-common-state", path = "../common/state" }
|
||||||
common-sys = { package = "veloren-common-sys", path = "../common/sys" }
|
common-systems = { package = "veloren-common-systems", path = "../common/systems" }
|
||||||
common-net = { package = "veloren-common-net", path = "../common/net" }
|
common-net = { package = "veloren-common-net", path = "../common/net" }
|
||||||
world = { package = "veloren-world", path = "../world" }
|
world = { package = "veloren-world", path = "../world" }
|
||||||
network = { package = "veloren-network", path = "../network", features = ["metrics", "compression"], default-features = false }
|
network = { package = "veloren-network", path = "../network", features = ["metrics", "compression"], default-features = false }
|
||||||
|
@ -81,7 +81,7 @@ use common_state::plugin::memory_manager::EcsWorld;
|
|||||||
#[cfg(feature = "plugins")]
|
#[cfg(feature = "plugins")]
|
||||||
use common_state::plugin::PluginMgr;
|
use common_state::plugin::PluginMgr;
|
||||||
use common_state::{BuildAreas, State};
|
use common_state::{BuildAreas, State};
|
||||||
use common_sys::add_local_systems;
|
use common_systems::add_local_systems;
|
||||||
use metrics::{EcsSystemMetrics, PhysicsMetrics, TickMetrics};
|
use metrics::{EcsSystemMetrics, PhysicsMetrics, TickMetrics};
|
||||||
use network::{Network, Pid, ProtocolAddr};
|
use network::{Network, Pid, ProtocolAddr};
|
||||||
use persistence::{
|
use persistence::{
|
||||||
|
@ -68,6 +68,7 @@ struct AgentData<'a> {
|
|||||||
is_gliding: bool,
|
is_gliding: bool,
|
||||||
health: Option<&'a Health>,
|
health: Option<&'a Health>,
|
||||||
char_state: &'a CharacterState,
|
char_state: &'a CharacterState,
|
||||||
|
cached_spatial_grid: &'a common::CachedSpatialGrid,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(SystemData)]
|
#[derive(SystemData)]
|
||||||
@ -76,6 +77,7 @@ pub struct ReadData<'a> {
|
|||||||
uid_allocator: Read<'a, UidAllocator>,
|
uid_allocator: Read<'a, UidAllocator>,
|
||||||
dt: Read<'a, DeltaTime>,
|
dt: Read<'a, DeltaTime>,
|
||||||
time: Read<'a, Time>,
|
time: Read<'a, Time>,
|
||||||
|
cached_spatial_grid: Read<'a, common::CachedSpatialGrid>,
|
||||||
group_manager: Read<'a, group::GroupManager>,
|
group_manager: Read<'a, group::GroupManager>,
|
||||||
energies: ReadStorage<'a, Energy>,
|
energies: ReadStorage<'a, Energy>,
|
||||||
positions: ReadStorage<'a, Pos>,
|
positions: ReadStorage<'a, Pos>,
|
||||||
@ -284,6 +286,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
is_gliding,
|
is_gliding,
|
||||||
health: read_data.healths.get(entity),
|
health: read_data.healths.get(entity),
|
||||||
char_state,
|
char_state,
|
||||||
|
cached_spatial_grid: &read_data.cached_spatial_grid,
|
||||||
};
|
};
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////
|
||||||
@ -729,6 +732,7 @@ impl<'a> AgentData<'a> {
|
|||||||
.actions
|
.actions
|
||||||
.push(ControlAction::CancelInput(InputKind::Fly))
|
.push(ControlAction::CancelInput(InputKind::Fly))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((bearing, speed)) = agent.chaser.chase(
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
||||||
&*read_data.terrain,
|
&*read_data.terrain,
|
||||||
self.pos.0,
|
self.pos.0,
|
||||||
@ -748,6 +752,7 @@ impl<'a> AgentData<'a> {
|
|||||||
|
|
||||||
controller.inputs.move_z = bearing.z
|
controller.inputs.move_z = bearing.z
|
||||||
+ if self.traversal_config.can_fly {
|
+ if self.traversal_config.can_fly {
|
||||||
|
// NOTE: costs 4 us (imbris)
|
||||||
let obstacle_ahead = read_data
|
let obstacle_ahead = read_data
|
||||||
.terrain
|
.terrain
|
||||||
.ray(
|
.ray(
|
||||||
@ -760,6 +765,7 @@ impl<'a> AgentData<'a> {
|
|||||||
.cast()
|
.cast()
|
||||||
.1
|
.1
|
||||||
.map_or(true, |b| b.is_some());
|
.map_or(true, |b| b.is_some());
|
||||||
|
|
||||||
let mut ground_too_close = self
|
let mut ground_too_close = self
|
||||||
.body
|
.body
|
||||||
.map(|body| {
|
.map(|body| {
|
||||||
@ -778,6 +784,8 @@ impl<'a> AgentData<'a> {
|
|||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
const NUM_RAYS: usize = 5;
|
const NUM_RAYS: usize = 5;
|
||||||
|
|
||||||
|
// NOTE: costs 15-20 us (imbris)
|
||||||
for i in 0..=NUM_RAYS {
|
for i in 0..=NUM_RAYS {
|
||||||
let magnitude = self.body.map_or(20.0, |b| b.flying_height());
|
let magnitude = self.body.map_or(20.0, |b| b.flying_height());
|
||||||
// Lerp between a line straight ahead and straight down to detect a
|
// Lerp between a line straight ahead and straight down to detect a
|
||||||
@ -830,6 +838,7 @@ impl<'a> AgentData<'a> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Stop if we're too close to a wall
|
// Stop if we're too close to a wall
|
||||||
|
// NOTE: costs 1 us (imbris)
|
||||||
agent.bearing *= 0.1
|
agent.bearing *= 0.1
|
||||||
+ if read_data
|
+ if read_data
|
||||||
.terrain
|
.terrain
|
||||||
@ -1374,10 +1383,19 @@ impl<'a> AgentData<'a> {
|
|||||||
) {
|
) {
|
||||||
agent.action_timer = 0.0;
|
agent.action_timer = 0.0;
|
||||||
|
|
||||||
// Search for new targets (this looks expensive, but it's only run occasionally)
|
// Search area
|
||||||
// TODO: Replace this with a better system that doesn't consider *all* entities
|
let target = self.cached_spatial_grid.0
|
||||||
let target = (&read_data.entities, &read_data.positions, &read_data.healths, &read_data.stats, &read_data.inventories, read_data.alignments.maybe(), read_data.char_states.maybe())
|
.in_circle_aabr(self.pos.0.xy(), SEARCH_DIST)
|
||||||
.join()
|
.filter_map(|entity| {
|
||||||
|
read_data.positions
|
||||||
|
.get(entity)
|
||||||
|
.and_then(|l| read_data.healths.get(entity).map(|r| (l, r)))
|
||||||
|
.and_then(|l| read_data.stats.get(entity).map(|r| (l, r)))
|
||||||
|
.and_then(|l| read_data.inventories.get(entity).map(|r| (l, r)))
|
||||||
|
.map(|(((pos, health), stats), inventory)| {
|
||||||
|
(entity, pos, health, stats, inventory, read_data.alignments.get(entity), read_data.char_states.get(entity))
|
||||||
|
})
|
||||||
|
})
|
||||||
.filter(|(e, e_pos, e_health, e_stats, e_inventory, e_alignment, char_state)| {
|
.filter(|(e, e_pos, e_health, e_stats, e_inventory, e_alignment, char_state)| {
|
||||||
let mut search_dist = SEARCH_DIST;
|
let mut search_dist = SEARCH_DIST;
|
||||||
let mut listen_dist = LISTEN_DIST;
|
let mut listen_dist = LISTEN_DIST;
|
||||||
@ -1443,15 +1461,12 @@ impl<'a> AgentData<'a> {
|
|||||||
.0 >= e_pos.0.distance(self.pos.0))
|
.0 >= e_pos.0.distance(self.pos.0))
|
||||||
.min_by_key(|(_, e_pos, _, _, _, _, _)| (e_pos.0.distance_squared(self.pos.0) * 100.0) as i32) // TODO choose target by more than just distance
|
.min_by_key(|(_, e_pos, _, _, _, _, _)| (e_pos.0.distance_squared(self.pos.0) * 100.0) as i32) // TODO choose target by more than just distance
|
||||||
.map(|(e, _, _, _, _, _, _)| e);
|
.map(|(e, _, _, _, _, _, _)| e);
|
||||||
if let Some(target) = target {
|
|
||||||
agent.target = Some(Target {
|
agent.target = target.map(|target| Target {
|
||||||
target,
|
target,
|
||||||
hostile: true,
|
hostile: true,
|
||||||
selected_at: read_data.time.0,
|
selected_at: read_data.time.0,
|
||||||
})
|
});
|
||||||
} else {
|
|
||||||
agent.target = None;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn jump_if(&self, controller: &mut Controller, condition: bool) {
|
fn jump_if(&self, controller: &mut Controller, condition: bool) {
|
||||||
|
@ -12,7 +12,7 @@ pub mod terrain_sync;
|
|||||||
pub mod waypoint;
|
pub mod waypoint;
|
||||||
|
|
||||||
use common_ecs::{dispatch, run_now, System};
|
use common_ecs::{dispatch, run_now, System};
|
||||||
use common_sys::{melee, projectile};
|
use common_systems::{melee, projectile};
|
||||||
use specs::DispatcherBuilder;
|
use specs::DispatcherBuilder;
|
||||||
use std::{
|
use std::{
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
|
@ -9,6 +9,7 @@ use common_ecs::{Job, Origin, ParMode, Phase, System};
|
|||||||
use common_net::msg::{ClientGeneral, ServerGeneral};
|
use common_net::msg::{ClientGeneral, ServerGeneral};
|
||||||
use rayon::iter::ParallelIterator;
|
use rayon::iter::ParallelIterator;
|
||||||
use specs::{Entities, Join, ParJoin, Read, ReadExpect, ReadStorage};
|
use specs::{Entities, Join, ParJoin, Read, ReadExpect, ReadStorage};
|
||||||
|
use std::sync::Arc;
|
||||||
use tracing::{debug, trace};
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
/// This system will handle new messages from clients
|
/// This system will handle new messages from clients
|
||||||
@ -74,12 +75,12 @@ impl<'a> System<'a> for Sys {
|
|||||||
true
|
true
|
||||||
};
|
};
|
||||||
if in_vd {
|
if in_vd {
|
||||||
match terrain.get_key(key) {
|
match terrain.get_key_arc(key) {
|
||||||
Some(chunk) => {
|
Some(chunk) => {
|
||||||
network_metrics.chunks_served_from_memory.inc();
|
network_metrics.chunks_served_from_memory.inc();
|
||||||
client.send(ServerGeneral::TerrainChunkUpdate {
|
client.send(ServerGeneral::TerrainChunkUpdate {
|
||||||
key,
|
key,
|
||||||
chunk: Ok(Box::new(chunk.clone())),
|
chunk: Ok(Arc::clone(chunk)),
|
||||||
})?
|
})?
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
|
@ -69,6 +69,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
|
|
||||||
// Fetch any generated `TerrainChunk`s and insert them into the terrain.
|
// Fetch any generated `TerrainChunk`s and insert them into the terrain.
|
||||||
// Also, send the chunk data to anybody that is close by.
|
// Also, send the chunk data to anybody that is close by.
|
||||||
|
let mut new_chunks = Vec::new();
|
||||||
'insert_terrain_chunks: while let Some((key, res)) = chunk_generator.recv_new_chunk() {
|
'insert_terrain_chunks: while let Some((key, res)) = chunk_generator.recv_new_chunk() {
|
||||||
let (chunk, supplement) = match res {
|
let (chunk, supplement) = match res {
|
||||||
Ok((chunk, supplement)) => (chunk, supplement),
|
Ok((chunk, supplement)) => (chunk, supplement),
|
||||||
@ -85,31 +86,16 @@ impl<'a> System<'a> for Sys {
|
|||||||
continue 'insert_terrain_chunks;
|
continue 'insert_terrain_chunks;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// Send the chunk to all nearby players.
|
|
||||||
let mut lazy_msg = None;
|
|
||||||
for (presence, pos, client) in (&presences, &positions, &clients).join() {
|
|
||||||
let chunk_pos = terrain.pos_key(pos.0.map(|e| e as i32));
|
|
||||||
// Subtract 2 from the offset before computing squared magnitude
|
|
||||||
// 1 since chunks need neighbors to be meshed
|
|
||||||
// 1 to act as a buffer if the player moves in that direction
|
|
||||||
let adjusted_dist_sqr = (chunk_pos - key)
|
|
||||||
.map(|e: i32| (e.abs() as u32).saturating_sub(2))
|
|
||||||
.magnitude_squared();
|
|
||||||
|
|
||||||
if adjusted_dist_sqr <= presence.view_distance.pow(2) {
|
// Arcify the chunk
|
||||||
if lazy_msg.is_none() {
|
let chunk = Arc::new(chunk);
|
||||||
lazy_msg = Some(client.prepare(ServerGeneral::TerrainChunkUpdate {
|
|
||||||
key,
|
// Add to list of chunks to send to nearby players.
|
||||||
chunk: Ok(Box::new(chunk.clone())),
|
new_chunks.push((key, Arc::clone(&chunk)));
|
||||||
}));
|
|
||||||
}
|
|
||||||
lazy_msg.as_ref().map(|ref msg| client.send_prepared(&msg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: code duplication for chunk insertion between here and state.rs
|
// TODO: code duplication for chunk insertion between here and state.rs
|
||||||
// Insert the chunk into terrain changes
|
// Insert the chunk into terrain changes
|
||||||
if terrain.insert(key, Arc::new(chunk)).is_some() {
|
if terrain.insert(key, chunk).is_some() {
|
||||||
terrain_changes.modified_chunks.insert(key);
|
terrain_changes.modified_chunks.insert(key);
|
||||||
} else {
|
} else {
|
||||||
terrain_changes.new_chunks.insert(key);
|
terrain_changes.new_chunks.insert(key);
|
||||||
@ -233,6 +219,36 @@ impl<'a> System<'a> for Sys {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send the chunk to all nearby players.
|
||||||
|
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||||
|
new_chunks.into_par_iter().for_each(|(key, chunk)| {
|
||||||
|
let mut msg = Some(ServerGeneral::TerrainChunkUpdate {
|
||||||
|
key,
|
||||||
|
chunk: Ok(chunk),
|
||||||
|
});
|
||||||
|
let mut lazy_msg = None;
|
||||||
|
|
||||||
|
(&presences, &positions, &clients)
|
||||||
|
.join()
|
||||||
|
.for_each(|(presence, pos, client)| {
|
||||||
|
let chunk_pos = terrain.pos_key(pos.0.map(|e| e as i32));
|
||||||
|
// Subtract 2 from the offset before computing squared magnitude
|
||||||
|
// 1 since chunks need neighbors to be meshed
|
||||||
|
// 1 to act as a buffer if the player moves in that direction
|
||||||
|
let adjusted_dist_sqr = (chunk_pos - key)
|
||||||
|
.map(|e: i32| (e.abs() as u32).saturating_sub(2))
|
||||||
|
.magnitude_squared();
|
||||||
|
|
||||||
|
if adjusted_dist_sqr <= presence.view_distance.pow(2) {
|
||||||
|
if let Some(msg) = msg.take() {
|
||||||
|
lazy_msg = Some(client.prepare(msg));
|
||||||
|
};
|
||||||
|
|
||||||
|
lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Remove chunks that are too far from players.
|
// Remove chunks that are too far from players.
|
||||||
let mut chunks_to_remove = Vec::new();
|
let mut chunks_to_remove = Vec::new();
|
||||||
terrain
|
terrain
|
||||||
|
@ -4,6 +4,7 @@ use common_ecs::{Job, Origin, Phase, System};
|
|||||||
use common_net::msg::ServerGeneral;
|
use common_net::msg::ServerGeneral;
|
||||||
use common_state::TerrainChanges;
|
use common_state::TerrainChanges;
|
||||||
use specs::{Join, Read, ReadExpect, ReadStorage};
|
use specs::{Join, Read, ReadExpect, ReadStorage};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// This systems sends new chunks to clients as well as changes to existing
|
/// This systems sends new chunks to clients as well as changes to existing
|
||||||
/// chunks
|
/// chunks
|
||||||
@ -37,10 +38,10 @@ impl<'a> System<'a> for Sys {
|
|||||||
if lazy_msg.is_none() {
|
if lazy_msg.is_none() {
|
||||||
lazy_msg = Some(client.prepare(ServerGeneral::TerrainChunkUpdate {
|
lazy_msg = Some(client.prepare(ServerGeneral::TerrainChunkUpdate {
|
||||||
key: *chunk_key,
|
key: *chunk_key,
|
||||||
chunk: Ok(Box::new(match terrain.get_key(*chunk_key) {
|
chunk: Ok(match terrain.get_key_arc(*chunk_key) {
|
||||||
Some(chunk) => chunk.clone(),
|
Some(chunk) => Arc::clone(chunk),
|
||||||
None => break 'chunk,
|
None => break 'chunk,
|
||||||
})),
|
}),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
lazy_msg.as_ref().map(|ref msg| client.send_prepared(&msg));
|
lazy_msg.as_ref().map(|ref msg| client.send_prepared(&msg));
|
||||||
|
@ -24,7 +24,7 @@ gl = ["gfx_device_gl", "gfx_gl"]
|
|||||||
hot-anim = ["anim/use-dyn-lib"]
|
hot-anim = ["anim/use-dyn-lib"]
|
||||||
singleplayer = ["server"]
|
singleplayer = ["server"]
|
||||||
simd = ["vek/platform_intrinsics"]
|
simd = ["vek/platform_intrinsics"]
|
||||||
tracy = ["common/tracy", "common-ecs/tracy", "common-frontend/tracy", "common-net/tracy", "common-state/tracy", "client/tracy"]
|
tracy = ["common/tracy", "common-ecs/tracy", "common-frontend/tracy", "common-net/tracy", "common-systems/tracy", "common-state/tracy", "client/tracy"]
|
||||||
plugins = ["client/plugins"]
|
plugins = ["client/plugins"]
|
||||||
|
|
||||||
default = ["gl", "singleplayer", "native-dialog", "plugins", "simd"]
|
default = ["gl", "singleplayer", "native-dialog", "plugins", "simd"]
|
||||||
@ -36,8 +36,8 @@ common-base = {package = "veloren-common-base", path = "../common/base"}
|
|||||||
common-ecs = {package = "veloren-common-ecs", path = "../common/ecs"}
|
common-ecs = {package = "veloren-common-ecs", path = "../common/ecs"}
|
||||||
common-frontend = {package = "veloren-common-frontend", path = "../common/frontend"}
|
common-frontend = {package = "veloren-common-frontend", path = "../common/frontend"}
|
||||||
common-net = {package = "veloren-common-net", path = "../common/net"}
|
common-net = {package = "veloren-common-net", path = "../common/net"}
|
||||||
|
common-systems = {package = "veloren-common-systems", path = "../common/systems"}
|
||||||
common-state = {package = "veloren-common-state", path = "../common/state"}
|
common-state = {package = "veloren-common-state", path = "../common/state"}
|
||||||
common-sys = {package = "veloren-common-sys", path = "../common/sys"}
|
|
||||||
|
|
||||||
anim = {package = "veloren-voxygen-anim", path = "anim"}
|
anim = {package = "veloren-voxygen-anim", path = "anim"}
|
||||||
|
|
||||||
|
@ -5,6 +5,6 @@ use common_ecs::{dispatch, System};
|
|||||||
use specs::DispatcherBuilder;
|
use specs::DispatcherBuilder;
|
||||||
|
|
||||||
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
|
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||||
dispatch::<interpolation::Sys>(dispatch_builder, &[&common_sys::phys::Sys::sys_name()]);
|
dispatch::<interpolation::Sys>(dispatch_builder, &[&common_systems::phys::Sys::sys_name()]);
|
||||||
dispatch::<floater::Sys>(dispatch_builder, &[&interpolation::Sys::sys_name()]);
|
dispatch::<floater::Sys>(dispatch_builder, &[&interpolation::Sys::sys_name()]);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user