From 6eedc02286315945fc0b9104b7c82d60c2e5bf38 Mon Sep 17 00:00:00 2001 From: Ben Wallis Date: Sun, 12 Mar 2023 23:21:53 +0000 Subject: [PATCH] Changed character deletion to go via batch update --- assets/voxygen/i18n/be_BY/char_selection.ftl | 1 - assets/voxygen/i18n/ca_CA/char_selection.ftl | 1 - assets/voxygen/i18n/cz_CZ/char_selection.ftl | 1 - assets/voxygen/i18n/de_DE/char_selection.ftl | 1 - assets/voxygen/i18n/en/char_selection.ftl | 1 - assets/voxygen/i18n/es_ES/char_selection.ftl | 1 - assets/voxygen/i18n/es_LA/char_selection.ftl | 1 - assets/voxygen/i18n/eu/char_selection.ftl | 1 - assets/voxygen/i18n/fr_FR/char_selection.ftl | 1 - assets/voxygen/i18n/hu_HU/char_selection.ftl | 1 - assets/voxygen/i18n/it_IT/char_selection.ftl | 1 - assets/voxygen/i18n/ja_JP/char_selection.ftl | 1 - assets/voxygen/i18n/ko_KR/char_selection.ftl | 1 - assets/voxygen/i18n/nl_NL/char_selection.ftl | 1 - assets/voxygen/i18n/no_NB/char_selection.ftl | 1 - assets/voxygen/i18n/pl_PL/char_selection.ftl | 1 - assets/voxygen/i18n/pt_BR/char_selection.ftl | 1 - assets/voxygen/i18n/ro_RO/char_selection.ftl | 1 - assets/voxygen/i18n/ru_RU/char_selection.ftl | 1 - assets/voxygen/i18n/sr_SR/char_selection.ftl | 1 - assets/voxygen/i18n/sv_SE/char_selection.ftl | 1 - assets/voxygen/i18n/th_TH/char_selection.ftl | 1 - assets/voxygen/i18n/tr_TR/char_selection.ftl | 1 - assets/voxygen/i18n/uk_UA/char_selection.ftl | 1 - assets/voxygen/i18n/vi_VI/char_selection.ftl | 1 - assets/voxygen/i18n/zh_CN/char_selection.ftl | 1 - client/src/lib.rs | 12 +- common/src/event.rs | 5 + server/src/events/entity_creation.rs | 29 +- server/src/events/mod.rs | 8 +- server/src/events/player.rs | 55 ++- server/src/lib.rs | 195 ++++----- server/src/persistence/character.rs | 14 +- server/src/persistence/character_loader.rs | 67 +-- server/src/persistence/character_updater.rs | 403 ++++++++++--------- server/src/sys/msg/character_screen.rs | 10 +- server/src/sys/persistence.rs | 10 +- voxygen/src/menu/char_selection/ui/mod.rs | 9 +- 38 files changed, 459 insertions(+), 384 deletions(-) diff --git a/assets/voxygen/i18n/be_BY/char_selection.ftl b/assets/voxygen/i18n/be_BY/char_selection.ftl index 8a03276425..2f791698c9 100644 --- a/assets/voxygen/i18n/be_BY/char_selection.ftl +++ b/assets/voxygen/i18n/be_BY/char_selection.ftl @@ -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 = Выхад diff --git a/assets/voxygen/i18n/ca_CA/char_selection.ftl b/assets/voxygen/i18n/ca_CA/char_selection.ftl index 0b5164110b..ed7a714e2f 100644 --- a/assets/voxygen/i18n/ca_CA/char_selection.ftl +++ b/assets/voxygen/i18n/ca_CA/char_selection.ftl @@ -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ó diff --git a/assets/voxygen/i18n/cz_CZ/char_selection.ftl b/assets/voxygen/i18n/cz_CZ/char_selection.ftl index ee004173e5..71f2e68d13 100644 --- a/assets/voxygen/i18n/cz_CZ/char_selection.ftl +++ b/assets/voxygen/i18n/cz_CZ/char_selection.ftl @@ -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 diff --git a/assets/voxygen/i18n/de_DE/char_selection.ftl b/assets/voxygen/i18n/de_DE/char_selection.ftl index 6f89de395c..d331acf069 100644 --- a/assets/voxygen/i18n/de_DE/char_selection.ftl +++ b/assets/voxygen/i18n/de_DE/char_selection.ftl @@ -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 diff --git a/assets/voxygen/i18n/en/char_selection.ftl b/assets/voxygen/i18n/en/char_selection.ftl index ba8aeb28b2..29d4006e41 100644 --- a/assets/voxygen/i18n/en/char_selection.ftl +++ b/assets/voxygen/i18n/en/char_selection.ftl @@ -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 diff --git a/assets/voxygen/i18n/es_ES/char_selection.ftl b/assets/voxygen/i18n/es_ES/char_selection.ftl index 00b1f2712a..c6b4df194f 100644 --- a/assets/voxygen/i18n/es_ES/char_selection.ftl +++ b/assets/voxygen/i18n/es_ES/char_selection.ftl @@ -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 diff --git a/assets/voxygen/i18n/es_LA/char_selection.ftl b/assets/voxygen/i18n/es_LA/char_selection.ftl index 7d7ed6c6dd..3238075bd6 100644 --- a/assets/voxygen/i18n/es_LA/char_selection.ftl +++ b/assets/voxygen/i18n/es_LA/char_selection.ftl @@ -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 diff --git a/assets/voxygen/i18n/eu/char_selection.ftl b/assets/voxygen/i18n/eu/char_selection.ftl index ac0b72fa43..fa08b36f33 100644 --- a/assets/voxygen/i18n/eu/char_selection.ftl +++ b/assets/voxygen/i18n/eu/char_selection.ftl @@ -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 diff --git a/assets/voxygen/i18n/fr_FR/char_selection.ftl b/assets/voxygen/i18n/fr_FR/char_selection.ftl index 707b67c0ee..c845fbb8a0 100644 --- a/assets/voxygen/i18n/fr_FR/char_selection.ftl +++ b/assets/voxygen/i18n/fr_FR/char_selection.ftl @@ -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 diff --git a/assets/voxygen/i18n/hu_HU/char_selection.ftl b/assets/voxygen/i18n/hu_HU/char_selection.ftl index 62b0b3d596..f23bcf5040 100644 --- a/assets/voxygen/i18n/hu_HU/char_selection.ftl +++ b/assets/voxygen/i18n/hu_HU/char_selection.ftl @@ -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 diff --git a/assets/voxygen/i18n/it_IT/char_selection.ftl b/assets/voxygen/i18n/it_IT/char_selection.ftl index 3692ed874c..a1ade47b6b 100644 --- a/assets/voxygen/i18n/it_IT/char_selection.ftl +++ b/assets/voxygen/i18n/it_IT/char_selection.ftl @@ -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 diff --git a/assets/voxygen/i18n/ja_JP/char_selection.ftl b/assets/voxygen/i18n/ja_JP/char_selection.ftl index 240c99ab2b..925964a2d3 100644 --- a/assets/voxygen/i18n/ja_JP/char_selection.ftl +++ b/assets/voxygen/i18n/ja_JP/char_selection.ftl @@ -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 = ログアウト diff --git a/assets/voxygen/i18n/ko_KR/char_selection.ftl b/assets/voxygen/i18n/ko_KR/char_selection.ftl index 015f8a9e68..d02631020b 100644 --- a/assets/voxygen/i18n/ko_KR/char_selection.ftl +++ b/assets/voxygen/i18n/ko_KR/char_selection.ftl @@ -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 = 세계 관전하기 diff --git a/assets/voxygen/i18n/nl_NL/char_selection.ftl b/assets/voxygen/i18n/nl_NL/char_selection.ftl index 4a934126d1..0eccc1da22 100644 --- a/assets/voxygen/i18n/nl_NL/char_selection.ftl +++ b/assets/voxygen/i18n/nl_NL/char_selection.ftl @@ -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 diff --git a/assets/voxygen/i18n/no_NB/char_selection.ftl b/assets/voxygen/i18n/no_NB/char_selection.ftl index 8bbc3ce32d..8b320538e3 100644 --- a/assets/voxygen/i18n/no_NB/char_selection.ftl +++ b/assets/voxygen/i18n/no_NB/char_selection.ftl @@ -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 diff --git a/assets/voxygen/i18n/pl_PL/char_selection.ftl b/assets/voxygen/i18n/pl_PL/char_selection.ftl index fa2c65ca6b..17977bddf1 100644 --- a/assets/voxygen/i18n/pl_PL/char_selection.ftl +++ b/assets/voxygen/i18n/pl_PL/char_selection.ftl @@ -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 diff --git a/assets/voxygen/i18n/pt_BR/char_selection.ftl b/assets/voxygen/i18n/pt_BR/char_selection.ftl index 1e26bfe466..aafd79091c 100644 --- a/assets/voxygen/i18n/pt_BR/char_selection.ftl +++ b/assets/voxygen/i18n/pt_BR/char_selection.ftl @@ -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 diff --git a/assets/voxygen/i18n/ro_RO/char_selection.ftl b/assets/voxygen/i18n/ro_RO/char_selection.ftl index f8ce01149d..c52d9fc63f 100644 --- a/assets/voxygen/i18n/ro_RO/char_selection.ftl +++ b/assets/voxygen/i18n/ro_RO/char_selection.ftl @@ -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 diff --git a/assets/voxygen/i18n/ru_RU/char_selection.ftl b/assets/voxygen/i18n/ru_RU/char_selection.ftl index 5b18ed1878..fe1265604f 100644 --- a/assets/voxygen/i18n/ru_RU/char_selection.ftl +++ b/assets/voxygen/i18n/ru_RU/char_selection.ftl @@ -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 = Режим наблюдателя diff --git a/assets/voxygen/i18n/sr_SR/char_selection.ftl b/assets/voxygen/i18n/sr_SR/char_selection.ftl index f202f3f04c..0ba4c39c46 100644 --- a/assets/voxygen/i18n/sr_SR/char_selection.ftl +++ b/assets/voxygen/i18n/sr_SR/char_selection.ftl @@ -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 = Одјави се diff --git a/assets/voxygen/i18n/sv_SE/char_selection.ftl b/assets/voxygen/i18n/sv_SE/char_selection.ftl index 65a744c7b8..12e93bb526 100644 --- a/assets/voxygen/i18n/sv_SE/char_selection.ftl +++ b/assets/voxygen/i18n/sv_SE/char_selection.ftl @@ -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 diff --git a/assets/voxygen/i18n/th_TH/char_selection.ftl b/assets/voxygen/i18n/th_TH/char_selection.ftl index 62bf8f3c3b..9d643b4dc1 100644 --- a/assets/voxygen/i18n/th_TH/char_selection.ftl +++ b/assets/voxygen/i18n/th_TH/char_selection.ftl @@ -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 = ชมโลก diff --git a/assets/voxygen/i18n/tr_TR/char_selection.ftl b/assets/voxygen/i18n/tr_TR/char_selection.ftl index e3cabfeefb..01a186bbb8 100644 --- a/assets/voxygen/i18n/tr_TR/char_selection.ftl +++ b/assets/voxygen/i18n/tr_TR/char_selection.ftl @@ -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 diff --git a/assets/voxygen/i18n/uk_UA/char_selection.ftl b/assets/voxygen/i18n/uk_UA/char_selection.ftl index 908d1ee9b2..13e618df00 100644 --- a/assets/voxygen/i18n/uk_UA/char_selection.ftl +++ b/assets/voxygen/i18n/uk_UA/char_selection.ftl @@ -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 = Режим Споглядача diff --git a/assets/voxygen/i18n/vi_VI/char_selection.ftl b/assets/voxygen/i18n/vi_VI/char_selection.ftl index 939f4a9372..c5af8862c3 100644 --- a/assets/voxygen/i18n/vi_VI/char_selection.ftl +++ b/assets/voxygen/i18n/vi_VI/char_selection.ftl @@ -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 diff --git a/assets/voxygen/i18n/zh_CN/char_selection.ftl b/assets/voxygen/i18n/zh_CN/char_selection.ftl index c801df1e75..6167d7cfa1 100644 --- a/assets/voxygen/i18n/zh_CN/char_selection.ftl +++ b/assets/voxygen/i18n/zh_CN/char_selection.ftl @@ -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 = 观察世界 diff --git a/client/src/lib.rs b/client/src/lib.rs index 34812b897b..4cd884136b 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -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)); } diff --git a/common/src/event.rs b/common/src/event.rs index d1b52e5c50..4e4d2f240f 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -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 { diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index a472d0ab5a..4552158dd7 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -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::(); + 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); } } diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index b43be72f3c..2ff37f5f56 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -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, diff --git a/server/src/events/player.rs b/server/src/events/player.rs index e6d18e77d5..a634686cb3 100644 --- a/server/src/events/player.rs +++ b/server/src/events/player.rs @@ -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::(); + 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::(); + 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 */ }, diff --git a/server/src/lib.rs b/server/src/lib.rs index e40fb7778a..c6941bec36 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -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::(); - let character_updater = self.state.ecs().read_resource::(); + let mut character_updater = self.state.ecs().write_resource::(); + let updater_messages: Vec = 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::>() - .emit_now(message); + // Clean up the entity data on the server + ServerEvent::ExitIngame { + entity: response.target_entity, + } + }, + }; + + self.state + .ecs() + .read_resource::>() + .emit_now(message); + }, + } }, }); diff --git a/server/src/persistence/character.rs b/server/src/persistence/character.rs index bde05d00c7..9211a28e3d 100644 --- a/server/src/persistence/character.rs +++ b/server/src/persistence/character.rs @@ -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 diff --git a/server/src/persistence/character_loader.rs b/server/src/persistence/character_loader.rs index e4b98b3fc1..0420cb9caf 100644 --- a/server/src/persistence/character_loader.rs +++ b/server/src/persistence/character_loader.rs @@ -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), 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, + update_rx: crossbeam_channel::Receiver, update_tx: crossbeam_channel::Sender, } impl CharacterLoader { pub fn new(settings: Arc>) -> Result { let (update_tx, internal_rx) = crossbeam_channel::unbounded::(); - let (internal_tx, update_rx) = crossbeam_channel::unbounded::(); + let (internal_tx, update_rx) = crossbeam_channel::unbounded::(); 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 { self.update_rx.try_iter() } + pub fn messages(&self) -> TryIter { self.update_rx.try_iter() } } diff --git a/server/src/persistence/character_updater.rs b/server/src/persistence/character_updater.rs index 3e1e5787c8..13d5ab76f4 100644 --- a/server/src/persistence/character_updater.rs +++ b/server/src/persistence/character_updater.rs @@ -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, @@ -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, + }, 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 { + 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), 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>, - response_rx: crossbeam_channel::Receiver, + update_tx: Option>, + response_rx: crossbeam_channel::Receiver, handle: Option>, - pending_logout_updates: HashMap, + /// 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, /// Will disconnect all characters (without persistence) on the next tick if /// set to true disconnect_all_clients_requested: Arc, + last_pending_database_event_id: u64, } impl CharacterUpdater { pub fn new(settings: Arc>) -> rusqlite::Result { - let (update_tx, update_rx) = crossbeam_channel::unbounded::(); - let (response_tx, response_rx) = crossbeam_channel::unbounded::(); + let (update_tx, update_rx) = crossbeam_channel::unbounded::(); + let (response_tx, response_rx) = crossbeam_channel::unbounded::(); 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 + '_ { - 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, - 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::>(); + pub fn batch_update(&mut self, updates: impl Iterator) { + 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::>(); + + 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 { self.response_rx.try_iter() } + pub fn messages(&self) -> TryIter { self.response_rx.try_iter() } } fn execute_batch_update( - updates: Vec<(CharacterId, CharacterUpdateData)>, + updates: impl Iterator, 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 { +) -> Result { 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 { +) -> Result { 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 { - 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 { - 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 { diff --git a/server/src/sys/msg/character_screen.rs b/server/src/sys/msg/character_screen.rs index 2511e61253..6a05a5a8fb 100644 --- a/server/src/sys/msg/character_screen.rs +++ b/server/src/sys/msg/character_screen.rs @@ -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, - ); + }); } }, _ => { diff --git a/server/src/sys/persistence.rs b/server/src/sys/persistence.rs index 4968b0dab8..de4eb10885 100644 --- a/server/src/sys/persistence.rs +++ b/server/src/sys/persistence.rs @@ -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, diff --git a/voxygen/src/menu/char_selection/ui/mod.rs b/voxygen/src/menu/char_selection/ui/mod.rs index 865e543b46..63afdf5273 100644 --- a/voxygen/src/menu/char_selection/ui/mod.rs +++ b/voxygen/src/menu/char_selection/ui/mod.rs @@ -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; } } },