Merge branch 'ccgauche/moved-plugins-ecs' into 'master'

Improved ECS system for plugins to enable async execution of plugins in systems

See merge request veloren/veloren!1857
This commit is contained in:
Marcel 2021-03-07 23:00:07 +00:00
commit b7079b454c
7 changed files with 106 additions and 74 deletions

View File

@ -1,14 +1,27 @@
use std::sync::atomic::{AtomicPtr, AtomicU32, AtomicU64, Ordering}; use std::sync::atomic::{AtomicPtr, AtomicU32, AtomicU64, Ordering};
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
use specs::World; use specs::{shred::Fetch, Entities, ReadStorage};
use wasmer::{Function, Memory, Value}; use wasmer::{Function, Memory, Value};
use common::{
comp::Health,
uid::{Uid, UidAllocator},
};
use super::errors::{MemoryAllocationError, PluginModuleError}; use super::errors::{MemoryAllocationError, PluginModuleError};
pub struct EcsWorld<'a> {
pub entities: Entities<'a>,
pub health: ReadStorage<'a, Health>,
pub uid: ReadStorage<'a, Uid>,
//pub player: ReadStorage<'a, Player>,
pub uid_allocator: Fetch<'a, UidAllocator>,
}
/// This structure wraps the ECS pointer to ensure safety /// This structure wraps the ECS pointer to ensure safety
pub struct EcsAccessManager { pub struct EcsAccessManager {
ecs_pointer: AtomicPtr<World>, ecs_pointer: AtomicPtr<EcsWorld<'static>>,
} }
impl Default for EcsAccessManager { impl Default for EcsAccessManager {
@ -22,7 +35,7 @@ impl Default for EcsAccessManager {
impl EcsAccessManager { impl EcsAccessManager {
// This function take a World reference and a function to execute ensuring the // This function take a World reference and a function to execute ensuring the
// pointer will never be corrupted during the execution of the function! // pointer will never be corrupted during the execution of the function!
pub fn execute_with<T>(&self, world: &World, func: impl FnOnce() -> T) -> T { pub fn execute_with<T>(&self, world: &EcsWorld, func: impl FnOnce() -> T) -> T {
let _guard = scopeguard::guard((), |_| { let _guard = scopeguard::guard((), |_| {
// ensure the pointer is cleared in any case // ensure the pointer is cleared in any case
self.ecs_pointer self.ecs_pointer
@ -45,7 +58,7 @@ impl EcsAccessManager {
/// reference somewhere else /// reference somewhere else
/// - All that ensure that the reference doesn't exceed the execute_with /// - All that ensure that the reference doesn't exceed the execute_with
/// function scope /// function scope
pub unsafe fn get(&self) -> Option<&World> { pub unsafe fn get(&self) -> Option<&EcsWorld> {
// ptr::as_ref will automatically check for null // ptr::as_ref will automatically check for null
self.ecs_pointer.load(Ordering::Relaxed).as_ref() self.ecs_pointer.load(Ordering::Relaxed).as_ref()
} }

View File

@ -5,7 +5,6 @@ pub mod wasm_env;
use common::assets::ASSETS_PATH; use common::assets::ASSETS_PATH;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::World;
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
fs, fs,
@ -18,6 +17,7 @@ use plugin_api::Event;
use self::{ use self::{
errors::PluginError, errors::PluginError,
memory_manager::EcsWorld,
module::{PluginModule, PreparedEventQuery}, module::{PluginModule, PreparedEventQuery},
}; };
@ -83,7 +83,7 @@ impl Plugin {
pub fn execute_prepared<T>( pub fn execute_prepared<T>(
&self, &self,
ecs: &World, ecs: &EcsWorld,
event: &PreparedEventQuery<T>, event: &PreparedEventQuery<T>,
) -> Result<Vec<T::Response>, PluginError> ) -> Result<Vec<T::Response>, PluginError>
where where
@ -121,7 +121,7 @@ impl PluginMgr {
pub fn execute_prepared<T>( pub fn execute_prepared<T>(
&self, &self,
ecs: &World, ecs: &EcsWorld,
event: &PreparedEventQuery<T>, event: &PreparedEventQuery<T>,
) -> Result<Vec<T::Response>, PluginError> ) -> Result<Vec<T::Response>, PluginError>
where where
@ -137,7 +137,11 @@ impl PluginMgr {
.collect()) .collect())
} }
pub fn execute_event<T>(&self, ecs: &World, event: &T) -> Result<Vec<T::Response>, PluginError> pub fn execute_event<T>(
&self,
ecs: &EcsWorld,
event: &T,
) -> Result<Vec<T::Response>, PluginError>
where where
T: Event, T: Event,
{ {

View File

@ -5,16 +5,12 @@ use std::{
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use common::{ use specs::saveload::MarkerAllocator;
comp::{Health, Player},
uid::UidAllocator,
};
use specs::{saveload::MarkerAllocator, World, WorldExt};
use wasmer::{imports, Cranelift, Function, Instance, Memory, Module, Store, Value, JIT}; use wasmer::{imports, Cranelift, Function, Instance, Memory, Module, Store, Value, JIT};
use super::{ use super::{
errors::{PluginError, PluginModuleError}, errors::{PluginError, PluginModuleError},
memory_manager::{self, EcsAccessManager, MemoryManager}, memory_manager::{self, EcsAccessManager, EcsWorld, MemoryManager},
wasm_env::HostFunctionEnvironement, wasm_env::HostFunctionEnvironement,
}; };
@ -110,7 +106,7 @@ impl PluginModule {
/// return None if the event doesn't exists /// return None if the event doesn't exists
pub fn try_execute<T>( pub fn try_execute<T>(
&self, &self,
ecs: &World, ecs: &EcsWorld,
request: &PreparedEventQuery<T>, request: &PreparedEventQuery<T>,
) -> Option<Result<T::Response, PluginModuleError>> ) -> Option<Result<T::Response, PluginModuleError>>
where where
@ -236,31 +232,29 @@ fn retrieve_action(
action: Retrieve, action: Retrieve,
) -> Result<RetrieveResult, RetrieveError> { ) -> Result<RetrieveResult, RetrieveError> {
match action { match action {
Retrieve::GetPlayerName(e) => { Retrieve::GetPlayerName(_e) => {
// Safety: No reference is leaked out the function so it is safe. // Safety: No reference is leaked out the function so it is safe.
let world = unsafe { // let world = unsafe {
ecs.get().ok_or(RetrieveError::EcsAccessError( // ecs.get().ok_or(RetrieveError::EcsAccessError(
EcsAccessError::EcsPointerNotAvailable, // EcsAccessError::EcsPointerNotAvailable,
))? // ))?
}; // };
let player = world // let player = world.uid_allocator.retrieve_entity_internal(e.0).ok_or(
.read_resource::<UidAllocator>() // RetrieveError::EcsAccessError(EcsAccessError::EcsEntityNotFound(e)),
.retrieve_entity_internal(e.0) // )?;
.ok_or(RetrieveError::EcsAccessError(
EcsAccessError::EcsEntityNotFound(e),
))?;
Ok(RetrieveResult::GetPlayerName( Ok(RetrieveResult::GetPlayerName(
world "<TODO>".to_owned(), /* world
.read_component::<Player>() * .player.get(player).ok_or_else(|| {
.get(player) *
.ok_or_else(|| { * RetrieveError::EcsAccessError(EcsAccessError::
RetrieveError::EcsAccessError(EcsAccessError::EcsComponentNotFound( * EcsComponentNotFound(
e, * e,
"Player".to_owned(), * "Player".to_owned(),
)) * ))
})? * })?
.alias * .alias
.to_owned(), * .to_owned() */
)) ))
}, },
Retrieve::GetEntityHealth(e) => { Retrieve::GetEntityHealth(e) => {
@ -270,22 +264,16 @@ fn retrieve_action(
EcsAccessError::EcsPointerNotAvailable, EcsAccessError::EcsPointerNotAvailable,
))? ))?
}; };
let player = world let player = world.uid_allocator.retrieve_entity_internal(e.0).ok_or(
.read_resource::<UidAllocator>() RetrieveError::EcsAccessError(EcsAccessError::EcsEntityNotFound(e)),
.retrieve_entity_internal(e.0) )?;
.ok_or(RetrieveError::EcsAccessError(
EcsAccessError::EcsEntityNotFound(e),
))?;
Ok(RetrieveResult::GetEntityHealth( Ok(RetrieveResult::GetEntityHealth(
*world *world.health.get(player).ok_or_else(|| {
.read_component::<Health>() RetrieveError::EcsAccessError(EcsAccessError::EcsComponentNotFound(
.get(player) e,
.ok_or_else(|| { "Health".to_owned(),
RetrieveError::EcsAccessError(EcsAccessError::EcsComponentNotFound( ))
e, })?,
"Health".to_owned(),
))
})?,
)) ))
}, },
} }

View File

@ -1,4 +1,6 @@
#[cfg(feature = "plugins")] #[cfg(feature = "plugins")]
use crate::plugin::memory_manager::EcsWorld;
#[cfg(feature = "plugins")]
use crate::plugin::PluginMgr; use crate::plugin::PluginMgr;
use common::{ use common::{
comp, comp,
@ -209,8 +211,17 @@ impl State {
#[cfg(feature = "plugins")] #[cfg(feature = "plugins")]
ecs.insert(match PluginMgr::from_assets() { ecs.insert(match PluginMgr::from_assets() {
Ok(plugin_mgr) => { Ok(plugin_mgr) => {
let ecs_world = EcsWorld {
entities: ecs.entities(),
health: ecs.read_component(),
uid: ecs.read_component(),
uid_allocator: ecs.read_resource(),
//player: Either::First(ecs.read_component()),
};
if let Err(e) = plugin_mgr if let Err(e) = plugin_mgr
.execute_event(&ecs, &plugin_api::event::PluginLoadEvent { game_mode }) .execute_event(&ecs_world, &plugin_api::event::PluginLoadEvent {
game_mode,
})
{ {
tracing::error!(?e, "Failed to run plugin init"); tracing::error!(?e, "Failed to run plugin init");
tracing::info!( tracing::info!(
@ -221,7 +232,8 @@ impl State {
plugin_mgr plugin_mgr
} }
}, },
Err(_) => { Err(e) => {
tracing::error!(?e, "Failed to read plugins from assets");
tracing::info!( tracing::info!(
"Error occurred when loading plugins. Running without plugins instead." "Error occurred when loading plugins. Running without plugins instead."
); );

View File

@ -76,7 +76,7 @@ use common_net::{
}; };
#[cfg(feature = "plugins")] #[cfg(feature = "plugins")]
use common_sys::plugin::PluginMgr; use common_sys::plugin::PluginMgr;
use common_sys::state::State; use common_sys::{plugin::memory_manager::EcsWorld, state::State};
use hashbrown::HashMap; use hashbrown::HashMap;
use metrics::{PhysicsMetrics, PlayerMetrics, StateTickMetrics, TickMetrics}; use metrics::{PhysicsMetrics, PlayerMetrics, StateTickMetrics, TickMetrics};
use network::{Network, Pid, ProtocolAddr}; use network::{Network, Pid, ProtocolAddr};
@ -1106,8 +1106,14 @@ impl Server {
#[cfg(feature = "plugins")] #[cfg(feature = "plugins")]
{ {
let plugin_manager = self.state.ecs().read_resource::<PluginMgr>(); let plugin_manager = self.state.ecs().read_resource::<PluginMgr>();
let ecs_world = EcsWorld {
entities: self.state.ecs().entities(),
health: self.state.ecs().read_component(),
uid: self.state.ecs().read_component(),
uid_allocator: self.state.ecs().read_resource(),
};
let rs = plugin_manager.execute_event( let rs = plugin_manager.execute_event(
self.state.ecs(), &ecs_world,
&plugin_api::event::ChatCommandEvent { &plugin_api::event::ChatCommandEvent {
command: kwd.clone(), command: kwd.clone(),
command_args: args.split(' ').map(|x| x.to_owned()).collect(), command_args: args.split(' ').map(|x| x.to_owned()).collect(),

View File

@ -1,11 +1,11 @@
use crate::settings::BanRecord; use crate::settings::BanRecord;
use authc::{AuthClient, AuthClientError, AuthToken, Uuid}; use authc::{AuthClient, AuthClientError, AuthToken, Uuid};
use common_net::msg::RegisterError; use common_net::msg::RegisterError;
use common_sys::plugin::memory_manager::EcsWorld;
#[cfg(feature = "plugins")] #[cfg(feature = "plugins")]
use common_sys::plugin::PluginMgr; use common_sys::plugin::PluginMgr;
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use plugin_api::event::{PlayerJoinEvent, PlayerJoinResult}; use plugin_api::event::{PlayerJoinEvent, PlayerJoinResult};
use specs::World;
use std::str::FromStr; use std::str::FromStr;
use tracing::{error, info}; use tracing::{error, info};
@ -57,7 +57,7 @@ impl LoginProvider {
pub fn try_login( pub fn try_login(
&mut self, &mut self,
username_or_token: &str, username_or_token: &str,
world: &World, world: &EcsWorld,
#[cfg(feature = "plugins")] plugin_manager: &PluginMgr, #[cfg(feature = "plugins")] plugin_manager: &PluginMgr,
admins: &HashSet<Uuid>, admins: &HashSet<Uuid>,
whitelist: &HashSet<Uuid>, whitelist: &HashSet<Uuid>,
@ -80,21 +80,23 @@ impl LoginProvider {
return Err(RegisterError::NotOnWhitelist); return Err(RegisterError::NotOnWhitelist);
} }
#[cfg(feature = "plugins")] #[cfg(feature = "plugins")]
match plugin_manager.execute_event(&world, &PlayerJoinEvent { {
player_name: username.clone(), match plugin_manager.execute_event(&world, &PlayerJoinEvent {
player_id: *uuid.as_bytes(), player_name: username.clone(),
}) { player_id: *uuid.as_bytes(),
Ok(e) => { }) {
for i in e.into_iter() { Ok(e) => {
if let PlayerJoinResult::Kick(a) = i { for i in e.into_iter() {
return Err(RegisterError::Kicked(a)); if let PlayerJoinResult::Kick(a) = i {
return Err(RegisterError::Kicked(a));
}
} }
} },
}, Err(e) => {
Err(e) => { error!("Error occured while executing `on_join`: {:?}",e);
error!("Error occured while executing `on_join`: {:?}",e); },
}, };
}; }
// add the user to self.accounts // add the user to self.accounts
self.login(uuid, username.clone())?; self.login(uuid, username.clone())?;

View File

@ -4,6 +4,7 @@ use common_net::msg::{
ServerRegisterAnswer, ServerRegisterAnswer,
}; };
use common_sys::plugin::memory_manager::EcsWorld;
#[cfg(feature = "plugins")] #[cfg(feature = "plugins")]
use common_sys::plugin::PluginMgr; use common_sys::plugin::PluginMgr;
use hashbrown::HashMap; use hashbrown::HashMap;
@ -33,9 +34,15 @@ pub(crate) fn handle_register_msg(
) -> Result<(), crate::error::Error> { ) -> Result<(), crate::error::Error> {
#[cfg(feature = "plugins")] #[cfg(feature = "plugins")]
let plugin_mgr = world.read_resource::<PluginMgr>(); let plugin_mgr = world.read_resource::<PluginMgr>();
let ecs_world = EcsWorld {
entities: world.entities(),
health: world.read_component(),
uid: world.read_component(),
uid_allocator: world.read_resource(),
};
let (username, uuid) = match login_provider.try_login( let (username, uuid) = match login_provider.try_login(
&msg.token_or_username, &msg.token_or_username,
world, &ecs_world,
#[cfg(feature = "plugins")] #[cfg(feature = "plugins")]
&plugin_mgr, &plugin_mgr,
&*editable_settings.admins, &*editable_settings.admins,