mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Start refactoring UidAllocator to contain mappings for different Id
types. * Add new `IdMaps` type that contains mappings to Entity from Uid, CharacterId, and RtsimEntity. * Add PresenceKind::LoadingCharacter variant for when the CharacterId has not actually been confirmed to exist and belong to the logged in client. Switches to the regular PresenceKind::Character once the character is loaded from the database and that is used to update the entity. * Start refactoring `delete_entity_recorded` to check for CharacterId and RtsimEntity values that may need to be removed from the map (just very start, not near complete). * Other misc tweaks.
This commit is contained in:
parent
3277d18265
commit
35922866a8
@ -53,16 +53,21 @@ impl WorldSyncExt for specs::World {
|
|||||||
|
|
||||||
fn create_entity_synced(&mut self) -> specs::EntityBuilder {
|
fn create_entity_synced(&mut self) -> specs::EntityBuilder {
|
||||||
let builder = self.create_entity();
|
let builder = self.create_entity();
|
||||||
|
// TODO: if we split the UidAllocator and the IdMaps into two things then we
|
||||||
|
// need to fetch 2 resources when creating entities.
|
||||||
let uid = builder
|
let uid = builder
|
||||||
.world
|
.world
|
||||||
.write_resource::<UidAllocator>()
|
.write_resource::<UidAllocator>()
|
||||||
.allocate(builder.entity, None);
|
.allocate(builder.entity);
|
||||||
builder.with(uid)
|
builder.with(uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This method should be used from the client-side when processing network
|
||||||
|
/// messages that delete entities.
|
||||||
|
// TODO: rename method
|
||||||
fn delete_entity_and_clear_from_uid_allocator(&mut self, uid: Uid) {
|
fn delete_entity_and_clear_from_uid_allocator(&mut self, uid: Uid) {
|
||||||
// Clear from uid allocator
|
// Clear from uid allocator
|
||||||
let maybe_entity = self.write_resource::<UidAllocator>().remove_entity(uid);
|
let maybe_entity = self.write_resource::<UidAllocator>().remove_entity_(uid);
|
||||||
if let Some(entity) = maybe_entity {
|
if let Some(entity) = maybe_entity {
|
||||||
if let Err(e) = self.delete_entity(entity) {
|
if let Err(e) = self.delete_entity(entity) {
|
||||||
error!(?e, "Failed to delete entity");
|
error!(?e, "Failed to delete entity");
|
||||||
|
@ -32,6 +32,9 @@ impl Component for Presence {
|
|||||||
pub enum PresenceKind {
|
pub enum PresenceKind {
|
||||||
Spectator,
|
Spectator,
|
||||||
Character(CharacterId),
|
Character(CharacterId),
|
||||||
|
// Note: we don't know if this character ID is valid and associated with the respective player
|
||||||
|
// until it the character has loaded successfully.
|
||||||
|
LoadingCharacter(CharacterId),
|
||||||
Possessor,
|
Possessor,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
#[cfg(not(target_arch = "wasm32"))]
|
// TODO: rename this module or create new one for ID maps
|
||||||
use hashbrown::HashMap;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
use specs::{Component, Entity, FlaggedStorage, VecStorage};
|
|
||||||
use std::{fmt, u64};
|
use std::{fmt, u64};
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use {
|
||||||
|
crate::character::CharacterId,
|
||||||
|
crate::rtsim::RtSimEntity,
|
||||||
|
hashbrown::HashMap,
|
||||||
|
specs::{Component, Entity, FlaggedStorage, VecStorage},
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: could we switch this to `NonZeroU64`?
|
||||||
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Uid(pub u64);
|
pub struct Uid(pub u64);
|
||||||
|
|
||||||
@ -20,45 +26,175 @@ impl fmt::Display for Uid {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) }
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub use not_wasm::*;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
impl Component for Uid {
|
mod not_wasm {
|
||||||
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
|
use super::*;
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
impl Component for Uid {
|
||||||
#[derive(Debug)]
|
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
|
||||||
pub struct UidAllocator {
|
}
|
||||||
next_id: u64,
|
|
||||||
mapping: HashMap<Uid, Entity>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
// NOTE: This is technically only needed by the server code server. Keeping here
|
||||||
impl UidAllocator {
|
// for now since this is related to the other code here.
|
||||||
pub fn new() -> Self {
|
#[derive(Debug)]
|
||||||
Self {
|
pub struct UidAllocator {
|
||||||
next_id: 0,
|
/// Next Uid.
|
||||||
mapping: HashMap::new(),
|
next_uid: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UidAllocator {
|
||||||
|
pub fn new() -> Self { Self { next_uid: 0 } }
|
||||||
|
|
||||||
|
pub fn allocate(&mut self, entity: Entity, id: Option<Uid>) -> Uid {
|
||||||
|
let id = id.unwrap_or_else(|| {
|
||||||
|
let id = self.next_uid;
|
||||||
|
self.next_uid += 1;
|
||||||
|
Uid(id)
|
||||||
|
});
|
||||||
|
self.uid_mapping.insert(id, entity);
|
||||||
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Useful for when a single entity is deleted because it doesn't reconstruct the
|
#[derive(Debug)]
|
||||||
// entire hashmap
|
pub struct IdMaps {
|
||||||
pub fn remove_entity(&mut self, id: Uid) -> Option<Entity> { self.mapping.remove(&id) }
|
/// "Universal" IDs (used to communicate entity identity over the
|
||||||
|
/// network).
|
||||||
|
uid_mapping: HashMap<Uid, Entity>,
|
||||||
|
|
||||||
pub fn allocate(&mut self, entity: Entity, id: Option<Uid>) -> Uid {
|
// Maps below are only used on the server.
|
||||||
let id = id.unwrap_or_else(|| {
|
/// Character IDs.
|
||||||
let id = self.next_id;
|
cid_mapping: HashMap<CharacterId, Entity>,
|
||||||
self.next_id += 1;
|
/// Rtsim Entities.
|
||||||
Uid(id)
|
rid_mapping: HashMap<RtsimEntity, Entity>,
|
||||||
});
|
|
||||||
self.mapping.insert(id, entity);
|
|
||||||
id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lookup_entity(&self, id: Uid) -> Option<Entity> { self.mapping.get(&id).copied() }
|
impl IdManager {
|
||||||
}
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
uid_mapping: HashMap::new(),
|
||||||
|
cid_mapping: HashMap::new(),
|
||||||
|
rid_mapping: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
/// Given a `Uid` retrieve the corresponding `Entity`
|
||||||
impl Default for UidAllocator {
|
pub fn uid_entity(&self, id: Uid) -> Option<Entity> { self.uid_mapping.get(&id).copied() }
|
||||||
fn default() -> Self { Self::new() }
|
|
||||||
|
/// Given a `CharacterId` retrieve the corresponding `Entity`
|
||||||
|
pub fn cid_entity(&self, id: CharacterId) -> Option<Entity> {
|
||||||
|
self.uid_mapping.get(&id).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given a `Uid` retrieve the corresponding `Entity`
|
||||||
|
pub fn rid_entity(&self, id: RtSimEntity) -> Option<Entity> {
|
||||||
|
self.uid_mapping.get(&id).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: This is only used on the client? Do we not remove on the server?
|
||||||
|
// NOTE: We need UID mapping also on the client but we don't need the other
|
||||||
|
// mappings on the client!
|
||||||
|
//
|
||||||
|
// Useful for when a single entity is deleted because it doesn't reconstruct the
|
||||||
|
// entire hashmap
|
||||||
|
pub fn remove_entity(
|
||||||
|
&mut self,
|
||||||
|
expected_entity: Option<Entity>,
|
||||||
|
uid: Uid,
|
||||||
|
cid: Option<CharacterId>,
|
||||||
|
rid: Option<RtsimEntity>,
|
||||||
|
) -> Option<Entity> {
|
||||||
|
#[cold]
|
||||||
|
#[inline(never)]
|
||||||
|
fn unexpected_entity<ID>() {
|
||||||
|
error!("{kind} mapped to an unexpected entity!");
|
||||||
|
}
|
||||||
|
#[cold]
|
||||||
|
#[inline(never)]
|
||||||
|
fn not_present<ID>() {
|
||||||
|
error!("{kind} 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 Some(expected) = expected && e != expected {
|
||||||
|
unexpected_entity::<ID>();
|
||||||
|
}
|
||||||
|
Some(e)
|
||||||
|
} else {
|
||||||
|
not_present::<ID>();
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let maybe_entity = remove(&mut self.uid_mapping, Some(uid), expected_entity);
|
||||||
|
let expected_entity = expected_entity.or(maybe_entity);
|
||||||
|
remove(&mut self.cid_mapping, cid, expected_entity);
|
||||||
|
remove(&mut self.rid_mapping, rid, expected_entity);
|
||||||
|
maybe_entity
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: we probably need separate methods here
|
||||||
|
// TODO: document what methods are called only on the client or the server
|
||||||
|
pub fn add_entity(
|
||||||
|
&mut self,
|
||||||
|
entity: Entity,
|
||||||
|
uid: Uid,
|
||||||
|
cid: Option<CharacterId>,
|
||||||
|
rid: Option<RtSimEntity>,
|
||||||
|
) {
|
||||||
|
#[cold]
|
||||||
|
#[inline(never)]
|
||||||
|
fn already_present<ID>() {
|
||||||
|
let kind = core::any::type_name::<ID>();
|
||||||
|
error!("{kind} 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) {
|
||||||
|
already_present::<ID>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
insert(&mut self.uid_mapping, uid, entity);
|
||||||
|
if let Some(cid) = cid {
|
||||||
|
insert(&mut self.cid_mapping, cid, entity);
|
||||||
|
}
|
||||||
|
if let Some(rid) = rid {
|
||||||
|
insert(&mut self.rid_mapping, rid, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allocate(&mut self, entity: Entity) -> Uid {
|
||||||
|
let id = id.unwrap_or_else(|| {
|
||||||
|
let id = self.next_uid;
|
||||||
|
self.next_uid += 1;
|
||||||
|
Uid(id)
|
||||||
|
});
|
||||||
|
// TODO: not sure we want to insert here?
|
||||||
|
self.uid_mapping.insert(id, entity);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for UidAllocator {
|
||||||
|
fn default() -> Self { Self::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for IdMaps {
|
||||||
|
fn default() -> Self { Self::new() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,12 +83,15 @@ pub fn handle_loaded_character_data(
|
|||||||
))),
|
))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
server
|
let result_msg = if let Err(err) = server.state.update_character_data(entity, loaded_components) {
|
||||||
.state
|
handle_exit_igname(entity, false); // remove client from in-game state
|
||||||
.update_character_data(entity, loaded_components);
|
ServerGeneral::CharacterDataLoadResult(Err(err))
|
||||||
sys::subscription::initialize_region_subscription(server.state.ecs(), entity);
|
} else {
|
||||||
// We notify the client with the metadata result from the operation.
|
sys::subscription::initialize_region_subscription(server.state.ecs(), entity);
|
||||||
server.notify_client(entity, ServerGeneral::CharacterDataLoadResult(Ok(metadata)));
|
// 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 {
|
pub fn handle_create_npc(server: &mut Server, pos: Pos, mut npc: NpcBuilder) -> EcsEntity {
|
||||||
@ -132,6 +135,7 @@ pub fn handle_create_npc(server: &mut Server, pos: Pos, mut npc: NpcBuilder) ->
|
|||||||
entity
|
entity
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: rtsim entity added here
|
||||||
let entity = if let Some(rtsim_entity) = npc.rtsim_entity {
|
let entity = if let Some(rtsim_entity) = npc.rtsim_entity {
|
||||||
entity.with(rtsim_entity).with(RepositionOnChunkLoad {
|
entity.with(rtsim_entity).with(RepositionOnChunkLoad {
|
||||||
needs_ground: false,
|
needs_ground: false,
|
||||||
|
@ -439,7 +439,7 @@ pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possessee_uid: Ui
|
|||||||
PresenceKind::Spectator => PresenceKind::Spectator,
|
PresenceKind::Spectator => PresenceKind::Spectator,
|
||||||
// This prevents persistence from overwriting original character info with stuff
|
// This prevents persistence from overwriting original character info with stuff
|
||||||
// from the new character.
|
// from the new character.
|
||||||
PresenceKind::Character(_) => {
|
PresenceKind::Character(_) | PresenceKind::LoadingCharacter(_) => {
|
||||||
delete_entity = Some(possessor);
|
delete_entity = Some(possessor);
|
||||||
PresenceKind::Possessor
|
PresenceKind::Possessor
|
||||||
},
|
},
|
||||||
|
@ -954,6 +954,8 @@ impl Server {
|
|||||||
active_abilities,
|
active_abilities,
|
||||||
map_marker,
|
map_marker,
|
||||||
);
|
);
|
||||||
|
// TODO: Does this need to be a server event? E.g. we could
|
||||||
|
// just handle it here.
|
||||||
ServerEvent::UpdateCharacterData {
|
ServerEvent::UpdateCharacterData {
|
||||||
entity: response.target_entity,
|
entity: response.target_entity,
|
||||||
components: character_data,
|
components: character_data,
|
||||||
@ -962,9 +964,8 @@ impl Server {
|
|||||||
},
|
},
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
// We failed to load data for the character from the DB. Notify
|
// We failed to load data for the character from the DB. Notify
|
||||||
// the client to push the
|
// the client to push the state back to character selection,
|
||||||
// state back to character selection, with the error
|
// with the error to display
|
||||||
// to display
|
|
||||||
self.notify_client(
|
self.notify_client(
|
||||||
response.target_entity,
|
response.target_entity,
|
||||||
ServerGeneral::CharacterDataLoadResult(Err(
|
ServerGeneral::CharacterDataLoadResult(Err(
|
||||||
|
@ -640,7 +640,7 @@ impl StateExt for State {
|
|||||||
|
|
||||||
self.write_component_ignore_entity_dead(
|
self.write_component_ignore_entity_dead(
|
||||||
entity,
|
entity,
|
||||||
Presence::new(view_distances, PresenceKind::Character(character_id)),
|
Presence::new(view_distances, PresenceKind::LoadingCharacter(character_id)),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Tell the client its request was successful.
|
// Tell the client its request was successful.
|
||||||
@ -675,7 +675,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 {
|
let PersistedComponents {
|
||||||
body,
|
body,
|
||||||
stats,
|
stats,
|
||||||
@ -687,7 +692,26 @@ impl StateExt for State {
|
|||||||
map_marker,
|
map_marker,
|
||||||
} = components;
|
} = components;
|
||||||
|
|
||||||
|
// TODO: CharacterId established here (update id map)
|
||||||
|
|
||||||
if let Some(player_uid) = self.read_component_copied::<Uid>(entity) {
|
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);
|
||||||
|
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}");
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
// Notify clients of a player list update
|
// Notify clients of a player list update
|
||||||
self.notify_players(ServerGeneral::PlayerListUpdate(
|
self.notify_players(ServerGeneral::PlayerListUpdate(
|
||||||
PlayerListUpdate::SelectedCharacter(player_uid, CharacterInfo {
|
PlayerListUpdate::SelectedCharacter(player_uid, CharacterInfo {
|
||||||
@ -768,7 +792,7 @@ impl StateExt for State {
|
|||||||
restore_pet(self.ecs(), pet_entity, entity, pet);
|
restore_pet(self.ecs(), pet_entity, entity, pet);
|
||||||
}
|
}
|
||||||
} else {
|
} 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>();
|
let presences = self.ecs().read_storage::<Presence>();
|
||||||
@ -801,6 +825,8 @@ impl StateExt for State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_chat_msg(
|
fn validate_chat_msg(
|
||||||
@ -1145,11 +1171,26 @@ impl StateExt for State {
|
|||||||
// Cancel extant trades
|
// Cancel extant trades
|
||||||
events::cancel_trades_for(self, entity);
|
events::cancel_trades_for(self, entity);
|
||||||
|
|
||||||
let (maybe_uid, maybe_pos) = (
|
let (maybe_uid, maybe_presence, maybe_rtsim_entity, maybe_pos) = (
|
||||||
self.ecs().read_storage::<Uid>().get(entity).copied(),
|
self.ecs().read_storage::<Uid>().get(entity).copied(),
|
||||||
|
self.ecs()
|
||||||
|
.read_storage::<Presence>()
|
||||||
|
.get(entity)
|
||||||
|
.map(|p| p.kind),
|
||||||
|
self.ecs()
|
||||||
|
.read_storage::<RtSimEntity>()
|
||||||
|
.get(entity)
|
||||||
|
.copied(),
|
||||||
self.ecs().read_storage::<comp::Pos>().get(entity).copied(),
|
self.ecs().read_storage::<comp::Pos>().get(entity).copied(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if let Some(uid) = a {
|
||||||
|
if self.ecs().write_resource::<IdMaps>()
|
||||||
|
.remove_entity().is_none()
|
||||||
|
} else {
|
||||||
|
warn!("Deleting entity without Uid component");
|
||||||
|
}
|
||||||
|
|
||||||
let res = self.ecs_mut().delete_entity(entity);
|
let res = self.ecs_mut().delete_entity(entity);
|
||||||
if res.is_ok() {
|
if res.is_ok() {
|
||||||
if let (Some(uid), Some(pos)) = (maybe_uid, maybe_pos) {
|
if let (Some(uid), Some(pos)) = (maybe_uid, maybe_pos) {
|
||||||
|
Loading…
Reference in New Issue
Block a user