diff --git a/Cargo.lock b/Cargo.lock index 7fd91c26e2..272df33c8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1577,6 +1577,11 @@ dependencies = [ "tiff 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "indexmap" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "inflate" version = "0.3.4" @@ -3592,7 +3597,9 @@ dependencies = [ "dot_vox 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "find_folder 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hibitset 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.22.2 (registry+https://github.com/rust-lang/crates.io-index)", + "indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "lz4-compress 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3618,6 +3625,7 @@ dependencies = [ "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hibitset 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "portpicker 0.1.0 (git+https://github.com/wusyong/portpicker-rs?branch=fix_ipv6)", @@ -3630,6 +3638,7 @@ dependencies = [ "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "specs 0.14.3 (registry+https://github.com/rust-lang/crates.io-index)", + "specs-idvs 0.1.0 (git+https://gitlab.com/veloren/specs-idvs.git)", "uvth 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "vek 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)", "veloren-common 0.4.0", @@ -4144,6 +4153,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" "checksum image 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "545f000e8aa4e569e93f49c446987133452e0091c2494ac3efd3606aa3d309f2" "checksum image 0.22.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ee0665404aa0f2ad154021777b785878b0e5b1c1da030455abc3d9ed257c2c67" +"checksum indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a61202fbe46c4a951e9404a720a0180bcf3212c750d735cb5c4ba4dc551299f3" "checksum inflate 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f5f9f47468e9a76a6452271efadc88fe865a82be91fe75e6c0c57b87ccea59d4" "checksum inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" "checksum inotify 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24e40d6fd5d64e2082e0c796495c8ef5ad667a96d03e5aaa0becfd9d47bcbfb8" diff --git a/chat-cli/src/main.rs b/chat-cli/src/main.rs index 00d609ab7a..975dec4e2b 100644 --- a/chat-cli/src/main.rs +++ b/chat-cli/src/main.rs @@ -65,7 +65,7 @@ fn main() { client.send_chat(msg) } - let events = match client.tick(comp::Controller::default(), clock.get_last_delta()) { + let events = match client.tick(comp::ControllerInputs::default(), clock.get_last_delta()) { Ok(events) => events, Err(err) => { error!("Error: {:?}", err); diff --git a/client/src/lib.rs b/client/src/lib.rs index df7aa88a1d..1aeb7c7b31 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -8,7 +8,7 @@ pub use crate::error::Error; pub use specs::{join::Join, saveload::Marker, Entity as EcsEntity, ReadStorage}; use common::{ - comp, + comp::{self, ControlEvent, Controller, ControllerInputs, InventoryManip}, msg::{ validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, RequestStateError, ServerError, ServerInfo, ServerMsg, MAX_BYTES_CHAT_MSG, @@ -201,22 +201,33 @@ impl Client { // Can't fail } - pub fn use_inventory_slot(&mut self, x: usize) { - self.postbox.send_message(ClientMsg::UseInventorySlot(x)) + pub fn use_inventory_slot(&mut self, slot: usize) { + self.postbox + .send_message(ClientMsg::ControlEvent(ControlEvent::InventoryManip( + InventoryManip::Use(slot), + ))); } pub fn swap_inventory_slots(&mut self, a: usize, b: usize) { self.postbox - .send_message(ClientMsg::SwapInventorySlots(a, b)) + .send_message(ClientMsg::ControlEvent(ControlEvent::InventoryManip( + InventoryManip::Swap(a, b), + ))); } - pub fn drop_inventory_slot(&mut self, x: usize) { - self.postbox.send_message(ClientMsg::DropInventorySlot(x)) + pub fn drop_inventory_slot(&mut self, slot: usize) { + self.postbox + .send_message(ClientMsg::ControlEvent(ControlEvent::InventoryManip( + InventoryManip::Drop(slot), + ))); } pub fn pick_up(&mut self, entity: EcsEntity) { if let Some(uid) = self.state.ecs().read_storage::().get(entity).copied() { - self.postbox.send_message(ClientMsg::PickUp(uid.id())); + self.postbox + .send_message(ClientMsg::ControlEvent(ControlEvent::InventoryManip( + InventoryManip::Pickup(uid), + ))); } } @@ -228,6 +239,18 @@ impl Client { .is_some() } + pub fn mount(&mut self, entity: EcsEntity) { + if let Some(uid) = self.state.ecs().read_storage::().get(entity).copied() { + self.postbox + .send_message(ClientMsg::ControlEvent(ControlEvent::Mount(uid))); + } + } + + pub fn unmount(&mut self) { + self.postbox + .send_message(ClientMsg::ControlEvent(ControlEvent::Unmount)); + } + pub fn view_distance(&self) -> Option { self.view_distance } @@ -283,16 +306,15 @@ impl Client { } pub fn collect_block(&mut self, pos: Vec3) { - self.postbox.send_message(ClientMsg::CollectBlock(pos)); + self.postbox + .send_message(ClientMsg::ControlEvent(ControlEvent::InventoryManip( + InventoryManip::Collect(pos), + ))); } /// Execute a single client tick, handle input and update the game state by the given duration. #[allow(dead_code)] - pub fn tick( - &mut self, - controller: comp::Controller, - dt: Duration, - ) -> Result, Error> { + pub fn tick(&mut self, inputs: ControllerInputs, dt: Duration) -> Result, Error> { // This tick function is the centre of the Veloren universe. Most client-side things are // managed from here, and as such it's important that it stays organised. Please consult // the core developers before making significant changes to this code. Here is the @@ -310,8 +332,15 @@ impl Client { // 1) Handle input from frontend. // Pass character actions from frontend input to the player's entity. if let ClientState::Character | ClientState::Dead = self.client_state { - self.state.write_component(self.entity, controller.clone()); - self.postbox.send_message(ClientMsg::Controller(controller)); + self.state.write_component( + self.entity, + Controller { + inputs: inputs.clone(), + events: Vec::new(), + }, + ); + self.postbox + .send_message(ClientMsg::ControllerInputs(inputs)); } // 2) Build up a list of events for this frame, to be passed to the frontend. @@ -319,7 +348,7 @@ impl Client { // Prepare for new events { - let ecs = self.state.ecs_mut(); + let ecs = self.state.ecs(); for (entity, _) in (&ecs.entities(), &ecs.read_storage::()).join() { let mut last_character_states = ecs.write_storage::>(); @@ -343,7 +372,7 @@ impl Client { // 3) Update client local data // 4) Tick the client's LocalState - self.state.tick(dt); + self.state.tick(dt, |_| {}); // 5) Terrain let pos = self @@ -501,6 +530,13 @@ impl Client { ServerMsg::EcsSync(sync_package) => { self.state.ecs_mut().sync_with_package(sync_package) } + ServerMsg::DeleteEntity(entity) => { + if let Some(entity) = self.state.ecs().entity_from_uid(entity) { + if entity != self.entity { + let _ = self.state.ecs_mut().delete_entity(entity); + } + } + } ServerMsg::EntityPos { entity, pos } => { if let Some(entity) = self.state.ecs().entity_from_uid(entity) { self.state.write_component(entity, pos); diff --git a/common/Cargo.toml b/common/Cargo.toml index f24e52d3d5..d1825f29fd 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -28,6 +28,9 @@ find_folder = "0.3.0" parking_lot = "0.9.0" crossbeam = "0.7.2" notify = "5.0.0-pre.1" +indexmap = "1.2.0" +# TODO: remove when upgrading to specs 0.15 +hibitset = "0.5.3" [dev-dependencies] criterion = "0.3" diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index 178e5a628e..6a45bebfbc 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -7,14 +7,14 @@ use vek::*; pub enum ControlEvent { Mount(Uid), Unmount, + InventoryManip(InventoryManip), + //Respawn, } #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -pub struct Controller { +pub struct ControllerInputs { pub primary: bool, pub secondary: bool, - pub move_dir: Vec2, - pub look_dir: Vec3, pub sit: bool, pub jump: bool, pub roll: bool, @@ -23,6 +23,14 @@ pub struct Controller { pub climb_down: bool, pub wall_leap: bool, pub respawn: bool, + pub move_dir: Vec2, + pub look_dir: Vec3, +} + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct Controller { + pub inputs: ControllerInputs, + // TODO: consider SmallVec pub events: Vec, } @@ -60,3 +68,12 @@ pub struct Mounting(pub Uid); impl Component for Mounting { type Storage = FlaggedStorage>; } + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum InventoryManip { + Pickup(Uid), + Collect(Vec3), + Use(usize), + Swap(usize, usize), + Drop(usize), +} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 438da87db6..5df88295b5 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -18,7 +18,9 @@ pub use admin::Admin; pub use agent::Agent; pub use body::{humanoid, object, quadruped, quadruped_medium, Body}; pub use character_state::{ActionState, CharacterState, MovementState}; -pub use controller::{ControlEvent, Controller, MountState, Mounting}; +pub use controller::{ + ControlEvent, Controller, ControllerInputs, InventoryManip, MountState, Mounting, +}; pub use inputs::CanBuild; pub use inventory::{item, Inventory, InventoryUpdate, Item}; pub use last::Last; diff --git a/common/src/event.rs b/common/src/event.rs index b2c771ab8a..c76bc59b16 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -30,6 +30,7 @@ pub enum ServerEvent { entity: EcsEntity, cause: comp::HealthSource, }, + InventoryManip(EcsEntity, comp::InventoryManip), Respawn(EcsEntity), Shoot { entity: EcsEntity, @@ -46,6 +47,22 @@ pub enum ServerEvent { Mount(EcsEntity, EcsEntity), Unmount(EcsEntity), Possess(Uid, Uid), + CreatePlayer { + entity: EcsEntity, + name: String, + body: comp::Body, + main: Option, + }, + CreateNpc { + pos: comp::Pos, + stats: comp::Stats, + body: comp::Body, + agent: comp::Agent, + scale: comp::Scale, + }, + ClientDisconnect(EcsEntity), + ChunkRequest(EcsEntity, Vec2), + ChatCmd(EcsEntity, String), } pub struct EventBus { diff --git a/common/src/lib.rs b/common/src/lib.rs index 34848ad1c8..06a775c88d 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -19,6 +19,7 @@ pub mod figure; pub mod msg; pub mod npc; pub mod ray; +pub mod region; pub mod state; pub mod sys; pub mod terrain; diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index 1e98732283..c699db93d1 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -14,12 +14,12 @@ pub enum ClientMsg { body: comp::Body, main: Option, }, - Controller(comp::Controller), + ControllerInputs(comp::ControllerInputs), + ControlEvent(comp::ControlEvent), RequestState(ClientState), SetViewDistance(u32), BreakBlock(Vec3), PlaceBlock(Vec3, Block), - CollectBlock(Vec3), Ping, Pong, ChatMsg { @@ -31,10 +31,6 @@ pub enum ClientMsg { vel: comp::Vel, ori: comp::Ori, }, - UseInventorySlot(usize), - SwapInventorySlots(usize, usize), - DropInventorySlot(usize), - PickUp(u64), TerrainChunkRequest { key: Vec2, }, diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index d0074d8e0d..cbf1d9ac8a 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -41,6 +41,7 @@ pub enum ServerMsg { }, SetPlayerEntity(u64), EcsSync(sphynx::SyncPackage), + DeleteEntity(u64), EntityPos { entity: u64, pos: comp::Pos, diff --git a/common/src/region.rs b/common/src/region.rs new file mode 100644 index 0000000000..de8e5a526d --- /dev/null +++ b/common/src/region.rs @@ -0,0 +1,360 @@ +use crate::comp::{Pos, Vel}; +use hashbrown::{hash_map::DefaultHashBuilder, HashSet}; +use hibitset::BitSetLike; +use indexmap::IndexMap; +use specs::{BitSet, Entities, Join, ReadStorage}; +use vek::*; + +pub enum Event { + // Contains the key of the region the entity moved to + Left(u32, Option>), + // Contains the key of the region the entity came from + Entered(u32, Option>), +} + +/// Region consisting of a bitset of entities within it +pub struct Region { + // Use specs bitset for simplicity (and joinability) + bitset: BitSet, + // Indices of neighboring regions + neighbors: [Option; 8], + // TODO consider SmallVec for these + // Entites that left or entered this region + events: Vec, +} +impl Region { + fn new() -> Self { + Self { + bitset: BitSet::new(), + neighbors: [None; 8], + events: Vec::new(), + } + } + /// Checks if the region contains no entities and no events + fn removable(&self) -> bool { + self.bitset.is_empty() && self.events.is_empty() + } + fn add(&mut self, id: u32, from: Option>) { + self.bitset.add(id); + self.events.push(Event::Entered(id, from)); + } + fn remove(&mut self, id: u32, to: Option>) { + self.bitset.remove(id); + self.events.push(Event::Left(id, to)); + } + pub fn events(&self) -> &[Event] { + &self.events + } + pub fn entities(&self) -> &BitSet { + &self.bitset + } +} + +/// How far can an entity roam outside its region before it is switched over to the neighboring one +/// In units of blocks (i.e. world pos) +/// Used to prevent rapid switching of entities between regions +pub const TETHER_LENGTH: u32 = 16; +/// Bitshift between region and world pos, i.e. log2(REGION_SIZE) +const REGION_LOG2: u8 = 9; +/// Region Size in blocks +pub const REGION_SIZE: u32 = 1 << REGION_LOG2; +/// Offsets to iterate though neighbors +/// Counter-clockwise order +const NEIGHBOR_OFFSETS: [Vec2; 8] = [ + Vec2::new(0, 1), + Vec2::new(-1, 1), + Vec2::new(-1, 0), + Vec2::new(-1, -1), + Vec2::new(0, -1), + Vec2::new(1, -1), + Vec2::new(1, 0), + Vec2::new(1, 1), +]; + +// TODO generic region size (16x16 for now) +// TODO compare to sweep and prune approach +/// A region system that tracks where entities are +pub struct RegionMap { + // Tree? + // Sorted Vec? (binary search lookup) + // Sort into multiple vecs (say 32) using lower bits of morton code, then binary search via upper bits? <-- sounds very promising to me (might not be super good though?) + regions: IndexMap, Region, DefaultHashBuilder>, + // If an entity isn't here it needs to be added to a region + tracked_entities: BitSet, + // Re-useable vecs + // (src, entity, pos) + entities_to_move: Vec<(usize, u32, Vec3)>, + // (region, entity) + entities_to_remove: Vec<(usize, u32)>, + // Track the current tick, used to enable not checking everything every tick + tick: u64, +} +impl RegionMap { + pub fn new() -> Self { + Self { + regions: IndexMap::default(), + tracked_entities: BitSet::new(), + entities_to_move: Vec::new(), + entities_to_remove: Vec::new(), + // rate is depedent on the rate the caller calls region_manager.tick() + tick: 0, + } + } + // TODO maintain within a system? + // TODO special case large entities + pub fn tick(&mut self, pos: ReadStorage, vel: ReadStorage, entities: Entities) { + self.tick += 1; + // Add any untracked entites + for (pos, id) in (&pos, &entities, !&self.tracked_entities) + .join() + .map(|(pos, e, _)| (pos, e.id())) + .collect::>() + { + // Add entity + self.tracked_entities.add(id); + self.add_entity(id, pos.0.map(|e| e as i32), None); + } + + let mut regions_to_remove = Vec::new(); + + for i in 0..self.regions.len() { + // Clear events within each region + self.regions + .get_index_mut(i) + .map(|(_, v)| v) + .unwrap() + .events + .clear(); + + for (maybe_pos, _maybe_vel, id) in ( + pos.maybe(), + vel.maybe(), + &self.regions.get_index(i).map(|(_, v)| v).unwrap().bitset, + ) + .join() + { + match maybe_pos { + // Switch regions for entities which need switching + // TODO don't check every tick (use velocity) (and use id to stagger) + // Starting parameters at v = 0 check every 100 ticks + // tether_length^2 / vel^2 (with a max of every tick) + Some(pos) => { + let pos = pos.0.map(|e| e as i32); + let current_region = self.index_key(i).unwrap(); + let key = Self::pos_key(pos); + // Consider switching + // Caculate distance outside border + if key != current_region + && (Vec2::::from(pos) - Self::key_pos(current_region)) + .map(|e| e.abs() as u32) + .reduce_max() + > TETHER_LENGTH + { + // Switch + self.entities_to_move.push((i, id, pos)); + } + } + // Remove any non-existant entities (or just ones that lost their position component) + // TODO: distribute this between ticks + None => { + // TODO: shouldn't there be a way to extract the bitset of entities with positions directly from specs? + self.entities_to_remove.push((i, id)); + } + } + } + + // Remove region if it is empty + // TODO: distribute this betweeen ticks + let (key, region) = self.regions.get_index(i).unwrap(); + if region.removable() { + regions_to_remove.push(*key); + } + } + + // Mutate + // Note entity moving is outside the whole loop so that the same entity is not checked twice (this may be fine though...) + while let Some((i, id, pos)) = self.entities_to_move.pop() { + let (prev_key, region) = self.regions.get_index_mut(i).map(|(k, v)| (*k, v)).unwrap(); + region.remove(id, Some(Self::pos_key(pos))); + + self.add_entity(id, pos, Some(prev_key)); + } + for (i, id) in self.entities_to_remove.drain(..) { + self.regions + .get_index_mut(i) + .map(|(_, v)| v) + .unwrap() + .remove(id, None); + self.tracked_entities.remove(id); + } + for key in regions_to_remove.into_iter() { + // Check that the region is still removable + if self.regions.get(&key).unwrap().removable() { + // Note we have to use key's here since the index can change when others are removed + self.remove(key); + } + } + } + fn add_entity(&mut self, id: u32, pos: Vec3, from: Option>) { + let key = Self::pos_key(pos); + if let Some(region) = self.regions.get_mut(&key) { + region.add(id, from); + return; + } + + let index = self.insert(key); + self.regions + .get_index_mut(index) + .map(|(_, v)| v) + .unwrap() + .add(id, None); + } + fn pos_key>>(pos: P) -> Vec2 { + pos.into().map(|e| e >> REGION_LOG2) + } + pub fn key_pos(key: Vec2) -> Vec2 { + key.map(|e| e << REGION_LOG2) + } + fn key_index(&self, key: Vec2) -> Option { + self.regions.get_full(&key).map(|(i, _, _)| i) + } + fn index_key(&self, index: usize) -> Option> { + self.regions.get_index(index).map(|(k, _)| k).copied() + } + /// Adds a new region + /// Returns the index of the region in the index map + fn insert(&mut self, key: Vec2) -> usize { + let (index, old_region) = self.regions.insert_full(key, Region::new()); + if old_region.is_some() { + panic!("Inserted a region that already exists!!!(this should never need to occur"); + } + // Add neighbors and add to neighbors + let mut neighbors = [None; 8]; + for i in 0..8 { + if let Some((idx, _, region)) = self.regions.get_full_mut(&(key + NEIGHBOR_OFFSETS[i])) + { + // Add neighbor to the new region + neighbors[i] = Some(idx); + // Add new region to neighbor + region.neighbors[(i + 4) % 8] = Some(index); + } + } + self.regions + .get_index_mut(index) + .map(|(_, v)| v) + .unwrap() + .neighbors = neighbors; + + index + } + /// Remove a region using its key + fn remove(&mut self, key: Vec2) { + if let Some(index) = self.key_index(key) { + self.remove_index(index); + } + } + /// Add a region using its key + fn remove_index(&mut self, index: usize) { + // Remap neighbor indices for neighbors of the region that will be moved from the end of the index map + if index != self.regions.len() - 1 { + let moved_neighbors = self + .regions + .get_index(self.regions.len() - 1) + .map(|(_, v)| v) + .unwrap() + .neighbors; + for i in 0..8 { + if let Some(idx) = moved_neighbors[i] { + self.regions + .get_index_mut(idx) + .map(|(_, v)| v) + .unwrap() + .neighbors[(i + 4) % 8] = Some(index); + } + } + } + if let Some(region) = self + .regions + .swap_remove_index(index) + .map(|(_, region)| region) + { + if !region.bitset.is_empty() { + panic!("Removed region containing entities"); + } + // Remove from neighbors + for i in 0..8 { + if let Some(idx) = region.neighbors[i] { + self.regions + .get_index_mut(idx) + .map(|(_, v)| v) + .unwrap() + .neighbors[(i + 4) % 8] = None; + } + } + } + } + // Returns a region given a key + pub fn get(&self, key: Vec2) -> Option<&Region> { + self.regions.get(&key) + } + // Returns an iterator of (Position, Region) + pub fn iter(&self) -> impl Iterator, &Region)> { + self.regions.iter().map(|(key, r)| (*key, r)) + } +} + +// Note vd is in blocks in this case +pub fn region_in_vd(key: Vec2, pos: Vec3, vd: f32) -> bool { + let vd_extended = vd + TETHER_LENGTH as f32 * 2.0f32.sqrt(); + + let min_region_pos = RegionMap::key_pos(key).map(|e| e as f32); + // Should be diff to closest point on the square (which can be in the middle of an edge) + let diff = (min_region_pos - Vec2::from(pos)).map(|e| { + if e < 0.0 { + (e + REGION_SIZE as f32).min(0.0) + } else { + e + } + }); + + diff.magnitude_squared() < vd_extended.powi(2) +} + +// Note vd is in blocks in this case +pub fn regions_in_vd(pos: Vec3, vd: f32) -> HashSet> { + let mut set = HashSet::new(); + + let pos_xy = Vec2::::from(pos); + let vd_extended = vd + TETHER_LENGTH as f32 * 2.0f32.sqrt(); + + let max = RegionMap::pos_key(pos_xy.map(|e| (e + vd_extended) as i32)); + let min = RegionMap::pos_key(pos_xy.map(|e| (e - vd_extended) as i32)); + + for x in min.x..=max.x { + for y in min.y..=max.y { + let key = Vec2::new(x, y); + + if region_in_vd(key, pos, vd) { + set.insert(key); + } + } + } + + set +} +// Iterator designed for use in collision systems +// Iterates through all regions yielding them along with half of their neighbors +// .................. + +/*fn interleave_i32_with_zeros(mut x: i32) -> i64 { + x = (x ^ (x << 16)) & 0x0000ffff0000ffff; + x = (x ^ (x << 8)) & 0x00ff00ff00ff00ff; + x = (x ^ (x << 4)) & 0x0f0f0f0f0f0f0f0f; + x = (x ^ (x << 2)) & 0x3333333333333333; + x = (x ^ (x << 1)) & 0x5555555555555555; + x +} + +fn morton_code(pos: Vec2) -> i64 { + interleave_i32_with_zeros(pos.x) | (interleave_i32_with_zeros(pos.y) << 1) +}*/ diff --git a/common/src/state.rs b/common/src/state.rs index a49af51625..640b283520 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -5,6 +5,7 @@ use crate::{ comp, event::{EventBus, LocalEvent, ServerEvent}, msg::{EcsCompPacket, EcsResPacket}, + region::RegionMap, sys, terrain::{Block, TerrainChunk, TerrainGrid}, vol::WriteVol, @@ -172,6 +173,7 @@ impl State { ecs.add_resource(TerrainChanges::default()); ecs.add_resource(EventBus::::default()); ecs.add_resource(EventBus::::default()); + ecs.add_resource(RegionMap::new()); } /// Register a component with the state's ECS. @@ -307,7 +309,7 @@ impl State { } /// Execute a single tick, simulating the game state by the given duration. - pub fn tick(&mut self, dt: Duration) { + pub fn tick(&mut self, dt: Duration, add_foreign_systems: impl Fn(&mut DispatcherBuilder)) { // Change the time accordingly. self.ecs.write_resource::().0 += dt.as_secs_f64() * DAY_CYCLE_FACTOR; self.ecs.write_resource::