Added safety section to the EcsAccessManager

This commit is contained in:
ccgauche 2021-02-22 21:37:59 +01:00 committed by Marcel Märtens
parent f85e79af07
commit 74ec5c652a
6 changed files with 57 additions and 39 deletions

View File

@ -635,7 +635,7 @@ impl CombatBuff {
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
fn equipped_item_and_tool(inv: &Inventory, slot: EquipSlot) -> Option<&Tool> { fn equipped_item_and_tool(inv: &Inventory, slot: EquipSlot) -> Option<(&Item, &Tool)> {
inv.equipped(slot).and_then(|i| { inv.equipped(slot).and_then(|i| {
if let ItemKind::Tool(tool) = &i.kind() { if let ItemKind::Tool(tool) = &i.kind() {
Some((i, tool)) Some((i, tool))

View File

@ -76,7 +76,7 @@ pub use self::{
misc::Object, misc::Object,
ori::Ori, ori::Ori,
phys::{ phys::{
Collider, ForceUpdate, Gravity, Mass, PhysicsState, Pos, PreviousVelDtCache, Scale, Sticky, Collider, ForceUpdate, Gravity, Mass, PhysicsState, Pos, PreviousPhysCache, Scale, Sticky,
Vel, Vel,
}, },
player::Player, player::Player,
@ -86,4 +86,6 @@ pub use self::{
skills::{Skill, SkillGroup, SkillGroupKind, SkillSet}, skills::{Skill, SkillGroup, SkillGroupKind, SkillSet},
stats::Stats, stats::Stats,
visual::{LightAnimation, LightEmitter}, visual::{LightAnimation, LightEmitter},
}; };
pub use health::{Health, HealthChange, HealthSource};

View File

@ -6,7 +6,7 @@ use wasmer::{Function, Memory, Value};
use super::errors::{MemoryAllocationError, PluginModuleError}; use super::errors::{MemoryAllocationError, PluginModuleError};
// 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<World>,
} }
@ -31,9 +31,21 @@ impl EcsAccessManager {
out out
} }
pub fn get(&self) -> Option<&World> { /// This unsafe function returns a reference to the Ecs World
///
/// # Safety
/// This function is safe to use if it matches the following requirements
/// - The reference and subreferences like Entities, Components ... aren't
/// leaked out the thread
/// - The reference and subreferences lifetime doesn't exceed the source
/// function lifetime
/// - Always safe when called from `retrieve_action` if you don't pass a
/// reference somewhere else
/// - All that ensure that the reference doesn't exceed the execute_with
/// function scope
pub unsafe fn get(&self) -> Option<&World> {
// ptr::as_ref will automatically check for null // ptr::as_ref will automatically check for null
unsafe { self.ecs_pointer.load(Ordering::Relaxed).as_ref() } self.ecs_pointer.load(Ordering::Relaxed).as_ref()
} }
} }
@ -52,9 +64,10 @@ impl Default for MemoryManager {
} }
impl MemoryManager { impl MemoryManager {
// This function check if the buffer is wide enough if not it realloc the buffer /// This function check if the buffer is wide enough if not it realloc the
// calling the `wasm_prepare_buffer` function Note: There is probably /// buffer calling the `wasm_prepare_buffer` function Note: There is
// optimizations that can be done using less restrictive ordering /// probably optimizations that can be done using less restrictive
/// ordering
pub fn get_pointer( pub fn get_pointer(
&self, &self,
object_length: u32, object_length: u32,
@ -74,8 +87,8 @@ impl MemoryManager {
Ok(pointer) Ok(pointer)
} }
// This function writes an object to WASM memory returning a pointer and a /// This function writes an object to WASM memory returning a pointer and a
// length. Will realloc the buffer is not wide enough /// length. Will realloc the buffer is not wide enough
pub fn write_data<T: Serialize>( pub fn write_data<T: Serialize>(
&self, &self,
memory: &Memory, memory: &Memory,
@ -89,8 +102,8 @@ impl MemoryManager {
) )
} }
// This function writes an raw bytes to WASM memory returning a pointer and a /// This function writes an raw bytes to WASM memory returning a pointer and
// length. Will realloc the buffer is not wide enough /// a length. Will realloc the buffer is not wide enough
pub fn write_bytes( pub fn write_bytes(
&self, &self,
memory: &Memory, memory: &Memory,
@ -109,8 +122,8 @@ impl MemoryManager {
} }
} }
// This function read data from memory at a position with the array length and /// This function read data from memory at a position with the array length and
// converts it to an object using bincode /// converts it to an object using bincode
pub fn read_data<T: DeserializeOwned>( pub fn read_data<T: DeserializeOwned>(
memory: &Memory, memory: &Memory,
position: i32, position: i32,
@ -119,7 +132,7 @@ pub fn read_data<T: DeserializeOwned>(
bincode::deserialize(&read_bytes(memory, position, length)) bincode::deserialize(&read_bytes(memory, position, length))
} }
// This function read raw bytes from memory at a position with the array length /// This function read raw bytes from memory at a position with the array length
pub fn read_bytes(memory: &Memory, position: i32, length: u32) -> Vec<u8> { pub fn read_bytes(memory: &Memory, position: i32, length: u32) -> Vec<u8> {
memory.view()[(position as usize)..(position as usize) + length as usize] memory.view()[(position as usize)..(position as usize) + length as usize]
.iter() .iter()

View File

@ -21,7 +21,7 @@ use super::{
use plugin_api::{Action, EcsAccessError, Event, Retrieve, RetrieveError, RetrieveResult}; use plugin_api::{Action, EcsAccessError, Event, Retrieve, RetrieveError, RetrieveResult};
#[derive(Clone)] #[derive(Clone)]
// This structure represent the WASM State of the plugin. /// This structure represent the WASM State of the plugin.
pub struct PluginModule { pub struct PluginModule {
ecs: Arc<EcsAccessManager>, ecs: Arc<EcsAccessManager>,
wasm_state: Arc<Mutex<Instance>>, wasm_state: Arc<Mutex<Instance>>,
@ -33,7 +33,7 @@ pub struct PluginModule {
} }
impl PluginModule { impl PluginModule {
// This function takes bytes from a WASM File and compile them /// This function takes bytes from a WASM File and compile them
pub fn new(name: String, wasm_data: &[u8]) -> Result<Self, PluginModuleError> { pub fn new(name: String, wasm_data: &[u8]) -> Result<Self, PluginModuleError> {
// This is creating the engine is this case a JIT based on Cranelift // This is creating the engine is this case a JIT based on Cranelift
let engine = JIT::new(Cranelift::default()).engine(); let engine = JIT::new(Cranelift::default()).engine();
@ -107,8 +107,8 @@ impl PluginModule {
}) })
} }
// This function tries to execute an event for the current module. Will return /// This function tries to execute an event for the current module. Will
// 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: &World,
@ -133,16 +133,17 @@ impl PluginModule {
} }
} }
// This structure represent a Pre-encoded event object (Useful to avoid /// This structure represent a Pre-encoded event object (Useful to avoid
// reencoding for each module in every plugin) /// reencoding for each module in every plugin)
pub struct PreparedEventQuery<T> { pub struct PreparedEventQuery<T> {
bytes: Vec<u8>, bytes: Vec<u8>,
_phantom: PhantomData<T>, _phantom: PhantomData<T>,
} }
impl<T: Event> PreparedEventQuery<T> { impl<T: Event> PreparedEventQuery<T> {
// Create a prepared query from an event reference (Encode to bytes the struct) /// Create a prepared query from an event reference (Encode to bytes the
// This Prepared Query is used by the `try_execute` method in `PluginModule` /// struct) This Prepared Query is used by the `try_execute` method in
/// `PluginModule`
pub fn new(event: &T) -> Result<Self, PluginError> pub fn new(event: &T) -> Result<Self, PluginError>
where where
T: Event, T: Event,
@ -222,9 +223,12 @@ fn retrieve_action(
) -> Result<RetrieveResult, RetrieveError> { ) -> Result<RetrieveResult, RetrieveError> {
match action { match action {
Retrieve::GetPlayerName(e) => { Retrieve::GetPlayerName(e) => {
let world = ecs.get().ok_or(RetrieveError::EcsAccessError( // Safety: No reference is leaked out the function so it is safe.
EcsAccessError::EcsPointerNotAvailable, let world = unsafe {
))?; ecs.get().ok_or(RetrieveError::EcsAccessError(
EcsAccessError::EcsPointerNotAvailable,
))?
};
let player = world let player = world
.read_resource::<UidAllocator>() .read_resource::<UidAllocator>()
.retrieve_entity_internal(e.0) .retrieve_entity_internal(e.0)
@ -246,9 +250,12 @@ fn retrieve_action(
)) ))
}, },
Retrieve::GetEntityHealth(e) => { Retrieve::GetEntityHealth(e) => {
let world = ecs.get().ok_or(RetrieveError::EcsAccessError( // Safety: No reference is leaked out the function so it is safe.
EcsAccessError::EcsPointerNotAvailable, let world = unsafe {
))?; ecs.get().ok_or(RetrieveError::EcsAccessError(
EcsAccessError::EcsPointerNotAvailable,
))?
};
let player = world let player = world
.read_resource::<UidAllocator>() .read_resource::<UidAllocator>()
.retrieve_entity_internal(e.0) .retrieve_entity_internal(e.0)

View File

@ -35,8 +35,8 @@ impl HostFunctionEnvironement {
} }
} }
// This function is a safe interface to WASM memory that writes data to the /// This function is a safe interface to WASM memory that writes data to the
// memory returning a pointer and length /// memory returning a pointer and length
pub fn write_data<T: Serialize>(&self, object: &T) -> Result<(i32, u32), PluginModuleError> { pub fn write_data<T: Serialize>(&self, object: &T) -> Result<(i32, u32), PluginModuleError> {
self.memory_manager.write_data( self.memory_manager.write_data(
self.memory.get_ref().unwrap(), self.memory.get_ref().unwrap(),
@ -45,8 +45,8 @@ impl HostFunctionEnvironement {
) )
} }
// This function is a safe interface to WASM memory that reads memory from /// This function is a safe interface to WASM memory that reads memory from
// pointer and length returning an object /// pointer and length returning an object
pub fn read_data<T: DeserializeOwned>( pub fn read_data<T: DeserializeOwned>(
&self, &self,
position: i32, position: i32,

View File

@ -210,11 +210,7 @@ impl State {
Ok(plugin_mgr) => { Ok(plugin_mgr) => {
if let Err(e) = if let Err(e) =
plugin_mgr.execute_event(&ecs, "on_load", &plugin_api::event::PluginLoadEvent { plugin_mgr.execute_event(&ecs, "on_load", &plugin_api::event::PluginLoadEvent {
game_mode: match game_mode { game_mode,
resources::GameMode::Server => plugin_api::GameMode::Server,
resources::GameMode::Client => plugin_api::GameMode::Client,
resources::GameMode::Singleplayer => plugin_api::GameMode::Singleplayer,
},
}) })
{ {
tracing::error!(?e, "Failed to run plugin init"); tracing::error!(?e, "Failed to run plugin init");