mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'christof/plugin_network' into 'master'
Load missing plugins from the server See merge request veloren/veloren!4256
This commit is contained in:
commit
24c511b5a4
@ -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
|
||||
|
||||
|
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -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",
|
||||
|
@ -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" }
|
||||
|
@ -67,6 +67,7 @@ fn main() {
|
||||
|provider| provider == "https://auth.veloren.net",
|
||||
&|_| {},
|
||||
|_| {},
|
||||
Default::default(),
|
||||
))
|
||||
.expect("Failed to create client instance");
|
||||
|
||||
|
@ -75,6 +75,7 @@ pub fn make_client(
|
||||
|_| true,
|
||||
&|_| {},
|
||||
|_| {},
|
||||
Default::default(),
|
||||
))
|
||||
.ok()
|
||||
}
|
||||
|
@ -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<f32>),
|
||||
SpectatePosition(Vec3<f32>),
|
||||
PluginDataReceived(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -326,6 +330,10 @@ pub struct Client {
|
||||
dt_adjustment: f64,
|
||||
|
||||
connected_server_constants: ServerConstants,
|
||||
/// Requested but not yet received plugins
|
||||
missing_plugins: HashSet<PluginHash>,
|
||||
/// Locally cached plugins needed by the server
|
||||
local_plugins: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
/// 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<Self, Error> {
|
||||
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<PluginHash> = Vec::new();
|
||||
let mut local_plugins: Vec<PathBuf> = Vec::new();
|
||||
#[cfg(feature = "plugins")]
|
||||
{
|
||||
let already_present = state.ecs().read_resource::<PluginMgr>().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::<comp::Last<CharacterState>>();
|
||||
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_set = missing_plugins.iter().cloned().collect();
|
||||
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_set,
|
||||
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,11 @@ impl Client {
|
||||
ServerGeneral::Notification(n) => {
|
||||
frontend_events.push(Event::Notification(n));
|
||||
},
|
||||
ServerGeneral::PluginData(d) => {
|
||||
let plugin_len = d.len();
|
||||
tracing::info!(?plugin_len, "plugin data");
|
||||
frontend_events.push(Event::PluginDataReceived(d));
|
||||
},
|
||||
_ => unreachable!("Not a general msg"),
|
||||
}
|
||||
Ok(())
|
||||
@ -3182,6 +3227,20 @@ impl Client {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// another plugin data received, is this the last one
|
||||
pub fn plugin_received(&mut self, hash: PluginHash) -> usize {
|
||||
if !self.missing_plugins.remove(&hash) {
|
||||
tracing::warn!(?hash, "received unrequested plugin");
|
||||
}
|
||||
self.missing_plugins.len()
|
||||
}
|
||||
|
||||
/// number of requested 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<PathBuf> { std::mem::take(&mut self.local_plugins) }
|
||||
}
|
||||
|
||||
impl Drop for Client {
|
||||
@ -3247,6 +3306,7 @@ mod tests {
|
||||
|suggestion: &str| suggestion == auth_server,
|
||||
&|_| {},
|
||||
|_| {},
|
||||
PathBuf::default(),
|
||||
));
|
||||
let localisation = LocalizationHandle::load_expect("en");
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -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<PluginHash>),
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -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<PluginHash>,
|
||||
},
|
||||
}
|
||||
|
||||
@ -219,6 +220,8 @@ pub enum ServerGeneral {
|
||||
/// Suggest the client to spectate a position. Called after client has
|
||||
/// requested teleport etc.
|
||||
SpectatePosition(Vec3<f32>),
|
||||
/// Plugin data requested from the server
|
||||
PluginData(Vec<u8>),
|
||||
}
|
||||
|
||||
impl ServerGeneral {
|
||||
@ -358,6 +361,7 @@ impl ServerMsg {
|
||||
| ServerGeneral::Disconnect(_)
|
||||
| ServerGeneral::Notification(_)
|
||||
| ServerGeneral::LodZoneUpdate { .. } => true,
|
||||
ServerGeneral::PluginData(_) => true,
|
||||
}
|
||||
},
|
||||
ServerMsg::Ping(_) => true,
|
||||
|
@ -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<i32>,
|
||||
pub enable: bool,
|
||||
}
|
||||
pub struct RequestPluginsEvent {
|
||||
pub entity: EcsEntity,
|
||||
pub plugins: Vec<PluginHash>,
|
||||
}
|
||||
|
||||
pub struct EventBus<E> {
|
||||
queue: Mutex<VecDeque<E>>,
|
||||
@ -548,6 +554,7 @@ pub fn register_event_busses(ecs: &mut World) {
|
||||
ecs.insert(EventBus::<StartTeleportingEvent>::default());
|
||||
ecs.insert(EventBus::<ToggleSpriteLightEvent>::default());
|
||||
ecs.insert(EventBus::<TransformEvent>::default());
|
||||
ecs.insert(EventBus::<RequestPluginsEvent>::default());
|
||||
}
|
||||
|
||||
/// Define ecs read data for event busses. And a way to convert them all to
|
||||
|
@ -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"] }
|
||||
|
@ -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,64 @@ pub struct PluginData {
|
||||
dependencies: HashSet<String>,
|
||||
}
|
||||
|
||||
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<PathBuf, std::io::Error> {
|
||||
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<u8>) -> Result<PathBuf, std::io::Error> {
|
||||
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<PathBuf, std::io::Error> {
|
||||
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<PluginModule>,
|
||||
#[allow(dead_code)]
|
||||
files: HashMap<PathBuf, Vec<u8>>,
|
||||
hash: PluginHash,
|
||||
#[allow(dead_code)]
|
||||
path: PathBuf,
|
||||
#[allow(dead_code)]
|
||||
data_buf: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Plugin {
|
||||
pub fn from_reader<R: Read>(mut reader: R) -> Result<Self, PluginError> {
|
||||
pub fn from_path(path_buf: PathBuf) -> Result<Self, PluginError> {
|
||||
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()
|
||||
@ -73,10 +122,14 @@ impl Plugin {
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let data_buf = fs::read(&path_buf).map_err(PluginError::Io)?;
|
||||
|
||||
Ok(Plugin {
|
||||
data,
|
||||
modules,
|
||||
files,
|
||||
hash: shasum,
|
||||
path: path_buf,
|
||||
data_buf,
|
||||
})
|
||||
}
|
||||
|
||||
@ -111,6 +164,12 @@ impl Plugin {
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
/// get the path to the plugin file
|
||||
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)]
|
||||
@ -140,14 +199,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 +226,37 @@ impl PluginMgr {
|
||||
Ok(Self { plugins })
|
||||
}
|
||||
|
||||
/// Add a plugin received from the server
|
||||
pub fn load_server_plugin(&mut self, path: PathBuf) -> Result<PluginHash, PluginError> {
|
||||
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;
|
||||
self.plugins.push(plugin);
|
||||
hash
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cache_server_plugin(
|
||||
&mut self,
|
||||
base_dir: &Path,
|
||||
data: Vec<u8>,
|
||||
) -> Result<PluginHash, PluginError> {
|
||||
let path = store_server_plugin(base_dir, data).map_err(PluginError::Io)?;
|
||||
self.load_server_plugin(path)
|
||||
}
|
||||
|
||||
/// list all registered plugins
|
||||
pub fn plugin_list(&self) -> Vec<PluginHash> {
|
||||
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,
|
||||
|
@ -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)
|
||||
},
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
use crate::client::Client;
|
||||
use common::event::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;
|
||||
@ -9,6 +11,8 @@ use super::{event_dispatch, ServerEvent};
|
||||
|
||||
pub(super) fn register_event_systems(builder: &mut DispatcherBuilder) {
|
||||
event_dispatch::<RequestSiteInfoEvent>(builder);
|
||||
#[cfg(feature = "plugins")]
|
||||
event_dispatch::<common::event::RequestPluginsEvent>(builder);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "worldgen"))]
|
||||
@ -64,3 +68,33 @@ impl ServerEvent for RequestSiteInfoEvent {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Send missing plugins to the client
|
||||
#[cfg(feature = "plugins")]
|
||||
impl ServerEvent for common::event::RequestPluginsEvent {
|
||||
type SystemData<'a> = (ReadExpect<'a, PluginMgr>, ReadStorage<'a, Client>);
|
||||
|
||||
fn handle(
|
||||
events: impl ExactSizeIterator<Item = Self>,
|
||||
(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) {
|
||||
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")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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<i32>) -> &lod::Zone {
|
||||
self.zones.get(&zone_pos).unwrap_or(&EMPTY_ZONE)
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -6,12 +6,12 @@ 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,
|
||||
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.");
|
||||
|
||||
|
@ -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_::<i32>(),
|
||||
#[cfg(not(feature = "worldgen"))]
|
||||
max: Vec2::one(),
|
||||
};
|
||||
|
||||
let (mut presences_positions_entities, mut presences_positions): (Vec<_>, Vec<_>) =
|
||||
|
@ -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<i16>, i32), specs::Entity)> = Vec::new();
|
||||
let real_max_view_distance =
|
||||
super::terrain::convert_to_loaded_vd(u32::MAX, max_view_distance);
|
||||
|
||||
|
@ -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<i32>,
|
||||
_rtsim_resources: Option<EnumMap<ChunkResource, f32>>,
|
||||
_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<i32>) -> Option<String> { None }
|
||||
}
|
||||
|
@ -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 }
|
||||
|
@ -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().num_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,27 @@ impl PlayState for CharSelectionState {
|
||||
Rc::clone(&self.client),
|
||||
)));
|
||||
},
|
||||
client::Event::PluginDataReceived(data) => {
|
||||
#[cfg(feature = "plugins")]
|
||||
{
|
||||
tracing::info!("plugin data {}", data.len());
|
||||
let mut client = self.client.borrow_mut();
|
||||
let hash = client
|
||||
.state()
|
||||
.ecs()
|
||||
.write_resource::<PluginMgr>()
|
||||
.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.
|
||||
_ => {},
|
||||
}
|
||||
|
@ -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<runtime::Runtime>,
|
||||
locale: Option<String>,
|
||||
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
|
||||
{
|
||||
|
@ -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,18 @@ 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(..) {
|
||||
if let Err(e) = client
|
||||
.state_mut()
|
||||
.ecs_mut()
|
||||
.write_resource::<PluginMgr>()
|
||||
.load_server_plugin(path)
|
||||
{
|
||||
tracing::error!(?e, "load local plugin");
|
||||
}
|
||||
}
|
||||
// Register voxygen components / resources
|
||||
crate::ecs::init(client.state_mut().ecs_mut());
|
||||
self.init = InitState::Pipeline(Box::new(client));
|
||||
@ -267,6 +283,29 @@ impl PlayState for MainMenuState {
|
||||
);
|
||||
self.init = InitState::None;
|
||||
},
|
||||
client::Event::PluginDataReceived(data) => {
|
||||
#[cfg(feature = "plugins")]
|
||||
{
|
||||
tracing::info!("plugin data {}", data.len());
|
||||
if let InitState::Pipeline(client) = &mut self.init {
|
||||
let hash = client
|
||||
.state()
|
||||
.ecs()
|
||||
.write_resource::<PluginMgr>()
|
||||
.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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
@ -378,6 +417,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 +649,7 @@ fn attempt_login(
|
||||
runtime: &Arc<runtime::Runtime>,
|
||||
locale: Option<String>,
|
||||
localized_strings: &LocalizationHandle,
|
||||
config_dir: &Path,
|
||||
) {
|
||||
let localization = localized_strings.read();
|
||||
if let Err(err) = comp::Player::alias_validate(&username) {
|
||||
@ -641,6 +682,7 @@ fn attempt_login(
|
||||
password,
|
||||
Arc::clone(runtime),
|
||||
locale,
|
||||
config_dir,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user