mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Changed character deletion to go via batch update
This commit is contained in:
parent
fd34e48d15
commit
6eedc02286
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Загрузка персанажаў...
|
||||
char_selection-delete_permanently = Назаўжды выдаліць гэтага персанажа?
|
||||
char_selection-deleting_character = Выдаленне персанажа...
|
||||
char_selection-change_server = Змяніць сервер
|
||||
char_selection-enter_world = Увайсці ў свет
|
||||
char_selection-logout = Выхад
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Carregant personatges...
|
||||
char_selection-delete_permanently = Esborrar permanentment aquest Personatge?
|
||||
char_selection-deleting_character = Esborrant Personatge...
|
||||
char_selection-change_server = Canviar Servidor
|
||||
char_selection-enter_world = Entrar al Món
|
||||
char_selection-logout = Tancar Sessió
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Načítání postavy...
|
||||
char_selection-delete_permanently = Chcete smazat tuto postavu?
|
||||
char_selection-deleting_character = Probíhá mazání postavy...
|
||||
char_selection-change_server = Změnit server
|
||||
char_selection-enter_world = Vstup do světa
|
||||
char_selection-logout = Odhlásit
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Lade Charaktere...
|
||||
char_selection-delete_permanently = Den Charakter unwiderruflich löschen?
|
||||
char_selection-deleting_character = Lösche Charakter...
|
||||
char_selection-change_server = Wechsle Server
|
||||
char_selection-enter_world = Welt betreten
|
||||
char_selection-spectate = Welt betrachten
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Loading characters...
|
||||
char_selection-delete_permanently = Permanently delete this Character?
|
||||
char_selection-deleting_character = Deleting Character...
|
||||
char_selection-change_server = Change Server
|
||||
char_selection-enter_world = Enter World
|
||||
char_selection-spectate = Spectate World
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Cargando personajes...
|
||||
char_selection-delete_permanently = ¿Quieres eliminar a este personaje para siempre?
|
||||
char_selection-deleting_character = Eliminando personaje...
|
||||
char_selection-change_server = Cambiar de servidor
|
||||
char_selection-enter_world = Entrar al mundo
|
||||
char_selection-spectate = Observar mundo
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Cargando personajes...
|
||||
char_selection-delete_permanently = ¿Borrar este personaje permanentemente?
|
||||
char_selection-deleting_character = Borrando Personaje...
|
||||
char_selection-change_server = Cambiar Servidor
|
||||
char_selection-enter_world = Entrar al Mundo
|
||||
char_selection-spectate = Espectar Mundo
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Pertsonaiak kargatzen...
|
||||
char_selection-delete_permanently = Pertsonaia hau betiko ezabatu nahi duzu?
|
||||
char_selection-deleting_character = Pertsonaia ezabatzen...
|
||||
char_selection-change_server = Aldatu zerbitzaria
|
||||
char_selection-enter_world = Sartu munduan
|
||||
char_selection-spectate = Ikuskatu mundua
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Chargement des personnages...
|
||||
char_selection-delete_permanently = Supprimer définitivement ce personnage ?
|
||||
char_selection-deleting_character = Suppression du personnage...
|
||||
char_selection-change_server = Changer de serveur
|
||||
char_selection-enter_world = Rejoindre
|
||||
char_selection-spectate = Spectateur
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Karakterek betöltése...
|
||||
char_selection-delete_permanently = Végérvényesen törlöd ezt a karaktert?
|
||||
char_selection-deleting_character = Karakter törlése...
|
||||
char_selection-change_server = Váltás másik szerverre
|
||||
char_selection-enter_world = Világ betöltése
|
||||
char_selection-logout = Kijelentkezés
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Caricamento personaggi...
|
||||
char_selection-delete_permanently = Eliminare permanentemente questo personaggio?
|
||||
char_selection-deleting_character = Eliminazione personaggio...
|
||||
char_selection-change_server = Cambia server
|
||||
char_selection-enter_world = Entra nel mondo
|
||||
char_selection-spectate = Mondo spettatore
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = キャラクターをロード中...
|
||||
char_selection-delete_permanently = このキャラクターを永久に削除しますか?
|
||||
char_selection-deleting_character = キャラクターを削除中...
|
||||
char_selection-change_server = サーバーを変更
|
||||
char_selection-enter_world = 世界に入る
|
||||
char_selection-logout = ログアウト
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = 캐릭터를 불러오는 중...
|
||||
char_selection-delete_permanently = 이 캐릭터를 영구히 삭제하시겠습니까?
|
||||
char_selection-deleting_character = 캐릭터 삭제중...
|
||||
char_selection-change_server = 서버 바꾸기
|
||||
char_selection-enter_world = 세계 들어가기
|
||||
char_selection-spectate = 세계 관전하기
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Karakters Worden Geladen...
|
||||
char_selection-delete_permanently = Karakter Permanent Verwijderen?
|
||||
char_selection-deleting_character = Karakter Wordt Verwijderen...
|
||||
char_selection-change_server = Server Wisselen
|
||||
char_selection-enter_world = Wereld Betreden
|
||||
char_selection-logout = Uitloggen
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Laster inn karakterer...
|
||||
char_selection-delete_permanently = Slett denne karakteren permanent?
|
||||
char_selection-deleting_character = Sletter karakter...
|
||||
char_selection-change_server = Bytt server
|
||||
char_selection-enter_world = Gå inn i verden
|
||||
char_selection-logout = Logg ut
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Ładowanie Postaci...
|
||||
char_selection-delete_permanently = Czy na pewno chcesz usunąć tę Postać na zawsze?
|
||||
char_selection-deleting_character = Usuwanie Postaci...
|
||||
char_selection-change_server = Zmień Serwer
|
||||
char_selection-enter_world = Dołącz do Świata
|
||||
char_selection-spectate = Oglądnij świat
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Carregando Personagens...
|
||||
char_selection-delete_permanently = Excluir permanentemente este Personagem?
|
||||
char_selection-deleting_character = Excluindo Personagem...
|
||||
char_selection-change_server = Alterar Servidor
|
||||
char_selection-enter_world = Entrar no Mundo
|
||||
char_selection-spectate = Assistir Mundo
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Se încarcă Personajele...
|
||||
char_selection-delete_permanently = Vrei să ștergi acest Personaj pentru totdeauna?
|
||||
char_selection-deleting_character = Se șterge Personajul...
|
||||
char_selection-change_server = Schimbă Serverul
|
||||
char_selection-enter_world = Intră în lume
|
||||
char_selection-logout = Ieși din cont
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Загрузка персонажей...
|
||||
char_selection-delete_permanently = Навсегда удалить этого персонажа?
|
||||
char_selection-deleting_character = Удаление Персонажа...
|
||||
char_selection-change_server = Сменить сервер
|
||||
char_selection-enter_world = Войти в мир
|
||||
char_selection-spectate = Режим наблюдателя
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Учитавање ликова...
|
||||
char_selection-delete_permanently = Заувек обриши Лик?
|
||||
char_selection-deleting_character = Обриши Лик...
|
||||
char_selection-change_server = Промени Сервер
|
||||
char_selection-enter_world = Уђи у Свет
|
||||
char_selection-logout = Одјави се
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Laddar rollpersoner...
|
||||
char_selection-delete_permanently = Vill du radera rollpersonen permanent?
|
||||
char_selection-deleting_character = Raderar rollperson...
|
||||
char_selection-change_server = Byt server
|
||||
char_selection-enter_world = Öppna värld
|
||||
char_selection-spectate = Övervaka värld
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = อยู่ระหว่างการโหลดตัวละคร ...
|
||||
char_selection-delete_permanently = ลบตัวละครทิ้งหรือไม่?
|
||||
char_selection-deleting_character = อยู่ระหว่างการลบตัวละคร ...
|
||||
char_selection-change_server = เปลี่ยนเซิร์ฟเวอร์
|
||||
char_selection-enter_world = เข้าสู่โลก
|
||||
char_selection-spectate = ชมโลก
|
||||
|
@ -2,7 +2,6 @@ char_selection-loading_characters = Karakterler yükleniyor...
|
||||
char_selection-delete_permanently =
|
||||
Bu karakteri kalıcı olarak
|
||||
silmek istediğinden emin misin?
|
||||
char_selection-deleting_character = Karakter siliniyor...
|
||||
char_selection-change_server = Sunucu Değiştir
|
||||
char_selection-enter_world = Dünyaya Gir
|
||||
char_selection-logout = Çıkış yap
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Завантаження персонажів...
|
||||
char_selection-delete_permanently = Видалити цього персонажа назавжди?
|
||||
char_selection-deleting_character = Видалення персонажа...
|
||||
char_selection-change_server = Змінити сервер
|
||||
char_selection-enter_world = Увійти в світ
|
||||
char_selection-spectate = Режим Споглядача
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = Đang tải nhân vật...
|
||||
char_selection-delete_permanently = Xóa vĩnh viễn Nhân vật này?
|
||||
char_selection-deleting_character = Đang Xóa Nhân Vật...
|
||||
char_selection-change_server = Đổi Máy Chủ
|
||||
char_selection-enter_world = Vào Trò Chơi
|
||||
char_selection-logout = Đăng xuất
|
||||
|
@ -1,6 +1,5 @@
|
||||
char_selection-loading_characters = 加载人物中...
|
||||
char_selection-delete_permanently = 确定永久删除此角色?
|
||||
char_selection-deleting_character = 删除角色中...
|
||||
char_selection-change_server = 切换服务器
|
||||
char_selection-enter_world = 进入世界
|
||||
char_selection-spectate = 观察世界
|
||||
|
@ -962,7 +962,17 @@ impl Client {
|
||||
|
||||
/// Character deletion
|
||||
pub fn delete_character(&mut self, character_id: CharacterId) {
|
||||
self.character_list.loading = true;
|
||||
// Pre-emptively remove the character to be deleted from the character list as
|
||||
// character deletes are processed asynchronously by the server so we can't rely
|
||||
// on a timely response to update the character list
|
||||
if let Some(pos) = self
|
||||
.character_list
|
||||
.characters
|
||||
.iter()
|
||||
.position(|x| x.character.id == Some(character_id))
|
||||
{
|
||||
self.character_list.characters.remove(pos);
|
||||
}
|
||||
self.send_msg(ClientGeneral::DeleteCharacter(character_id));
|
||||
}
|
||||
|
||||
|
@ -233,6 +233,11 @@ pub enum ServerEvent {
|
||||
admin: comp::Admin,
|
||||
uuid: Uuid,
|
||||
},
|
||||
DeleteCharacter {
|
||||
entity: EcsEntity,
|
||||
requesting_player_uuid: String,
|
||||
character_id: CharacterId,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct EventBus<E> {
|
||||
|
@ -1,4 +1,7 @@
|
||||
use crate::{client::Client, persistence::PersistedComponents, sys, Server, StateExt};
|
||||
use crate::{
|
||||
client::Client, events::player::handle_exit_ingame, persistence::PersistedComponents, sys,
|
||||
CharacterUpdater, Server, StateExt,
|
||||
};
|
||||
use common::{
|
||||
character::CharacterId,
|
||||
comp::{
|
||||
@ -32,13 +35,23 @@ pub fn handle_initialize_character(
|
||||
character_id: CharacterId,
|
||||
requested_view_distances: ViewDistances,
|
||||
) {
|
||||
let clamped_vds = requested_view_distances.clamp(server.settings().max_view_distance);
|
||||
server
|
||||
.state
|
||||
.initialize_character_data(entity, character_id, clamped_vds);
|
||||
// Correct client if its requested VD is too high.
|
||||
if requested_view_distances.terrain != clamped_vds.terrain {
|
||||
server.notify_client(entity, ServerGeneral::SetViewDistance(clamped_vds.terrain));
|
||||
let updater = server.state.ecs().fetch::<CharacterUpdater>();
|
||||
let pending_database_action = updater.has_pending_database_action(character_id);
|
||||
drop(updater);
|
||||
|
||||
if !pending_database_action {
|
||||
let clamped_vds = requested_view_distances.clamp(server.settings().max_view_distance);
|
||||
server
|
||||
.state
|
||||
.initialize_character_data(entity, character_id, clamped_vds);
|
||||
// Correct client if its requested VD is too high.
|
||||
if requested_view_distances.terrain != clamped_vds.terrain {
|
||||
server.notify_client(entity, ServerGeneral::SetViewDistance(clamped_vds.terrain));
|
||||
}
|
||||
} 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.
|
||||
handle_exit_ingame(server, entity, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ use player::{handle_client_disconnect, handle_exit_ingame, handle_possess};
|
||||
use specs::{Builder, Entity as EcsEntity, WorldExt};
|
||||
use trade::handle_process_trade_action;
|
||||
|
||||
use crate::events::player::handle_character_delete;
|
||||
pub use group_manip::update_map_markers;
|
||||
pub(crate) use trade::cancel_trades_for;
|
||||
|
||||
@ -151,6 +152,11 @@ impl Server {
|
||||
ServerEvent::InitSpectator(entity, requested_view_distances) => {
|
||||
handle_initialize_spectator(self, entity, requested_view_distances)
|
||||
},
|
||||
ServerEvent::DeleteCharacter {
|
||||
entity,
|
||||
requesting_player_uuid,
|
||||
character_id,
|
||||
} => handle_character_delete(self, entity, requesting_player_uuid, character_id),
|
||||
ServerEvent::UpdateCharacterData {
|
||||
entity,
|
||||
components,
|
||||
@ -179,7 +185,7 @@ impl Server {
|
||||
handle_loaded_character_data(self, entity, components, metadata);
|
||||
},
|
||||
ServerEvent::ExitIngame { entity } => {
|
||||
handle_exit_ingame(self, entity);
|
||||
handle_exit_ingame(self, entity, false);
|
||||
},
|
||||
ServerEvent::CreateNpc {
|
||||
pos,
|
||||
|
@ -4,6 +4,7 @@ use crate::{
|
||||
presence::Presence, state_ext::StateExt, BattleModeBuffer, Server,
|
||||
};
|
||||
use common::{
|
||||
character::CharacterId,
|
||||
comp,
|
||||
comp::{group, pet::is_tameable},
|
||||
uid::{Uid, UidAllocator},
|
||||
@ -14,13 +15,45 @@ use common_state::State;
|
||||
use specs::{saveload::MarkerAllocator, Builder, Entity as EcsEntity, Join, WorldExt};
|
||||
use tracing::{debug, error, trace, warn, Instrument};
|
||||
|
||||
pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) {
|
||||
pub fn handle_character_delete(
|
||||
server: &mut Server,
|
||||
entity: EcsEntity,
|
||||
requesting_player_uuid: String,
|
||||
character_id: CharacterId,
|
||||
) {
|
||||
// Can't process a character delete for a player that has an in-game presence,
|
||||
// so kick them out before processing the delete.
|
||||
// NOTE: This relies on StateExt::handle_initialize_character adding the
|
||||
// Presence component when a character is initialized to detect whether a client
|
||||
// is in-game.
|
||||
let has_presence = {
|
||||
let presences = server.state.ecs().read_storage::<Presence>();
|
||||
presences.get(entity).is_some()
|
||||
};
|
||||
if has_presence {
|
||||
warn!(
|
||||
?requesting_player_uuid,
|
||||
?character_id,
|
||||
"Character delete received while in-game, disconnecting client."
|
||||
);
|
||||
handle_exit_ingame(server, entity, true);
|
||||
}
|
||||
|
||||
let mut updater = server.state.ecs().fetch_mut::<CharacterUpdater>();
|
||||
updater.queue_character_deletion(requesting_player_uuid, character_id);
|
||||
}
|
||||
|
||||
pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity, skip_persistence: bool) {
|
||||
span!(_guard, "handle_exit_ingame");
|
||||
let state = server.state_mut();
|
||||
|
||||
// Sync the player's character data to the database. This must be done before
|
||||
// removing any components from the entity
|
||||
let entity = persist_entity(state, entity);
|
||||
let entity = if !skip_persistence {
|
||||
persist_entity(state, entity)
|
||||
} else {
|
||||
entity
|
||||
};
|
||||
|
||||
// Create new entity with just `Client`, `Uid`, `Player`, and `...Stream`
|
||||
// components.
|
||||
@ -264,17 +297,15 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity {
|
||||
})
|
||||
.collect();
|
||||
|
||||
character_updater.add_pending_logout_update(
|
||||
character_updater.add_pending_logout_update((
|
||||
char_id,
|
||||
(
|
||||
skill_set.clone(),
|
||||
inventory.clone(),
|
||||
pets,
|
||||
waypoint,
|
||||
active_abilities.clone(),
|
||||
map_marker,
|
||||
),
|
||||
);
|
||||
skill_set.clone(),
|
||||
inventory.clone(),
|
||||
pets,
|
||||
waypoint,
|
||||
active_abilities.clone(),
|
||||
map_marker,
|
||||
));
|
||||
},
|
||||
PresenceKind::Spectator => { /* Do nothing, spectators do not need persisting */ },
|
||||
PresenceKind::Possessor => { /* Do nothing, possessor's are not persisted */ },
|
||||
|
@ -9,6 +9,7 @@
|
||||
option_zip,
|
||||
unwrap_infallible
|
||||
)]
|
||||
#![feature(hash_drain_filter)]
|
||||
|
||||
pub mod automod;
|
||||
mod character_creator;
|
||||
@ -92,7 +93,7 @@ use common_systems::add_local_systems;
|
||||
use metrics::{EcsSystemMetrics, PhysicsMetrics, TickMetrics};
|
||||
use network::{ListenAddr, Network, Pid};
|
||||
use persistence::{
|
||||
character_loader::{CharacterLoader, CharacterLoaderResponseKind},
|
||||
character_loader::{CharacterLoader, CharacterUpdaterMessage},
|
||||
character_updater::CharacterUpdater,
|
||||
};
|
||||
use prometheus::Registry;
|
||||
@ -125,6 +126,7 @@ use {
|
||||
common_state::plugin::{memory_manager::EcsWorld, PluginMgr},
|
||||
};
|
||||
|
||||
use crate::persistence::character_loader::CharacterScreenResponseKind;
|
||||
use common::comp::Anchor;
|
||||
#[cfg(feature = "worldgen")]
|
||||
use world::{
|
||||
@ -834,104 +836,115 @@ impl Server {
|
||||
|
||||
let character_loader = self.state.ecs().read_resource::<CharacterLoader>();
|
||||
|
||||
let character_updater = self.state.ecs().read_resource::<CharacterUpdater>();
|
||||
let mut character_updater = self.state.ecs().write_resource::<CharacterUpdater>();
|
||||
let updater_messages: Vec<CharacterUpdaterMessage> = character_updater.messages().collect();
|
||||
|
||||
// Get character-related database responses and notify the requesting client
|
||||
character_loader
|
||||
.messages()
|
||||
.chain(character_updater.messages())
|
||||
.for_each(|query_result| match query_result.result {
|
||||
CharacterLoaderResponseKind::CharacterList(result) => match result {
|
||||
Ok(character_list_data) => self.notify_client(
|
||||
query_result.entity,
|
||||
ServerGeneral::CharacterListUpdate(character_list_data),
|
||||
),
|
||||
Err(error) => self.notify_client(
|
||||
query_result.entity,
|
||||
ServerGeneral::CharacterActionError(error.to_string()),
|
||||
),
|
||||
.chain(updater_messages)
|
||||
.for_each(|message| match message {
|
||||
CharacterUpdaterMessage::DatabaseBatchCompletion(batch_id) => {
|
||||
character_updater.process_batch_completion(batch_id);
|
||||
},
|
||||
CharacterLoaderResponseKind::CharacterCreation(result) => match result {
|
||||
Ok((character_id, list)) => {
|
||||
self.notify_client(
|
||||
query_result.entity,
|
||||
ServerGeneral::CharacterListUpdate(list),
|
||||
);
|
||||
self.notify_client(
|
||||
query_result.entity,
|
||||
ServerGeneral::CharacterCreated(character_id),
|
||||
);
|
||||
},
|
||||
Err(error) => self.notify_client(
|
||||
query_result.entity,
|
||||
ServerGeneral::CharacterActionError(error.to_string()),
|
||||
),
|
||||
},
|
||||
CharacterLoaderResponseKind::CharacterEdit(result) => match result {
|
||||
Ok((character_id, list)) => {
|
||||
self.notify_client(
|
||||
query_result.entity,
|
||||
ServerGeneral::CharacterListUpdate(list),
|
||||
);
|
||||
self.notify_client(
|
||||
query_result.entity,
|
||||
ServerGeneral::CharacterEdited(character_id),
|
||||
);
|
||||
},
|
||||
Err(error) => self.notify_client(
|
||||
query_result.entity,
|
||||
ServerGeneral::CharacterActionError(error.to_string()),
|
||||
),
|
||||
},
|
||||
CharacterLoaderResponseKind::CharacterData(result) => {
|
||||
let message = match *result {
|
||||
Ok((character_data, skill_set_persistence_load_error)) => {
|
||||
let PersistedComponents {
|
||||
body,
|
||||
stats,
|
||||
skill_set,
|
||||
inventory,
|
||||
waypoint,
|
||||
pets,
|
||||
active_abilities,
|
||||
map_marker,
|
||||
} = character_data;
|
||||
let character_data = (
|
||||
body,
|
||||
stats,
|
||||
skill_set,
|
||||
inventory,
|
||||
waypoint,
|
||||
pets,
|
||||
active_abilities,
|
||||
map_marker,
|
||||
);
|
||||
ServerEvent::UpdateCharacterData {
|
||||
entity: query_result.entity,
|
||||
components: character_data,
|
||||
metadata: skill_set_persistence_load_error,
|
||||
}
|
||||
CharacterUpdaterMessage::CharacterScreenResponse(response) => {
|
||||
match response.response_kind {
|
||||
CharacterScreenResponseKind::CharacterList(result) => match result {
|
||||
Ok(character_list_data) => self.notify_client(
|
||||
response.target_entity,
|
||||
ServerGeneral::CharacterListUpdate(character_list_data),
|
||||
),
|
||||
Err(error) => self.notify_client(
|
||||
response.target_entity,
|
||||
ServerGeneral::CharacterActionError(error.to_string()),
|
||||
),
|
||||
},
|
||||
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
|
||||
self.notify_client(
|
||||
query_result.entity,
|
||||
ServerGeneral::CharacterDataLoadResult(Err(error.to_string())),
|
||||
);
|
||||
|
||||
// Clean up the entity data on the server
|
||||
ServerEvent::ExitIngame {
|
||||
entity: query_result.entity,
|
||||
}
|
||||
CharacterScreenResponseKind::CharacterCreation(result) => match result {
|
||||
Ok((character_id, list)) => {
|
||||
self.notify_client(
|
||||
response.target_entity,
|
||||
ServerGeneral::CharacterListUpdate(list),
|
||||
);
|
||||
self.notify_client(
|
||||
response.target_entity,
|
||||
ServerGeneral::CharacterCreated(character_id),
|
||||
);
|
||||
},
|
||||
Err(error) => self.notify_client(
|
||||
response.target_entity,
|
||||
ServerGeneral::CharacterActionError(error.to_string()),
|
||||
),
|
||||
},
|
||||
};
|
||||
CharacterScreenResponseKind::CharacterEdit(result) => match result {
|
||||
Ok((character_id, list)) => {
|
||||
self.notify_client(
|
||||
response.target_entity,
|
||||
ServerGeneral::CharacterListUpdate(list),
|
||||
);
|
||||
self.notify_client(
|
||||
response.target_entity,
|
||||
ServerGeneral::CharacterEdited(character_id),
|
||||
);
|
||||
},
|
||||
Err(error) => self.notify_client(
|
||||
response.target_entity,
|
||||
ServerGeneral::CharacterActionError(error.to_string()),
|
||||
),
|
||||
},
|
||||
CharacterScreenResponseKind::CharacterData(result) => {
|
||||
let message = match *result {
|
||||
Ok((character_data, skill_set_persistence_load_error)) => {
|
||||
let PersistedComponents {
|
||||
body,
|
||||
stats,
|
||||
skill_set,
|
||||
inventory,
|
||||
waypoint,
|
||||
pets,
|
||||
active_abilities,
|
||||
map_marker,
|
||||
} = character_data;
|
||||
let character_data = (
|
||||
body,
|
||||
stats,
|
||||
skill_set,
|
||||
inventory,
|
||||
waypoint,
|
||||
pets,
|
||||
active_abilities,
|
||||
map_marker,
|
||||
);
|
||||
ServerEvent::UpdateCharacterData {
|
||||
entity: response.target_entity,
|
||||
components: character_data,
|
||||
metadata: skill_set_persistence_load_error,
|
||||
}
|
||||
},
|
||||
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
|
||||
self.notify_client(
|
||||
response.target_entity,
|
||||
ServerGeneral::CharacterDataLoadResult(Err(
|
||||
error.to_string()
|
||||
)),
|
||||
);
|
||||
|
||||
self.state
|
||||
.ecs()
|
||||
.read_resource::<EventBus<ServerEvent>>()
|
||||
.emit_now(message);
|
||||
// Clean up the entity data on the server
|
||||
ServerEvent::ExitIngame {
|
||||
entity: response.target_entity,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
self.state
|
||||
.ecs()
|
||||
.read_resource::<EventBus<ServerEvent>>()
|
||||
.emit_now(message);
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -588,12 +588,14 @@ pub fn edit_character(
|
||||
char_list.map(|list| (character_id, list))
|
||||
}
|
||||
|
||||
/// Delete a character. Returns the updated character list.
|
||||
/// Permanently deletes a character
|
||||
pub fn delete_character(
|
||||
requesting_player_uuid: &str,
|
||||
char_id: CharacterId,
|
||||
transaction: &mut Transaction,
|
||||
) -> CharacterListResult {
|
||||
) -> Result<(), PersistenceError> {
|
||||
debug!(?requesting_player_uuid, ?char_id, "Deleting character");
|
||||
|
||||
let mut stmt = transaction.prepare_cached(
|
||||
"
|
||||
SELECT COUNT(1)
|
||||
@ -609,9 +611,9 @@ pub fn delete_character(
|
||||
drop(stmt);
|
||||
|
||||
if result != 1 {
|
||||
return Err(PersistenceError::OtherError(
|
||||
"Requested character to delete does not belong to the requesting player".to_string(),
|
||||
));
|
||||
// The character does not exist, or does not belong to the requesting player so
|
||||
// silently drop the request.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Delete skill groups
|
||||
@ -698,7 +700,7 @@ pub fn delete_character(
|
||||
)));
|
||||
}
|
||||
|
||||
load_character_list(requesting_player_uuid, transaction)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Before creating a character, we ensure that the limit on the number of
|
||||
|
@ -31,34 +31,39 @@ enum CharacterLoaderRequestKind {
|
||||
},
|
||||
}
|
||||
|
||||
/// Wrapper for results for character actions. Can be a list of
|
||||
/// characters, or component data belonging to an individual character
|
||||
#[derive(Debug)]
|
||||
pub enum CharacterLoaderResponseKind {
|
||||
pub enum CharacterUpdaterMessage {
|
||||
CharacterScreenResponse(CharacterScreenResponse),
|
||||
DatabaseBatchCompletion(u64),
|
||||
}
|
||||
|
||||
/// An event emitted from CharacterUpdater in response to a request made from
|
||||
/// the character selection/editing screen
|
||||
#[derive(Debug)]
|
||||
pub struct CharacterScreenResponse {
|
||||
pub target_entity: specs::Entity,
|
||||
pub response_kind: CharacterScreenResponseKind,
|
||||
}
|
||||
|
||||
impl CharacterScreenResponse {
|
||||
pub fn is_err(&self) -> bool {
|
||||
matches!(
|
||||
&self.response_kind,
|
||||
CharacterScreenResponseKind::CharacterData(box Err(_))
|
||||
| CharacterScreenResponseKind::CharacterList(Err(_))
|
||||
| CharacterScreenResponseKind::CharacterCreation(Err(_))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CharacterScreenResponseKind {
|
||||
CharacterList(CharacterListResult),
|
||||
CharacterData(Box<CharacterDataResult>),
|
||||
CharacterCreation(CharacterCreationResult),
|
||||
CharacterEdit(CharacterEditResult),
|
||||
}
|
||||
|
||||
/// Common message format dispatched in response to an update request
|
||||
#[derive(Debug)]
|
||||
pub struct CharacterLoaderResponse {
|
||||
pub entity: specs::Entity,
|
||||
pub result: CharacterLoaderResponseKind,
|
||||
}
|
||||
|
||||
impl CharacterLoaderResponse {
|
||||
pub fn is_err(&self) -> bool {
|
||||
matches!(
|
||||
&self.result,
|
||||
CharacterLoaderResponseKind::CharacterData(box Err(_))
|
||||
| CharacterLoaderResponseKind::CharacterList(Err(_))
|
||||
| CharacterLoaderResponseKind::CharacterCreation(Err(_))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A bi-directional messaging resource for making requests to modify or load
|
||||
/// character data in a background thread.
|
||||
///
|
||||
@ -72,14 +77,14 @@ impl CharacterLoaderResponse {
|
||||
/// Responses are polled on each server tick in the format
|
||||
/// [`CharacterLoaderResponse`]
|
||||
pub struct CharacterLoader {
|
||||
update_rx: crossbeam_channel::Receiver<CharacterLoaderResponse>,
|
||||
update_rx: crossbeam_channel::Receiver<CharacterUpdaterMessage>,
|
||||
update_tx: crossbeam_channel::Sender<CharacterLoaderRequest>,
|
||||
}
|
||||
|
||||
impl CharacterLoader {
|
||||
pub fn new(settings: Arc<RwLock<DatabaseSettings>>) -> Result<Self, PersistenceError> {
|
||||
let (update_tx, internal_rx) = crossbeam_channel::unbounded::<CharacterLoaderRequest>();
|
||||
let (internal_tx, update_rx) = crossbeam_channel::unbounded::<CharacterLoaderResponse>();
|
||||
let (internal_tx, update_rx) = crossbeam_channel::unbounded::<CharacterUpdaterMessage>();
|
||||
|
||||
let builder = std::thread::Builder::new().name("persistence_loader".into());
|
||||
builder
|
||||
@ -115,13 +120,13 @@ impl CharacterLoader {
|
||||
fn process_request(
|
||||
request: CharacterLoaderRequest,
|
||||
connection: &Connection,
|
||||
) -> CharacterLoaderResponse {
|
||||
) -> CharacterUpdaterMessage {
|
||||
let (entity, kind) = request;
|
||||
CharacterLoaderResponse {
|
||||
entity,
|
||||
result: match kind {
|
||||
CharacterUpdaterMessage::CharacterScreenResponse(CharacterScreenResponse {
|
||||
target_entity: entity,
|
||||
response_kind: match kind {
|
||||
CharacterLoaderRequestKind::LoadCharacterList { player_uuid } => {
|
||||
CharacterLoaderResponseKind::CharacterList(load_character_list(
|
||||
CharacterScreenResponseKind::CharacterList(load_character_list(
|
||||
&player_uuid,
|
||||
connection,
|
||||
))
|
||||
@ -137,10 +142,10 @@ impl CharacterLoader {
|
||||
"Error loading character data for character_id: {}", character_id
|
||||
);
|
||||
}
|
||||
CharacterLoaderResponseKind::CharacterData(Box::new(result))
|
||||
CharacterScreenResponseKind::CharacterData(Box::new(result))
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Loads a list of characters belonging to the player identified by
|
||||
@ -175,5 +180,5 @@ impl CharacterLoader {
|
||||
}
|
||||
|
||||
/// Returns a non-blocking iterator over CharacterLoaderResponse messages
|
||||
pub fn messages(&self) -> TryIter<CharacterLoaderResponse> { self.update_rx.try_iter() }
|
||||
pub fn messages(&self) -> TryIter<CharacterUpdaterMessage> { self.update_rx.try_iter() }
|
||||
}
|
||||
|
@ -2,13 +2,15 @@ use crate::comp;
|
||||
use common::character::CharacterId;
|
||||
|
||||
use crate::persistence::{
|
||||
character_loader::{CharacterLoaderResponse, CharacterLoaderResponseKind},
|
||||
character_loader::{
|
||||
CharacterScreenResponse, CharacterScreenResponseKind, CharacterUpdaterMessage,
|
||||
},
|
||||
error::PersistenceError,
|
||||
establish_connection, ConnectionMode, DatabaseSettings, EditableComponents,
|
||||
PersistedComponents, VelorenConnection,
|
||||
};
|
||||
use crossbeam_channel::TryIter;
|
||||
use rusqlite::{DropBehavior, Transaction};
|
||||
use rusqlite::DropBehavior;
|
||||
use specs::Entity;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@ -20,6 +22,7 @@ use std::{
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
|
||||
pub type CharacterUpdateData = (
|
||||
CharacterId,
|
||||
comp::SkillSet,
|
||||
comp::Inventory,
|
||||
Vec<PetPersistenceData>,
|
||||
@ -31,8 +34,11 @@ pub type CharacterUpdateData = (
|
||||
pub type PetPersistenceData = (comp::Pet, comp::Body, comp::Stats);
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum CharacterUpdaterEvent {
|
||||
BatchUpdate(Vec<(CharacterId, CharacterUpdateData)>),
|
||||
enum CharacterUpdaterAction {
|
||||
BatchUpdate {
|
||||
batch_id: u64,
|
||||
updates: Vec<DatabaseActionKind>,
|
||||
},
|
||||
CreateCharacter {
|
||||
entity: Entity,
|
||||
player_uuid: String,
|
||||
@ -46,12 +52,34 @@ pub enum CharacterUpdaterEvent {
|
||||
character_alias: String,
|
||||
editable_components: EditableComponents,
|
||||
},
|
||||
DisconnectedSuccess,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum DatabaseAction {
|
||||
New(DatabaseActionKind),
|
||||
Submitted { batch_id: u64 },
|
||||
}
|
||||
|
||||
impl DatabaseAction {
|
||||
fn take_new(&mut self, batch_id: u64) -> Option<DatabaseActionKind> {
|
||||
match core::mem::replace(self, Self::Submitted { batch_id }) {
|
||||
Self::New(action) => Some(action),
|
||||
submitted @ Self::Submitted { .. } => {
|
||||
*self = submitted; // restore old batch_id
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum DatabaseActionKind {
|
||||
UpdateCharacter(Box<CharacterUpdateData>),
|
||||
DeleteCharacter {
|
||||
entity: Entity,
|
||||
requesting_player_uuid: String,
|
||||
character_id: CharacterId,
|
||||
},
|
||||
DisconnectedSuccess,
|
||||
}
|
||||
|
||||
/// A unidirectional messaging resource for saving characters in a
|
||||
@ -60,19 +88,22 @@ pub enum CharacterUpdaterEvent {
|
||||
/// This is used to make updates to a character and their persisted components,
|
||||
/// such as inventory, loadout, etc...
|
||||
pub struct CharacterUpdater {
|
||||
update_tx: Option<crossbeam_channel::Sender<CharacterUpdaterEvent>>,
|
||||
response_rx: crossbeam_channel::Receiver<CharacterLoaderResponse>,
|
||||
update_tx: Option<crossbeam_channel::Sender<CharacterUpdaterAction>>,
|
||||
response_rx: crossbeam_channel::Receiver<CharacterUpdaterMessage>,
|
||||
handle: Option<std::thread::JoinHandle<()>>,
|
||||
pending_logout_updates: HashMap<CharacterId, CharacterUpdateData>,
|
||||
/// Pending actions to be performed during the next persistence batch, such
|
||||
/// as updates for recently logged out players and character deletions
|
||||
pending_database_actions: HashMap<CharacterId, DatabaseAction>,
|
||||
/// Will disconnect all characters (without persistence) on the next tick if
|
||||
/// set to true
|
||||
disconnect_all_clients_requested: Arc<AtomicBool>,
|
||||
last_pending_database_event_id: u64,
|
||||
}
|
||||
|
||||
impl CharacterUpdater {
|
||||
pub fn new(settings: Arc<RwLock<DatabaseSettings>>) -> rusqlite::Result<Self> {
|
||||
let (update_tx, update_rx) = crossbeam_channel::unbounded::<CharacterUpdaterEvent>();
|
||||
let (response_tx, response_rx) = crossbeam_channel::unbounded::<CharacterLoaderResponse>();
|
||||
let (update_tx, update_rx) = crossbeam_channel::unbounded::<CharacterUpdaterAction>();
|
||||
let (response_tx, response_rx) = crossbeam_channel::unbounded::<CharacterUpdaterMessage>();
|
||||
|
||||
let disconnect_all_clients_requested = Arc::new(AtomicBool::new(false));
|
||||
let disconnect_all_clients_requested_clone = Arc::clone(&disconnect_all_clients_requested);
|
||||
@ -84,9 +115,9 @@ impl CharacterUpdater {
|
||||
// taken that could cause the RwLock to become poisoned.
|
||||
let mut conn =
|
||||
establish_connection(&settings.read().unwrap(), ConnectionMode::ReadWrite);
|
||||
while let Ok(updates) = update_rx.recv() {
|
||||
match updates {
|
||||
CharacterUpdaterEvent::BatchUpdate(updates) => {
|
||||
while let Ok(action) = update_rx.recv() {
|
||||
match action {
|
||||
CharacterUpdaterAction::BatchUpdate { batch_id, updates } => {
|
||||
if disconnect_all_clients_requested_clone.load(Ordering::Relaxed) {
|
||||
debug!(
|
||||
"Skipping persistence due to pending disconnection of all \
|
||||
@ -95,17 +126,29 @@ impl CharacterUpdater {
|
||||
continue;
|
||||
}
|
||||
conn.update_log_mode(&settings);
|
||||
if let Err(e) = execute_batch_update(updates, &mut conn) {
|
||||
|
||||
if let Err(e) = execute_batch_update(updates.into_iter(), &mut conn) {
|
||||
error!(
|
||||
?e,
|
||||
"Error during character batch update, disconnecting all \
|
||||
clients to avoid loss of data integrity. Error: {:?}",
|
||||
e
|
||||
clients to avoid loss of data integrity."
|
||||
);
|
||||
disconnect_all_clients_requested_clone
|
||||
.store(true, Ordering::Relaxed);
|
||||
};
|
||||
|
||||
if let Err(e) = response_tx
|
||||
.send(CharacterUpdaterMessage::DatabaseBatchCompletion(batch_id))
|
||||
{
|
||||
error!(?e, "Could not send DatabaseBatchCompletion message");
|
||||
} else {
|
||||
debug!(
|
||||
"Submitted DatabaseBatchCompletion - Batch ID: {}",
|
||||
batch_id
|
||||
);
|
||||
}
|
||||
},
|
||||
CharacterUpdaterEvent::CreateCharacter {
|
||||
CharacterUpdaterAction::CreateCharacter {
|
||||
entity,
|
||||
character_alias,
|
||||
player_uuid,
|
||||
@ -134,7 +177,7 @@ impl CharacterUpdater {
|
||||
),
|
||||
}
|
||||
},
|
||||
CharacterUpdaterEvent::EditCharacter {
|
||||
CharacterUpdaterAction::EditCharacter {
|
||||
entity,
|
||||
character_id,
|
||||
character_alias,
|
||||
@ -165,34 +208,7 @@ impl CharacterUpdater {
|
||||
),
|
||||
}
|
||||
},
|
||||
CharacterUpdaterEvent::DeleteCharacter {
|
||||
entity,
|
||||
requesting_player_uuid,
|
||||
character_id,
|
||||
} => {
|
||||
match execute_character_delete(
|
||||
entity,
|
||||
&requesting_player_uuid,
|
||||
character_id,
|
||||
&mut conn,
|
||||
) {
|
||||
Ok(response) => {
|
||||
if let Err(e) = response_tx.send(response) {
|
||||
error!(?e, "Could not send character deletion response");
|
||||
} else {
|
||||
debug!(
|
||||
"Processed character delete for character ID {}",
|
||||
character_id
|
||||
);
|
||||
}
|
||||
},
|
||||
Err(e) => error!(
|
||||
"Error deleting character ID {}, error: {:?}",
|
||||
character_id, e
|
||||
),
|
||||
}
|
||||
},
|
||||
CharacterUpdaterEvent::DisconnectedSuccess => {
|
||||
CharacterUpdaterAction::DisconnectedSuccess => {
|
||||
info!(
|
||||
"CharacterUpdater received DisconnectedSuccess event, resuming \
|
||||
batch updates"
|
||||
@ -210,37 +226,56 @@ impl CharacterUpdater {
|
||||
update_tx: Some(update_tx),
|
||||
response_rx,
|
||||
handle: Some(handle),
|
||||
pending_logout_updates: HashMap::new(),
|
||||
pending_database_actions: HashMap::new(),
|
||||
disconnect_all_clients_requested,
|
||||
last_pending_database_event_id: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Adds a character to the list of characters that have recently logged out
|
||||
/// and will be persisted in the next batch update.
|
||||
pub fn add_pending_logout_update(
|
||||
&mut self,
|
||||
character_id: CharacterId,
|
||||
update_data: CharacterUpdateData,
|
||||
) {
|
||||
if !self
|
||||
pub fn add_pending_logout_update(&mut self, update_data: CharacterUpdateData) {
|
||||
if self
|
||||
.disconnect_all_clients_requested
|
||||
.load(Ordering::Relaxed)
|
||||
{
|
||||
self.pending_logout_updates
|
||||
.insert(character_id, update_data);
|
||||
} else {
|
||||
warn!(
|
||||
"Ignoring request to add pending logout update for character ID {} as there is a \
|
||||
disconnection of all clients in progress",
|
||||
character_id
|
||||
update_data.0
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if self.pending_database_actions.contains_key(&update_data.0) {
|
||||
warn!(
|
||||
"Ignoring request to add pending logout update for character ID {} as there is \
|
||||
already a pending delete for this character",
|
||||
update_data.0
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
self.pending_database_actions.insert(
|
||||
update_data.0, // CharacterId
|
||||
DatabaseAction::New(DatabaseActionKind::UpdateCharacter(Box::new(update_data))),
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the character IDs of characters that have recently logged out
|
||||
/// and are awaiting persistence in the next batch update.
|
||||
pub fn characters_pending_logout(&self) -> impl Iterator<Item = CharacterId> + '_ {
|
||||
self.pending_logout_updates.keys().copied()
|
||||
pub fn has_pending_database_action(&self, character_id: CharacterId) -> bool {
|
||||
self.pending_database_actions.get(&character_id).is_some()
|
||||
}
|
||||
|
||||
pub fn process_batch_completion(&mut self, completed_batch_id: u64) {
|
||||
self.pending_database_actions.drain_filter(|_, event| {
|
||||
matches!(event, DatabaseAction::Submitted {
|
||||
batch_id,
|
||||
} if completed_batch_id == *batch_id)
|
||||
});
|
||||
debug!(
|
||||
"Processed database batch completion - Batch ID: {}",
|
||||
completed_batch_id
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a value indicating whether there is a pending request to
|
||||
@ -261,7 +296,7 @@ impl CharacterUpdater {
|
||||
self.update_tx
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send(CharacterUpdaterEvent::CreateCharacter {
|
||||
.send(CharacterUpdaterAction::CreateCharacter {
|
||||
entity,
|
||||
player_uuid: requesting_player_uuid,
|
||||
character_alias: alias,
|
||||
@ -284,7 +319,7 @@ impl CharacterUpdater {
|
||||
self.update_tx
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send(CharacterUpdaterEvent::EditCharacter {
|
||||
.send(CharacterUpdaterAction::EditCharacter {
|
||||
entity,
|
||||
player_uuid: requesting_player_uuid,
|
||||
character_id,
|
||||
@ -296,81 +331,64 @@ impl CharacterUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_character(
|
||||
fn next_pending_database_event_id(&mut self) -> u64 {
|
||||
self.last_pending_database_event_id += 1;
|
||||
self.last_pending_database_event_id
|
||||
}
|
||||
|
||||
pub fn queue_character_deletion(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
requesting_player_uuid: String,
|
||||
character_id: CharacterId,
|
||||
) {
|
||||
if let Err(e) =
|
||||
self.update_tx
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send(CharacterUpdaterEvent::DeleteCharacter {
|
||||
entity,
|
||||
requesting_player_uuid,
|
||||
character_id,
|
||||
})
|
||||
{
|
||||
error!(?e, "Could not send character deletion request");
|
||||
} else {
|
||||
// Once a delete request has been sent to the channel we must remove any pending
|
||||
// updates for the character in the event that it has recently logged out.
|
||||
// Since the user has actively chosen to delete the character there is no value
|
||||
// in the pending update data anyway.
|
||||
self.pending_logout_updates.remove(&character_id);
|
||||
}
|
||||
// Insert the delete as a pending database action - if the player has recently
|
||||
// logged out this will replace their pending update with a delete which
|
||||
// is fine, as the user has actively chosen to delete the character.
|
||||
self.pending_database_actions.insert(
|
||||
character_id,
|
||||
DatabaseAction::New(DatabaseActionKind::DeleteCharacter {
|
||||
requesting_player_uuid,
|
||||
character_id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a collection of characters based on their id and components
|
||||
pub fn batch_update<'a>(
|
||||
&mut self,
|
||||
updates: impl Iterator<
|
||||
Item = (
|
||||
CharacterId,
|
||||
&'a comp::SkillSet,
|
||||
&'a comp::Inventory,
|
||||
Vec<PetPersistenceData>,
|
||||
Option<&'a comp::Waypoint>,
|
||||
&'a comp::ability::ActiveAbilities,
|
||||
Option<&'a comp::MapMarker>,
|
||||
),
|
||||
>,
|
||||
) {
|
||||
let updates = updates
|
||||
.map(
|
||||
|(
|
||||
character_id,
|
||||
skill_set,
|
||||
inventory,
|
||||
pets,
|
||||
waypoint,
|
||||
active_abilities,
|
||||
map_marker,
|
||||
)| {
|
||||
(
|
||||
character_id,
|
||||
(
|
||||
skill_set.clone(),
|
||||
inventory.clone(),
|
||||
pets,
|
||||
waypoint.cloned(),
|
||||
active_abilities.clone(),
|
||||
map_marker.cloned(),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
.chain(self.pending_logout_updates.drain())
|
||||
.collect::<Vec<_>>();
|
||||
pub fn batch_update(&mut self, updates: impl Iterator<Item = CharacterUpdateData>) {
|
||||
let batch_id = self.next_pending_database_event_id();
|
||||
|
||||
if let Err(e) = self
|
||||
.update_tx
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send(CharacterUpdaterEvent::BatchUpdate(updates))
|
||||
{
|
||||
error!(?e, "Could not send stats updates");
|
||||
// Collect any new updates, ignoring updates from a previous update that are
|
||||
// still pending completion
|
||||
let existing_pending_actions = self
|
||||
.pending_database_actions
|
||||
.iter_mut()
|
||||
.filter_map(|(_, event)| event.take_new(batch_id));
|
||||
|
||||
// Combine the pending actions with the updates for logged in characters
|
||||
let pending_actions = existing_pending_actions
|
||||
.into_iter()
|
||||
.chain(updates.map(|update| DatabaseActionKind::UpdateCharacter(Box::new(update))))
|
||||
.collect::<Vec<DatabaseActionKind>>();
|
||||
|
||||
if !pending_actions.is_empty() {
|
||||
debug!(
|
||||
"Sending persistence update batch ID {} containing {} updates",
|
||||
batch_id,
|
||||
pending_actions.len()
|
||||
);
|
||||
if let Err(e) =
|
||||
self.update_tx
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send(CharacterUpdaterAction::BatchUpdate {
|
||||
batch_id,
|
||||
updates: pending_actions,
|
||||
})
|
||||
{
|
||||
error!(?e, "Could not send persistence batch update");
|
||||
}
|
||||
} else {
|
||||
trace!("Skipping persistence batch - no pending updates")
|
||||
}
|
||||
}
|
||||
|
||||
@ -380,7 +398,7 @@ impl CharacterUpdater {
|
||||
self.update_tx
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send(CharacterUpdaterEvent::DisconnectedSuccess)
|
||||
.send(CharacterUpdaterAction::DisconnectedSuccess)
|
||||
.expect(
|
||||
"Failed to send DisconnectedSuccess event - not sending this event will prevent \
|
||||
future persistence batches from running",
|
||||
@ -388,30 +406,45 @@ impl CharacterUpdater {
|
||||
}
|
||||
|
||||
/// Returns a non-blocking iterator over CharacterLoaderResponse messages
|
||||
pub fn messages(&self) -> TryIter<CharacterLoaderResponse> { self.response_rx.try_iter() }
|
||||
pub fn messages(&self) -> TryIter<CharacterUpdaterMessage> { self.response_rx.try_iter() }
|
||||
}
|
||||
|
||||
fn execute_batch_update(
|
||||
updates: Vec<(CharacterId, CharacterUpdateData)>,
|
||||
updates: impl Iterator<Item = DatabaseActionKind>,
|
||||
connection: &mut VelorenConnection,
|
||||
) -> Result<(), PersistenceError> {
|
||||
let mut transaction = connection.connection.transaction()?;
|
||||
transaction.set_drop_behavior(DropBehavior::Rollback);
|
||||
trace!("Transaction started for character batch update");
|
||||
updates.into_iter().try_for_each(
|
||||
|(character_id, (stats, inventory, pets, waypoint, active_abilities, map_marker))| {
|
||||
super::character::update(
|
||||
character_id,
|
||||
stats,
|
||||
inventory,
|
||||
pets,
|
||||
waypoint,
|
||||
active_abilities,
|
||||
map_marker,
|
||||
&mut transaction,
|
||||
)
|
||||
},
|
||||
)?;
|
||||
updates.into_iter().try_for_each(|event| match event {
|
||||
DatabaseActionKind::UpdateCharacter(box (
|
||||
character_id,
|
||||
stats,
|
||||
inventory,
|
||||
pets,
|
||||
waypoint,
|
||||
active_abilities,
|
||||
map_marker,
|
||||
)) => super::character::update(
|
||||
character_id,
|
||||
stats,
|
||||
inventory,
|
||||
pets,
|
||||
waypoint,
|
||||
active_abilities,
|
||||
map_marker,
|
||||
&mut transaction,
|
||||
),
|
||||
DatabaseActionKind::DeleteCharacter {
|
||||
requesting_player_uuid,
|
||||
character_id,
|
||||
} => super::character::delete_character(
|
||||
&requesting_player_uuid,
|
||||
character_id,
|
||||
&mut transaction,
|
||||
),
|
||||
})?;
|
||||
|
||||
transaction.commit()?;
|
||||
|
||||
trace!("Commit for character batch update completed");
|
||||
@ -424,16 +457,26 @@ fn execute_character_create(
|
||||
requesting_player_uuid: &str,
|
||||
persisted_components: PersistedComponents,
|
||||
connection: &mut VelorenConnection,
|
||||
) -> Result<CharacterLoaderResponse, PersistenceError> {
|
||||
) -> Result<CharacterUpdaterMessage, PersistenceError> {
|
||||
let mut transaction = connection.connection.transaction()?;
|
||||
let result =
|
||||
CharacterLoaderResponseKind::CharacterCreation(super::character::create_character(
|
||||
requesting_player_uuid,
|
||||
&alias,
|
||||
persisted_components,
|
||||
&mut transaction,
|
||||
));
|
||||
check_response(entity, transaction, result)
|
||||
|
||||
let response = CharacterScreenResponse {
|
||||
target_entity: entity,
|
||||
response_kind: CharacterScreenResponseKind::CharacterCreation(
|
||||
super::character::create_character(
|
||||
requesting_player_uuid,
|
||||
&alias,
|
||||
persisted_components,
|
||||
&mut transaction,
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
if !response.is_err() {
|
||||
transaction.commit()?;
|
||||
};
|
||||
|
||||
Ok(CharacterUpdaterMessage::CharacterScreenResponse(response))
|
||||
}
|
||||
|
||||
fn execute_character_edit(
|
||||
@ -443,45 +486,27 @@ fn execute_character_edit(
|
||||
requesting_player_uuid: &str,
|
||||
editable_components: EditableComponents,
|
||||
connection: &mut VelorenConnection,
|
||||
) -> Result<CharacterLoaderResponse, PersistenceError> {
|
||||
) -> Result<CharacterUpdaterMessage, PersistenceError> {
|
||||
let mut transaction = connection.connection.transaction()?;
|
||||
let result = CharacterLoaderResponseKind::CharacterEdit(super::character::edit_character(
|
||||
editable_components,
|
||||
&mut transaction,
|
||||
character_id,
|
||||
requesting_player_uuid,
|
||||
&alias,
|
||||
));
|
||||
check_response(entity, transaction, result)
|
||||
}
|
||||
|
||||
fn execute_character_delete(
|
||||
entity: Entity,
|
||||
requesting_player_uuid: &str,
|
||||
character_id: CharacterId,
|
||||
connection: &mut VelorenConnection,
|
||||
) -> Result<CharacterLoaderResponse, PersistenceError> {
|
||||
let mut transaction = connection.connection.transaction()?;
|
||||
let result = CharacterLoaderResponseKind::CharacterList(super::character::delete_character(
|
||||
requesting_player_uuid,
|
||||
character_id,
|
||||
&mut transaction,
|
||||
));
|
||||
check_response(entity, transaction, result)
|
||||
}
|
||||
|
||||
fn check_response(
|
||||
entity: Entity,
|
||||
transaction: Transaction,
|
||||
result: CharacterLoaderResponseKind,
|
||||
) -> Result<CharacterLoaderResponse, PersistenceError> {
|
||||
let response = CharacterLoaderResponse { entity, result };
|
||||
let response = CharacterScreenResponse {
|
||||
target_entity: entity,
|
||||
response_kind: CharacterScreenResponseKind::CharacterEdit(
|
||||
super::character::edit_character(
|
||||
editable_components,
|
||||
&mut transaction,
|
||||
character_id,
|
||||
requesting_player_uuid,
|
||||
&alias,
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
if !response.is_err() {
|
||||
transaction.commit()?;
|
||||
};
|
||||
|
||||
Ok(response)
|
||||
Ok(CharacterUpdaterMessage::CharacterScreenResponse(response))
|
||||
}
|
||||
|
||||
impl Drop for CharacterUpdater {
|
||||
|
@ -78,9 +78,7 @@ impl Sys {
|
||||
if let Some(player) = players.get(entity) {
|
||||
if presences.contains(entity) {
|
||||
debug!("player already ingame, aborting");
|
||||
} else if character_updater
|
||||
.characters_pending_logout()
|
||||
.any(|x| x == character_id)
|
||||
} else if character_updater.has_pending_database_action(character_id)
|
||||
{
|
||||
debug!("player recently logged out pending persistence, aborting");
|
||||
client.send(ServerGeneral::CharacterDataLoadResult(Err(
|
||||
@ -192,11 +190,11 @@ impl Sys {
|
||||
},
|
||||
ClientGeneral::DeleteCharacter(character_id) => {
|
||||
if let Some(player) = players.get(entity) {
|
||||
character_updater.delete_character(
|
||||
server_emitter.emit(ServerEvent::DeleteCharacter {
|
||||
entity,
|
||||
player.uuid().to_string(),
|
||||
requesting_player_uuid: player.uuid().to_string(),
|
||||
character_id,
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
|
@ -92,12 +92,12 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
Some((
|
||||
id,
|
||||
skill_set,
|
||||
inventory,
|
||||
skill_set.clone(),
|
||||
inventory.clone(),
|
||||
pets,
|
||||
waypoint,
|
||||
active_abilities,
|
||||
map_marker,
|
||||
waypoint.cloned(),
|
||||
active_abilities.clone(),
|
||||
map_marker.cloned(),
|
||||
))
|
||||
},
|
||||
PresenceKind::Spectator | PresenceKind::Possessor => None,
|
||||
|
@ -260,7 +260,6 @@ enum InfoContent {
|
||||
LoadingCharacters,
|
||||
CreatingCharacter,
|
||||
EditingCharacter,
|
||||
DeletingCharacter,
|
||||
JoiningCharacter,
|
||||
CharacterError(String),
|
||||
}
|
||||
@ -455,7 +454,6 @@ impl Controls {
|
||||
Some(InfoContent::LoadingCharacters)
|
||||
| Some(InfoContent::CreatingCharacter)
|
||||
| Some(InfoContent::EditingCharacter)
|
||||
| Some(InfoContent::DeletingCharacter)
|
||||
) && !client.character_list().loading
|
||||
{
|
||||
*info_content = None;
|
||||
@ -779,11 +777,6 @@ impl Controls {
|
||||
.size(fonts.cyri.scale(24))
|
||||
.into()
|
||||
},
|
||||
InfoContent::DeletingCharacter => {
|
||||
Text::new(i18n.get_msg("char_selection-deleting_character"))
|
||||
.size(fonts.cyri.scale(24))
|
||||
.into()
|
||||
},
|
||||
InfoContent::JoiningCharacter => {
|
||||
Text::new(i18n.get_msg("char_selection-joining_character"))
|
||||
.size(fonts.cyri.scale(24))
|
||||
@ -1443,7 +1436,7 @@ impl Controls {
|
||||
events.push(Event::SelectCharacter(None));
|
||||
}
|
||||
}
|
||||
*info_content = Some(InfoContent::DeletingCharacter);
|
||||
*info_content = None;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user