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 serde::{de::DeserializeOwned, Serialize};
use specs::World;
use specs::{shred::Fetch, Entities, ReadStorage};
use wasmer::{Function, Memory, Value};
use common::{
comp::Health,
uid::{Uid, UidAllocator},
};
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
pub struct EcsAccessManager {
ecs_pointer: AtomicPtr<World>,
ecs_pointer: AtomicPtr<EcsWorld<'static>>,
}
impl Default for EcsAccessManager {
@ -22,7 +35,7 @@ impl Default for EcsAccessManager {
impl EcsAccessManager {
// This function take a World reference and a function to execute ensuring the
// 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((), |_| {
// ensure the pointer is cleared in any case
self.ecs_pointer
@ -45,7 +58,7 @@ impl EcsAccessManager {
/// reference somewhere else
/// - All that ensure that the reference doesn't exceed the execute_with
/// function scope
pub unsafe fn get(&self) -> Option<&World> {
pub unsafe fn get(&self) -> Option<&EcsWorld> {
// ptr::as_ref will automatically check for null
self.ecs_pointer.load(Ordering::Relaxed).as_ref()
}

View File

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

View File

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

View File

@ -1,4 +1,6 @@
#[cfg(feature = "plugins")]
use crate::plugin::memory_manager::EcsWorld;
#[cfg(feature = "plugins")]
use crate::plugin::PluginMgr;
use common::{
comp,
@ -209,8 +211,17 @@ impl State {
#[cfg(feature = "plugins")]
ecs.insert(match PluginMgr::from_assets() {
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
.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::info!(
@ -221,7 +232,8 @@ impl State {
plugin_mgr
}
},
Err(_) => {
Err(e) => {
tracing::error!(?e, "Failed to read plugins from assets");
tracing::info!(
"Error occurred when loading plugins. Running without plugins instead."
);

View File

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

View File

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

View File

@ -4,6 +4,7 @@ use common_net::msg::{
ServerRegisterAnswer,
};
use common_sys::plugin::memory_manager::EcsWorld;
#[cfg(feature = "plugins")]
use common_sys::plugin::PluginMgr;
use hashbrown::HashMap;
@ -33,9 +34,15 @@ pub(crate) fn handle_register_msg(
) -> Result<(), crate::error::Error> {
#[cfg(feature = "plugins")]
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(
&msg.token_or_username,
world,
&ecs_world,
#[cfg(feature = "plugins")]
&plugin_mgr,
&*editable_settings.admins,