From 3e0ca7d6d40db79bb6dd215f4af361ade9addf0a Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Sat, 13 Jan 2024 00:11:26 +0100 Subject: [PATCH 1/9] Load missing plugins from the server individual commits combined here: send active plugins compute plugin sha hash single position for defining Hash type request plugins from the server Server sending the plugin to the client store received plugin in file and use it handle plugin data at the right place pass config_dir to client init load local plugins operational plugin caching simplify the interface clippy suggestions remove artifacts fix compilation of test world ChangeLog entry code quality fixes improve readability adapt to multiple systems --- CHANGELOG.md | 1 + Cargo.lock | 3 + Cargo.toml | 2 + client/examples/chat-cli/main.rs | 1 + client/src/bin/bot/main.rs | 1 + client/src/lib.rs | 63 ++++++++++++++- common/Cargo.toml | 2 +- common/net/src/msg/client.rs | 7 +- common/net/src/msg/server.rs | 6 +- common/src/event.rs | 7 ++ common/state/Cargo.toml | 4 +- common/state/src/plugin/mod.rs | 105 ++++++++++++++++++++++--- server/src/client.rs | 3 +- server/src/events/information.rs | 57 +++++++++++++- server/src/lib.rs | 11 ++- server/src/lod.rs | 4 +- server/src/sys/msg/character_screen.rs | 3 + server/src/sys/msg/general.rs | 6 +- server/src/sys/msg/in_game.rs | 3 +- server/src/sys/msg/register.rs | 12 ++- server/src/sys/terrain.rs | 3 + server/src/sys/terrain_sync.rs | 3 + server/src/test_world.rs | 5 ++ voxygen/Cargo.toml | 1 + voxygen/src/menu/char_selection/mod.rs | 20 ++++- voxygen/src/menu/main/client_init.rs | 4 + voxygen/src/menu/main/mod.rs | 33 +++++++- voxygen/src/session/mod.rs | 3 + 28 files changed, 340 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf5d302f47..abfa5e9cba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Sand and crystal cave biome - In commands that reference assets you can now use `#name` and press tab to cycle through assets with that name. - Allow moving and resizing the chat with left and right mouse button respectively +- Missing plugins are requested from the server and cached locally ### Changed diff --git a/Cargo.lock b/Cargo.lock index de6b420bc8..0df615346e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7114,10 +7114,12 @@ dependencies = [ "bytes", "futures", "hashbrown 0.13.2", + "hex", "num_cpus", "rayon", "scopeguard", "serde", + "sha2", "specs", "tar", "timer-queue", @@ -7364,6 +7366,7 @@ dependencies = [ "rodio", "ron", "serde", + "sha2", "shaderc", "slab", "specs", diff --git a/Cargo.toml b/Cargo.toml index d13e9ccbbb..c8d02902e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -154,6 +154,8 @@ rayon = { version = "1.5" } clap = { version = "4.2", features = ["derive"]} async-trait = "0.1.42" +sha2 = "0.10" +hex = "0.4.3" [patch.crates-io] shred = { git = "https://github.com/amethyst/shred.git", rev = "5d52c6fc390dd04c12158633e77591f6523d1f85" } diff --git a/client/examples/chat-cli/main.rs b/client/examples/chat-cli/main.rs index 841b0ed49a..381c331e89 100644 --- a/client/examples/chat-cli/main.rs +++ b/client/examples/chat-cli/main.rs @@ -67,6 +67,7 @@ fn main() { |provider| provider == "https://auth.veloren.net", &|_| {}, |_| {}, + Default::default(), )) .expect("Failed to create client instance"); diff --git a/client/src/bin/bot/main.rs b/client/src/bin/bot/main.rs index beb6c41642..f54a927147 100644 --- a/client/src/bin/bot/main.rs +++ b/client/src/bin/bot/main.rs @@ -75,6 +75,7 @@ pub fn make_client( |_| true, &|_| {}, |_| {}, + Default::default(), )) .ok() } diff --git a/client/src/lib.rs b/client/src/lib.rs index 87f3bfaed1..18ee331920 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -31,7 +31,7 @@ use common::{ GroupManip, InputKind, InventoryAction, InventoryEvent, InventoryUpdateEvent, MapMarkerChange, PresenceKind, UtteranceKind, }, - event::{EventBus, LocalEvent, UpdateCharacterMetadata}, + event::{EventBus, LocalEvent, PluginHash, UpdateCharacterMetadata}, grid::Grid, link::Is, lod, @@ -64,6 +64,8 @@ use common_net::{ }, sync::WorldSyncExt, }; +#[cfg(feature = "plugins")] +use common_state::plugin::PluginMgr; use common_state::State; use common_systems::add_local_systems; use comp::BuffKind; @@ -82,6 +84,7 @@ use std::{ collections::{BTreeMap, VecDeque}, fmt::Debug, mem, + path::PathBuf, sync::Arc, time::{Duration, Instant, SystemTime}, }; @@ -120,6 +123,7 @@ pub enum Event { MapMarker(comp::MapMarkerUpdate), StartSpectate(Vec3), SpectatePosition(Vec3), + PluginDataReceived(Vec), } #[derive(Debug)] @@ -326,6 +330,10 @@ pub struct Client { dt_adjustment: f64, connected_server_constants: ServerConstants, + /// Requested but not yet received plugins + missing_plugins: u32, + /// Locally cached plugins needed by the server + local_plugins: Vec, } /// Holds data related to the current players characters, as well as some @@ -392,6 +400,7 @@ impl Client { auth_trusted: impl FnMut(&str) -> bool, init_stage_update: &(dyn Fn(ClientInitStage) + Send + Sync), add_foreign_systems: impl Fn(&mut DispatcherBuilder) + Send + 'static, + config_dir: PathBuf, ) -> Result { let network = Network::new(Pid::new(), &runtime); @@ -580,6 +589,7 @@ impl Client { server_constants, repair_recipe_book, description, + active_plugins, } = loop { tokio::select! { // Spawn in a blocking thread (leaving the network thread free). This is mostly @@ -614,6 +624,25 @@ impl Client { add_foreign_systems(dispatch_builder); }, ); + let mut missing_plugins: Vec = Vec::new(); + let mut local_plugins: Vec = Vec::new(); + #[cfg(feature = "plugins")] + { + let already_present = state.ecs().read_resource::().plugin_list(); + for hash in active_plugins.iter() { + if !already_present.contains(hash) { + // look in config_dir first (cache) + if let Ok(local_path) = common_state::plugin::find_cached(&config_dir, hash) + { + local_plugins.push(local_path); + } else { + //tracing::info!("cache not found {local_path:?}"); + tracing::info!("Server requires plugin {hash:x?}"); + missing_plugins.push(*hash); + } + } + } + } // Client-only components state.ecs_mut().register::>(); let entity = state.ecs_mut().apply_entity_package(entity_package); @@ -887,6 +916,8 @@ impl Client { repair_recipe_book, max_group_size, client_timeout, + missing_plugins, + local_plugins, )) }); @@ -904,12 +935,18 @@ impl Client { repair_recipe_book, max_group_size, client_timeout, + missing_plugins, + local_plugins, ) = loop { tokio::select! { res = &mut task => break res.expect("Client thread should not panic")?, _ = ping_interval.tick() => ping_stream.send(PingMsg::Ping)?, } }; + let missing_plugins_num = missing_plugins.len(); + if !missing_plugins.is_empty() { + stream.send(ClientGeneral::RequestPlugins(missing_plugins))?; + } ping_stream.send(PingMsg::Ping)?; debug!("Initial sync done"); @@ -990,6 +1027,8 @@ impl Client { dt_adjustment: 1.0, connected_server_constants: server_constants, + missing_plugins: missing_plugins_num as u32, + local_plugins, }) } @@ -1123,7 +1162,8 @@ impl Client { // Always possible ClientGeneral::ChatMsg(_) | ClientGeneral::Command(_, _) - | ClientGeneral::Terminate => &mut self.general_stream, + | ClientGeneral::Terminate + | ClientGeneral::RequestPlugins(_) => &mut self.general_stream, }; #[cfg(feature = "tracy")] { @@ -2539,6 +2579,10 @@ impl Client { ServerGeneral::Notification(n) => { frontend_events.push(Event::Notification(n)); }, + ServerGeneral::PluginData(d) => { + tracing::info!("plugin data #1 {}", d.len()); + frontend_events.push(Event::PluginDataReceived(d)); + }, _ => unreachable!("Not a general msg"), } Ok(()) @@ -3182,6 +3226,20 @@ impl Client { Ok(()) } + + /// another plugin data received, is this the last one + pub fn decrement_missing_plugins(&mut self) -> u32 { + if self.missing_plugins > 0 { + self.missing_plugins -= 1; + } + self.missing_plugins + } + + /// number of requested plugins + pub fn missing_plugins(&self) -> u32 { self.missing_plugins } + + /// extract list of locally cached plugins to load + pub fn take_local_plugins(&mut self) -> Vec { std::mem::take(&mut self.local_plugins) } } impl Drop for Client { @@ -3247,6 +3305,7 @@ mod tests { |suggestion: &str| suggestion == auth_server, &|_| {}, |_| {}, + PathBuf::default(), )); let localisation = LocalizationHandle::load_expect("en"); diff --git a/common/Cargo.toml b/common/Cargo.toml index 607103a468..3e55266347 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -33,7 +33,7 @@ chrono = { workspace = true } chrono-tz = { workspace = true } itertools = { workspace = true } serde_json = { workspace = true } -sha2 = "0.10" +sha2 = { workspace = true } # Strum strum = { workspace = true } diff --git a/common/net/src/msg/client.rs b/common/net/src/msg/client.rs index fc02f6b2a2..c4081df13b 100644 --- a/common/net/src/msg/client.rs +++ b/common/net/src/msg/client.rs @@ -1,5 +1,8 @@ use super::{world_msg::SiteId, PingMsg}; -use common::{character::CharacterId, comp, comp::Skill, terrain::block::Block, ViewDistances}; +use common::{ + character::CharacterId, comp, comp::Skill, event::PluginHash, terrain::block::Block, + ViewDistances, +}; use serde::{Deserialize, Serialize}; use vek::*; @@ -95,6 +98,7 @@ pub enum ClientGeneral { RequestLossyTerrainCompression { lossy_terrain_compression: bool, }, + RequestPlugins(Vec), } impl ClientMsg { @@ -143,6 +147,7 @@ impl ClientMsg { | ClientGeneral::Terminate // LodZoneRequest is required by the char select screen | ClientGeneral::LodZoneRequest { .. } => true, + | ClientGeneral::RequestPlugins(_) => true, } }, ClientMsg::Ping(_) => true, diff --git a/common/net/src/msg/server.rs b/common/net/src/msg/server.rs index 211891842c..e6832a8884 100644 --- a/common/net/src/msg/server.rs +++ b/common/net/src/msg/server.rs @@ -7,7 +7,7 @@ use common::{ calendar::Calendar, character::{self, CharacterItem}, comp::{self, body::Gender, invite::InviteKind, item::MaterialStatManifest, Content}, - event::UpdateCharacterMetadata, + event::{PluginHash, UpdateCharacterMetadata}, lod, outcome::Outcome, recipe::{ComponentRecipeBook, RecipeBook, RepairRecipeBook}, @@ -75,6 +75,7 @@ pub enum ServerInit { ability_map: comp::item::tool::AbilityMap, server_constants: ServerConstants, description: ServerDescription, + active_plugins: Vec, }, } @@ -219,6 +220,8 @@ pub enum ServerGeneral { /// Suggest the client to spectate a position. Called after client has /// requested teleport etc. SpectatePosition(Vec3), + /// Plugin data requested from the server + PluginData(Vec), } impl ServerGeneral { @@ -358,6 +361,7 @@ impl ServerMsg { | ServerGeneral::Disconnect(_) | ServerGeneral::Notification(_) | ServerGeneral::LodZoneUpdate { .. } => true, + ServerGeneral::PluginData(_) => true, } }, ServerMsg::Ping(_) => true, diff --git a/common/src/event.rs b/common/src/event.rs index 35a24c364c..3e7c1213e0 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -27,6 +27,8 @@ use uuid::Uuid; use vek::*; pub type SiteId = u64; +/// Plugin identifier (sha256) +pub type PluginHash = [u8; 32]; pub enum LocalEvent { /// Applies upward force to entity's `Vel` @@ -424,6 +426,10 @@ pub struct ToggleSpriteLightEvent { pub pos: Vec3, pub enable: bool, } +pub struct RequestPluginsEvent { + pub entity: EcsEntity, + pub plugins: Vec, +} pub struct EventBus { queue: Mutex>, @@ -548,6 +554,7 @@ pub fn register_event_busses(ecs: &mut World) { ecs.insert(EventBus::::default()); ecs.insert(EventBus::::default()); ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); } /// Define ecs read data for event busses. And a way to convert them all to diff --git a/common/state/Cargo.toml b/common/state/Cargo.toml index e267f50639..a510ea1328 100644 --- a/common/state/Cargo.toml +++ b/common/state/Cargo.toml @@ -6,7 +6,7 @@ version = "0.10.0" [features] simd = ["vek/platform_intrinsics"] -plugins = ["common-assets/plugins", "toml", "wasmtime", "wasmtime-wasi", "tar", "bincode", "serde"] +plugins = ["common-assets/plugins", "toml", "wasmtime", "wasmtime-wasi", "tar", "bincode", "serde", "dep:sha2", "dep:hex"] default = ["simd"] @@ -40,6 +40,8 @@ wasmtime-wasi = { version = "17.0.0", optional = true } async-trait = { workspace = true } bytes = "^1" futures = "0.3.30" +sha2 = { workspace = true, optional = true } +hex = { workspace = true, optional = true } # Tweak running code #inline_tweak = { version = "1.0.8", features = ["release_tweak"] } diff --git a/common/state/src/plugin/mod.rs b/common/state/src/plugin/mod.rs index c5bec966d3..a6982ca42b 100644 --- a/common/state/src/plugin/mod.rs +++ b/common/state/src/plugin/mod.rs @@ -3,12 +3,12 @@ pub mod memory_manager; pub mod module; use bincode::ErrorKind; -use common::{assets::ASSETS_PATH, uid::Uid}; +use common::{assets::ASSETS_PATH, event::PluginHash, uid::Uid}; use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, HashSet}, fs, - io::Read, + io::{Read, Write}, path::{Path, PathBuf}, }; use tracing::{error, info}; @@ -19,6 +19,8 @@ use self::{ module::PluginModule, }; +use sha2::Digest; + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PluginData { name: String, @@ -26,17 +28,62 @@ pub struct PluginData { dependencies: HashSet, } +fn compute_hash(data: &[u8]) -> PluginHash { + let shasum = sha2::Sha256::digest(data); + let mut shasum_iter = shasum.iter(); + // a newer generic-array supports into_array ... + let shasum: PluginHash = std::array::from_fn(|_| *shasum_iter.next().unwrap()); + shasum +} + +fn cache_file_name( + mut base_dir: PathBuf, + hash: &PluginHash, + create_dir: bool, +) -> Result { + base_dir.push("server-plugins"); + if create_dir { + std::fs::create_dir_all(base_dir.as_path())?; + } + let name = hex::encode(hash); + base_dir.push(name); + base_dir.set_extension("plugin.tar"); + Ok(base_dir) +} + +// write received plugin to disk cache +pub fn store_server_plugin(base_dir: &Path, data: Vec) -> Result { + let shasum = compute_hash(data.as_slice()); + let result = cache_file_name(base_dir.to_path_buf(), &shasum, true)?; + let mut file = std::fs::File::create(result.as_path())?; + file.write_all(data.as_slice())?; + Ok(result) +} + +pub fn find_cached(base_dir: &Path, hash: &PluginHash) -> Result { + let local_path = cache_file_name(base_dir.to_path_buf(), hash, false)?; + if local_path.as_path().exists() { + Ok(local_path) + } else { + Err(std::io::Error::from(std::io::ErrorKind::NotFound)) + } +} + pub struct Plugin { data: PluginData, modules: Vec, #[allow(dead_code)] - files: HashMap>, + hash: PluginHash, + #[allow(dead_code)] + path: PathBuf, } impl Plugin { - pub fn from_reader(mut reader: R) -> Result { + pub fn from_path(path_buf: PathBuf) -> Result { + let mut reader = fs::File::open(path_buf.as_path()).map_err(PluginError::Io)?; let mut buf = Vec::new(); reader.read_to_end(&mut buf).map_err(PluginError::Io)?; + let shasum = compute_hash(buf.as_slice()); let mut files = tar::Archive::new(&*buf) .entries() @@ -76,7 +123,8 @@ impl Plugin { Ok(Plugin { data, modules, - files, + hash: shasum, + path: path_buf, }) } @@ -111,6 +159,9 @@ impl Plugin { }); result } + + /// get the path to the plugin file + pub fn path(&self) -> &Path { return self.path.as_path() } } #[derive(Default)] @@ -140,14 +191,12 @@ impl PluginMgr { .unwrap_or(false) { info!("Loading plugin at {:?}", entry.path()); - Plugin::from_reader(fs::File::open(entry.path()).map_err(PluginError::Io)?).map( - |plugin| { - if let Err(e) = common::assets::register_tar(entry.path()) { - error!("Plugin {:?} tar error {e:?}", entry.path()); - } - Some(plugin) - }, - ) + Plugin::from_path(entry.path()).map(|plugin| { + if let Err(e) = common::assets::register_tar(entry.path()) { + error!("Plugin {:?} tar error {e:?}", entry.path()); + } + Some(plugin) + }) } else { Ok(None) } @@ -169,6 +218,36 @@ impl PluginMgr { Ok(Self { plugins }) } + /// Add a plugin received from the server + pub fn load_server_plugin(&mut self, path: PathBuf) { + let _ = Plugin::from_path(path.clone()).map(|plugin| { + if let Err(e) = common::assets::register_tar(path.clone()) { + error!("Plugin {:?} tar error {e:?}", path.as_path()); + } + self.plugins.push(plugin); + }); + } + + pub fn cache_server_plugin( + &mut self, + base_dir: &Path, + data: Vec, + ) -> Result<(), std::io::Error> { + let path = store_server_plugin(base_dir, data)?; + self.load_server_plugin(path); + Ok(()) + } + + /// list all registered plugins + pub fn plugin_list(&self) -> Vec { + self.plugins.iter().map(|plugin| plugin.hash).collect() + } + + /// retrieve a specific plugin + pub fn find(&self, hash: &PluginHash) -> Option<&Plugin> { + self.plugins.iter().find(|plugin| &plugin.hash == hash) + } + pub fn load_event( &mut self, ecs: &EcsWorld, diff --git a/server/src/client.rs b/server/src/client.rs index 4b28ba8c93..6df53d894e 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -213,7 +213,8 @@ impl Client { | ServerGeneral::CreateEntity(_) | ServerGeneral::DeleteEntity(_) | ServerGeneral::Disconnect(_) - | ServerGeneral::Notification(_) => { + | ServerGeneral::Notification(_) + | ServerGeneral::PluginData(_) => { PreparedMsg::new(3, &g, &self.general_stream_params) }, } diff --git a/server/src/events/information.rs b/server/src/events/information.rs index 97d062e38a..04aeb03b2c 100644 --- a/server/src/events/information.rs +++ b/server/src/events/information.rs @@ -1,14 +1,17 @@ -use crate::client::Client; -use common::event::RequestSiteInfoEvent; +use crate::{client::Client, events::DispatcherBuilder}; +use common::event::{RequestPluginsEvent, RequestSiteInfoEvent}; use common_net::msg::{world_msg::EconomyInfo, ServerGeneral}; use specs::{DispatcherBuilder, ReadExpect, ReadStorage}; use std::collections::HashMap; use world::IndexOwned; +#[cfg(feature = "plugins")] +use {common_state::plugin::PluginMgr, std::io::Read}; use super::{event_dispatch, ServerEvent}; pub(super) fn register_event_systems(builder: &mut DispatcherBuilder) { event_dispatch::(builder); + event_dispatch::(builder); } #[cfg(not(feature = "worldgen"))] @@ -64,3 +67,53 @@ impl ServerEvent for RequestSiteInfoEvent { } } } + +/// Send missing plugins to the client +#[cfg(feature = "plugins")] +impl ServerEvent for RequestPluginsEvent { + type SystemData<'a> = (ReadExpect<'a, PluginMgr>, ReadStorage<'a, Client>); + + fn handle( + events: impl ExactSizeIterator, + (plugin_mgr, clients): Self::SystemData<'_>, + ) { + for mut ev in events { + for hash in ev.plugins.drain(..) { + if let Some(plugin) = plugin_mgr.find(&hash) { + match std::fs::File::open(plugin.path()) { + Ok(mut reader) => { + let mut buf = Vec::new(); + match reader.read_to_end(&mut buf) { + Ok(_) => { + clients.get(ev.entity).map(|c| { + c.send(ServerGeneral::PluginData(buf)).unwrap_or_else(|e| { + tracing::warn!( + "Error {e} sending plugin {hash:?} to client" + ) + }) + }); + }, + Err(e) => { + tracing::warn!( + "Error {e} reading plugin file {:?}", + plugin.path() + ); + }, + } + }, + Err(e) => { + tracing::warn!("Error {e} opening plugin file {:?}", plugin.path()); + }, + } + } + } + } + } +} + +#[cfg(not(feature = "plugins"))] +impl ServerEvent for RequestPluginsEvent { + type SystemData<'a> = (); + + fn handle(events: impl ExactSizeIterator, _: Self::SystemData<'_>) {} +} diff --git a/server/src/lib.rs b/server/src/lib.rs index 88371b9414..b2d74f8a0d 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -117,12 +117,15 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; -#[cfg(not(feature = "worldgen"))] -use test_world::{IndexOwned, World}; use tokio::runtime::Runtime; use tracing::{debug, error, info, trace, warn}; use vek::*; pub use world::{civ::WorldCivStage, sim::WorldSimStage, WorldGenerateStage}; +#[cfg(not(feature = "worldgen"))] +use { + common_net::msg::WorldMapMsg, + test_world::{IndexOwned, World}, +}; use crate::{ persistence::{DatabaseSettings, SqlLogMode}, @@ -308,6 +311,7 @@ impl Server { horizons: [(vec![0], vec![0]), (vec![0], vec![0])], alt: Grid::new(Vec2::new(1, 1), 1), sites: Vec::new(), + possible_starting_sites: Vec::new(), pois: Vec::new(), default_chunk: Arc::new(world.generate_oob_chunk()), }; @@ -318,7 +322,10 @@ impl Server { let mut state = State::server( Arc::clone(&pools), + #[cfg(feature = "worldgen")] world.sim().map_size_lg(), + #[cfg(not(feature = "worldgen"))] + common::terrain::map::MapSizeLg::new(Vec2::one()).unwrap(), Arc::clone(&map.default_chunk), |dispatcher_builder| { add_local_systems(dispatcher_builder); diff --git a/server/src/lod.rs b/server/src/lod.rs index 513fec45fa..60e0c57461 100644 --- a/server/src/lod.rs +++ b/server/src/lod.rs @@ -37,7 +37,9 @@ impl Lod { } #[cfg(not(feature = "worldgen"))] - pub fn from_world(world: &World, index: IndexRef) -> Self { Self::default() } + pub fn from_world(world: &World, index: IndexRef, _threadpool: &rayon::ThreadPool) -> Self { + Self::default() + } pub fn zone(&self, zone_pos: Vec2) -> &lod::Zone { self.zones.get(&zone_pos).unwrap_or(&EMPTY_ZONE) diff --git a/server/src/sys/msg/character_screen.rs b/server/src/sys/msg/character_screen.rs index a9ac9f26c0..4ea0812412 100644 --- a/server/src/sys/msg/character_screen.rs +++ b/server/src/sys/msg/character_screen.rs @@ -182,6 +182,7 @@ impl Sys { offhand.clone(), body, character_updater, + #[cfg(feature = "worldgen")] start_site.and_then(|site_idx| { // TODO: This corresponds to the ID generation logic in // `world/src/lib.rs`. Really, we should have @@ -214,6 +215,8 @@ impl Sys { ) }) }), + #[cfg(not(feature = "worldgen"))] + None, ) { debug!( ?error, diff --git a/server/src/sys/msg/general.rs b/server/src/sys/msg/general.rs index 423811b126..1c1e82a3ae 100644 --- a/server/src/sys/msg/general.rs +++ b/server/src/sys/msg/general.rs @@ -17,7 +17,7 @@ event_emitters! { command: event::CommandEvent, client_disconnect: event::ClientDisconnectEvent, chat: event::ChatEvent, - + plugins: event::RequestPluginsEvent, } } @@ -72,6 +72,10 @@ impl Sys { common::comp::DisconnectReason::ClientRequested, )); }, + ClientGeneral::RequestPlugins(plugins) => { + tracing::info!("Plugin request {plugins:x?}, {}", player.is_some()); + emitters.emit(event::RequestPluginsEvent { entity, plugins }); + }, _ => { debug!("Kicking possible misbehaving client due to invalid message request"); emitters.emit(event::ClientDisconnectEvent( diff --git a/server/src/sys/msg/in_game.rs b/server/src/sys/msg/in_game.rs index 29dacca4c3..830dc4a0ee 100644 --- a/server/src/sys/msg/in_game.rs +++ b/server/src/sys/msg/in_game.rs @@ -259,7 +259,8 @@ impl Sys { | ClientGeneral::LodZoneRequest { .. } | ClientGeneral::ChatMsg(_) | ClientGeneral::Command(..) - | ClientGeneral::Terminate => { + | ClientGeneral::Terminate + | ClientGeneral::RequestPlugins(_) => { debug!("Kicking possibly misbehaving client due to invalid client in game request"); emitters.emit(event::ClientDisconnectEvent( entity, diff --git a/server/src/sys/msg/register.rs b/server/src/sys/msg/register.rs index db4fb10286..7cd2b9c953 100644 --- a/server/src/sys/msg/register.rs +++ b/server/src/sys/msg/register.rs @@ -11,7 +11,7 @@ use common::{ recipe::{default_component_recipe_book, default_recipe_book, default_repair_recipe_book}, resources::TimeOfDay, shared_server_config::ServerConstants, - uid::{IdMaps, Uid}, + uid::Uid, }; use common_base::prof_span; use common_ecs::{Job, Origin, Phase, System}; @@ -52,9 +52,8 @@ pub struct ReadData<'a> { ability_map: ReadExpect<'a, comp::item::tool::AbilityMap>, map: ReadExpect<'a, WorldMapMsg>, trackers: TrackedStorages<'a>, - _healths: ReadStorage<'a, Health>, // used by plugin feature - _plugin_mgr: ReadPlugin<'a>, // used by plugin feature - _id_maps: Read<'a, IdMaps>, // used by plugin feature + #[allow(dead_code)] + plugin_mgr: ReadPlugin<'a>, // only used by plugins feature } /// This system will handle new messages from clients @@ -330,6 +329,10 @@ impl<'a> System<'a> for Sys { // Tell the client its request was successful. client.send(Ok(()))?; + #[cfg(feature = "plugins")] + let active_plugins = read_data.plugin_mgr.plugin_list(); + #[cfg(not(feature = "plugins"))] + let active_plugins = Vec::default(); let server_descriptions = &read_data.editable_settings.server_description; let description = ServerDescription { @@ -358,6 +361,7 @@ impl<'a> System<'a> for Sys { day_cycle_coefficient: read_data.settings.day_cycle_coefficient() }, description, + active_plugins, })?; debug!("Done initial sync with client."); diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 6fed09913e..b637822b22 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -717,11 +717,14 @@ where let world_aabr_in_chunks = Aabr { min: Vec2::zero(), // NOTE: Cast is correct because chunk coordinates must fit in an i32 (actually, i16). + #[cfg(feature = "worldgen")] max: world .sim() .get_size() .map(|x| x.saturating_sub(1)) .as_::(), + #[cfg(not(feature = "worldgen"))] + max: Vec2::one(), }; let (mut presences_positions_entities, mut presences_positions): (Vec<_>, Vec<_>) = diff --git a/server/src/sys/terrain_sync.rs b/server/src/sys/terrain_sync.rs index cfc32fe04e..74606e50d6 100644 --- a/server/src/sys/terrain_sync.rs +++ b/server/src/sys/terrain_sync.rs @@ -45,6 +45,7 @@ impl<'a> System<'a> for Sys { ): Self::SystemData, ) { let max_view_distance = server_settings.max_view_distance.unwrap_or(u32::MAX); + #[cfg(feature = "worldgen")] let (presences_position_entities, _) = super::terrain::prepare_player_presences( &world, max_view_distance, @@ -53,6 +54,8 @@ impl<'a> System<'a> for Sys { &presences, &clients, ); + #[cfg(not(feature = "worldgen"))] + let presences_position_entities: Vec<((vek::Vec2, i32), specs::Entity)> = Vec::new(); let real_max_view_distance = super::terrain::convert_to_loaded_vd(u32::MAX, max_view_distance); diff --git a/server/src/test_world.rs b/server/src/test_world.rs index 6cb3c1d992..eaaf930a6e 100644 --- a/server/src/test_world.rs +++ b/server/src/test_world.rs @@ -2,11 +2,13 @@ use common::{ calendar::Calendar, generation::{ChunkSupplement, EntityInfo}, resources::TimeOfDay, + rtsim::ChunkResource, terrain::{ Block, BlockKind, MapSizeLg, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize, }, vol::{ReadVol, RectVolSize, WriteVol}, }; +use enum_map::EnumMap; use rand::{prelude::*, rngs::SmallRng}; use std::time::Duration; use vek::*; @@ -48,6 +50,7 @@ impl World { &self, _index: IndexRef, chunk_pos: Vec2, + _rtsim_resources: Option>, _should_continue: impl FnMut() -> bool, _time: Option<(TimeOfDay, Calendar)>, ) -> Result<(TerrainChunk, ChunkSupplement), ()> { @@ -71,4 +74,6 @@ impl World { supplement, )) } + + pub fn get_location_name(&self, _index: IndexRef, _wpos2d: Vec2) -> Option { None } } diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 36885d0d20..c8a372ea9e 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -127,6 +127,7 @@ tokio = { workspace = true, features = ["rt-multi-thread"] } num_cpus = "1.0" inline_tweak = { workspace = true } itertools = { workspace = true } +sha2 = { workspace = true } # Discord RPC discord-sdk = { version = "0.3.0", optional = true } diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 431f411c64..3b2421139e 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -12,6 +12,8 @@ use crate::{ use client::{self, Client}; use common::{comp, event::UpdateCharacterMetadata, resources::DeltaTime}; use common_base::span; +#[cfg(feature = "plugins")] +use common_state::plugin::PluginMgr; use specs::WorldExt; use std::{cell::RefCell, rc::Rc}; use tracing::error; @@ -69,7 +71,9 @@ impl CharSelectionState { impl PlayState for CharSelectionState { fn enter(&mut self, global_state: &mut GlobalState, _: Direction) { // Load the player's character list - self.client.borrow_mut().load_character_list(); + if self.client.borrow().missing_plugins() == 0 { + self.client.borrow_mut().load_character_list(); + } // Updated localization in case the selected language was changed self.char_selection_ui.update_language(global_state.i18n); @@ -274,6 +278,20 @@ impl PlayState for CharSelectionState { Rc::clone(&self.client), ))); }, + client::Event::PluginDataReceived(data) => { + tracing::info!("plugin data {}", data.len()); + let mut client = self.client.borrow_mut(); + #[cfg(feature = "plugins")] + let _ = client + .state() + .ecs() + .write_resource::() + .cache_server_plugin(&global_state.config_dir, data); + if client.decrement_missing_plugins() == 0 { + // now load characters (plugins might contain items) + client.load_character_list(); + } + }, // TODO: See if we should handle StartSpectate here instead. _ => {}, } diff --git a/voxygen/src/menu/main/client_init.rs b/voxygen/src/menu/main/client_init.rs index fe7620d6f6..15a0b7cfd3 100644 --- a/voxygen/src/menu/main/client_init.rs +++ b/voxygen/src/menu/main/client_init.rs @@ -5,6 +5,7 @@ use client::{ }; use crossbeam_channel::{unbounded, Receiver, Sender, TryRecvError}; use std::{ + path::Path, sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -50,6 +51,7 @@ impl ClientInit { password: String, runtime: Arc, locale: Option, + config_dir: &Path, ) -> Self { let (tx, rx) = unbounded(); let (trust_tx, trust_rx) = unbounded(); @@ -58,6 +60,7 @@ impl ClientInit { let cancel2 = Arc::clone(&cancel); let runtime2 = Arc::clone(&runtime); + let config_dir = config_dir.to_path_buf(); runtime.spawn(async move { let trust_fn = |auth_server: &str| { @@ -89,6 +92,7 @@ impl ClientInit { let _ = init_stage_tx.send(stage); }, crate::ecs::sys::add_local_systems, + config_dir.clone(), ) .await { diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 86cc02230b..6fa7da0f6b 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -18,10 +18,13 @@ use client::{ use client_init::{ClientInit, Error as InitError, Msg as InitMsg}; use common::comp; use common_base::span; +#[cfg(feature = "plugins")] +use common_state::plugin::PluginMgr; use i18n::LocalizationHandle; #[cfg(feature = "singleplayer")] use server::ServerInitStage; -use std::sync::Arc; +use specs::WorldExt; +use std::{path::Path, sync::Arc}; use tokio::runtime; use tracing::error; use ui::{Event as MainMenuEvent, MainMenuUi}; @@ -129,6 +132,7 @@ impl PlayState for MainMenuState { global_state.settings.language.selected_language.clone(), ), &global_state.i18n, + &global_state.config_dir, ); }, Ok(Err(e)) => { @@ -216,6 +220,15 @@ impl PlayState for MainMenuState { // Poll client creation. match self.init.client().and_then(|init| init.poll()) { Some(InitMsg::Done(Ok(mut client))) => { + // load local plugins needed by the server + #[cfg(feature = "plugins")] + for path in client.take_local_plugins().drain(..) { + client + .state_mut() + .ecs_mut() + .write_resource::() + .load_server_plugin(path); + } // Register voxygen components / resources crate::ecs::init(client.state_mut().ecs_mut()); self.init = InitState::Pipeline(Box::new(client)); @@ -267,6 +280,21 @@ impl PlayState for MainMenuState { ); self.init = InitState::None; }, + client::Event::PluginDataReceived(data) => { + tracing::info!("plugin data {}", data.len()); + if let InitState::Pipeline(client) = &mut self.init { + #[cfg(feature = "plugins")] + let _ = client + .state() + .ecs() + .write_resource::() + .cache_server_plugin(&global_state.config_dir, data); + if client.decrement_missing_plugins() == 0 { + // now load characters (plugins might contain items) + client.load_character_list(); + } + } + }, _ => {}, } } @@ -378,6 +406,7 @@ impl PlayState for MainMenuState { .send_to_server .then_some(global_state.settings.language.selected_language.clone()), &global_state.i18n, + &global_state.config_dir, ); }, MainMenuEvent::CancelLoginAttempt => { @@ -609,6 +638,7 @@ fn attempt_login( runtime: &Arc, locale: Option, localized_strings: &LocalizationHandle, + config_dir: &Path, ) { let localization = localized_strings.read(); if let Err(err) = comp::Player::alias_validate(&username) { @@ -641,6 +671,7 @@ fn attempt_login( password, Arc::clone(runtime), locale, + config_dir, )); } } diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index b74d3b12b7..843465fd46 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -447,6 +447,9 @@ impl SessionState { client::Event::SpectatePosition(pos) => { self.scene.camera_mut().force_focus_pos(pos); }, + client::Event::PluginDataReceived(data) => { + tracing::warn!("Received plugin data at wrong time {}", data.len()); + }, } } From 12e9b491a45becf512b5c51a166f3c70684746ea Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Thu, 8 Feb 2024 23:48:08 +0100 Subject: [PATCH 2/9] fix clippy in non-plugin feature --- server/src/events/information.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/events/information.rs b/server/src/events/information.rs index 04aeb03b2c..7e521d23ca 100644 --- a/server/src/events/information.rs +++ b/server/src/events/information.rs @@ -115,5 +115,5 @@ impl ServerEvent for RequestPluginsEvent { impl ServerEvent for RequestPluginsEvent { type SystemData<'a> = (); - fn handle(events: impl ExactSizeIterator, _: Self::SystemData<'_>) {} + fn handle(_events: impl ExactSizeIterator, _: Self::SystemData<'_>) {} } From 029f3e2a1f686bbc8da1e1e2df296ead13d7381e Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Sat, 10 Feb 2024 18:05:41 +0100 Subject: [PATCH 3/9] remove merge artifact --- server/src/sys/msg/register.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/sys/msg/register.rs b/server/src/sys/msg/register.rs index 7cd2b9c953..024920ef01 100644 --- a/server/src/sys/msg/register.rs +++ b/server/src/sys/msg/register.rs @@ -6,7 +6,7 @@ use crate::{ EditableSettings, Settings, }; use common::{ - comp::{self, Admin, Health, Player, Stats}, + comp::{self, Admin, Player, Stats}, event::{ClientDisconnectEvent, EventBus, MakeAdminEvent}, recipe::{default_component_recipe_book, default_recipe_book, default_repair_recipe_book}, resources::TimeOfDay, From ed25ea98ced2a7cf0d13935b5f23c507de05c993 Mon Sep 17 00:00:00 2001 From: Isse Date: Tue, 27 Feb 2024 13:44:14 +0100 Subject: [PATCH 4/9] store plugin data in memory --- common/state/src/plugin/mod.rs | 10 ++++++- server/src/events/information.rs | 51 ++++++++++---------------------- 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/common/state/src/plugin/mod.rs b/common/state/src/plugin/mod.rs index a6982ca42b..1fa71a8de1 100644 --- a/common/state/src/plugin/mod.rs +++ b/common/state/src/plugin/mod.rs @@ -76,6 +76,8 @@ pub struct Plugin { hash: PluginHash, #[allow(dead_code)] path: PathBuf, + #[allow(dead_code)] + data_buf: Vec, } impl Plugin { @@ -120,11 +122,14 @@ impl Plugin { }) .collect::>()?; + let data_buf = fs::read(&path_buf).map_err(PluginError::Io)?; + Ok(Plugin { data, modules, hash: shasum, path: path_buf, + data_buf, }) } @@ -161,7 +166,10 @@ impl Plugin { } /// get the path to the plugin file - pub fn path(&self) -> &Path { return self.path.as_path() } + pub fn path(&self) -> &Path { self.path.as_path() } + + /// Get the data of this plugin + pub fn data_buf(&self) -> &[u8] { &self.data_buf } } #[derive(Default)] diff --git a/server/src/events/information.rs b/server/src/events/information.rs index 7e521d23ca..74fa678c8f 100644 --- a/server/src/events/information.rs +++ b/server/src/events/information.rs @@ -1,16 +1,17 @@ -use crate::{client::Client, events::DispatcherBuilder}; +use crate::client::Client; use common::event::{RequestPluginsEvent, RequestSiteInfoEvent}; use common_net::msg::{world_msg::EconomyInfo, ServerGeneral}; +#[cfg(feature = "plugins")] +use common_state::plugin::PluginMgr; use specs::{DispatcherBuilder, ReadExpect, ReadStorage}; use std::collections::HashMap; use world::IndexOwned; -#[cfg(feature = "plugins")] -use {common_state::plugin::PluginMgr, std::io::Read}; use super::{event_dispatch, ServerEvent}; pub(super) fn register_event_systems(builder: &mut DispatcherBuilder) { event_dispatch::(builder); + #[cfg(feature = "plugins")] event_dispatch::(builder); } @@ -78,42 +79,22 @@ impl ServerEvent for RequestPluginsEvent { (plugin_mgr, clients): Self::SystemData<'_>, ) { for mut ev in events { + let Some(client) = clients.get(ev.entity) else { + continue; + }; + for hash in ev.plugins.drain(..) { if let Some(plugin) = plugin_mgr.find(&hash) { - match std::fs::File::open(plugin.path()) { - Ok(mut reader) => { - let mut buf = Vec::new(); - match reader.read_to_end(&mut buf) { - Ok(_) => { - clients.get(ev.entity).map(|c| { - c.send(ServerGeneral::PluginData(buf)).unwrap_or_else(|e| { - tracing::warn!( - "Error {e} sending plugin {hash:?} to client" - ) - }) - }); - }, - Err(e) => { - tracing::warn!( - "Error {e} reading plugin file {:?}", - plugin.path() - ); - }, - } - }, - Err(e) => { - tracing::warn!("Error {e} opening plugin file {:?}", plugin.path()); - }, - } + let buf = Vec::from(plugin.data_buf()); + // TODO: @perf We could possibly make this more performant by caching prepared + // messages for each plugin. + client + .send(ServerGeneral::PluginData(buf)) + .unwrap_or_else(|e| { + tracing::warn!("Error {e} sending plugin {hash:?} to client") + }); } } } } } - -#[cfg(not(feature = "plugins"))] -impl ServerEvent for RequestPluginsEvent { - type SystemData<'a> = (); - - fn handle(_events: impl ExactSizeIterator, _: Self::SystemData<'_>) {} -} From eccdc18eb7f708bdae498a9e776cc9a4347f788f Mon Sep 17 00:00:00 2001 From: Isse Date: Tue, 27 Feb 2024 14:38:47 +0100 Subject: [PATCH 5/9] fix ci-clippy2 --- server/src/events/information.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/events/information.rs b/server/src/events/information.rs index 74fa678c8f..b5e742d46c 100644 --- a/server/src/events/information.rs +++ b/server/src/events/information.rs @@ -1,5 +1,5 @@ use crate::client::Client; -use common::event::{RequestPluginsEvent, RequestSiteInfoEvent}; +use common::event::RequestSiteInfoEvent; use common_net::msg::{world_msg::EconomyInfo, ServerGeneral}; #[cfg(feature = "plugins")] use common_state::plugin::PluginMgr; @@ -12,7 +12,7 @@ use super::{event_dispatch, ServerEvent}; pub(super) fn register_event_systems(builder: &mut DispatcherBuilder) { event_dispatch::(builder); #[cfg(feature = "plugins")] - event_dispatch::(builder); + event_dispatch::(builder); } #[cfg(not(feature = "worldgen"))] @@ -71,7 +71,7 @@ impl ServerEvent for RequestSiteInfoEvent { /// Send missing plugins to the client #[cfg(feature = "plugins")] -impl ServerEvent for RequestPluginsEvent { +impl ServerEvent for common::event::RequestPluginsEvent { type SystemData<'a> = (ReadExpect<'a, PluginMgr>, ReadStorage<'a, Client>); fn handle( From 8dd4729965b82cec8b29a118960a65d62b0ae9c4 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Thu, 14 Mar 2024 00:10:39 +0100 Subject: [PATCH 6/9] make missing plugins a set --- client/src/lib.rs | 19 ++++++++++--------- common/state/src/plugin/mod.rs | 13 +++++++------ voxygen/src/menu/char_selection/mod.rs | 14 ++++++++------ voxygen/src/menu/main/mod.rs | 12 +++++++----- 4 files changed, 32 insertions(+), 26 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 18ee331920..a5ab702d33 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -331,7 +331,7 @@ pub struct Client { connected_server_constants: ServerConstants, /// Requested but not yet received plugins - missing_plugins: u32, + missing_plugins: HashSet, /// Locally cached plugins needed by the server local_plugins: Vec, } @@ -943,7 +943,7 @@ impl Client { _ = ping_interval.tick() => ping_stream.send(PingMsg::Ping)?, } }; - let missing_plugins_num = missing_plugins.len(); + let missing_plugins_set = missing_plugins.iter().collect(); if !missing_plugins.is_empty() { stream.send(ClientGeneral::RequestPlugins(missing_plugins))?; } @@ -1027,7 +1027,7 @@ impl Client { dt_adjustment: 1.0, connected_server_constants: server_constants, - missing_plugins: missing_plugins_num as u32, + missing_plugins: missing_plugins_set, local_plugins, }) } @@ -2580,7 +2580,8 @@ impl Client { frontend_events.push(Event::Notification(n)); }, ServerGeneral::PluginData(d) => { - tracing::info!("plugin data #1 {}", d.len()); + let plugin_len = d.len(); + tracing::info!(?plugin_len, "plugin data"); frontend_events.push(Event::PluginDataReceived(d)); }, _ => unreachable!("Not a general msg"), @@ -3228,15 +3229,15 @@ impl Client { } /// another plugin data received, is this the last one - pub fn decrement_missing_plugins(&mut self) -> u32 { - if self.missing_plugins > 0 { - self.missing_plugins -= 1; + pub fn plugin_received(&mut self, hash: PluginHash) -> usize { + if !self.missing_plugins.remove(&hash) { + tracing::warn!(?hash, "received unrequested plugin"); } - self.missing_plugins + self.missing_plugins.len() } /// number of requested plugins - pub fn missing_plugins(&self) -> u32 { self.missing_plugins } + pub fn num_missing_plugins(&self) -> usize { self.missing_plugins.len() } /// extract list of locally cached plugins to load pub fn take_local_plugins(&mut self) -> Vec { std::mem::take(&mut self.local_plugins) } diff --git a/common/state/src/plugin/mod.rs b/common/state/src/plugin/mod.rs index 1fa71a8de1..5ff2c32017 100644 --- a/common/state/src/plugin/mod.rs +++ b/common/state/src/plugin/mod.rs @@ -227,23 +227,24 @@ impl PluginMgr { } /// Add a plugin received from the server - pub fn load_server_plugin(&mut self, path: PathBuf) { - let _ = Plugin::from_path(path.clone()).map(|plugin| { + pub fn load_server_plugin(&mut self, path: PathBuf) -> Result { + Plugin::from_path(path.clone()).map(|plugin| { if let Err(e) = common::assets::register_tar(path.clone()) { error!("Plugin {:?} tar error {e:?}", path.as_path()); } + let hash = plugin.hash.clone(); self.plugins.push(plugin); - }); + hash + }) } pub fn cache_server_plugin( &mut self, base_dir: &Path, data: Vec, - ) -> Result<(), std::io::Error> { + ) -> Result { let path = store_server_plugin(base_dir, data)?; - self.load_server_plugin(path); - Ok(()) + self.load_server_plugin(path) } /// list all registered plugins diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 3b2421139e..7641ad3fa8 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -71,7 +71,7 @@ impl CharSelectionState { impl PlayState for CharSelectionState { fn enter(&mut self, global_state: &mut GlobalState, _: Direction) { // Load the player's character list - if self.client.borrow().missing_plugins() == 0 { + if self.client.borrow().num_missing_plugins() == 0 { self.client.borrow_mut().load_character_list(); } @@ -282,14 +282,16 @@ impl PlayState for CharSelectionState { tracing::info!("plugin data {}", data.len()); let mut client = self.client.borrow_mut(); #[cfg(feature = "plugins")] - let _ = client + if let Ok(hash) = client .state() .ecs() .write_resource::() - .cache_server_plugin(&global_state.config_dir, data); - if client.decrement_missing_plugins() == 0 { - // now load characters (plugins might contain items) - client.load_character_list(); + .cache_server_plugin(&global_state.config_dir, data) + { + if client.plugin_received(hash) == 0 { + // now load characters (plugins might contain items) + client.load_character_list(); + } } }, // TODO: See if we should handle StartSpectate here instead. diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 6fa7da0f6b..3c27443b30 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -284,14 +284,16 @@ impl PlayState for MainMenuState { tracing::info!("plugin data {}", data.len()); if let InitState::Pipeline(client) = &mut self.init { #[cfg(feature = "plugins")] - let _ = client + if let Ok(hash) = client .state() .ecs() .write_resource::() - .cache_server_plugin(&global_state.config_dir, data); - if client.decrement_missing_plugins() == 0 { - // now load characters (plugins might contain items) - client.load_character_list(); + .cache_server_plugin(&global_state.config_dir, data) + { + if client.plugin_received(hash) == 0 { + // now load characters (plugins might contain items) + client.load_character_list(); + } } } }, From 166653b355c53c379daa74d15b94f754d7fc9fdc Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Thu, 14 Mar 2024 00:21:18 +0100 Subject: [PATCH 7/9] report plugin errors to the user --- common/state/src/plugin/mod.rs | 2 +- voxygen/src/menu/char_selection/mod.rs | 13 ++++++++----- voxygen/src/menu/main/mod.rs | 13 ++++++++----- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/common/state/src/plugin/mod.rs b/common/state/src/plugin/mod.rs index 5ff2c32017..bb37436a04 100644 --- a/common/state/src/plugin/mod.rs +++ b/common/state/src/plugin/mod.rs @@ -243,7 +243,7 @@ impl PluginMgr { base_dir: &Path, data: Vec, ) -> Result { - let path = store_server_plugin(base_dir, data)?; + let path = store_server_plugin(base_dir, data).map_err(PluginError::Io)?; self.load_server_plugin(path) } diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 7641ad3fa8..09f23532fb 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -282,16 +282,19 @@ impl PlayState for CharSelectionState { tracing::info!("plugin data {}", data.len()); let mut client = self.client.borrow_mut(); #[cfg(feature = "plugins")] - if let Ok(hash) = client + match client .state() .ecs() .write_resource::() .cache_server_plugin(&global_state.config_dir, data) { - if client.plugin_received(hash) == 0 { - // now load characters (plugins might contain items) - client.load_character_list(); - } + Ok(hash) => { + if client.plugin_received(hash) == 0 { + // now load characters (plugins might contain items) + client.load_character_list(); + } + }, + Err(e) => tracing::error!(?e, "cache_server_plugin"), } }, // TODO: See if we should handle StartSpectate here instead. diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 3c27443b30..5b924692c7 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -284,16 +284,19 @@ impl PlayState for MainMenuState { tracing::info!("plugin data {}", data.len()); if let InitState::Pipeline(client) = &mut self.init { #[cfg(feature = "plugins")] - if let Ok(hash) = client + match client .state() .ecs() .write_resource::() .cache_server_plugin(&global_state.config_dir, data) { - if client.plugin_received(hash) == 0 { - // now load characters (plugins might contain items) - client.load_character_list(); - } + Ok(hash) => { + if client.plugin_received(hash) == 0 { + // now load characters (plugins might contain items) + client.load_character_list(); + } + }, + Err(e) => tracing::error!(?e, "cache_server_plugin"), } } }, From f0194e6d9bb401c527fe785c51d13b852cf88f0b Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Thu, 14 Mar 2024 00:32:55 +0100 Subject: [PATCH 8/9] more elaborate error handling --- client/src/lib.rs | 2 +- voxygen/src/menu/char_selection/mod.rs | 30 +++++++++--------- voxygen/src/menu/main/mod.rs | 42 +++++++++++++++----------- 3 files changed, 41 insertions(+), 33 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index a5ab702d33..c08cb3a0bc 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -943,7 +943,7 @@ impl Client { _ = ping_interval.tick() => ping_stream.send(PingMsg::Ping)?, } }; - let missing_plugins_set = missing_plugins.iter().collect(); + let missing_plugins_set = missing_plugins.iter().cloned().collect(); if !missing_plugins.is_empty() { stream.send(ClientGeneral::RequestPlugins(missing_plugins))?; } diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 09f23532fb..d2b8d52de1 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -279,22 +279,24 @@ impl PlayState for CharSelectionState { ))); }, client::Event::PluginDataReceived(data) => { - tracing::info!("plugin data {}", data.len()); - let mut client = self.client.borrow_mut(); #[cfg(feature = "plugins")] - match client - .state() - .ecs() - .write_resource::() - .cache_server_plugin(&global_state.config_dir, data) { - Ok(hash) => { - if client.plugin_received(hash) == 0 { - // now load characters (plugins might contain items) - client.load_character_list(); - } - }, - Err(e) => tracing::error!(?e, "cache_server_plugin"), + tracing::info!("plugin data {}", data.len()); + let mut client = self.client.borrow_mut(); + let hash = client + .state() + .ecs() + .write_resource::() + .cache_server_plugin(&global_state.config_dir, data); + match hash { + Ok(hash) => { + if client.plugin_received(hash) == 0 { + // now load characters (plugins might contain items) + client.load_character_list(); + } + }, + Err(e) => tracing::error!(?e, "cache_server_plugin"), + } } }, // TODO: See if we should handle StartSpectate here instead. diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 5b924692c7..42cb84f9d6 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -223,11 +223,14 @@ impl PlayState for MainMenuState { // load local plugins needed by the server #[cfg(feature = "plugins")] for path in client.take_local_plugins().drain(..) { - client + if let Err(e) = client .state_mut() .ecs_mut() .write_resource::() - .load_server_plugin(path); + .load_server_plugin(path) + { + tracing::error!(?e, "load local plugin"); + } } // Register voxygen components / resources crate::ecs::init(client.state_mut().ecs_mut()); @@ -281,22 +284,25 @@ impl PlayState for MainMenuState { self.init = InitState::None; }, client::Event::PluginDataReceived(data) => { - tracing::info!("plugin data {}", data.len()); - if let InitState::Pipeline(client) = &mut self.init { - #[cfg(feature = "plugins")] - match client - .state() - .ecs() - .write_resource::() - .cache_server_plugin(&global_state.config_dir, data) - { - Ok(hash) => { - if client.plugin_received(hash) == 0 { - // now load characters (plugins might contain items) - client.load_character_list(); - } - }, - Err(e) => tracing::error!(?e, "cache_server_plugin"), + #[cfg(feature = "plugins")] + { + tracing::info!("plugin data {}", data.len()); + if let InitState::Pipeline(client) = &mut self.init { + let hash = client + .state() + .ecs() + .write_resource::() + .cache_server_plugin(&global_state.config_dir, data); + match hash { + Ok(hash) => { + if client.plugin_received(hash) == 0 { + // now load characters (plugins might contain + // items) + client.load_character_list(); + } + }, + Err(e) => tracing::error!(?e, "cache_server_plugin"), + } } } }, From f21d76000fab1bc3f5f878a8760c7a35ad59816e Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Thu, 14 Mar 2024 00:46:06 +0100 Subject: [PATCH 9/9] fix clippy --- common/state/src/plugin/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/state/src/plugin/mod.rs b/common/state/src/plugin/mod.rs index bb37436a04..d4ce8dcf0c 100644 --- a/common/state/src/plugin/mod.rs +++ b/common/state/src/plugin/mod.rs @@ -232,7 +232,7 @@ impl PluginMgr { if let Err(e) = common::assets::register_tar(path.clone()) { error!("Plugin {:?} tar error {e:?}", path.as_path()); } - let hash = plugin.hash.clone(); + let hash = plugin.hash; self.plugins.push(plugin); hash })