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