Merge branch 'imbris/id-maps' into 'master'

IdMaps: Uid refactor and adding mappings from rtsim entities and character IDs to ecs entity IDs.

See merge request veloren/veloren!3971
This commit is contained in:
Imbris 2023-06-05 20:21:23 +00:00
commit f8ae6cdbbe
57 changed files with 845 additions and 626 deletions

View File

@ -89,6 +89,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Multiple model support for dropped items (orichalcum armor)
- Made rtsim monsters not go into too deep water, and certainly not outside the map.
- Fixed bug where npcs would be dismounted from vehicles if loaded/unloaded in a certain order.
- Fixed a slow leak on the server where Uid -> Entity mappings weren't cleaned up.
- Clients going back into the character screen now properly have their old entity cleaned up on
other clients.
## [0.14.0] - 2023-01-07

View File

@ -10,9 +10,7 @@ pub use crate::error::Error;
pub use authc::AuthClientError;
pub use common_net::msg::ServerInfo;
pub use specs::{
join::Join,
saveload::{Marker, MarkerAllocator},
Builder, DispatcherBuilder, Entity as EcsEntity, ReadStorage, World, WorldExt,
join::Join, Builder, DispatcherBuilder, Entity as EcsEntity, ReadStorage, World, WorldExt,
};
use crate::addr::ConnectionArgs;
@ -49,7 +47,7 @@ use common::{
TerrainGrid,
},
trade::{PendingTrade, SitePrices, TradeAction, TradeId, TradeResult},
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
vol::RectVolSize,
weather::{Weather, WeatherGrid},
};
@ -2203,7 +2201,7 @@ impl Client {
self.chat_mode = m;
},
ServerGeneral::SetPlayerEntity(uid) => {
if let Some(entity) = self.state.ecs().entity_from_uid(uid.0) {
if let Some(entity) = self.state.ecs().entity_from_uid(uid) {
let old_player_entity = mem::replace(
&mut *self.state.ecs_mut().write_resource(),
PlayerEntity(Some(entity)),
@ -2223,6 +2221,7 @@ impl Client {
if let Some(presence) = self.presence {
self.presence = Some(match presence {
PresenceKind::Spectator => PresenceKind::Spectator,
PresenceKind::LoadingCharacter(_) => PresenceKind::Possessor,
PresenceKind::Character(_) => PresenceKind::Possessor,
PresenceKind::Possessor => PresenceKind::Possessor,
});
@ -2252,9 +2251,10 @@ impl Client {
self.dt_adjustment = dt_adjustment * time_scale.0;
},
ServerGeneral::EntitySync(entity_sync_package) => {
let uid = self.uid();
self.state
.ecs_mut()
.apply_entity_sync_package(entity_sync_package);
.apply_entity_sync_package(entity_sync_package, uid);
},
ServerGeneral::CompSync(comp_sync_package, force_counter) => {
self.force_update_counter = force_counter;
@ -2265,11 +2265,11 @@ impl Client {
ServerGeneral::CreateEntity(entity_package) => {
self.state.ecs_mut().apply_entity_package(entity_package);
},
ServerGeneral::DeleteEntity(entity) => {
if self.uid() != Some(entity) {
ServerGeneral::DeleteEntity(entity_uid) => {
if self.uid() != Some(entity_uid) {
self.state
.ecs_mut()
.delete_entity_and_clear_from_uid_allocator(entity.0);
.delete_entity_and_clear_uid_mapping(entity_uid);
}
},
ServerGeneral::Notification(n) => {
@ -2738,24 +2738,21 @@ impl Client {
// Clear pending trade
self.pending_trade = None;
let client_uid = self
.uid()
.map(|u| u.into())
.expect("Client doesn't have a Uid!!!");
let client_uid = self.uid().expect("Client doesn't have a Uid!!!");
// Clear ecs of all entities
self.state.ecs_mut().delete_all();
self.state.ecs_mut().maintain();
self.state.ecs_mut().insert(UidAllocator::default());
self.state.ecs_mut().insert(IdMaps::default());
// Recreate client entity with Uid
let entity_builder = self.state.ecs_mut().create_entity();
let uid = entity_builder
entity_builder
.world
.write_resource::<UidAllocator>()
.allocate(entity_builder.entity, Some(client_uid));
.write_resource::<IdMaps>()
.add_entity(client_uid, entity_builder.entity);
let entity = entity_builder.with(uid).build();
let entity = entity_builder.with(client_uid).build();
self.state.ecs().write_resource::<PlayerEntity>().0 = Some(entity);
}

View File

@ -7,7 +7,7 @@ mod sync_ext;
mod track;
// Reexports
pub use common::uid::{Uid, UidAllocator};
pub use common::uid::{IdMaps, Uid};
pub use net_sync::{NetSync, SyncFrom};
pub use packet::{
handle_insert, handle_interp_insert, handle_interp_modify, handle_interp_remove, handle_modify,

View File

@ -101,26 +101,26 @@ pub enum CompUpdateKind<P: CompPacket> {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EntityPackage<P: CompPacket> {
pub uid: u64,
pub uid: Uid,
pub comps: Vec<P>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EntitySyncPackage {
pub created_entities: Vec<u64>,
pub deleted_entities: Vec<u64>,
pub created_entities: Vec<Uid>,
pub deleted_entities: Vec<Uid>,
}
impl EntitySyncPackage {
pub fn new(
uids: &ReadStorage<'_, Uid>,
uid_tracker: &UpdateTracker<Uid>,
filter: impl Join + Copy,
deleted_entities: Vec<u64>,
deleted_entities: Vec<Uid>,
) -> Self {
// Add created and deleted entities
let created_entities = (uids, filter, uid_tracker.inserted())
.join()
.map(|(uid, _, _)| (*uid).into())
.map(|(uid, _, _)| *uid)
.collect();
Self {

View File

@ -4,13 +4,9 @@ use super::{
};
use common::{
resources::PlayerEntity,
uid::{Uid, UidAllocator},
};
use specs::{
saveload::{MarkedBuilder, MarkerAllocator},
world::Builder,
WorldExt,
uid::{IdMaps, Uid},
};
use specs::{world::Builder, WorldExt};
use tracing::error;
pub trait WorldSyncExt {
@ -22,21 +18,21 @@ pub trait WorldSyncExt {
where
C::Storage: Default + specs::storage::Tracked;
fn create_entity_synced(&mut self) -> specs::EntityBuilder;
fn delete_entity_and_clear_from_uid_allocator(&mut self, uid: u64);
fn delete_entity_and_clear_uid_mapping(&mut self, uid: Uid);
fn uid_from_entity(&self, entity: specs::Entity) -> Option<Uid>;
fn entity_from_uid(&self, uid: u64) -> Option<specs::Entity>;
fn entity_from_uid(&self, uid: Uid) -> Option<specs::Entity>;
fn apply_entity_package<P: CompPacket>(
&mut self,
entity_package: EntityPackage<P>,
) -> specs::Entity;
fn apply_entity_sync_package(&mut self, package: EntitySyncPackage);
fn apply_entity_sync_package(&mut self, package: EntitySyncPackage, client_uid: Option<Uid>);
fn apply_comp_sync_package<P: CompPacket>(&mut self, package: CompSyncPackage<P>);
}
impl WorldSyncExt for specs::World {
fn register_sync_marker(&mut self) {
self.register_synced::<Uid>();
self.insert(UidAllocator::new());
self.insert(IdMaps::new());
}
fn register_synced<C: specs::Component + Clone + Send + Sync>(&mut self)
@ -56,12 +52,29 @@ impl WorldSyncExt for specs::World {
}
fn create_entity_synced(&mut self) -> specs::EntityBuilder {
self.create_entity().marked::<Uid>()
// TODO: Add metric for number of new entities created in a tick? Most
// convenient would be to store counter in `IdMaps` so that we don't
// have to fetch another resource nor require an additional parameter here
// nor use globals.
let builder = self.create_entity();
let uid = builder
.world
.write_resource::<IdMaps>()
.allocate(builder.entity);
builder.with(uid)
}
fn delete_entity_and_clear_from_uid_allocator(&mut self, uid: u64) {
/// This method should be used from the client-side when processing network
/// messages that delete entities.
///
/// Only used on the client.
fn delete_entity_and_clear_uid_mapping(&mut self, uid: Uid) {
// Clear from uid allocator
let maybe_entity = self.write_resource::<UidAllocator>().remove_entity(uid);
let maybe_entity = self.write_resource::<IdMaps>()
// Note, rtsim entity and character id mappings don't exist on the client but it is
// easier to reuse the same structure here since there is shared code that needs to
// lookup the entity from a uid.
.remove_entity(None, Some(uid), None, None);
if let Some(entity) = maybe_entity {
if let Err(e) = self.delete_entity(entity) {
error!(?e, "Failed to delete entity");
@ -75,9 +88,8 @@ impl WorldSyncExt for specs::World {
}
/// Get an entity from a UID
fn entity_from_uid(&self, uid: u64) -> Option<specs::Entity> {
self.read_resource::<UidAllocator>()
.retrieve_entity_internal(uid)
fn entity_from_uid(&self, uid: Uid) -> Option<specs::Entity> {
self.read_resource::<IdMaps>().uid_entity(uid)
}
fn apply_entity_package<P: CompPacket>(
@ -94,7 +106,7 @@ impl WorldSyncExt for specs::World {
entity
}
fn apply_entity_sync_package(&mut self, package: EntitySyncPackage) {
fn apply_entity_sync_package(&mut self, package: EntitySyncPackage, client_uid: Option<Uid>) {
// Take ownership of the fields
let EntitySyncPackage {
created_entities,
@ -108,7 +120,13 @@ impl WorldSyncExt for specs::World {
// Attempt to delete entities that were marked for deletion
deleted_entities.into_iter().for_each(|uid| {
self.delete_entity_and_clear_from_uid_allocator(uid);
// Skip deleting the client's own entity. It will appear here when exiting
// "in-game" and the client already handles cleaning things up
// there. Although the client might not get this anyway since it
// should no longer be subscribed to any regions.
if client_uid != Some(uid) {
self.delete_entity_and_clear_uid_mapping(uid);
}
});
}
@ -116,10 +134,7 @@ impl WorldSyncExt for specs::World {
// Update components
let player_entity = self.read_resource::<PlayerEntity>().0;
package.comp_updates.into_iter().for_each(|(uid, update)| {
if let Some(entity) = self
.read_resource::<UidAllocator>()
.retrieve_entity_internal(uid)
{
if let Some(entity) = self.read_resource::<IdMaps>().uid_entity(uid.into()) {
let force_update = player_entity == Some(entity);
match update {
CompUpdateKind::Inserted(packet) => {
@ -138,20 +153,23 @@ impl WorldSyncExt for specs::World {
}
// Private utilities
fn create_entity_with_uid(specs_world: &mut specs::World, entity_uid: u64) -> specs::Entity {
let existing_entity = specs_world
.read_resource::<UidAllocator>()
.retrieve_entity_internal(entity_uid);
//
// Only used on the client.
fn create_entity_with_uid(specs_world: &mut specs::World, entity_uid: Uid) -> specs::Entity {
let existing_entity = specs_world.read_resource::<IdMaps>().uid_entity(entity_uid);
// TODO: Are there any expected cases where there is an existing entity with
// this UID? If not, we may want to log an error. Otherwise, it may be useful to
// document these cases.
match existing_entity {
Some(entity) => entity,
None => {
let entity_builder = specs_world.create_entity();
let uid = entity_builder
entity_builder
.world
.write_resource::<UidAllocator>()
.allocate(entity_builder.entity, Some(entity_uid));
entity_builder.with(uid).build()
.write_resource::<IdMaps>()
.add_entity(entity_uid, entity_builder.entity);
entity_builder.with(entity_uid).build()
},
}
}

View File

@ -19,7 +19,7 @@ use crate::{
outcome::Outcome,
resources::Secs,
states::utils::StageSection,
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
util::Dir,
};
@ -29,7 +29,7 @@ use crate::{comp::Group, resources::Time};
#[cfg(not(target_arch = "wasm32"))]
use {
rand::Rng,
specs::{saveload::MarkerAllocator, Entity as EcsEntity, ReadStorage},
specs::{Entity as EcsEntity, ReadStorage},
std::ops::{Mul, MulAssign},
vek::*,
};
@ -714,7 +714,7 @@ impl Attack {
pub fn may_harm(
alignments: &ReadStorage<Alignment>,
players: &ReadStorage<Player>,
uid_allocator: &UidAllocator,
id_maps: &IdMaps,
attacker: Option<EcsEntity>,
target: EcsEntity,
) -> bool {
@ -725,9 +725,7 @@ pub fn may_harm(
if let Some(Alignment::Owned(uid)) = alignment {
// return original entity
// if can't get owner
uid_allocator
.retrieve_entity_internal(uid.into())
.unwrap_or(entity)
id_maps.uid_entity(uid).unwrap_or(entity)
} else {
entity
}

View File

@ -8,6 +8,9 @@ use vek::*;
pub struct Presence {
pub terrain_view_distance: ViewDistance,
pub entity_view_distance: ViewDistance,
/// If mutating this (or the adding/replacing the Presence component as a
/// whole), make sure the mapping of `CharacterId` in `IdMaps` is
/// updated!
pub kind: PresenceKind,
pub lossy_terrain_compression: bool,
}
@ -31,6 +34,10 @@ impl Component for Presence {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PresenceKind {
Spectator,
// Note: we don't know if this character ID is valid and associated with the player until the
// character has loaded successfully. The ID should only be trusted and included in the
// mapping when the variant is changed to `Character`.
LoadingCharacter(CharacterId),
Character(CharacterId),
Possessor,
}
@ -63,7 +70,7 @@ enum Direction {
/// (e.g. shifting from increasing the value to decreasing it). This is useful
/// since we want to avoid rapid cycles of shrinking and expanding of the view
/// distance.
#[derive(Debug)]
#[derive(Debug, Clone, Copy)]
pub struct ViewDistance {
direction: Direction,
last_direction_change_time: Instant,

View File

@ -2,14 +2,14 @@ use crate::{
comp::{self, pet::is_mountable, ship::figuredata::VOXEL_COLLIDER_MANIFEST},
link::{Is, Link, LinkHandle, Role},
terrain::{Block, TerrainGrid},
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
vol::ReadVol,
};
use hashbrown::HashSet;
use serde::{Deserialize, Serialize};
use specs::{
saveload::MarkerAllocator, storage::GenericWriteStorage, Component, DenseVecStorage, Entities,
Entity, Read, ReadExpect, ReadStorage, Write, WriteStorage,
storage::GenericWriteStorage, Component, DenseVecStorage, Entities, Entity, Read, ReadExpect,
ReadStorage, Write, WriteStorage,
};
use vek::*;
@ -41,13 +41,13 @@ pub enum MountingError {
impl Link for Mounting {
type CreateData<'a> = (
Read<'a, UidAllocator>,
Read<'a, IdMaps>,
WriteStorage<'a, Is<Mount>>,
WriteStorage<'a, Is<Rider>>,
ReadStorage<'a, Is<VolumeRider>>,
);
type DeleteData<'a> = (
Read<'a, UidAllocator>,
Read<'a, IdMaps>,
WriteStorage<'a, Is<Mount>>,
WriteStorage<'a, Is<Rider>>,
WriteStorage<'a, comp::Pos>,
@ -56,7 +56,7 @@ impl Link for Mounting {
);
type Error = MountingError;
type PersistData<'a> = (
Read<'a, UidAllocator>,
Read<'a, IdMaps>,
Entities<'a>,
ReadStorage<'a, comp::Health>,
ReadStorage<'a, comp::Body>,
@ -67,9 +67,9 @@ impl Link for Mounting {
fn create(
this: &LinkHandle<Self>,
(uid_allocator, is_mounts, is_riders, is_volume_rider): &mut Self::CreateData<'_>,
(id_maps, is_mounts, is_riders, is_volume_rider): &mut Self::CreateData<'_>,
) -> Result<(), Self::Error> {
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
let entity = |uid: Uid| id_maps.uid_entity(uid);
if this.mount == this.rider {
// Forbid self-mounting
@ -97,9 +97,9 @@ impl Link for Mounting {
fn persist(
this: &LinkHandle<Self>,
(uid_allocator, entities, healths, bodies, is_mounts, is_riders, character_states): &mut Self::PersistData<'_>,
(id_maps, entities, healths, bodies, is_mounts, is_riders, character_states): &mut Self::PersistData<'_>,
) -> bool {
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
let entity = |uid: Uid| id_maps.uid_entity(uid);
if let Some((mount, rider)) = entity(this.mount).zip(entity(this.rider)) {
let is_alive = |entity| {
@ -126,9 +126,11 @@ impl Link for Mounting {
fn delete(
this: &LinkHandle<Self>,
(uid_allocator, is_mounts, is_riders, positions, force_update, terrain): &mut Self::DeleteData<'_>,
(id_maps, is_mounts, is_riders, positions, force_update, terrain): &mut Self::DeleteData<
'_,
>,
) {
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
let entity = |uid: Uid| id_maps.uid_entity(uid);
let mount = entity(this.mount);
let rider = entity(this.rider);
@ -218,7 +220,7 @@ impl VolumePos {
pub fn get_block_and_transform(
&self,
terrain: &TerrainGrid,
uid_allocator: &UidAllocator,
id_maps: &IdMaps,
mut read_pos_and_ori: impl FnMut(Entity) -> Option<(comp::Pos, comp::Ori)>,
colliders: &ReadStorage<comp::Collider>,
) -> Option<(Mat4<f32>, comp::Ori, Block)> {
@ -228,26 +230,22 @@ impl VolumePos {
comp::Ori::default(),
*terrain.get(self.pos).ok()?,
)),
Volume::Entity(uid) => {
uid_allocator
.retrieve_entity_internal(uid.0)
.and_then(|entity| {
let collider = colliders.get(entity)?;
let (pos, ori) = read_pos_and_ori(entity)?;
Volume::Entity(uid) => id_maps.uid_entity(uid).and_then(|entity| {
let collider = colliders.get(entity)?;
let (pos, ori) = read_pos_and_ori(entity)?;
let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
let voxel_collider = collider.get_vol(&voxel_colliders_manifest)?;
let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
let voxel_collider = collider.get_vol(&voxel_colliders_manifest)?;
let block = *voxel_collider.volume().get(self.pos).ok()?;
let block = *voxel_collider.volume().get(self.pos).ok()?;
let local_translation = voxel_collider.translation + self.pos.as_();
let local_translation = voxel_collider.translation + self.pos.as_();
let trans = Mat4::from(ori.to_quat()).translated_3d(pos.0)
* Mat4::<f32>::translation_3d(local_translation);
let trans = Mat4::from(ori.to_quat()).translated_3d(pos.0)
* Mat4::<f32>::translation_3d(local_translation);
Some((trans, ori, block))
})
},
Some((trans, ori, block))
}),
}
}
@ -255,25 +253,21 @@ impl VolumePos {
pub fn get_block(
&self,
terrain: &TerrainGrid,
uid_allocator: &UidAllocator,
id_maps: &IdMaps,
colliders: &ReadStorage<comp::Collider>,
) -> Option<Block> {
match self.kind {
Volume::Terrain => Some(*terrain.get(self.pos).ok()?),
Volume::Entity(uid) => {
uid_allocator
.retrieve_entity_internal(uid.0)
.and_then(|entity| {
let collider = colliders.get(entity)?;
Volume::Entity(uid) => id_maps.uid_entity(uid).and_then(|entity| {
let collider = colliders.get(entity)?;
let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
let voxel_collider = collider.get_vol(&voxel_colliders_manifest)?;
let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
let voxel_collider = collider.get_vol(&voxel_colliders_manifest)?;
let block = *voxel_collider.volume().get(self.pos).ok()?;
let block = *voxel_collider.volume().get(self.pos).ok()?;
Some(block)
})
},
Some(block)
}),
}
}
}
@ -301,14 +295,14 @@ impl Link for VolumeMounting {
WriteStorage<'a, Is<VolumeRider>>,
ReadStorage<'a, Is<Rider>>,
ReadExpect<'a, TerrainGrid>,
Read<'a, UidAllocator>,
Read<'a, IdMaps>,
ReadStorage<'a, comp::Collider>,
);
type DeleteData<'a> = (
Write<'a, VolumeRiders>,
WriteStorage<'a, VolumeRiders>,
WriteStorage<'a, Is<VolumeRider>>,
Read<'a, UidAllocator>,
Read<'a, IdMaps>,
);
type Error = MountingError;
type PersistData<'a> = (
@ -318,7 +312,7 @@ impl Link for VolumeMounting {
ReadStorage<'a, VolumeRiders>,
ReadStorage<'a, Is<VolumeRider>>,
ReadExpect<'a, TerrainGrid>,
Read<'a, UidAllocator>,
Read<'a, IdMaps>,
ReadStorage<'a, comp::Collider>,
);
@ -330,11 +324,11 @@ impl Link for VolumeMounting {
is_volume_riders,
is_riders,
terrain_grid,
uid_allocator,
id_maps,
colliders,
): &mut Self::CreateData<'_>,
) -> Result<(), Self::Error> {
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
let entity = |uid: Uid| id_maps.uid_entity(uid);
let riders = match this.pos.kind {
Volume::Terrain => &mut *terrain_riders,
@ -351,7 +345,7 @@ impl Link for VolumeMounting {
{
let block = this
.pos
.get_block(terrain_grid, uid_allocator, colliders)
.get_block(terrain_grid, id_maps, colliders)
.ok_or(MountingError::NoSuchEntity)?;
if block == this.block {
@ -375,11 +369,11 @@ impl Link for VolumeMounting {
volume_riders,
is_volume_riders,
terrain_grid,
uid_allocator,
id_maps,
colliders,
): &mut Self::PersistData<'_>,
) -> bool {
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
let entity = |uid: Uid| id_maps.uid_entity(uid);
let is_alive =
|entity| entities.is_alive(entity) && healths.get(entity).map_or(true, |h| !h.is_dead);
let riders = match this.pos.kind {
@ -401,7 +395,7 @@ impl Link for VolumeMounting {
let block_exists = this
.pos
.get_block(terrain_grid, uid_allocator, colliders)
.get_block(terrain_grid, id_maps, colliders)
.map_or(false, |block| block == this.block);
rider_exists && mount_spot_exists && block_exists
@ -409,9 +403,9 @@ impl Link for VolumeMounting {
fn delete(
this: &LinkHandle<Self>,
(terrain_riders, volume_riders, is_rider, uid_allocator): &mut Self::DeleteData<'_>,
(terrain_riders, volume_riders, is_rider, id_maps): &mut Self::DeleteData<'_>,
) {
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
let entity = |uid: Uid| id_maps.uid_entity(uid);
let riders = match this.pos.kind {
Volume::Terrain => Some(&mut **terrain_riders),

View File

@ -7,6 +7,9 @@ use vek::*;
pub enum Event {
// Contains the key of the region the entity moved to
// TODO: We only actually use the info from this when the entity moves to another region and
// isn't deleted, but we still generate and process these events in the case where the entity
// was deleted.
Left(u32, Option<Vec2<i32>>),
// Contains the key of the region the entity came from
Entered(u32, Option<Vec2<i32>>),
@ -147,7 +150,8 @@ impl RegionMap {
// 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?
// positions directly from specs? Yes, with `.mask()` on the component
// storage.
entities_to_remove.push((i, id));
},
}

View File

@ -24,7 +24,7 @@ slotmap::new_key_type! { pub struct FactionId; }
slotmap::new_key_type! { pub struct ReportId; }
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct RtSimEntity(pub NpcId);
impl Component for RtSimEntity {

View File

@ -1,14 +1,17 @@
#[cfg(not(target_arch = "wasm32"))]
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
#[cfg(not(target_arch = "wasm32"))]
use specs::{
saveload::{Marker, MarkerAllocator},
world::EntitiesRes,
Component, Entity, FlaggedStorage, Join, ReadStorage, VecStorage,
};
use std::{fmt, u64};
#[cfg(not(target_arch = "wasm32"))]
use {
crate::character::CharacterId,
crate::rtsim::RtSimEntity,
core::hash::Hash,
hashbrown::HashMap,
specs::{Component, Entity, FlaggedStorage, VecStorage},
tracing::error,
};
// TODO: could we switch this to `NonZeroU64`?
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct Uid(pub u64);
@ -24,67 +27,171 @@ impl fmt::Display for Uid {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) }
}
pub use not_wasm::*;
#[cfg(not(target_arch = "wasm32"))]
impl Component for Uid {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}
mod not_wasm {
use super::*;
#[cfg(not(target_arch = "wasm32"))]
impl Marker for Uid {
type Allocator = UidAllocator;
type Identifier = u64;
fn id(&self) -> u64 { self.0 }
fn update(&mut self, update: Self) {
assert_eq!(self.0, update.0);
impl Component for Uid {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Debug)]
pub struct UidAllocator {
index: u64,
mapping: HashMap<u64, Entity>,
}
#[derive(Debug)]
struct UidAllocator {
/// Next Uid.
next_uid: u64,
}
#[cfg(not(target_arch = "wasm32"))]
impl UidAllocator {
pub fn new() -> Self {
Self {
index: 0,
mapping: HashMap::new(),
impl UidAllocator {
fn new() -> Self { Self { next_uid: 0 } }
fn allocate(&mut self) -> Uid {
let id = self.next_uid;
self.next_uid += 1;
Uid(id)
}
}
// Useful for when a single entity is deleted because it doesn't reconstruct the
// entire hashmap
pub fn remove_entity(&mut self, id: u64) -> Option<Entity> { self.mapping.remove(&id) }
}
/// Mappings from various Id types to `Entity`s.
#[derive(Default, Debug)]
pub struct IdMaps {
/// "Universal" IDs (used to communicate entity identity over the
/// network).
uid_mapping: HashMap<Uid, Entity>,
#[cfg(not(target_arch = "wasm32"))]
impl Default for UidAllocator {
fn default() -> Self { Self::new() }
}
// -- Fields below are only used on the server --
uid_allocator: UidAllocator,
#[cfg(not(target_arch = "wasm32"))]
impl MarkerAllocator<Uid> for UidAllocator {
fn allocate(&mut self, entity: Entity, id: Option<u64>) -> Uid {
let id = id.unwrap_or_else(|| {
let id = self.index;
self.index += 1;
id
});
self.mapping.insert(id, entity);
Uid(id)
/// Character IDs.
character_to_ecs: HashMap<CharacterId, Entity>,
/// Rtsim Entities.
rtsim_to_ecs: HashMap<RtSimEntity, Entity>,
}
fn retrieve_entity_internal(&self, id: u64) -> Option<Entity> { self.mapping.get(&id).copied() }
impl IdMaps {
pub fn new() -> Self { Default::default() }
fn maintain(&mut self, entities: &EntitiesRes, storage: &ReadStorage<Uid>) {
self.mapping = (entities, storage)
.join()
.map(|(e, m)| (m.id(), e))
.collect();
/// Given a `Uid` retrieve the corresponding `Entity`.
pub fn uid_entity(&self, id: Uid) -> Option<Entity> { self.uid_mapping.get(&id).copied() }
/// Given a `CharacterId` retrieve the corresponding `Entity`.
pub fn character_entity(&self, id: CharacterId) -> Option<Entity> {
self.character_to_ecs.get(&id).copied()
}
/// Given a `RtSimEntity` retrieve the corresponding `Entity`.
pub fn rtsim_entity(&self, id: RtSimEntity) -> Option<Entity> {
self.rtsim_to_ecs.get(&id).copied()
}
/// Removes mappings for the provided Id(s).
///
/// Returns the `Entity` that the provided `Uid` was mapped to.
///
/// Used on both the client and the server when deleting entities,
/// although the client only ever provides a Some value for the
/// `Uid` parameter since the other mappings are not used on the
/// client.
pub fn remove_entity(
&mut self,
expected_entity: Option<Entity>,
uid: Option<Uid>,
cid: Option<CharacterId>,
rid: Option<RtSimEntity>,
) -> Option<Entity> {
#[cold]
#[inline(never)]
fn unexpected_entity<ID>() {
let kind = core::any::type_name::<ID>();
error!("Provided {kind} was mapped to an unexpected entity!");
}
#[cold]
#[inline(never)]
fn not_present<ID>() {
let kind = core::any::type_name::<ID>();
error!("Provided {kind} was not mapped to any entity!");
}
fn remove<ID: Hash + Eq>(
mapping: &mut HashMap<ID, Entity>,
id: Option<ID>,
expected: Option<Entity>,
) -> Option<Entity> {
if let Some(id) = id {
if let Some(e) = mapping.remove(&id) {
if expected.map_or(false, |expected| e != expected) {
unexpected_entity::<ID>();
}
Some(e)
} else {
not_present::<ID>();
None
}
} else {
None
}
}
let maybe_entity = remove(&mut self.uid_mapping, uid, expected_entity);
let expected_entity = expected_entity.or(maybe_entity);
remove(&mut self.character_to_ecs, cid, expected_entity);
remove(&mut self.rtsim_to_ecs, rid, expected_entity);
maybe_entity
}
/// Only used on the client (server solely uses `Self::allocate` to
/// allocate and add Uid mappings and `Self::remap` to move the `Uid` to
/// a different entity).
pub fn add_entity(&mut self, uid: Uid, entity: Entity) {
Self::insert(&mut self.uid_mapping, uid, entity);
}
/// Only used on the server.
pub fn add_character(&mut self, cid: CharacterId, entity: Entity) {
Self::insert(&mut self.character_to_ecs, cid, entity);
}
/// Only used on the server.
pub fn add_rtsim(&mut self, rid: RtSimEntity, entity: Entity) {
Self::insert(&mut self.rtsim_to_ecs, rid, entity);
}
/// Allocates a new `Uid` and links it to the provided entity.
///
/// Only used on the server.
pub fn allocate(&mut self, entity: Entity) -> Uid {
let uid = self.uid_allocator.allocate();
self.uid_mapping.insert(uid, entity);
uid
}
/// Links an existing `Uid` to a new entity.
///
/// Only used on the server.
///
/// Used for `handle_exit_ingame` which moves the same `Uid` to a new
/// entity.
pub fn remap_entity(&mut self, uid: Uid, new_entity: Entity) {
if self.uid_mapping.insert(uid, new_entity).is_none() {
error!("Uid {uid:?} remaped but there was no existing entry for it!");
}
}
#[cold]
#[inline(never)]
fn already_present<ID>() {
let kind = core::any::type_name::<ID>();
error!("Provided {kind} was already mapped to an entity!!!");
}
fn insert<ID: Hash + Eq>(mapping: &mut HashMap<ID, Entity>, new_id: ID, entity: Entity) {
if let Some(_previous_entity) = mapping.insert(new_id, entity) {
Self::already_present::<ID>();
}
}
}
impl Default for UidAllocator {
fn default() -> Self { Self::new() }
}
}

View File

@ -8,7 +8,7 @@ use wasmer::{Function, Memory, Value};
use common::{
comp::{Health, Player},
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
};
use super::errors::{MemoryAllocationError, PluginModuleError};
@ -18,7 +18,7 @@ pub struct EcsWorld<'a, 'b> {
pub health: EcsComponentAccess<'a, 'b, Health>,
pub uid: EcsComponentAccess<'a, 'b, Uid>,
pub player: EcsComponentAccess<'a, 'b, Player>,
pub uid_allocator: &'b Read<'a, UidAllocator>,
pub id_maps: &'b Read<'a, IdMaps>,
}
pub enum EcsComponentAccess<'a, 'b, T: Component> {

View File

@ -5,7 +5,6 @@ use std::{
sync::{Arc, Mutex},
};
use specs::saveload::MarkerAllocator;
use wasmer::{imports, Cranelift, Function, Instance, Memory, Module, Store, Universal, Value};
use super::{
@ -239,9 +238,12 @@ fn retrieve_action(
EcsAccessError::EcsPointerNotAvailable,
))?
};
let player = world.uid_allocator.retrieve_entity_internal(e.0).ok_or(
RetrieveError::EcsAccessError(EcsAccessError::EcsEntityNotFound(e)),
)?;
let player = world
.id_maps
.uid_entity(e)
.ok_or(RetrieveError::EcsAccessError(
EcsAccessError::EcsEntityNotFound(e),
))?;
Ok(RetrieveResult::GetPlayerName(
world
@ -264,9 +266,12 @@ fn retrieve_action(
EcsAccessError::EcsPointerNotAvailable,
))?
};
let player = world.uid_allocator.retrieve_entity_internal(e.0).ok_or(
RetrieveError::EcsAccessError(EcsAccessError::EcsEntityNotFound(e)),
)?;
let player = world
.id_maps
.uid_entity(e)
.ok_or(RetrieveError::EcsAccessError(
EcsAccessError::EcsEntityNotFound(e),
))?;
Ok(RetrieveResult::GetEntityHealth(
world
.health

View File

@ -4,7 +4,7 @@ use crate::plugin::memory_manager::EcsWorld;
use crate::plugin::PluginMgr;
use crate::{BuildArea, NoDurabilityArea};
#[cfg(feature = "plugins")]
use common::uid::UidAllocator;
use common::uid::IdMaps;
use common::{
calendar::Calendar,
comp,
@ -309,7 +309,7 @@ impl State {
entities: &ecs.entities(),
health: ecs.read_component().into(),
uid: ecs.read_component().into(),
uid_allocator: &ecs.read_resource::<UidAllocator>().into(),
id_maps: &ecs.read_resource::<IdMaps>().into(),
player: ecs.read_component().into(),
};
if let Err(e) = plugin_mgr

View File

@ -8,12 +8,11 @@ use common::{
},
event::{Emitter, EventBus, ServerEvent},
resources::Time,
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
};
use common_ecs::{Job, Origin, Phase, System};
use specs::{
saveload::MarkerAllocator, shred::ResourceId, Entities, Entity as EcsEntity, Join, Read,
ReadStorage, SystemData, World,
shred::ResourceId, Entities, Entity as EcsEntity, Join, Read, ReadStorage, SystemData, World,
};
#[derive(SystemData)]
@ -22,7 +21,7 @@ pub struct ReadData<'a> {
players: ReadStorage<'a, Player>,
time: Read<'a, Time>,
server_bus: Read<'a, EventBus<ServerEvent>>,
uid_allocator: Read<'a, UidAllocator>,
id_maps: Read<'a, IdMaps>,
cached_spatial_grid: Read<'a, common::CachedSpatialGrid>,
positions: ReadStorage<'a, Pos>,
char_states: ReadStorage<'a, CharacterState>,
@ -96,8 +95,8 @@ impl<'a> System<'a> for Sys {
// Ensure the entity is in the group we want to target
let same_group = |uid: Uid| {
read_data
.uid_allocator
.retrieve_entity_internal(uid.into())
.id_maps
.uid_entity(uid)
.and_then(|e| read_data.groups.get(e))
.map_or(false, |owner_group| {
Some(owner_group) == read_data.groups.get(target)
@ -167,11 +166,7 @@ fn activate_aura(
Alignment::Owned(uid) => Some(uid),
_ => None,
})
.and_then(|uid| {
read_data
.uid_allocator
.retrieve_entity_internal((*uid).into())
})
.and_then(|uid| read_data.id_maps.uid_entity(*uid))
.and_then(|owner| read_data.char_states.get(owner))
.map_or(false, CharacterState::is_sitting))
},
@ -188,15 +183,13 @@ fn activate_aura(
// when we will add this.
let may_harm = || {
let owner = match source {
BuffSource::Character { by } => {
read_data.uid_allocator.retrieve_entity_internal(by.into())
},
BuffSource::Character { by } => read_data.id_maps.uid_entity(by),
_ => None,
};
combat::may_harm(
&read_data.alignments,
&read_data.players,
&read_data.uid_allocator,
&read_data.id_maps,
owner,
target,
)

View File

@ -9,7 +9,7 @@ use common::{
outcome::Outcome,
resources::{DeltaTime, Time},
terrain::TerrainGrid,
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
vol::ReadVol,
GroupTarget,
};
@ -17,8 +17,8 @@ use common_ecs::{Job, Origin, ParMode, Phase, System};
use rand::Rng;
use rayon::iter::ParallelIterator;
use specs::{
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, ParJoin, Read, ReadExpect,
ReadStorage, SystemData, World, WriteStorage,
shred::ResourceId, Entities, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData, World,
WriteStorage,
};
use std::time::Duration;
use vek::*;
@ -31,7 +31,7 @@ pub struct ReadData<'a> {
time: Read<'a, Time>,
dt: Read<'a, DeltaTime>,
terrain: ReadExpect<'a, TerrainGrid>,
uid_allocator: Read<'a, UidAllocator>,
id_maps: Read<'a, IdMaps>,
cached_spatial_grid: Read<'a, common::CachedSpatialGrid>,
uids: ReadStorage<'a, Uid>,
positions: ReadStorage<'a, Pos>,
@ -95,9 +95,9 @@ impl<'a> System<'a> for Sys {
};
let end_time = creation_time + beam_segment.duration.as_secs_f64();
let beam_owner = beam_segment.owner.and_then(|uid| {
read_data.uid_allocator.retrieve_entity_internal(uid.into())
});
let beam_owner = beam_segment
.owner
.and_then(|uid| read_data.id_maps.uid_entity(uid));
// Note: rayon makes it difficult to hold onto a thread-local RNG, if grabbing
// this becomes a bottleneck we can look into alternatives.
@ -243,7 +243,7 @@ impl<'a> System<'a> for Sys {
let may_harm = combat::may_harm(
&read_data.alignments,
&read_data.players,
&read_data.uid_allocator,
&read_data.id_maps,
beam_owner,
target,
);

View File

@ -15,15 +15,15 @@ use common::{
event::{Emitter, EventBus, ServerEvent},
resources::{DeltaTime, Secs, Time},
terrain::SpriteKind,
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
Damage, DamageSource,
};
use common_base::prof_span;
use common_ecs::{Job, Origin, ParMode, Phase, System};
use rayon::iter::ParallelIterator;
use specs::{
saveload::MarkerAllocator, shred::ResourceId, Entities, Entity, Join, ParJoin, Read,
ReadExpect, ReadStorage, SystemData, World, WriteStorage,
shred::ResourceId, Entities, Entity, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData,
World, WriteStorage,
};
#[derive(SystemData)]
@ -36,7 +36,7 @@ pub struct ReadData<'a> {
energies: ReadStorage<'a, Energy>,
physics_states: ReadStorage<'a, PhysicsState>,
groups: ReadStorage<'a, Group>,
uid_allocator: Read<'a, UidAllocator>,
id_maps: Read<'a, IdMaps>,
time: Read<'a, Time>,
msm: ReadExpect<'a, MaterialStatManifest>,
buffs: ReadStorage<'a, Buffs>,
@ -275,10 +275,7 @@ impl<'a> System<'a> for Sys {
}
})
.for_each(|(buff_id, buff, uid, aura_key)| {
let replace = if let Some(aura_entity) = read_data
.uid_allocator
.retrieve_entity_internal((*uid).into())
{
let replace = if let Some(aura_entity) = read_data.id_maps.uid_entity(*uid) {
if let Some(aura) = read_data
.auras
.get(aura_entity)
@ -504,12 +501,9 @@ fn execute_effect(
ModifierKind::Fractional => health.maximum() * amount,
};
let damage_contributor = by.and_then(|uid| {
read_data
.uid_allocator
.retrieve_entity_internal(uid.0)
.map(|entity| {
DamageContributor::new(uid, read_data.groups.get(entity).cloned())
})
read_data.id_maps.uid_entity(uid).map(|entity| {
DamageContributor::new(uid, read_data.groups.get(entity).cloned())
})
});
server_emitter.emit(ServerEvent::HealthChange {
entity,

View File

@ -6,20 +6,19 @@ use common::{
},
event::{EventBus, ServerEvent},
terrain::TerrainGrid,
uid::UidAllocator,
uid::IdMaps,
};
use common_ecs::{Job, Origin, Phase, System};
use specs::{
saveload::{Marker, MarkerAllocator},
shred::ResourceId,
Entities, Join, Read, ReadExpect, ReadStorage, SystemData, World, WriteStorage,
shred::ResourceId, Entities, Join, Read, ReadExpect, ReadStorage, SystemData, World,
WriteStorage,
};
use vek::*;
#[derive(SystemData)]
pub struct ReadData<'a> {
entities: Entities<'a>,
uid_allocator: Read<'a, UidAllocator>,
id_maps: Read<'a, IdMaps>,
server_bus: Read<'a, EventBus<ServerEvent>>,
terrain_grid: ReadExpect<'a, TerrainGrid>,
positions: ReadStorage<'a, Pos>,
@ -49,17 +48,14 @@ impl<'a> System<'a> for Sys {
for event in controller.events.drain(..) {
match event {
ControlEvent::Mount(mountee_uid) => {
if let Some(mountee_entity) = read_data
.uid_allocator
.retrieve_entity_internal(mountee_uid.id())
{
if let Some(mountee_entity) = read_data.id_maps.uid_entity(mountee_uid) {
server_emitter.emit(ServerEvent::Mount(entity, mountee_entity));
}
},
ControlEvent::MountVolume(volume) => {
if let Some(block) = volume.get_block(
&read_data.terrain_grid,
&read_data.uid_allocator,
&read_data.id_maps,
&read_data.colliders,
) {
if block.is_mountable() {
@ -81,10 +77,7 @@ impl<'a> System<'a> for Sys {
server_emitter.emit(ServerEvent::DisableLantern(entity))
},
ControlEvent::Interact(npc_uid, subject) => {
if let Some(npc_entity) = read_data
.uid_allocator
.retrieve_entity_internal(npc_uid.id())
{
if let Some(npc_entity) = read_data.id_maps.uid_entity(npc_uid) {
server_emitter
.emit(ServerEvent::NpcInteract(entity, npc_entity, subject));
}

View File

@ -10,7 +10,7 @@ use common::{
outcome::Outcome,
resources::Time,
terrain::TerrainGrid,
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
util::{find_dist::Cylinder, Dir},
vol::ReadVol,
GroupTarget,
@ -27,7 +27,7 @@ use vek::*;
pub struct ReadData<'a> {
time: Read<'a, Time>,
terrain: ReadExpect<'a, TerrainGrid>,
uid_allocator: Read<'a, UidAllocator>,
id_maps: Read<'a, IdMaps>,
entities: Entities<'a>,
players: ReadStorage<'a, Player>,
uids: ReadStorage<'a, Uid>,
@ -213,7 +213,7 @@ impl<'a> System<'a> for Sys {
let may_harm = combat::may_harm(
&read_data.alignments,
&read_data.players,
&read_data.uid_allocator,
&read_data.id_maps,
Some(attacker),
target,
);

View File

@ -3,13 +3,10 @@ use common::{
link::Is,
mounting::{Mount, VolumeRider},
terrain::TerrainGrid,
uid::UidAllocator,
uid::IdMaps,
};
use common_ecs::{Job, Origin, Phase, System};
use specs::{
saveload::{Marker, MarkerAllocator},
Entities, Join, Read, ReadExpect, ReadStorage, WriteStorage,
};
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, WriteStorage};
use tracing::error;
use vek::*;
@ -18,7 +15,7 @@ use vek::*;
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Read<'a, UidAllocator>,
Read<'a, IdMaps>,
ReadExpect<'a, TerrainGrid>,
Entities<'a>,
WriteStorage<'a, Controller>,
@ -39,7 +36,7 @@ impl<'a> System<'a> for Sys {
fn run(
_job: &mut Job<Self>,
(
uid_allocator,
id_maps,
terrain,
entities,
mut controllers,
@ -56,8 +53,8 @@ impl<'a> System<'a> for Sys {
// For each mount...
for (entity, is_mount, body) in (&entities, &is_mounts, bodies.maybe()).join() {
// ...find the rider...
let Some((inputs_and_actions, rider)) = uid_allocator
.retrieve_entity_internal(is_mount.rider.id())
let Some((inputs_and_actions, rider)) = id_maps
.uid_entity(is_mount.rider)
.and_then(|rider| {
controllers
.get_mut(rider)
@ -105,7 +102,7 @@ impl<'a> System<'a> for Sys {
for (entity, is_volume_rider) in (&entities, &is_volume_riders).join() {
if let Some((mut mat, volume_ori, _)) = is_volume_rider.pos.get_block_and_transform(
&terrain,
&uid_allocator,
&id_maps,
|e| positions.get(e).copied().zip(orientations.get(e).copied()),
&colliders,
) {
@ -140,10 +137,7 @@ impl<'a> System<'a> for Sys {
let v = match is_volume_rider.pos.kind {
common::mounting::Volume::Terrain => Vec3::zero(),
common::mounting::Volume::Entity(uid) => {
if let Some(v) = uid_allocator
.retrieve_entity_internal(uid.into())
.and_then(|e| velocities.get(e))
{
if let Some(v) = id_maps.uid_entity(uid).and_then(|e| velocities.get(e)) {
v.0
} else {
Vec3::zero()
@ -174,9 +168,8 @@ impl<'a> System<'a> for Sys {
if let Some((actions, inputs)) = inputs {
match is_volume_rider.pos.kind {
common::mounting::Volume::Entity(uid) => {
if let Some(controller) = uid_allocator
.retrieve_entity_internal(uid.into())
.and_then(|e| controllers.get_mut(e))
if let Some(controller) =
id_maps.uid_entity(uid).and_then(|e| controllers.get_mut(e))
{
controller.inputs = inputs;
controller.actions = actions;

View File

@ -8,7 +8,7 @@ use common::{
event::{Emitter, EventBus, ServerEvent},
outcome::Outcome,
resources::{DeltaTime, Time},
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
util::Dir,
GroupTarget,
};
@ -17,8 +17,8 @@ use common::vol::ReadVol;
use common_ecs::{Job, Origin, Phase, System};
use rand::Rng;
use specs::{
saveload::MarkerAllocator, shred::ResourceId, Entities, Entity as EcsEntity, Join, Read,
ReadExpect, ReadStorage, SystemData, World, WriteStorage,
shred::ResourceId, Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage,
SystemData, World, WriteStorage,
};
use std::time::Duration;
use vek::*;
@ -31,7 +31,7 @@ pub struct ReadData<'a> {
entities: Entities<'a>,
players: ReadStorage<'a, Player>,
dt: Read<'a, DeltaTime>,
uid_allocator: Read<'a, UidAllocator>,
id_maps: Read<'a, IdMaps>,
server_bus: Read<'a, EventBus<ServerEvent>>,
uids: ReadStorage<'a, Uid>,
positions: ReadStorage<'a, Pos>,
@ -85,7 +85,7 @@ impl<'a> System<'a> for Sys {
{
let projectile_owner = projectile
.owner
.and_then(|uid| read_data.uid_allocator.retrieve_entity_internal(uid.into()));
.and_then(|uid| read_data.id_maps.uid_entity(uid));
if physics.on_surface().is_none() && rng.gen_bool(0.05) {
server_emitter.emit(ServerEvent::Sound {
@ -103,8 +103,8 @@ impl<'a> System<'a> for Sys {
// if there is at least one touching entity
.and_then(|e| read_data.groups.get(e))
.map_or(false, |owner_group|
Some(owner_group) == read_data.uid_allocator
.retrieve_entity_internal(other.into())
Some(owner_group) == read_data.id_maps
.uid_entity(other)
.and_then(|e| read_data.groups.get(e))
);
@ -125,8 +125,7 @@ impl<'a> System<'a> for Sys {
let projectile = &mut *projectile;
let entity_of =
|uid: Uid| read_data.uid_allocator.retrieve_entity_internal(uid.into());
let entity_of = |uid: Uid| read_data.id_maps.uid_entity(uid);
// Don't hit if there is terrain between the projectile and where the entity was
// supposed to be hit by it.
@ -334,7 +333,7 @@ fn dispatch_hit(
let may_harm = combat::may_harm(
&read_data.alignments,
&read_data.players,
&read_data.uid_allocator,
&read_data.id_maps,
owner,
target,
);

View File

@ -8,15 +8,14 @@ use common::{
event::{EventBus, ServerEvent},
outcome::Outcome,
resources::{DeltaTime, Time},
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
util::Dir,
GroupTarget,
};
use common_ecs::{Job, Origin, Phase, System};
use rand::Rng;
use specs::{
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData,
World, WriteStorage,
shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData, World, WriteStorage,
};
use vek::*;
@ -27,7 +26,7 @@ pub struct ReadData<'a> {
time: Read<'a, Time>,
players: ReadStorage<'a, Player>,
dt: Read<'a, DeltaTime>,
uid_allocator: Read<'a, UidAllocator>,
id_maps: Read<'a, IdMaps>,
uids: ReadStorage<'a, Uid>,
positions: ReadStorage<'a, Pos>,
orientations: ReadStorage<'a, Ori>,
@ -92,7 +91,7 @@ impl<'a> System<'a> for Sys {
let shockwave_owner = shockwave
.owner
.and_then(|uid| read_data.uid_allocator.retrieve_entity_internal(uid.into()));
.and_then(|uid| read_data.id_maps.uid_entity(uid));
if rng.gen_bool(0.05) {
server_emitter.emit(ServerEvent::Sound {
@ -229,7 +228,7 @@ impl<'a> System<'a> for Sys {
let may_harm = combat::may_harm(
&read_data.alignments,
&read_data.players,
&read_data.uid_allocator,
&read_data.id_maps,
shockwave_owner,
target,
);

View File

@ -39,7 +39,7 @@ use common::{
};
use itertools::Itertools;
use rand::{thread_rng, Rng};
use specs::{saveload::Marker, Entity as EcsEntity};
use specs::Entity as EcsEntity;
use vek::*;
#[cfg(feature = "use-dyn-lib")]
@ -397,7 +397,7 @@ impl<'a> AgentData<'a> {
break 'activity;
}
} else if let Some(Alignment::Owned(owner_uid)) = self.alignment
&& let Some(owner) = get_entity_by_id(owner_uid.id(), read_data)
&& let Some(owner) = get_entity_by_id(*owner_uid, read_data)
&& let Some(pos) = read_data.positions.get(owner)
&& pos.0.distance_squared(self.pos.0) < MAX_MOUNT_RANGE.powi(2)
&& rng.gen_bool(0.01)
@ -455,7 +455,7 @@ impl<'a> AgentData<'a> {
if let Some(patrol_origin) = agent.patrol_origin
// Use owner as patrol origin otherwise
.or_else(|| if let Some(Alignment::Owned(owner_uid)) = self.alignment
&& let Some(owner) = get_entity_by_id(owner_uid.id(), read_data)
&& let Some(owner) = get_entity_by_id(*owner_uid, read_data)
&& let Some(pos) = read_data.positions.get(owner)
{
Some(pos.0)
@ -1613,7 +1613,7 @@ impl<'a> AgentData<'a> {
if let Some(Target { target, .. }) = agent.target {
if let Some(tgt_health) = read_data.healths.get(target) {
if let Some(by) = tgt_health.last_change.damage_by() {
if let Some(attacker) = get_entity_by_id(by.uid().0, read_data) {
if let Some(attacker) = get_entity_by_id(by.uid(), read_data) {
if agent.target.is_none() {
controller.push_utterance(UtteranceKind::Angry);
}

View File

@ -10,8 +10,8 @@ use common::{
slot::EquipSlot,
},
ActiveAbilities, Alignment, Body, CharacterState, Combo, Energy, Health, Inventory,
LightEmitter, LootOwner, Ori, PhysicsState, Poise, Pos, Presence, PresenceKind, Scale,
SkillSet, Stance, Stats, Vel,
LightEmitter, LootOwner, Ori, PhysicsState, Poise, Pos, Presence, Scale, SkillSet, Stance,
Stats, Vel,
},
consts::GRAVITY,
link::Is,
@ -21,11 +21,11 @@ use common::{
rtsim::{Actor, RtSimEntity},
states::utils::{ForcedMovement, StageSection},
terrain::TerrainGrid,
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
};
use specs::{
shred::ResourceId, Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage,
SystemData, World,
shred::ResourceId, Entities, Entity as EcsEntity, Read, ReadExpect, ReadStorage, SystemData,
World,
};
// TODO: Move rtsim back into AgentData after rtsim2 when it has a separate
@ -284,7 +284,7 @@ impl SwordTactics {
#[derive(SystemData)]
pub struct ReadData<'a> {
pub entities: Entities<'a>,
pub uid_allocator: Read<'a, UidAllocator>,
pub id_maps: Read<'a, IdMaps>,
pub dt: Read<'a, DeltaTime>,
pub time: Read<'a, Time>,
pub cached_spatial_grid: Read<'a, common::CachedSpatialGrid>,
@ -325,18 +325,9 @@ pub struct ReadData<'a> {
impl<'a> ReadData<'a> {
pub fn lookup_actor(&self, actor: Actor) -> Option<EcsEntity> {
// TODO: We really shouldn't be doing a linear search here. The only saving
// grace is that the set of entities that fit each case should be
// *relatively* small.
match actor {
Actor::Character(character_id) => (&self.entities, &self.presences)
.join()
.find(|(_, p)| p.kind == PresenceKind::Character(character_id))
.map(|(entity, _)| entity),
Actor::Npc(npc_id) => (&self.entities, &self.rtsim_entities)
.join()
.find(|(_, e)| e.0 == npc_id)
.map(|(entity, _)| entity),
Actor::Character(character_id) => self.id_maps.character_entity(character_id),
Actor::Npc(npc_id) => self.id_maps.rtsim_entity(RtSimEntity(npc_id)),
}
}
}

View File

@ -9,15 +9,13 @@ use common::{
},
consts::GRAVITY,
terrain::Block,
uid::Uid,
util::Dir,
vol::ReadVol,
};
use core::f32::consts::PI;
use rand::Rng;
use specs::{
saveload::{Marker, MarkerAllocator},
Entity as EcsEntity,
};
use specs::Entity as EcsEntity;
use vek::*;
pub fn is_dead_or_invulnerable(entity: EcsEntity, read_data: &ReadData) -> bool {
@ -43,8 +41,8 @@ pub fn try_owner_alignment<'a>(
alignment: Option<&'a Alignment>,
read_data: &'a ReadData,
) -> Option<&'a Alignment> {
if let Some(Alignment::Owned(owner_uid)) = alignment {
if let Some(owner) = get_entity_by_id(owner_uid.id(), read_data) {
if let Some(&Alignment::Owned(owner_uid)) = alignment {
if let Some(owner) = get_entity_by_id(owner_uid, read_data) {
return read_data.alignments.get(owner);
}
}
@ -66,8 +64,8 @@ pub fn aim_projectile(speed: f32, pos: Vec3<f32>, tgt: Vec3<f32>) -> Option<Dir>
Dir::from_unnormalized(to_tgt)
}
pub fn get_entity_by_id(id: u64, read_data: &ReadData) -> Option<EcsEntity> {
read_data.uid_allocator.retrieve_entity_internal(id)
pub fn get_entity_by_id(uid: Uid, read_data: &ReadData) -> Option<EcsEntity> {
read_data.id_maps.uid_entity(uid)
}
/// Calculates whether the agent should continue chase or let the target escape.
@ -198,7 +196,7 @@ pub fn get_attacker(entity: EcsEntity, read_data: &ReadData) -> Option<EcsEntity
.get(entity)
.filter(|health| health.last_change.amount < 0.0)
.and_then(|health| health.last_change.damage_by())
.and_then(|damage_contributor| get_entity_by_id(damage_contributor.uid().0, read_data))
.and_then(|damage_contributor| get_entity_by_id(damage_contributor.uid(), read_data))
}
impl<'a> AgentData<'a> {

View File

@ -47,7 +47,7 @@ use common::{
resources::{BattleMode, PlayerPhysicsSettings, Secs, Time, TimeOfDay, TimeScale},
rtsim::{Actor, Role},
terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize},
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
vol::ReadVol,
weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
};
@ -60,9 +60,7 @@ use core::{cmp::Ordering, convert::TryFrom};
use hashbrown::{HashMap, HashSet};
use humantime::Duration as HumanDuration;
use rand::{thread_rng, Rng};
use specs::{
saveload::MarkerAllocator, storage::StorageEntry, Builder, Entity as EcsEntity, Join, WorldExt,
};
use specs::{storage::StorageEntry, Builder, Entity as EcsEntity, Join, WorldExt};
use std::{fmt::Write, ops::DerefMut, str::FromStr, sync::Arc};
use vek::*;
use wiring::{Circuit, Wire, WireNode, WiringAction, WiringActionEffect, WiringElement};
@ -247,8 +245,8 @@ fn position_mut<T>(
server
.state
.ecs()
.read_resource::<UidAllocator>()
.retrieve_entity_internal(is_rider.mount.into())
.read_resource::<IdMaps>()
.uid_entity(is_rider.mount)
})
.map(Ok)
.or_else(|| {
@ -264,8 +262,8 @@ fn position_mut<T>(
common::mounting::Volume::Entity(uid) => Ok(server
.state
.ecs()
.read_resource::<UidAllocator>()
.retrieve_entity_internal(uid.into())?),
.read_resource::<IdMaps>()
.uid_entity(uid)?),
})
})
})
@ -2279,7 +2277,7 @@ fn handle_kill_npcs(
.filter_map(|(entity, _health, (), alignment, pos)| {
let should_kill = kill_pets
|| if let Some(Alignment::Owned(owned)) = alignment {
ecs.entity_from_uid(owned.0)
ecs.entity_from_uid(*owned)
.map_or(true, |owner| !players.contains(owner))
} else {
true
@ -2562,6 +2560,7 @@ fn handle_light(
.ecs_mut()
.create_entity_synced()
.with(pos)
// TODO: I don't think we intend to add this component to non-client entities?
.with(comp::ForceUpdate::forced())
.with(light_emitter);
if let Some(light_offset) = light_offset_opt {

View File

@ -18,7 +18,7 @@ use common::{
outcome::Outcome,
resources::{Secs, Time},
rtsim::RtSimVehicle,
uid::Uid,
uid::{IdMaps, Uid},
util::Dir,
vol::IntoFullVolIterator,
ViewDistances,
@ -50,7 +50,8 @@ pub fn handle_initialize_character(
}
} else {
// A character delete or update was somehow initiated after the login commenced,
// so disconnect the client without saving any data and abort the login process.
// so kick the client out of "ingame" without saving any data and abort
// the character loading process.
handle_exit_ingame(server, entity, true);
}
}
@ -83,12 +84,19 @@ pub fn handle_loaded_character_data(
))),
);
}
server
let result_msg = if let Err(err) = server
.state
.update_character_data(entity, loaded_components);
sys::subscription::initialize_region_subscription(server.state.ecs(), entity);
// We notify the client with the metadata result from the operation.
server.notify_client(entity, ServerGeneral::CharacterDataLoadResult(Ok(metadata)));
.update_character_data(entity, loaded_components)
{
handle_exit_ingame(server, entity, false); // remove client from in-game state
ServerGeneral::CharacterDataLoadResult(Err(err))
} else {
sys::subscription::initialize_region_subscription(server.state.ecs(), entity);
// We notify the client with the metadata result from the operation.
ServerGeneral::CharacterDataLoadResult(Ok(metadata))
};
server.notify_client(entity, result_msg);
}
pub fn handle_create_npc(server: &mut Server, pos: Pos, mut npc: NpcBuilder) -> EcsEntity {
@ -132,6 +140,7 @@ pub fn handle_create_npc(server: &mut Server, pos: Pos, mut npc: NpcBuilder) ->
entity
};
// Rtsim entity added to IdMaps below.
let entity = if let Some(rtsim_entity) = npc.rtsim_entity {
entity.with(rtsim_entity).with(RepositionOnChunkLoad {
needs_ground: false,
@ -148,13 +157,21 @@ pub fn handle_create_npc(server: &mut Server, pos: Pos, mut npc: NpcBuilder) ->
let new_entity = entity.build();
if let Some(rtsim_entity) = npc.rtsim_entity {
server
.state()
.ecs()
.write_resource::<IdMaps>()
.add_rtsim(rtsim_entity, new_entity);
}
// Add to group system if a pet
if let comp::Alignment::Owned(owner_uid) = npc.alignment {
let state = server.state();
let clients = state.ecs().read_storage::<Client>();
let uids = state.ecs().read_storage::<Uid>();
let mut group_manager = state.ecs().write_resource::<comp::group::GroupManager>();
if let Some(owner) = state.ecs().entity_from_uid(owner_uid.into()) {
if let Some(owner) = state.ecs().entity_from_uid(owner_uid) {
let map_markers = state.ecs().read_storage::<comp::MapMarker>();
group_manager.new_pet(
new_entity,

View File

@ -32,7 +32,7 @@ use common::{
states::utils::StageSection,
terrain::{Block, BlockKind, TerrainGrid},
trade::{TradeResult, Trades},
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
util::Dir,
vol::ReadVol,
Damage, DamageKind, DamageSource, Explosion, GroupTarget, RadiusEffect,
@ -41,9 +41,7 @@ use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
use common_state::{AreasContainer, BlockChange, NoDurabilityArea};
use hashbrown::HashSet;
use rand::Rng;
use specs::{
join::Join, saveload::MarkerAllocator, Builder, Entity as EcsEntity, Entity, WorldExt,
};
use specs::{join::Join, Builder, Entity as EcsEntity, Entity, WorldExt};
use std::{collections::HashMap, iter, sync::Arc, time::Duration};
use tracing::{debug, error};
use vek::{Vec2, Vec3};
@ -143,7 +141,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt
let get_attacker_name = |cause_of_death: KillType, by: Uid| -> KillSource {
// Get attacker entity
if let Some(char_entity) = state.ecs().entity_from_uid(by.into()) {
if let Some(char_entity) = state.ecs().entity_from_uid(by) {
// Check if attacker is another player or entity with stats (npc)
if state
.ecs()
@ -259,7 +257,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt
for (damage_contributor, damage) in entity_health.damage_contributions() {
match damage_contributor {
DamageContributor::Solo(uid) => {
if let Some(attacker) = state.ecs().entity_from_uid(uid.0) {
if let Some(attacker) = state.ecs().entity_from_uid(*uid) {
damage_contributors.insert(DamageContrib::Solo(attacker), (*damage, 0.0));
} else {
// An entity who was not in a group contributed damage but is now either
@ -570,8 +568,8 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt
| DamageContributor::Group { entity_uid, .. })| {
state
.ecs()
.read_resource::<UidAllocator>()
.retrieve_entity_internal((*entity_uid).into())
.read_resource::<IdMaps>()
.uid_entity(*entity_uid)
},
)
.and_then(|killer| state.entity_as_actor(killer)),
@ -744,10 +742,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
let settings = server.settings();
let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>();
let time = ecs.read_resource::<Time>();
let owner_entity = owner.and_then(|uid| {
ecs.read_resource::<UidAllocator>()
.retrieve_entity_internal(uid.into())
});
let owner_entity = owner.and_then(|uid| ecs.read_resource::<IdMaps>().uid_entity(uid));
let explosion_volume = 6.25 * explosion.radius;
let mut emitter = server_eventbus.emitter();
@ -952,7 +947,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
let combos = &ecs.read_storage::<comp::Combo>();
let inventories = &ecs.read_storage::<Inventory>();
let alignments = &ecs.read_storage::<Alignment>();
let uid_allocator = &ecs.read_resource::<UidAllocator>();
let id_maps = &ecs.read_resource::<IdMaps>();
let players = &ecs.read_storage::<Player>();
let buffs = &ecs.read_storage::<comp::Buffs>();
let stats = &ecs.read_storage::<comp::Stats>();
@ -1045,13 +1040,8 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
.and_then(|cs| cs.attack_immunities())
.map_or(false, |i| i.explosions);
// PvP check
let may_harm = combat::may_harm(
alignments,
players,
uid_allocator,
owner_entity,
entity_b,
);
let may_harm =
combat::may_harm(alignments, players, id_maps, owner_entity, entity_b);
let attack_options = combat::AttackOptions {
target_dodging,
may_harm,
@ -1077,7 +1067,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
},
RadiusEffect::Entity(mut effect) => {
let alignments = &ecs.read_storage::<Alignment>();
let uid_allocator = &ecs.read_resource::<UidAllocator>();
let id_maps = &ecs.read_resource::<IdMaps>();
let players = &ecs.read_storage::<Player>();
for (entity_b, pos_b, body_b_maybe) in (
&ecs.entities(),
@ -1109,7 +1099,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
//
// This can be changed later.
let may_harm = || {
combat::may_harm(alignments, players, uid_allocator, owner_entity, entity_b)
combat::may_harm(alignments, players, id_maps, owner_entity, entity_b)
|| owner_entity.map_or(true, |entity_a| entity_a == entity_b)
};
if strength > 0.0 {
@ -1421,7 +1411,7 @@ pub fn handle_teleport_to(server: &Server, entity: EcsEntity, target: Uid, max_r
let mut positions = ecs.write_storage::<Pos>();
let target_pos = ecs
.entity_from_uid(target.into())
.entity_from_uid(target)
.and_then(|e| positions.get(e))
.copied();
@ -1493,7 +1483,7 @@ pub fn handle_entity_attacked_hook(server: &Server, entity: EcsEntity) {
if let Some(trade) = trades.entity_trades.get(uid).copied() {
trades
.decline_trade(trade, *uid)
.and_then(|uid| ecs.entity_from_uid(uid.0))
.and_then(|uid| ecs.entity_from_uid(uid))
.map(|entity_b| {
// Notify both parties that the trade ended
let clients = ecs.read_storage::<Client>();

View File

@ -148,7 +148,7 @@ pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) {
let uids = state.ecs().read_storage::<Uid>();
let alignments = state.ecs().read_storage::<comp::Alignment>();
let target = match state.ecs().entity_from_uid(uid.into()) {
let target = match state.ecs().entity_from_uid(uid) {
Some(t) => t,
None => {
// Inform of failure
@ -254,7 +254,7 @@ pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) {
let state = server.state_mut();
let clients = state.ecs().read_storage::<Client>();
let uids = state.ecs().read_storage::<Uid>();
let target = match state.ecs().entity_from_uid(uid.into()) {
let target = match state.ecs().entity_from_uid(uid) {
Some(t) => t,
None => {
// Inform of failure
@ -305,7 +305,7 @@ pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) {
));
}
// Tell the old leader that the transfer was succesful
if let Some(client) = clients.get(target) {
if let Some(client) = clients.get(entity) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"You are no longer the group leader.",

View File

@ -1,4 +1,4 @@
use specs::{saveload::MarkerAllocator, world::WorldExt, Entity as EcsEntity, Join};
use specs::{world::WorldExt, Entity as EcsEntity, Join};
use vek::*;
use common::{
@ -21,7 +21,7 @@ use common::{
outcome::Outcome,
rtsim::RtSimVehicle,
terrain::{Block, SpriteKind},
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
vol::ReadVol,
};
use common_net::sync::WorldSyncExt;
@ -177,11 +177,11 @@ pub fn handle_mount_volume(server: &mut Server, rider: EcsEntity, volume_pos: Vo
}).is_ok();
#[cfg(feature = "worldgen")]
if _link_successful {
let uid_allocator = state.ecs().read_resource::<UidAllocator>();
if let Some(rider_entity) = uid_allocator.retrieve_entity_internal(rider.0)
let uid_allocator = state.ecs().read_resource::<IdMaps>();
if let Some(rider_entity) = uid_allocator.uid_entity(rider)
&& let Some(rider_actor) = state.entity_as_actor(rider_entity)
&& let Some(volume_pos) = volume_pos.try_map_entity(|uid| {
let entity = uid_allocator.retrieve_entity_internal(uid.0)?;
let entity = uid_allocator.uid_entity(uid)?;
state.read_storage::<RtSimVehicle>().get(entity).map(|v| v.0)
}) {
state.ecs().write_resource::<RtSim>().hook_character_mount_volume(

View File

@ -120,16 +120,15 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
match manip {
comp::InventoryManip::Pickup(pickup_uid) => {
let item_entity =
if let Some(item_entity) = state.ecs().entity_from_uid(pickup_uid.into()) {
item_entity
} else {
// Item entity could not be found - most likely because the entity
// attempted to pick up the same item very quickly before its deletion of the
// world from the first pickup attempt was processed.
debug!("Failed to get entity for item Uid: {}", pickup_uid);
return;
};
let item_entity = if let Some(item_entity) = state.ecs().entity_from_uid(pickup_uid) {
item_entity
} else {
// Item entity could not be found - most likely because the entity
// attempted to pick up the same item very quickly before its deletion of the
// world from the first pickup attempt was processed.
debug!("Failed to get entity for item Uid: {}", pickup_uid);
return;
};
let entity_cylinder = get_cylinder(state, entity);
// FIXME: Raycast so we can't pick up items through walls.

View File

@ -30,7 +30,7 @@ pub fn handle_invite(server: &mut Server, inviter: Entity, invitee_uid: Uid, kin
let max_group_size = server.settings().max_player_group_size;
let state = server.state_mut();
let clients = state.ecs().read_storage::<Client>();
let invitee = match state.ecs().entity_from_uid(invitee_uid.into()) {
let invitee = match state.ecs().entity_from_uid(invitee_uid) {
Some(t) => t,
None => {
// Inform of failure
@ -85,7 +85,7 @@ pub fn handle_invite(server: &mut Server, inviter: Entity, invitee_uid: Uid, kin
if let Some(active_trade) = trades.entity_trades.get(&inviter_uid).copied() {
trades
.decline_trade(active_trade, inviter_uid)
.and_then(|u| state.ecs().entity_from_uid(u.0))
.and_then(|u| state.ecs().entity_from_uid(u))
.map(|e| {
if let Some(client) = clients.get(e) {
client
@ -309,7 +309,7 @@ fn handle_invite_answer(
if let Some(active_trade) = trades.entity_trades.get(&invitee_uid).copied() {
trades
.decline_trade(active_trade, invitee_uid)
.and_then(|u| state.ecs().entity_from_uid(u.0))
.and_then(|u| state.ecs().entity_from_uid(u))
.map(|e| {
if let Some(client) = clients.get(e) {
client

View File

@ -8,12 +8,12 @@ use common::{
comp,
comp::{group, pet::is_tameable, Presence, PresenceKind},
resources::Time,
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
};
use common_base::span;
use common_net::msg::{PlayerListUpdate, ServerGeneral};
use common_state::State;
use specs::{saveload::MarkerAllocator, Builder, Entity as EcsEntity, Join, WorldExt};
use specs::{Builder, Entity as EcsEntity, Join, WorldExt};
use tracing::{debug, error, trace, warn, Instrument};
pub fn handle_character_delete(
@ -56,59 +56,55 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity, skip_persisten
entity
};
// Create new entity with just `Client`, `Uid`, `Player`, and `...Stream`
// Create new entity with just `Client`, `Uid`, `Player`, `Admin`, `Group`
// components.
//
// Easier than checking and removing all other known components.
//
// Also, allows clients to not update their Uid based references to this
// client (e.g. for this specific client's knowledge of its own Uid and for
// groups since exiting in-game does not affect group membership)
//
// Note: If other `ServerEvent`s are referring to this entity they will be
// disrupted.
// Since we remove `Uid` below, any trades won't be cancelled by
// `delete_entity_recorded`. So we cancel the trade here. (maybe the trade
// key could be switched from `Uid` to `Entity`)
// Cancel trades here since we don't use `delete_entity_recorded` and we
// remove `Uid` below.
super::cancel_trades_for(state, entity);
let maybe_admin = state.ecs().write_storage::<comp::Admin>().remove(entity);
let maybe_group = state
.ecs()
.write_storage::<group::Group>()
.get(entity)
.cloned();
let maybe_group = state.read_component_copied::<group::Group>(entity);
let maybe_admin = state.delete_component::<comp::Admin>(entity);
// Not sure if we still need to actually remove the Uid or if the group
// logic below relies on this...
let maybe_uid = state.delete_component::<Uid>(entity);
if let Some((client, uid, player)) = (|| {
let ecs = state.ecs();
Some((
ecs.write_storage::<Client>().remove(entity)?,
ecs.write_storage::<Uid>().remove(entity)?,
ecs.write_storage::<comp::Player>().remove(entity)?,
))
})() {
if let Some(client) = state.delete_component::<Client>(entity)
&& let Some(uid) = maybe_uid
&& let Some(player) = state.delete_component::<comp::Player>(entity)
{
// Tell client its request was successful
client.send_fallible(ServerGeneral::ExitInGameSuccess);
let entity_builder = state.ecs_mut().create_entity().with(client).with(player);
let new_entity = state
.ecs_mut()
.create_entity()
.with(client)
.with(player)
// Preserve group component if present
.maybe_with(maybe_group)
// Preserve admin component if present
.maybe_with(maybe_admin)
.with(uid)
.build();
// Preserve group component if present
let entity_builder = match maybe_group {
Some(group) => entity_builder.with(group),
None => entity_builder,
};
// Ensure IdMaps maps this uid to the new entity.
state.mut_resource::<IdMaps>().remap_entity(uid, new_entity);
// Preserve admin component if present
let entity_builder = match maybe_admin {
Some(admin) => entity_builder.with(admin),
None => entity_builder,
};
// Ensure UidAllocator maps this uid to the new entity
let uid = entity_builder
.world
.write_resource::<UidAllocator>()
.allocate(entity_builder.entity, Some(uid.into()));
let new_entity = entity_builder.with(uid).build();
let ecs = state.ecs();
// Note, we use `delete_entity_common` directly to avoid `delete_entity_recorded` from
// making any changes to the group.
if let Some(group) = maybe_group {
let mut group_manager = state.ecs().write_resource::<group::GroupManager>();
let mut group_manager = ecs.write_resource::<group::GroupManager>();
if group_manager
.group_info(group)
.map(|info| info.leader == entity)
@ -116,21 +112,38 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity, skip_persisten
{
group_manager.assign_leader(
new_entity,
&state.ecs().read_storage(),
&state.ecs().entities(),
&state.ecs().read_storage(),
&state.ecs().read_storage(),
&ecs.read_storage(),
&ecs.entities(),
&ecs.read_storage(),
&ecs.read_storage(),
// Nothing actually changing since Uid is transferred
|_, _| {},
);
}
}
// delete_entity_recorded` is not used so we don't need to worry aobut
// group restructuring when deleting this entity.
} else {
error!("handle_exit_ingame called with entity that is missing expected components");
}
// Erase group component to avoid group restructure when deleting the entity
state.ecs().write_storage::<group::Group>().remove(entity);
let maybe_character = state
.read_storage::<Presence>()
.get(entity)
.and_then(|p| p.kind.character_id());
let maybe_rtsim = state.read_component_copied::<common::rtsim::RtSimEntity>(entity);
state.mut_resource::<IdMaps>().remove_entity(
Some(entity),
None, // Uid re-mapped, we don't want to remove the mapping
maybe_character,
maybe_rtsim,
);
// We don't want to use delete_entity_recorded since we are transfering the
// Uid to a new entity (and e.g. don't want it to be unmapped).
//
// Delete old entity
if let Err(e) = state.delete_entity_recorded(entity) {
if let Err(e) = crate::state_ext::delete_entity_common(state, entity, maybe_uid) {
error!(
?e,
?entity,
@ -260,6 +273,12 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity {
state.ecs().fetch_mut::<BattleModeBuffer>(),
) {
match presence.kind {
PresenceKind::LoadingCharacter(_char_id) => {
error!(
"Unexpected state when persist_entity is called! Some of the components \
required above should only be present after a character is loaded!"
);
},
PresenceKind::Character(char_id) => {
let waypoint = state
.ecs()
@ -331,12 +350,12 @@ pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possessee_uid: Ui
let mut delete_entity = None;
if let (Some(possessor), Some(possessee)) = (
state.ecs().entity_from_uid(possessor_uid.into()),
state.ecs().entity_from_uid(possessee_uid.into()),
state.ecs().entity_from_uid(possessor_uid),
state.ecs().entity_from_uid(possessee_uid),
) {
// In this section we check various invariants and can return early if any of
// them are not met.
{
let new_presence = {
let ecs = state.ecs();
// Check that entities still exist
if !possessor.gen().is_alive()
@ -353,6 +372,7 @@ pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possessee_uid: Ui
let clients = ecs.read_storage::<Client>();
let players = ecs.read_storage::<comp::Player>();
let presences = ecs.read_storage::<comp::Presence>();
if clients.contains(possessee) || players.contains(possessee) {
error!("Can't possess other players!");
@ -379,8 +399,32 @@ pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possessee_uid: Ui
return;
}
if let Some(presence) = presences.get(possessor) {
delete_entity = match presence.kind {
k @ (PresenceKind::LoadingCharacter(_) | PresenceKind::Spectator) => {
error!(?k, "Unexpected presence kind for a possessor.");
return;
},
PresenceKind::Possessor => None,
// Since we call `persist_entity` below we will want to delete the entity (to
// avoid item duplication).
PresenceKind::Character(_) => Some(possessor),
};
Some(Presence {
terrain_view_distance: presence.terrain_view_distance,
entity_view_distance: presence.entity_view_distance,
// This kind (rather than copying Character presence) prevents persistence
// from overwriting original character info with stuff from the new character.
kind: PresenceKind::Possessor,
lossy_terrain_compression: presence.lossy_terrain_compression,
})
} else {
None
}
// No early returns allowed after this.
}
};
// Sync the player's character data to the database. This must be done before
// moving any components from the entity.
@ -428,31 +472,38 @@ pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possessee_uid: Ui
}
let mut players = ecs.write_storage::<comp::Player>();
let mut presence = ecs.write_storage::<Presence>();
let mut subscriptions = ecs.write_storage::<RegionSubscription>();
let mut admins = ecs.write_storage::<comp::Admin>();
let mut waypoints = ecs.write_storage::<comp::Waypoint>();
let mut force_updates = ecs.write_storage::<comp::ForceUpdate>();
transfer_component(&mut players, possessor, possessee, |x| x);
transfer_component(&mut presence, possessor, possessee, |mut presence| {
presence.kind = match presence.kind {
PresenceKind::Spectator => PresenceKind::Spectator,
// This prevents persistence from overwriting original character info with stuff
// from the new character.
PresenceKind::Character(_) => {
delete_entity = Some(possessor);
PresenceKind::Possessor
},
PresenceKind::Possessor => PresenceKind::Possessor,
};
presence
});
transfer_component(&mut subscriptions, possessor, possessee, |x| x);
transfer_component(&mut admins, possessor, possessee, |x| x);
transfer_component(&mut waypoints, possessor, possessee, |x| x);
let mut update_counter = 0;
transfer_component(&mut force_updates, possessor, possessee, |mut x| {
x.update();
update_counter = x.counter();
x
});
// If a player is posessing, add possessee to playerlist as player and remove
let mut presences = ecs.write_storage::<Presence>();
// We leave Presence on the old entity for character IDs to be properly removed
// from the ID mapping if deleting the previous entity.
//
// If the entity is not going to be deleted, we remove it so that the entity
// doesn't keep an area loaded.
if delete_entity.is_none() {
presences.remove(possessor);
}
if let Some(p) = new_presence {
presences
.insert(possessee, p)
.expect("Checked entity was alive!");
}
// If a player is possessing, add possessee to playerlist as player and remove
// old player.
// Fetches from possessee entity here since we have transferred over the
// `Player` component.
@ -527,7 +578,7 @@ pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possessee_uid: Ui
possessee,
);
if !comp_sync_package.is_empty() {
client.send_fallible(ServerGeneral::CompSync(comp_sync_package, 0)); // TODO: Check if this should be zero
client.send_fallible(ServerGeneral::CompSync(comp_sync_package, update_counter));
}
}

View File

@ -64,7 +64,7 @@ pub(super) fn handle_process_trade_action(
if let TradeAction::Decline = action {
let to_notify = trades.decline_trade(trade_id, uid);
to_notify
.and_then(|u| server.state.ecs().entity_from_uid(u.0))
.and_then(|u| server.state.ecs().entity_from_uid(u))
.map(|e| {
server.notify_client(e, ServerGeneral::FinishedTrade(TradeResult::Declined));
notify_agent_simple(
@ -78,7 +78,7 @@ pub(super) fn handle_process_trade_action(
let ecs = server.state.ecs();
let inventories = ecs.read_component::<Inventory>();
let get_inventory = |uid: Uid| {
if let Some(entity) = ecs.entity_from_uid(uid.0) {
if let Some(entity) = ecs.entity_from_uid(uid) {
inventories.get(entity)
} else {
None
@ -92,7 +92,7 @@ pub(super) fn handle_process_trade_action(
let result = commit_trade(server.state.ecs(), entry.get());
entry.remove();
for party in parties.iter() {
if let Some(e) = server.state.ecs().entity_from_uid(party.0) {
if let Some(e) = server.state.ecs().entity_from_uid(*party) {
server.notify_client(e, ServerGeneral::FinishedTrade(result.clone()));
notify_agent_simple(
server.state.ecs().write_storage::<Agent>(),
@ -110,7 +110,7 @@ pub(super) fn handle_process_trade_action(
// sadly there is no map and collect on arrays
for i in 0..2 {
// parties.len()) {
entities[i] = server.state.ecs().entity_from_uid(parties[i].0);
entities[i] = server.state.ecs().entity_from_uid(parties[i]);
if let Some(e) = entities[i] {
inventories[i] = server
.state
@ -180,7 +180,7 @@ pub(crate) fn cancel_trades_for(state: &mut common_state::State, entity: EcsEnti
};
let to_notify = trades.decline_trade(active_trade, uid);
to_notify.and_then(|u| ecs.entity_from_uid(u.0)).map(|e| {
to_notify.and_then(|u| ecs.entity_from_uid(u)).map(|e| {
if let Some(c) = ecs.read_storage::<crate::Client>().get(e) {
c.send_fallible(ServerGeneral::FinishedTrade(TradeResult::Declined));
}
@ -198,7 +198,7 @@ pub(crate) fn cancel_trades_for(state: &mut common_state::State, entity: EcsEnti
fn commit_trade(ecs: &specs::World, trade: &PendingTrade) -> TradeResult {
let mut entities = Vec::new();
for party in trade.parties.iter() {
match ecs.entity_from_uid(party.0) {
match ecs.entity_from_uid(*party) {
Some(entity) => entities.push(entity),
None => return TradeResult::Declined,
}
@ -410,7 +410,7 @@ mod tests {
use hashbrown::HashMap;
use super::*;
use common::{comp::slot::InvSlotId, uid::UidAllocator};
use common::{comp::slot::InvSlotId, uid::IdMaps};
use specs::{Builder, World};
@ -423,7 +423,7 @@ mod tests {
merchant_inv_size: usize,
) -> (World, EcsEntity, EcsEntity) {
let mut mockworld = World::new();
mockworld.insert(UidAllocator::new());
mockworld.insert(IdMaps::new());
mockworld.insert(MaterialStatManifest::load().cloned());
mockworld.insert(AbilityMap::load().cloned());
mockworld.register::<Inventory>();
@ -440,12 +440,11 @@ mod tests {
.build();
{
use specs::saveload::MarkerAllocator;
let mut uids = mockworld.write_component::<Uid>();
let mut uid_allocator = mockworld.write_resource::<UidAllocator>();
uids.insert(player, uid_allocator.allocate(player, None))
let mut id_maps = mockworld.write_resource::<IdMaps>();
uids.insert(player, id_maps.allocate(player))
.expect("inserting player uid failed");
uids.insert(merchant, uid_allocator.allocate(merchant, None))
uids.insert(merchant, id_maps.allocate(merchant))
.expect("inserting merchant uid failed");
}

View File

@ -123,7 +123,7 @@ use crate::settings::Protocol;
#[cfg(feature = "plugins")]
use {
common::uid::UidAllocator,
common::uid::IdMaps,
common_state::plugin::{memory_manager::EcsWorld, PluginMgr},
};
@ -954,6 +954,8 @@ impl Server {
active_abilities,
map_marker,
);
// TODO: Does this need to be a server event? E.g. we could
// just handle it here.
ServerEvent::UpdateCharacterData {
entity: response.target_entity,
components: character_data,
@ -962,9 +964,8 @@ impl Server {
},
Err(error) => {
// We failed to load data for the character from the DB. Notify
// the client to push the
// state back to character selection, with the error
// to display
// the client to push the state back to character selection,
// with the error to display
self.notify_client(
response.target_entity,
ServerGeneral::CharacterDataLoadResult(Err(
@ -1218,7 +1219,7 @@ impl Server {
entities: &self.state.ecs().entities(),
health: self.state.ecs().read_component().into(),
uid: self.state.ecs().read_component().into(),
uid_allocator: &self.state.ecs().read_resource::<UidAllocator>().into(),
id_maps: &self.state.ecs().read_resource::<IdMaps>().into(),
player: self.state.ecs().read_component().into(),
};
let uid = if let Some(uid) = ecs_world.uid.get(entity).copied() {

View File

@ -27,7 +27,7 @@ use common::{
resources::{Secs, Time, TimeOfDay},
rtsim::{Actor, RtSimEntity},
slowjob::SlowJobPool,
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
LoadoutBuilder, ViewDistances,
};
use common_net::{
@ -36,12 +36,9 @@ use common_net::{
};
use common_state::State;
use rand::prelude::*;
use specs::{
saveload::MarkerAllocator, Builder, Entity as EcsEntity, EntityBuilder as EcsEntityBuilder,
Join, WorldExt,
};
use specs::{Builder, Entity as EcsEntity, EntityBuilder as EcsEntityBuilder, Join, WorldExt};
use std::time::{Duration, Instant};
use tracing::{trace, warn};
use tracing::{error, trace, warn};
use vek::*;
pub trait StateExt {
@ -128,7 +125,11 @@ pub trait StateExt {
fn initialize_spectator_data(&mut self, entity: EcsEntity, view_distances: ViewDistances);
/// Update the components associated with the entity's current character.
/// Performed after loading component data from the database
fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents);
fn update_character_data(
&mut self,
entity: EcsEntity,
components: PersistedComponents,
) -> Result<(), String>;
/// Iterates over registered clients and send each `ServerMsg`
fn validate_chat_msg(
&self,
@ -169,7 +170,7 @@ impl StateExt for State {
let groups = self.ecs().read_storage::<Group>();
let damage_contributor = source.and_then(|uid| {
self.ecs().entity_from_uid(uid.0).map(|attacker_entity| {
self.ecs().entity_from_uid(uid).map(|attacker_entity| {
DamageContributor::new(uid, groups.get(attacker_entity).cloned())
})
});
@ -214,7 +215,7 @@ impl StateExt for State {
if !character_state.is_stunned() {
let groups = self.ecs().read_storage::<Group>();
let damage_contributor = source.and_then(|uid| {
self.ecs().entity_from_uid(uid.0).map(|attacker_entity| {
self.ecs().entity_from_uid(uid).map(|attacker_entity| {
DamageContributor::new(uid, groups.get(attacker_entity).cloned())
})
});
@ -643,7 +644,7 @@ impl StateExt for State {
self.write_component_ignore_entity_dead(
entity,
Presence::new(view_distances, PresenceKind::Character(character_id)),
Presence::new(view_distances, PresenceKind::LoadingCharacter(character_id)),
);
// Tell the client its request was successful.
@ -678,7 +679,12 @@ impl StateExt for State {
}
}
fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents) {
/// Returned error intended to be sent to the client.
fn update_character_data(
&mut self,
entity: EcsEntity,
components: PersistedComponents,
) -> Result<(), String> {
let PersistedComponents {
body,
stats,
@ -691,6 +697,27 @@ impl StateExt for State {
} = components;
if let Some(player_uid) = self.read_component_copied::<Uid>(entity) {
let result =
if let Some(presence) = self.ecs().write_storage::<Presence>().get_mut(entity) {
if let PresenceKind::LoadingCharacter(id) = presence.kind {
presence.kind = PresenceKind::Character(id);
self.ecs()
.write_resource::<IdMaps>()
.add_character(id, entity);
Ok(())
} else {
Err("PresenceKind is not LoadingCharacter")
}
} else {
Err("Presence component missing")
};
if let Err(err) = result {
let err = format!("Unexpected state when applying loaded character info: {err}");
error!("{err}");
// TODO: we could produce a `comp::Content` for this to allow localization.
return Err(err);
}
// Notify clients of a player list update
self.notify_players(ServerGeneral::PlayerListUpdate(
PlayerListUpdate::SelectedCharacter(player_uid, CharacterInfo {
@ -733,6 +760,9 @@ impl StateExt for State {
self.write_component_ignore_entity_dead(entity, waypoint);
self.write_component_ignore_entity_dead(entity, comp::Pos(waypoint.get_pos()));
self.write_component_ignore_entity_dead(entity, comp::Vel(Vec3::zero()));
// TODO: We probably want to increment the existing force update counter since
// it is added in initialized_character (to be robust we can also insert it if
// it doesn't exist)
self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());
}
@ -771,7 +801,7 @@ impl StateExt for State {
restore_pet(self.ecs(), pet_entity, entity, pet);
}
} else {
warn!("Player has no pos, cannot load {} pets", pets.len());
error!("Player has no pos, cannot load {} pets", pets.len());
}
let presences = self.ecs().read_storage::<Presence>();
@ -789,6 +819,9 @@ impl StateExt for State {
player_info.last_battlemode_change = Some(*change);
}
} else {
// TODO: this sounds related to handle_exit_ingame? Actually, sounds like
// trying to place character specific info on the `Player` component. TODO
// document component better.
// FIXME:
// ???
//
@ -804,6 +837,8 @@ impl StateExt for State {
}
}
}
Ok(())
}
fn validate_chat_msg(
@ -862,16 +897,17 @@ impl StateExt for State {
.clone()
.map_group(|_| group_info.map_or_else(|| "???".to_string(), |i| i.name.clone()));
let id_maps = ecs.read_resource::<IdMaps>();
let entity_from_uid = |uid| id_maps.uid_entity(uid);
if msg.chat_type.uid().map_or(true, |sender| {
(*ecs.read_resource::<UidAllocator>())
.retrieve_entity_internal(sender.0)
.map_or(false, |e| {
self.validate_chat_msg(
e,
&msg.chat_type,
msg.content().as_plain().unwrap_or_default(),
)
})
entity_from_uid(sender).map_or(false, |e| {
self.validate_chat_msg(
e,
&msg.chat_type,
msg.content().as_plain().unwrap_or_default(),
)
})
}) {
match &msg.chat_type {
comp::ChatType::Offline(_)
@ -911,8 +947,7 @@ impl StateExt for State {
.unwrap_or_default()
{
// Send kill message to the dead player's group
let killed_entity =
(*ecs.read_resource::<UidAllocator>()).retrieve_entity_internal(uid.0);
let killed_entity = entity_from_uid(*uid);
let groups = ecs.read_storage::<Group>();
let killed_group = killed_entity.and_then(|e| groups.get(e));
if let Some(g) = &killed_group {
@ -947,8 +982,7 @@ impl StateExt for State {
}
},
comp::ChatType::Say(uid) => {
let entity_opt =
(*ecs.read_resource::<UidAllocator>()).retrieve_entity_internal(uid.0);
let entity_opt = entity_from_uid(*uid);
let positions = ecs.read_storage::<comp::Pos>();
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
@ -960,8 +994,7 @@ impl StateExt for State {
}
},
comp::ChatType::Region(uid) => {
let entity_opt =
(*ecs.read_resource::<UidAllocator>()).retrieve_entity_internal(uid.0);
let entity_opt = entity_from_uid(*uid);
let positions = ecs.read_storage::<comp::Pos>();
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
@ -973,8 +1006,7 @@ impl StateExt for State {
}
},
comp::ChatType::Npc(uid) => {
let entity_opt =
(*ecs.read_resource::<UidAllocator>()).retrieve_entity_internal(uid.0);
let entity_opt = entity_from_uid(*uid);
let positions = ecs.read_storage::<comp::Pos>();
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
@ -986,8 +1018,7 @@ impl StateExt for State {
}
},
comp::ChatType::NpcSay(uid) => {
let entity_opt =
(*ecs.read_resource::<UidAllocator>()).retrieve_entity_internal(uid.0);
let entity_opt = entity_from_uid(*uid);
let positions = ecs.read_storage::<comp::Pos>();
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
@ -1121,7 +1152,11 @@ impl StateExt for State {
&mut self,
entity: EcsEntity,
) -> Result<(), specs::error::WrongGeneration> {
// Remove entity from a group if they are in one
// NOTE: both this and handle_exit_ingame call delete_entity_common, so cleanup
// added here may need to be duplicated in handle_exit_ingame (depending
// on its nature).
// Remove entity from a group if they are in one.
{
let clients = self.ecs().read_storage::<Client>();
let uids = self.ecs().read_storage::<Uid>();
@ -1152,37 +1187,23 @@ impl StateExt for State {
// Cancel extant trades
events::cancel_trades_for(self, entity);
let (maybe_uid, maybe_pos) = (
self.ecs().read_storage::<Uid>().get(entity).copied(),
self.ecs().read_storage::<comp::Pos>().get(entity).copied(),
// NOTE: We expect that these 3 components are never removed from an entity (nor
// mutated) (at least not without updating the relevant mappings)!
let maybe_uid = self.read_component_copied::<Uid>(entity);
let maybe_character = self
.read_storage::<Presence>()
.get(entity)
.and_then(|p| p.kind.character_id());
let maybe_rtsim = self.read_component_copied::<RtSimEntity>(entity);
self.mut_resource::<IdMaps>().remove_entity(
Some(entity),
maybe_uid,
maybe_character,
maybe_rtsim,
);
let res = self.ecs_mut().delete_entity(entity);
if res.is_ok() {
if let (Some(uid), Some(pos)) = (maybe_uid, maybe_pos) {
if let Some(region_key) = self
.ecs()
.read_resource::<common::region::RegionMap>()
.find_region(entity, pos.0)
{
self.ecs()
.write_resource::<DeletedEntities>()
.record_deleted_entity(uid, region_key);
} else {
// Don't panic if the entity wasn't found in a region maybe it was just created
// and then deleted before the region manager had a chance to assign it a
// region
warn!(
?uid,
?pos,
"Failed to find region containing entity during entity deletion, assuming \
it wasn't sent to any clients and so deletion doesn't need to be \
recorded for sync purposes"
);
}
}
}
res
delete_entity_common(self, entity, maybe_uid)
}
fn entity_as_actor(&self, entity: EcsEntity) -> Option<Actor> {
@ -1213,3 +1234,50 @@ fn send_to_group(g: &Group, ecs: &specs::World, msg: &comp::ChatMsg) {
}
}
}
/// This should only be called from `handle_exit_ingame` and
/// `delete_entity_recorded`!!!!!!!
pub(crate) fn delete_entity_common(
state: &mut State,
entity: EcsEntity,
maybe_uid: Option<Uid>,
) -> Result<(), specs::error::WrongGeneration> {
if maybe_uid.is_none() {
// For now we expect all entities have a Uid component.
error!("Deleting entity without Uid component");
}
let maybe_pos = state.read_component_copied::<comp::Pos>(entity);
let res = state.ecs_mut().delete_entity(entity);
if res.is_ok() {
// Note: Adding the `Uid` to the deleted list when exiting "in-game" relies on
// the client not being able to immediately re-enter the game in the
// same tick (since we could then mix up the ordering of things and
// tell other clients to forget the new entity).
//
// The client will ignore requests to delete its own entity that are triggered
// by this.
if let Some((uid, pos)) = maybe_uid.zip(maybe_pos) {
let region_key = state
.ecs()
.read_resource::<common::region::RegionMap>()
.find_region(entity, pos.0);
if let Some(region_key) = region_key {
state
.mut_resource::<DeletedEntities>()
.record_deleted_entity(uid, region_key);
} else {
// Don't panic if the entity wasn't found in a region, maybe it was just created
// and then deleted before the region manager had a chance to assign it a region
warn!(
?uid,
?pos,
"Failed to find region containing entity during entity deletion, assuming it \
wasn't sent to any clients and so deletion doesn't need to be recorded for \
sync purposes"
);
}
}
}
res
}

View File

@ -19,7 +19,7 @@ use common_base::prof_span;
use common_ecs::{Job, Origin, ParMode, Phase, System};
use rand::thread_rng;
use rayon::iter::ParallelIterator;
use specs::{saveload::MarkerAllocator, Join, ParJoin, Read, WriteStorage};
use specs::{Join, ParJoin, Read, WriteStorage};
/// This system will allow NPCs to modify their controller
#[derive(Default)]
@ -102,18 +102,12 @@ impl<'a> System<'a> for Sys {
// The entity that is moving, if riding it's the mount, otherwise it's itself
let moving_entity = is_rider
.and_then(|is_rider| {
read_data
.uid_allocator
.retrieve_entity_internal(is_rider.mount.into())
})
.and_then(|is_rider| read_data.id_maps.uid_entity(is_rider.mount))
.or_else(|| {
is_volume_rider.and_then(|is_volume_rider| {
match is_volume_rider.pos.kind {
Volume::Terrain => None,
Volume::Entity(uid) => {
read_data.uid_allocator.retrieve_entity_internal(uid.into())
},
Volume::Entity(uid) => read_data.id_maps.uid_entity(uid),
}
})
})

View File

@ -13,10 +13,7 @@ use common::{
rtsim::{NpcAction, RtSimEntity},
};
use rand::{prelude::ThreadRng, thread_rng, Rng};
use specs::{
saveload::{Marker, MarkerAllocator},
Entity as EcsEntity,
};
use specs::Entity as EcsEntity;
use vek::{Vec2, Vec3};
use self::interaction::{
@ -247,11 +244,7 @@ fn target_if_attacked(bdata: &mut BehaviorData) -> bool {
&& health.last_change.amount < 0.0 =>
{
if let Some(by) = health.last_change.damage_by() {
if let Some(attacker) = bdata
.read_data
.uid_allocator
.retrieve_entity_internal(by.uid().0)
{
if let Some(attacker) = bdata.read_data.id_maps.uid_entity(by.uid()) {
// If target is dead or invulnerable (for now, this only
// means safezone), untarget them and idle.
if is_dead_or_invulnerable(attacker, bdata.read_data) {
@ -460,7 +453,7 @@ fn set_owner_if_no_target(bdata: &mut BehaviorData) -> bool {
if bdata.agent.target.is_none() && small_chance {
if let Some(Alignment::Owned(owner)) = bdata.agent_data.alignment {
if let Some(owner) = get_entity_by_id(owner.id(), bdata.read_data) {
if let Some(owner) = get_entity_by_id(*owner, bdata.read_data) {
let owner_pos = bdata.read_data.positions.get(owner).map(|pos| pos.0);
bdata.agent.target = Some(Target::new(

View File

@ -14,7 +14,6 @@ use common::{
trade::{TradeAction, TradePhase, TradeResult},
};
use rand::{thread_rng, Rng};
use specs::saveload::Marker;
use crate::sys::agent::util::get_entity_by_id;
@ -87,7 +86,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
}
if let Some(AgentEvent::Talk(by, subject)) = agent.inbox.pop_front() {
let by_entity = get_entity_by_id(by.id(), read_data);
let by_entity = get_entity_by_id(by, read_data);
if let Some(rtsim_outbox) = &mut agent.rtsim_outbox {
if let Subject::Regular
@ -297,7 +296,7 @@ pub fn handle_inbox_trade_invite(bdata: &mut BehaviorData) -> bool {
// stand still and looking towards the trading player
controller.push_action(ControlAction::Stand);
controller.push_action(ControlAction::Talk);
if let Some(target) = get_entity_by_id(with.id(), read_data) {
if let Some(target) = get_entity_by_id(with, read_data) {
let target_pos = read_data.positions.get(target).map(|pos| pos.0);
agent.target = Some(Target::new(
@ -344,7 +343,7 @@ pub fn handle_inbox_trade_accepted(bdata: &mut BehaviorData) -> bool {
if let Some(AgentEvent::TradeAccepted(with)) = agent.inbox.pop_front() {
if !agent.behavior.is(BehaviorState::TRADING) {
if let Some(target) = get_entity_by_id(with.id(), read_data) {
if let Some(target) = get_entity_by_id(with, read_data) {
let target_pos = read_data.positions.get(target).map(|pos| pos.0);
agent.target = Some(Target::new(
@ -561,7 +560,7 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
let used = match msg {
AgentEvent::Talk(by, _) | AgentEvent::TradeAccepted(by) => {
if let (Some(target), Some(speaker)) =
(agent.target, get_entity_by_id(by.id(), bdata.read_data))
(agent.target, get_entity_by_id(*by, bdata.read_data))
{
// in combat, speak to players that aren't the current target
if !target.hostile || target.target != speaker {
@ -578,7 +577,7 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
AgentEvent::TradeInvite(by) => {
controller.push_invite_response(InviteResponse::Decline);
if let (Some(target), Some(speaker)) =
(agent.target, get_entity_by_id(by.id(), bdata.read_data))
(agent.target, get_entity_by_id(*by, bdata.read_data))
{
// in combat, speak to players that aren't the current target
if !target.hostile || target.target != speaker {

View File

@ -8,7 +8,6 @@ use common::{
region::{Event as RegionEvent, RegionMap},
resources::{PlayerPhysicsSettings, Time, TimeOfDay, TimeScale},
terrain::TerrainChunkSize,
uid::Uid,
vol::RectVolSize,
};
use common_ecs::{Job, Origin, Phase, System};
@ -191,6 +190,9 @@ impl<'a> System<'a> for Sys {
.map(|key| !regions.contains(key))
.unwrap_or(true)
{
// TODO: I suspect it would be more efficient (in terms of
// bandwidth) to batch messages like this (same in
// subscription.rs).
client.send_fallible(ServerGeneral::DeleteEntity(uid));
}
}
@ -309,6 +311,11 @@ impl<'a> System<'a> for Sys {
}
}
// TODO: force update counter only needs to be sent once per frame (and only if
// it changed, although it might not be worth having a separate message for
// optionally sending it since individual messages may have a bandwidth
// overhead), however, here we send it potentially 2 times per subscribed
// region by including it in the `CompSync` message.
client.send_fallible(ServerGeneral::CompSync(
comp_sync_package,
force_updates.get(*client_entity).map_or(0, |f| f.counter()),
@ -350,7 +357,7 @@ impl<'a> System<'a> for Sys {
})
{
for uid in &deleted {
client.send_fallible(ServerGeneral::DeleteEntity(Uid(*uid)));
client.send_fallible(ServerGeneral::DeleteEntity(*uid));
}
}
}

View File

@ -1,9 +1,9 @@
use common::{
comp::{group::GroupManager, loot_owner::LootOwnerKind, LootOwner},
uid::UidAllocator,
uid::IdMaps,
};
use common_ecs::{Job, Origin, Phase, System};
use specs::{saveload::MarkerAllocator, Entities, Entity, Join, Read, WriteStorage};
use specs::{Entities, Entity, Join, Read, WriteStorage};
use tracing::debug;
// This system manages loot that exists in the world
@ -13,7 +13,7 @@ impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
WriteStorage<'a, LootOwner>,
Read<'a, UidAllocator>,
Read<'a, IdMaps>,
Read<'a, GroupManager>,
);
@ -23,7 +23,7 @@ impl<'a> System<'a> for Sys {
fn run(
_job: &mut Job<Self>,
(entities, mut loot_owners, uid_allocator, group_manager): Self::SystemData,
(entities, mut loot_owners, id_maps, group_manager): Self::SystemData,
) {
// Find and remove expired loot ownership. Loot ownership is expired when either
// the expiry time has passed, or the owner no longer exists
@ -32,8 +32,8 @@ impl<'a> System<'a> for Sys {
.filter(|(_, loot_owner)| {
loot_owner.expired()
|| match loot_owner.owner() {
LootOwnerKind::Player(uid) => uid_allocator
.retrieve_entity_internal(uid.into())
LootOwnerKind::Player(uid) => id_maps
.uid_entity(uid)
.map_or(true, |entity| !entities.is_alive(entity)),
LootOwnerKind::Group(group) => group_manager.group_info(group).is_none(),
}

View File

@ -85,6 +85,12 @@ impl Sys {
},
ClientGeneral::Character(character_id, requested_view_distances) => {
if let Some(player) = players.get(entity) {
// NOTE: Because clients retain their Uid when exiting to the character
// selection screen, we rely on this check to prevent them from immediately
// re-entering in-game in the same tick so that we can synchronize their
// removal without this being mixed up (and e.g. telling other clients to
// delete the re-joined version) or requiring more complex handling to avoid
// this.
if presences.contains(entity) {
debug!("player already ingame, aborting");
} else if character_updater.has_pending_database_action(character_id)

View File

@ -102,15 +102,12 @@ impl Sys {
}
},
ClientGeneral::ControlEvent(event) => {
if presence.kind.controlling_char() {
if presence.kind.controlling_char() && let Some(controller) = controller {
// Skip respawn if client entity is alive
if let ControlEvent::Respawn = event {
if healths.get(entity).map_or(true, |h| !h.is_dead) {
//Todo: comment why return!
return Ok(());
}
}
if let Some(controller) = controller {
let skip_respawn = matches!(event, ControlEvent::Respawn)
&& healths.get(entity).map_or(true, |h| !h.is_dead);
if !skip_respawn {
controller.push_event(event);
}
}

View File

@ -11,7 +11,7 @@ use common::{
recipe::{default_component_recipe_book, default_recipe_book, default_repair_recipe_book},
resources::TimeOfDay,
shared_server_config::ServerConstants,
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
};
use common_base::prof_span;
use common_ecs::{Job, Origin, Phase, System};
@ -54,7 +54,7 @@ pub struct ReadData<'a> {
trackers: TrackedStorages<'a>,
_healths: ReadStorage<'a, Health>, // used by plugin feature
_plugin_mgr: ReadPlugin<'a>, // used by plugin feature
_uid_allocator: Read<'a, UidAllocator>, // used by plugin feature
_id_maps: Read<'a, IdMaps>, // used by plugin feature
}
/// This system will handle new messages from clients
@ -146,7 +146,7 @@ impl<'a> System<'a> for Sys {
// NOTE: Only the old player list is provided, to avoid scalability
// bottlenecks.
player: (&players).into(),
uid_allocator: &read_data._uid_allocator,
id_maps: &read_data._id_maps,
};
// NOTE: this is just default value.

View File

@ -9,6 +9,7 @@ use common::{
};
use common_ecs::{Job, Origin, Phase, System};
use specs::{Join, ReadStorage, Write, WriteExpect};
use tracing::error;
#[derive(Default)]
pub struct Sys;
@ -74,6 +75,14 @@ impl<'a> System<'a> for Sys {
active_abilities,
map_marker,
)| match presence.kind {
PresenceKind::LoadingCharacter(_char_id) => {
error!(
"Unexpected state when persisting characters! Some of the \
components required above should only be present after a \
character is loaded!"
);
None
},
PresenceKind::Character(id) => {
let pets = (&alignments, &bodies, &stats, &pets)
.join()

View File

@ -1,12 +1,10 @@
use common::{
comp::{Alignment, Pet, PhysicsState, Pos},
terrain::TerrainGrid,
uid::UidAllocator,
uid::IdMaps,
};
use common_ecs::{Job, Origin, Phase, System};
use specs::{
saveload::MarkerAllocator, Entities, Entity, Join, Read, ReadExpect, ReadStorage, WriteStorage,
};
use specs::{Entities, Entity, Join, Read, ReadExpect, ReadStorage, WriteStorage};
/// This system is responsible for handling pets
#[derive(Default)]
@ -19,7 +17,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Alignment>,
ReadStorage<'a, Pet>,
ReadStorage<'a, PhysicsState>,
Read<'a, UidAllocator>,
Read<'a, IdMaps>,
);
const NAME: &'static str = "pets";
@ -28,7 +26,7 @@ impl<'a> System<'a> for Sys {
fn run(
_job: &mut Job<Self>,
(entities, terrain, mut positions, alignments, pets, physics, uid_allocator): Self::SystemData,
(entities, terrain, mut positions, alignments, pets, physics, id_maps): Self::SystemData,
) {
const LOST_PET_DISTANCE_THRESHOLD: f32 = 200.0;
@ -36,20 +34,18 @@ impl<'a> System<'a> for Sys {
let lost_pets: Vec<(Entity, Pos)> = (&entities, &positions, &alignments, &pets)
.join()
.filter_map(|(entity, pos, alignment, _)| match alignment {
Alignment::Owned(owner_uid) => Some((entity, pos, owner_uid)),
Alignment::Owned(owner_uid) => Some((entity, pos, *owner_uid)),
_ => None,
})
.filter_map(|(pet_entity, pet_pos, owner_uid)| {
uid_allocator
.retrieve_entity_internal(owner_uid.0)
.and_then(|owner_entity| {
match (positions.get(owner_entity), physics.get(owner_entity)) {
(Some(position), Some(physics)) => {
Some((pet_entity, position, physics, pet_pos))
},
_ => None,
}
})
id_maps.uid_entity(owner_uid).and_then(|owner_entity| {
match (positions.get(owner_entity), physics.get(owner_entity)) {
(Some(position), Some(physics)) => {
Some((pet_entity, position, physics, pet_pos))
},
_ => None,
}
})
})
.filter(|(_, owner_pos, owner_physics, pet_pos)| {
// Don't teleport pets to the player if they're in the air, nobody wants

View File

@ -1,4 +1,3 @@
#![allow(clippy::large_enum_variant)]
use common::{
comp::{
item::{tool::AbilityMap, MaterialStatManifest},
@ -80,7 +79,6 @@ macro_rules! trackers {
vel: Option<Vel>,
ori: Option<Ori>,
) -> EntityPackage<EcsCompPacket> {
let uid = uid.0;
let mut comps = Vec::new();
// NOTE: we could potentially include a bitmap indicating which components are present instead of tagging
// components with the type in order to save bandwidth
@ -208,7 +206,7 @@ macro_rules! trackers {
&self,
comps: &TrackedStorages,
filter: impl Join + Copy,
deleted_entities: Vec<u64>,
deleted_entities: Vec<Uid>,
) -> (EntitySyncPackage, CompSyncPackage<EcsCompPacket>) {
let entity_sync_package =
EntitySyncPackage::new(&comps.uid, &self.uid, filter, deleted_entities);
@ -278,9 +276,13 @@ use common_net::synced_components::*;
// of components. This will declare the types defined in the macro above.
common_net::synced_components!(trackers);
/// Deleted entities grouped by region
/// Deleted entities grouped by region.
///
/// Note, this is primarily for syncing purposes and can include the Uid of
/// clients that have left "in-game" to return to the character screen (even
/// though there is still an entity with this Uid!).
pub struct DeletedEntities {
map: HashMap<Vec2<i32>, Vec<u64>>,
map: HashMap<Vec2<i32>, Vec<Uid>>,
}
impl Default for DeletedEntities {
@ -293,18 +295,18 @@ impl Default for DeletedEntities {
impl DeletedEntities {
pub fn record_deleted_entity(&mut self, uid: Uid, region_key: Vec2<i32>) {
self.map.entry(region_key).or_default().push(uid.into());
self.map.entry(region_key).or_default().push(uid);
}
pub fn take_deleted_in_region(&mut self, key: Vec2<i32>) -> Vec<u64> {
pub fn take_deleted_in_region(&mut self, key: Vec2<i32>) -> Vec<Uid> {
self.map.remove(&key).unwrap_or_default()
}
pub fn get_deleted_in_region(&self, key: Vec2<i32>) -> &[u64] {
pub fn get_deleted_in_region(&self, key: Vec2<i32>) -> &[Uid] {
self.map.get(&key).map_or(&[], |v| v.as_slice())
}
pub fn take_remaining_deleted(&mut self) -> impl Iterator<Item = (Vec2<i32>, Vec<u64>)> + '_ {
pub fn take_remaining_deleted(&mut self) -> impl Iterator<Item = (Vec2<i32>, Vec<Uid>)> + '_ {
self.map.drain()
}
}

View File

@ -135,10 +135,10 @@ impl<'a> System<'a> for Sys {
// TODO: consider changing system ordering??
for event in region.events() {
match event {
RegionEvent::Entered(_, _) => {}, /* These don't need to be */
// processed because this
// region is being thrown out
// anyway
RegionEvent::Entered(_, _) => {
// These don't need to be processed because
// this region is being thrown out anyway
},
RegionEvent::Left(id, maybe_key) => {
// Lookup UID for entity
// Doesn't overlap with entity deletion in sync packages
@ -147,7 +147,9 @@ impl<'a> System<'a> for Sys {
if let Some(&uid) = uids.get(entities.entity(*id)) {
if !maybe_key
.as_ref()
// Don't need to check that this isn't also in the regions to remove since the entity will be removed when we get to that one
// Don't need to check that this isn't also in the
// regions to remove since the entity will be removed
// when we get to that one.
.map(|key| subscription.regions.contains(key))
.unwrap_or(false)
{
@ -162,10 +164,10 @@ impl<'a> System<'a> for Sys {
client.send_fallible(ServerGeneral::DeleteEntity(uid));
}
}
// Send deleted entities since they won't be processed for this client in entity
// sync
// Send deleted entities since they won't be processed for this client
// in entity sync
for uid in deleted_entities.get_deleted_in_region(key).iter() {
client.send_fallible(ServerGeneral::DeleteEntity(Uid(*uid)));
client.send_fallible(ServerGeneral::DeleteEntity(*uid));
}
}
@ -195,6 +197,7 @@ impl<'a> System<'a> for Sys {
ori.copied(),
)
})
// TODO: batch this into a single message
.for_each(|msg| {
// Send message to create entity and tracked components and
// physics components

View File

@ -18,7 +18,7 @@ use common::{
combat,
comp::{group::Role, inventory::item::MaterialStatManifest, invite::InviteKind, Stats},
resources::Time,
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
};
use common_net::sync::WorldSyncExt;
use conrod_core::{
@ -28,7 +28,7 @@ use conrod_core::{
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
};
use i18n::Localization;
use specs::{saveload::MarkerAllocator, WorldExt};
use specs::WorldExt;
widget_ids! {
pub struct Ids {
@ -202,7 +202,7 @@ impl<'a> Widget for Group<'a> {
None => client
.state()
.ecs()
.entity_from_uid(uid.0)
.entity_from_uid(uid)
.and_then(|entity| {
client
.state()
@ -373,7 +373,7 @@ impl<'a> Widget for Group<'a> {
let energy = client_state.ecs().read_storage::<common::comp::Energy>();
let buffs = client_state.ecs().read_storage::<common::comp::Buffs>();
let inventory = client_state.ecs().read_storage::<common::comp::Inventory>();
let uid_allocator = client_state.ecs().read_resource::<UidAllocator>();
let id_maps = client_state.ecs().read_resource::<IdMaps>();
let bodies = client_state.ecs().read_storage::<common::comp::Body>();
let poises = client_state.ecs().read_storage::<common::comp::Poise>();
let stances = client_state.ecs().read_storage::<common::comp::Stance>();
@ -382,7 +382,7 @@ impl<'a> Widget for Group<'a> {
let mut total_buff_count = 0;
for (i, &uid) in group_members.iter().copied().enumerate() {
self.show.group = true;
let entity = uid_allocator.retrieve_entity_internal(uid.into());
let entity = id_maps.uid_entity(uid);
let stats = entity.and_then(|entity| stats.get(entity));
let skill_set = entity.and_then(|entity| skill_sets.get(entity));
let health = entity.and_then(|entity| healths.get(entity));

View File

@ -27,7 +27,7 @@ use conrod_core::{
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, UiCell, Widget, WidgetCommon,
};
use i18n::Localization;
use specs::{saveload::MarkerAllocator, WorldExt};
use specs::WorldExt;
use std::borrow::Cow;
use vek::*;
use winit::event::MouseButton;
@ -1240,9 +1240,9 @@ impl<'a> Widget for Map<'a> {
.collect::<Vec<_>>();
let group_size = group_members.len();
//let in_group = !group_members.is_empty();
let uid_allocator = client_state
let id_maps = client_state
.ecs()
.read_resource::<common_net::sync::UidAllocator>();
.read_resource::<common_net::sync::IdMaps>();
if state.ids.member_indicators.len() < group_size {
state.update(|s| {
s.ids
@ -1251,7 +1251,7 @@ impl<'a> Widget for Map<'a> {
})
};
for (i, &uid) in group_members.iter().copied().enumerate() {
let entity = uid_allocator.retrieve_entity_internal(uid.into());
let entity = id_maps.uid_entity(uid);
let member_pos = entity.and_then(|entity| member_pos.get(entity));
let stats = entity.and_then(|entity| stats.get(entity));
let name = if let Some(stats) = stats {
@ -1323,8 +1323,8 @@ impl<'a> Widget for Map<'a> {
.get(&uid)
.map(|info| info.player_alias.as_str())
.or_else(|| {
uid_allocator
.retrieve_entity_internal(uid.into())
id_maps
.uid_entity(uid)
.and_then(|entity| stats.get(entity))
.map(|stats| stats.name.as_str())
})

View File

@ -28,7 +28,7 @@ use conrod_core::{
};
use hashbrown::HashMap;
use image::{DynamicImage, RgbaImage};
use specs::{saveload::MarkerAllocator, WorldExt};
use specs::WorldExt;
use std::sync::Arc;
use vek::*;
@ -762,9 +762,9 @@ impl<'a> Widget for MiniMap<'a> {
.collect::<Vec<_>>();
let group_size = group_members.len();
//let in_group = !group_members.is_empty();
let uid_allocator = client_state
let id_maps = client_state
.ecs()
.read_resource::<common_net::sync::UidAllocator>();
.read_resource::<common_net::sync::IdMaps>();
if state.ids.member_indicators.len() < group_size {
state.update(|s| {
s.ids
@ -773,7 +773,7 @@ impl<'a> Widget for MiniMap<'a> {
})
};
for (i, &uid) in group_members.iter().copied().enumerate() {
let entity = uid_allocator.retrieve_entity_internal(uid.into());
let entity = id_maps.uid_entity(uid);
let member_pos = entity.and_then(|entity| member_pos.get(entity));
if let Some(member_pos) = member_pos {

View File

@ -1247,12 +1247,11 @@ impl HudCollectFailedReason {
CollectFailedReason::LootOwned { owner, expiry_secs } => {
let owner = match owner {
LootOwnerKind::Player(owner_uid) => {
let maybe_owner_name =
ecs.entity_from_uid((*owner_uid).into()).and_then(|entity| {
ecs.read_storage::<comp::Stats>()
.get(entity)
.map(|stats| stats.name.clone())
});
let maybe_owner_name = ecs.entity_from_uid(*owner_uid).and_then(|entity| {
ecs.read_storage::<comp::Stats>()
.get(entity)
.map(|stats| stats.name.clone())
});
if let Some(name) = maybe_owner_name {
HudLootOwner::Name(name)
@ -1351,6 +1350,7 @@ impl Hud {
let character_id = match client.presence().unwrap() {
PresenceKind::Character(id) => Some(id),
PresenceKind::LoadingCharacter(id) => Some(id),
PresenceKind::Spectator => None,
PresenceKind::Possessor => None,
};
@ -4056,7 +4056,7 @@ impl Hud {
let ecs = client.state().ecs();
let inventories = ecs.read_component::<comp::Inventory>();
let get_inventory = |uid: Uid| {
if let Some(entity) = ecs.entity_from_uid(uid.0) {
if let Some(entity) = ecs.entity_from_uid(uid) {
inventories.get(entity)
} else {
None
@ -4914,7 +4914,7 @@ impl Hud {
let me = client.entity();
let my_uid = uids.get(me);
if let Some(entity) = ecs.entity_from_uid(info.target.0) {
if let Some(entity) = ecs.entity_from_uid(info.target) {
if let Some(floater_list) = hp_floater_lists.get_mut(entity) {
let hit_me = my_uid.map_or(false, |&uid| {
(info.target == uid) && global_state.settings.interface.sct_inc_dmg

View File

@ -204,7 +204,7 @@ impl<'a> Trade<'a> {
let inventories = self.client.inventories();
let check_if_us = |who: usize| -> Option<_> {
let uid = trade.parties[who];
let entity = self.client.state().ecs().entity_from_uid(uid.0)?;
let entity = self.client.state().ecs().entity_from_uid(uid)?;
let is_ours = entity == self.client.entity();
Some(((who, uid, entity), is_ours))
};

View File

@ -48,7 +48,7 @@ use common::{
resources::{DeltaTime, Time},
states::{equipping, idle, utils::StageSection, wielding},
terrain::{Block, SpriteKind, TerrainChunk, TerrainGrid},
uid::UidAllocator,
uid::IdMaps,
util::Dir,
vol::{ReadVol, RectRasterableVol},
};
@ -62,7 +62,7 @@ use core::{
};
use guillotiere::AtlasAllocator;
use hashbrown::HashMap;
use specs::{saveload::MarkerAllocator, Entity as EcsEntity, Join, LazyUpdate, WorldExt};
use specs::{Entity as EcsEntity, Join, LazyUpdate, WorldExt};
use std::sync::Arc;
use treeculler::{BVol, BoundingSphere};
use vek::*;
@ -829,7 +829,7 @@ impl FigureMgr {
let mut update_buf = [Default::default(); anim::MAX_BONE_COUNT];
let uid_allocator = ecs.read_resource::<UidAllocator>();
let id_maps = ecs.read_resource::<IdMaps>();
let bodies = ecs.read_storage::<Body>();
@ -1055,7 +1055,7 @@ impl FigureMgr {
let mount_transform_pos = (|| -> Option<_> {
if let Some(is_rider) = is_rider {
let mount = is_rider.mount;
let mount = uid_allocator.retrieve_entity_internal(mount.into())?;
let mount = id_maps.uid_entity(mount)?;
let body = *bodies.get(mount)?;
let meta = self.states.get_mut(&body, &mount)?;
Some((meta.mount_transform, meta.mount_world_pos))

View File

@ -20,13 +20,13 @@ use common::{
spiral::Spiral2d,
states::{self, utils::StageSection},
terrain::{Block, SpriteKind, TerrainChunk, TerrainGrid},
uid::UidAllocator,
uid::IdMaps,
vol::{ReadVol, RectRasterableVol, SizedVol},
};
use common_base::span;
use hashbrown::HashMap;
use rand::prelude::*;
use specs::{saveload::MarkerAllocator, Join, WorldExt};
use specs::{Join, WorldExt};
use std::{
f32::consts::{PI, TAU},
time::Duration,
@ -287,10 +287,7 @@ impl ParticleMgr {
if target.is_some() {
let ecs = scene_data.state.ecs();
if target
.and_then(|target| {
ecs.read_resource::<UidAllocator>()
.retrieve_entity_internal(target.0)
})
.and_then(|target| ecs.read_resource::<IdMaps>().uid_entity(target))
.and_then(|entity| {
ecs.read_storage::<Body>()
.get(entity)

View File

@ -14,7 +14,7 @@ use common::{
link::Is,
mounting::{Mount, Rider, VolumePos, VolumeRider},
terrain::{Block, TerrainGrid, UnlockKind},
uid::{Uid, UidAllocator},
uid::{IdMaps, Uid},
util::find_dist::{Cube, Cylinder, FindDist},
vol::ReadVol,
CachedSpatialGrid,
@ -53,12 +53,12 @@ impl Interactable {
fn from_block_pos(
terrain: &TerrainGrid,
uid_allocator: &UidAllocator,
id_maps: &IdMaps,
colliders: &ReadStorage<Collider>,
volume_pos: VolumePos,
interaction: Interaction,
) -> Option<Self> {
let Some(block) = volume_pos.get_block(terrain, uid_allocator, colliders) else { return None };
let Some(block) = volume_pos.get_block(terrain, id_maps, colliders) else { return None };
let block_interaction = match interaction {
Interaction::Collect => {
// Check if this is an unlockable sprite
@ -346,7 +346,7 @@ pub(super) fn select_interactable(
.and_then(|(_, block_pos, interaction)| {
Interactable::from_block_pos(
&terrain,
&ecs.read_resource::<UidAllocator>(),
&ecs.read_resource::<IdMaps>(),
&ecs.read_storage(),
block_pos,
*interaction,

View File

@ -294,7 +294,7 @@ impl SessionState {
};
let target_name = match client.player_list().get(&target) {
Some(info) => info.player_alias.clone(),
None => match client.state().ecs().entity_from_uid(target.0) {
None => match client.state().ecs().entity_from_uid(target) {
Some(entity) => {
let stats = client.state().read_storage::<Stats>();
stats
@ -362,9 +362,7 @@ impl SessionState {
entity: uid,
reason,
} => {
if let Some(entity) =
client.state().ecs().entity_from_uid(uid.into())
{
if let Some(entity) = client.state().ecs().entity_from_uid(uid) {
self.hud.add_failed_entity_pickup(
entity,
HudCollectFailedReason::from_server_reason(
@ -1680,6 +1678,7 @@ impl PlayState for SessionState {
// If we are changing the hotbar state this CANNOT be None.
let character_id = match client.presence().unwrap() {
PresenceKind::Character(id) => Some(id),
PresenceKind::LoadingCharacter(id) => Some(id),
PresenceKind::Spectator => {
unreachable!("HUD adaption in Spectator mode!")
},