Merge branch 'xvar/rework-char-deletes' into 'master'

Character deletes are now processed by CharacterUpdater and clear any pending...

See merge request veloren/veloren!2128
This commit is contained in:
Ben Wallis 2021-04-15 00:39:17 +00:00
commit b6cd1f836e
6 changed files with 132 additions and 45 deletions

View File

@ -610,11 +610,20 @@ impl Server {
// 7 Persistence updates // 7 Persistence updates
let before_persistence_updates = Instant::now(); let before_persistence_updates = Instant::now();
// Get character-related database responses and notify the requesting client let character_loader = self
self.state .state
.ecs() .ecs()
.read_resource::<persistence::character_loader::CharacterLoader>() .read_resource::<persistence::character_loader::CharacterLoader>();
let character_updater = self
.state
.ecs()
.read_resource::<persistence::character_updater::CharacterUpdater>();
// Get character-related database responses and notify the requesting client
character_loader
.messages() .messages()
.chain(character_updater.messages())
.for_each(|query_result| match query_result.result { .for_each(|query_result| match query_result.result {
CharacterLoaderResponseKind::CharacterList(result) => match result { CharacterLoaderResponseKind::CharacterList(result) => match result {
Ok(character_list_data) => self.notify_client( Ok(character_list_data) => self.notify_client(
@ -671,6 +680,9 @@ impl Server {
}, },
}); });
drop(character_loader);
drop(character_updater);
{ {
// Check for new chunks; cancel and regenerate all chunks if the asset has been // Check for new chunks; cancel and regenerate all chunks if the asset has been
// reloaded. Note that all of these assignments are no-ops, so the // reloaded. Note that all of these assignments are no-ops, so the

View File

@ -9,7 +9,7 @@ extern crate rusqlite;
use super::{error::PersistenceError, models::*}; use super::{error::PersistenceError, models::*};
use crate::{ use crate::{
comp, comp,
comp::{item::MaterialStatManifest, Inventory}, comp::Inventory,
persistence::{ persistence::{
character::conversions::{ character::conversions::{
convert_body_from_database, convert_body_to_database_json, convert_body_from_database, convert_body_to_database_json,
@ -101,7 +101,6 @@ pub fn load_character_data(
requesting_player_uuid: String, requesting_player_uuid: String,
char_id: CharacterId, char_id: CharacterId,
connection: &mut Transaction, connection: &mut Transaction,
msm: &MaterialStatManifest,
) -> CharacterDataResult { ) -> CharacterDataResult {
let character_containers = get_pseudo_containers(connection, char_id)?; let character_containers = get_pseudo_containers(connection, char_id)?;
let inventory_items = load_items_bfs(connection, character_containers.inventory_container_id)?; let inventory_items = load_items_bfs(connection, character_containers.inventory_container_id)?;
@ -205,7 +204,6 @@ pub fn load_character_data(
&inventory_items, &inventory_items,
character_containers.loadout_container_id, character_containers.loadout_container_id,
&loadout_items, &loadout_items,
msm,
)?, )?,
char_waypoint, char_waypoint,
)) ))
@ -221,7 +219,6 @@ pub fn load_character_data(
pub fn load_character_list( pub fn load_character_list(
player_uuid_: &str, player_uuid_: &str,
connection: &mut Transaction, connection: &mut Transaction,
msm: &MaterialStatManifest,
) -> CharacterListResult { ) -> CharacterListResult {
let characters; let characters;
{ {
@ -282,7 +279,7 @@ pub fn load_character_list(
let loadout_items = load_items_bfs(connection, loadout_container_id)?; let loadout_items = load_items_bfs(connection, loadout_container_id)?;
let loadout = let loadout =
convert_loadout_from_database_items(loadout_container_id, &loadout_items, msm)?; convert_loadout_from_database_items(loadout_container_id, &loadout_items)?;
Ok(CharacterItem { Ok(CharacterItem {
character: char, character: char,
@ -298,7 +295,6 @@ pub fn create_character(
character_alias: &str, character_alias: &str,
persisted_components: PersistedComponents, persisted_components: PersistedComponents,
connection: &mut Transaction, connection: &mut Transaction,
msm: &MaterialStatManifest,
) -> CharacterCreationResult { ) -> CharacterCreationResult {
check_character_limit(uuid, connection)?; check_character_limit(uuid, connection)?;
@ -445,7 +441,7 @@ pub fn create_character(
} }
drop(stmt); drop(stmt);
load_character_list(uuid, connection, msm).map(|list| (character_id, list)) load_character_list(uuid, connection).map(|list| (character_id, list))
} }
/// Delete a character. Returns the updated character list. /// Delete a character. Returns the updated character list.
@ -453,7 +449,6 @@ pub fn delete_character(
requesting_player_uuid: &str, requesting_player_uuid: &str,
char_id: CharacterId, char_id: CharacterId,
connection: &mut Transaction, connection: &mut Transaction,
msm: &MaterialStatManifest,
) -> CharacterListResult { ) -> CharacterListResult {
#[rustfmt::skip] #[rustfmt::skip]
let mut stmt = connection.prepare_cached(" let mut stmt = connection.prepare_cached("
@ -549,7 +544,7 @@ pub fn delete_character(
))); )));
} }
load_character_list(requesting_player_uuid, connection, msm) load_character_list(requesting_player_uuid, connection)
} }
/// 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

View File

@ -22,6 +22,7 @@ use common::{
}; };
use core::{convert::TryFrom, num::NonZeroU64}; use core::{convert::TryFrom, num::NonZeroU64};
use hashbrown::HashMap; use hashbrown::HashMap;
use lazy_static::lazy_static;
use std::{collections::VecDeque, sync::Arc}; use std::{collections::VecDeque, sync::Arc};
use tracing::trace; use tracing::trace;
@ -31,6 +32,13 @@ pub struct ItemModelPair {
pub model: Item, pub model: Item,
} }
// Decoupled from the ECS resource because the plumbing is getting complicated;
// shouldn't matter unless someone's hot-reloading material stats on the live
// server
lazy_static! {
pub static ref MATERIAL_STATS_MANIFEST: MaterialStatManifest = MaterialStatManifest::default();
}
/// Returns a vector that contains all item rows to upsert; parent is /// Returns a vector that contains all item rows to upsert; parent is
/// responsible for deleting items from the same owner that aren't affirmatively /// responsible for deleting items from the same owner that aren't affirmatively
/// kept by this. /// kept by this.
@ -230,7 +238,6 @@ pub fn convert_inventory_from_database_items(
inventory_items: &[Item], inventory_items: &[Item],
loadout_container_id: i64, loadout_container_id: i64,
loadout_items: &[Item], loadout_items: &[Item],
msm: &MaterialStatManifest,
) -> Result<Inventory, PersistenceError> { ) -> Result<Inventory, PersistenceError> {
// Loadout items must be loaded before inventory items since loadout items // Loadout items must be loaded before inventory items since loadout items
// provide inventory slots. Since items stored inside loadout items actually // provide inventory slots. Since items stored inside loadout items actually
@ -238,7 +245,7 @@ pub fn convert_inventory_from_database_items(
// on populating the loadout items first, and then inserting the items into the // on populating the loadout items first, and then inserting the items into the
// inventory at the correct position. // inventory at the correct position.
// //
let loadout = convert_loadout_from_database_items(loadout_container_id, loadout_items, msm)?; let loadout = convert_loadout_from_database_items(loadout_container_id, loadout_items)?;
let mut inventory = Inventory::new_with_loadout(loadout); let mut inventory = Inventory::new_with_loadout(loadout);
let mut item_indices = HashMap::new(); let mut item_indices = HashMap::new();
@ -310,7 +317,7 @@ pub fn convert_inventory_from_database_items(
} }
} else if let Some(&j) = item_indices.get(&db_item.parent_container_item_id) { } else if let Some(&j) = item_indices.get(&db_item.parent_container_item_id) {
if let Some(Some(parent)) = inventory.slot_mut(slot(&inventory_items[j].position)?) { if let Some(Some(parent)) = inventory.slot_mut(slot(&inventory_items[j].position)?) {
parent.add_component(item, msm); parent.add_component(item, &MATERIAL_STATS_MANIFEST);
} else { } else {
return Err(PersistenceError::ConversionError(format!( return Err(PersistenceError::ConversionError(format!(
"Parent slot {} for component {} was empty even though it occurred earlier in \ "Parent slot {} for component {} was empty even though it occurred earlier in \
@ -332,7 +339,6 @@ pub fn convert_inventory_from_database_items(
pub fn convert_loadout_from_database_items( pub fn convert_loadout_from_database_items(
loadout_container_id: i64, loadout_container_id: i64,
database_items: &[Item], database_items: &[Item],
msm: &MaterialStatManifest,
) -> Result<Loadout, PersistenceError> { ) -> Result<Loadout, PersistenceError> {
let loadout_builder = LoadoutBuilder::new(); let loadout_builder = LoadoutBuilder::new();
let mut loadout = loadout_builder.build(); let mut loadout = loadout_builder.build();
@ -367,7 +373,7 @@ pub fn convert_loadout_from_database_items(
} else if let Some(&j) = item_indices.get(&db_item.parent_container_item_id) { } else if let Some(&j) = item_indices.get(&db_item.parent_container_item_id) {
loadout loadout
.update_item_at_slot_using_persistence_key(&database_items[j].position, |parent| { .update_item_at_slot_using_persistence_key(&database_items[j].position, |parent| {
parent.add_component(item, msm); parent.add_component(item, &MATERIAL_STATS_MANIFEST);
}) })
.map_err(convert_error)?; .map_err(convert_error)?;
} else { } else {

View File

@ -3,12 +3,8 @@ use crate::persistence::{
error::PersistenceError, error::PersistenceError,
establish_connection, DatabaseSettings, PersistedComponents, establish_connection, DatabaseSettings, PersistedComponents,
}; };
use common::{ use common::character::{CharacterId, CharacterItem};
character::{CharacterId, CharacterItem},
comp::inventory::item::MaterialStatManifest,
};
use crossbeam_channel::{self, TryIter}; use crossbeam_channel::{self, TryIter};
use lazy_static::lazy_static;
use rusqlite::Transaction; use rusqlite::Transaction;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use tracing::{error, trace}; use tracing::{error, trace};
@ -83,13 +79,6 @@ pub struct CharacterLoader {
update_tx: crossbeam_channel::Sender<CharacterLoaderRequest>, update_tx: crossbeam_channel::Sender<CharacterLoaderRequest>,
} }
// Decoupled from the ECS resource because the plumbing is getting complicated;
// shouldn't matter unless someone's hot-reloading material stats on the live
// server
lazy_static! {
pub static ref MATERIAL_STATS_MANIFEST: MaterialStatManifest = MaterialStatManifest::default();
}
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>();
@ -163,7 +152,6 @@ impl CharacterLoader {
&character_alias, &character_alias,
persisted_components, persisted_components,
&mut transaction, &mut transaction,
&MATERIAL_STATS_MANIFEST,
)), )),
CharacterLoaderRequestKind::DeleteCharacter { CharacterLoaderRequestKind::DeleteCharacter {
player_uuid, player_uuid,
@ -172,25 +160,18 @@ impl CharacterLoader {
&player_uuid, &player_uuid,
character_id, character_id,
&mut transaction, &mut transaction,
&MATERIAL_STATS_MANIFEST,
)), )),
CharacterLoaderRequestKind::LoadCharacterList { player_uuid } => { CharacterLoaderRequestKind::LoadCharacterList { player_uuid } => {
CharacterLoaderResponseKind::CharacterList(load_character_list( CharacterLoaderResponseKind::CharacterList(load_character_list(
&player_uuid, &player_uuid,
&mut transaction, &mut transaction,
&MATERIAL_STATS_MANIFEST,
)) ))
}, },
CharacterLoaderRequestKind::LoadCharacterData { CharacterLoaderRequestKind::LoadCharacterData {
player_uuid, player_uuid,
character_id, character_id,
} => { } => {
let result = load_character_data( let result = load_character_data(player_uuid, character_id, &mut transaction);
player_uuid,
character_id,
&mut transaction,
&MATERIAL_STATS_MANIFEST,
);
if result.is_err() { if result.is_err() {
error!( error!(
?result, ?result,

View File

@ -2,9 +2,13 @@ use crate::comp;
use common::character::CharacterId; use common::character::CharacterId;
use crate::persistence::{ use crate::persistence::{
error::PersistenceError, establish_connection, DatabaseSettings, VelorenConnection, character_loader::{CharacterLoaderResponse, CharacterLoaderResponseKind},
error::PersistenceError,
establish_connection, DatabaseSettings, VelorenConnection,
}; };
use crossbeam_channel::TryIter;
use rusqlite::DropBehavior; use rusqlite::DropBehavior;
use specs::Entity;
use std::{ use std::{
collections::HashMap, collections::HashMap,
sync::{ sync::{
@ -18,6 +22,11 @@ pub type CharacterUpdateData = (comp::SkillSet, comp::Inventory, Option<comp::Wa
pub enum CharacterUpdaterEvent { pub enum CharacterUpdaterEvent {
BatchUpdate(Vec<(CharacterId, CharacterUpdateData)>), BatchUpdate(Vec<(CharacterId, CharacterUpdateData)>),
DeleteCharacter {
entity: Entity,
requesting_player_uuid: String,
character_id: CharacterId,
},
DisconnectedSuccess, DisconnectedSuccess,
} }
@ -28,6 +37,7 @@ pub enum CharacterUpdaterEvent {
/// 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<CharacterUpdaterEvent>>,
response_rx: crossbeam_channel::Receiver<CharacterLoaderResponse>,
handle: Option<std::thread::JoinHandle<()>>, handle: Option<std::thread::JoinHandle<()>>,
pending_logout_updates: HashMap<CharacterId, CharacterUpdateData>, pending_logout_updates: HashMap<CharacterId, CharacterUpdateData>,
/// Will disconnect all characters (without persistence) on the next tick if /// Will disconnect all characters (without persistence) on the next tick if
@ -38,6 +48,8 @@ pub struct CharacterUpdater {
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::<CharacterUpdaterEvent>();
let (response_tx, response_rx) = crossbeam_channel::unbounded::<CharacterLoaderResponse>();
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);
@ -68,6 +80,33 @@ impl CharacterUpdater {
.store(true, Ordering::Relaxed); .store(true, Ordering::Relaxed);
}; };
}, },
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 => { CharacterUpdaterEvent::DisconnectedSuccess => {
info!( info!(
"CharacterUpdater received DisconnectedSuccess event, resuming \ "CharacterUpdater received DisconnectedSuccess event, resuming \
@ -84,6 +123,7 @@ impl CharacterUpdater {
Ok(Self { Ok(Self {
update_tx: Some(update_tx), update_tx: Some(update_tx),
response_rx,
handle: Some(handle), handle: Some(handle),
pending_logout_updates: HashMap::new(), pending_logout_updates: HashMap::new(),
disconnect_all_clients_requested, disconnect_all_clients_requested,
@ -125,6 +165,32 @@ impl CharacterUpdater {
.load(Ordering::Relaxed) .load(Ordering::Relaxed)
} }
pub fn delete_character(
&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);
}
}
/// 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<'a>(
&mut self, &mut self,
@ -185,6 +251,9 @@ impl CharacterUpdater {
future persistence batches from running", future persistence batches from running",
); );
} }
/// Returns a non-blocking iterator over CharacterLoaderResponse messages
pub fn messages(&self) -> TryIter<CharacterLoaderResponse> { self.response_rx.try_iter() }
} }
fn execute_batch_update( fn execute_batch_update(
@ -205,6 +274,30 @@ fn execute_batch_update(
Ok(()) Ok(())
} }
fn execute_character_delete(
entity: Entity,
requesting_player_uuid: &str,
character_id: CharacterId,
connection: &mut VelorenConnection,
) -> Result<CharacterLoaderResponse, PersistenceError> {
let mut transaction = connection.connection.transaction()?;
let response = CharacterLoaderResponse {
entity,
result: CharacterLoaderResponseKind::CharacterList(super::character::delete_character(
requesting_player_uuid,
character_id,
&mut transaction,
)),
};
if !response.is_err() {
transaction.commit()?;
};
Ok(response)
}
impl Drop for CharacterUpdater { impl Drop for CharacterUpdater {
fn drop(&mut self) { fn drop(&mut self) {
drop(self.update_tx.take()); drop(self.update_tx.take());

View File

@ -13,7 +13,7 @@ use common::{
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::{ClientGeneral, ServerGeneral}; use common_net::msg::{ClientGeneral, ServerGeneral};
use specs::{Entities, Join, Read, ReadExpect, ReadStorage}; use specs::{Entities, Join, Read, ReadExpect, ReadStorage, WriteExpect};
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use tracing::{debug, warn}; use tracing::{debug, warn};
@ -24,7 +24,7 @@ impl Sys {
entity: specs::Entity, entity: specs::Entity,
client: &Client, client: &Client,
character_loader: &ReadExpect<'_, CharacterLoader>, character_loader: &ReadExpect<'_, CharacterLoader>,
character_updater: &ReadExpect<'_, CharacterUpdater>, character_updater: &mut WriteExpect<'_, CharacterUpdater>,
uids: &ReadStorage<'_, Uid>, uids: &ReadStorage<'_, Uid>,
players: &ReadStorage<'_, Player>, players: &ReadStorage<'_, Player>,
presences: &ReadStorage<'_, Presence>, presences: &ReadStorage<'_, Presence>,
@ -132,7 +132,7 @@ 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_loader.delete_character( character_updater.delete_character(
entity, entity,
player.uuid().to_string(), player.uuid().to_string(),
character_id, character_id,
@ -154,7 +154,7 @@ impl<'a> System<'a> for Sys {
Entities<'a>, Entities<'a>,
Read<'a, EventBus<ServerEvent>>, Read<'a, EventBus<ServerEvent>>,
ReadExpect<'a, CharacterLoader>, ReadExpect<'a, CharacterLoader>,
ReadExpect<'a, CharacterUpdater>, WriteExpect<'a, CharacterUpdater>,
ReadStorage<'a, Uid>, ReadStorage<'a, Uid>,
ReadStorage<'a, Client>, ReadStorage<'a, Client>,
ReadStorage<'a, Player>, ReadStorage<'a, Player>,
@ -173,7 +173,7 @@ impl<'a> System<'a> for Sys {
entities, entities,
server_event_bus, server_event_bus,
character_loader, character_loader,
character_updater, mut character_updater,
uids, uids,
clients, clients,
players, players,
@ -191,7 +191,7 @@ impl<'a> System<'a> for Sys {
entity, entity,
client, client,
&character_loader, &character_loader,
&character_updater, &mut character_updater,
&uids, &uids,
&players, &players,
&presences, &presences,