From cd694b0d6c12e24b4c4f4ad8336939b5475cd51e Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 12 Jun 2021 03:26:16 +0100 Subject: [PATCH] Began overhauling plugin API --- common/state/src/plugin/memory_manager.rs | 6 +- common/state/src/plugin/mod.rs | 26 ++--- common/state/src/plugin/module.rs | 121 ++++++++++------------ common/state/src/plugin/wasm_env.rs | 6 +- common/state/src/state.rs | 14 +-- plugin/api/src/entity.rs | 23 ++++ plugin/api/src/event.rs | 19 ++++ plugin/api/src/event/command.rs | 39 +++++++ plugin/api/src/event/init.rs | 30 ++++++ plugin/api/src/event/player_join.rs | 40 +++++++ plugin/api/src/lib.rs | 31 ++++-- plugin/api/src/raw.rs | 24 +++++ plugin/derive/src/lib.rs | 56 +++++++--- plugin/rt/Cargo.toml | 3 - plugin/rt/examples/hello.rs | 55 +++++----- plugin/rt/src/lib.rs | 57 ++++------ plugin/rt/src/retrieve.rs | 35 ------- server/src/lib.rs | 11 +- server/src/login_provider.rs | 16 +-- server/src/sys/msg/register.rs | 3 +- 20 files changed, 387 insertions(+), 228 deletions(-) create mode 100644 plugin/api/src/entity.rs create mode 100644 plugin/api/src/event.rs create mode 100644 plugin/api/src/event/command.rs create mode 100644 plugin/api/src/event/init.rs create mode 100644 plugin/api/src/event/player_join.rs create mode 100644 plugin/api/src/raw.rs delete mode 100644 plugin/rt/src/retrieve.rs diff --git a/common/state/src/plugin/memory_manager.rs b/common/state/src/plugin/memory_manager.rs index b21f819090..5e23e4f0e0 100644 --- a/common/state/src/plugin/memory_manager.rs +++ b/common/state/src/plugin/memory_manager.rs @@ -1,6 +1,6 @@ use std::sync::atomic::{AtomicPtr, AtomicU32, AtomicU64, Ordering}; -use serde::{de::DeserializeOwned, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use specs::{ storage::GenericReadStorage, Component, Entities, Entity, Read, ReadStorage, WriteStorage, }; @@ -223,8 +223,8 @@ impl MemoryManager { /// This function read data from memory at a position with the array length and /// converts it to an object using bincode -pub fn read_data( - memory: &Memory, +pub fn read_data<'a, T: for<'b> Deserialize<'b>>( + memory: &'a Memory, position: u64, length: u64, ) -> Result { diff --git a/common/state/src/plugin/mod.rs b/common/state/src/plugin/mod.rs index f79aa4a344..123762bd18 100644 --- a/common/state/src/plugin/mod.rs +++ b/common/state/src/plugin/mod.rs @@ -81,13 +81,13 @@ impl Plugin { }) } - pub fn execute_prepared( + pub fn execute_prepared<'a, T>( &self, ecs: &EcsWorld, - event: &PreparedEventQuery, - ) -> Result, PluginError> + event: &PreparedEventQuery<'a, T>, + ) -> Result>::Response>, PluginError> where - T: Event, + T: Event<'a>, { self.modules .iter() @@ -119,13 +119,13 @@ impl PluginMgr { Self::from_dir(assets_path) } - pub fn execute_prepared( + pub fn execute_prepared<'a, T>( &self, ecs: &EcsWorld, - event: &PreparedEventQuery, - ) -> Result, PluginError> + event: &PreparedEventQuery<'a, T>, + ) -> Result>::Response>, PluginError> where - T: Event, + T: Event<'a>, { Ok(self .plugins @@ -137,15 +137,15 @@ impl PluginMgr { .collect()) } - pub fn execute_event( + pub fn execute_event<'a, T>( &self, ecs: &EcsWorld, - event: &T, - ) -> Result, PluginError> + raw_event: &'a T::Raw, + ) -> Result>::Response>, PluginError> where - T: Event, + T: Event<'a>, { - self.execute_prepared(ecs, &PreparedEventQuery::new(event)?) + self.execute_prepared::(ecs, &PreparedEventQuery::new(raw_event)?) } pub fn from_dir>(path: P) -> Result { diff --git a/common/state/src/plugin/module.rs b/common/state/src/plugin/module.rs index a55f155ce2..ccaff2112f 100644 --- a/common/state/src/plugin/module.rs +++ b/common/state/src/plugin/module.rs @@ -15,7 +15,10 @@ use super::{ wasm_env::HostFunctionEnvironment, }; -use plugin_api::{Action, EcsAccessError, Event, Retrieve, RetrieveError, RetrieveResult}; +use plugin_api::{ + raw::{RawAction, RawRequest, RawResponse}, + EcsAccessError, Event, +}; #[derive(Clone)] /// This structure represent the WASM State of the plugin. @@ -41,19 +44,20 @@ impl PluginModule { // This is the function imported into the wasm environement fn raw_emit_actions(env: &HostFunctionEnvironment, ptr: i64, len: i64) { - handle_actions(match env.read_data(from_i64(ptr), from_i64(len)) { - Ok(e) => e, - Err(e) => { - tracing::error!(?e, "Can't decode action"); - return; + match env.read_data::>(from_i64(ptr), from_i64(len)) { + Ok(actions) => { + for action in actions { + tracing::debug!("At this point, we should handle {:?}", action); + } }, - }); + Err(e) => tracing::error!(?e, "Can't decode action"), + } } - fn raw_retrieve_action(env: &HostFunctionEnvironment, ptr: i64, len: i64) -> i64 { + fn raw_request(env: &HostFunctionEnvironment, ptr: i64, len: i64) -> i64 { let out = match env.read_data(from_i64(ptr), from_i64(len)) { - Ok(data) => retrieve_action(&env.ecs, data), - Err(e) => Err(RetrieveError::BincodeError(e.to_string())), + Ok(data) => request(&env.ecs, data), + Err(e) => Err(()), }; // If an error happen set the i64 to 0 so the WASM side can tell an error @@ -79,7 +83,7 @@ impl PluginModule { let import_object = imports! { "env" => { "raw_emit_actions" => Function::new_native_with_env(&store, HostFunctionEnvironment::new(name.clone(), ecs.clone(), memory_manager.clone()), raw_emit_actions), - "raw_retrieve_action" => Function::new_native_with_env(&store, HostFunctionEnvironment::new(name.clone(), ecs.clone(), memory_manager.clone()), raw_retrieve_action), + "raw_request" => Function::new_native_with_env(&store, HostFunctionEnvironment::new(name.clone(), ecs.clone(), memory_manager.clone()), raw_request), "raw_print" => Function::new_native_with_env(&store, HostFunctionEnvironment::new(name.clone(), ecs.clone(), memory_manager.clone()), raw_print), } }; @@ -112,13 +116,13 @@ impl PluginModule { /// This function tries to execute an event for the current module. Will /// return None if the event doesn't exists - pub fn try_execute( + pub fn try_execute<'a, T>( &self, ecs: &EcsWorld, request: &PreparedEventQuery, - ) -> Option> + ) -> Option>::Response, PluginModuleError>> where - T: Event, + T: Event<'a>, { if !self.events.contains(request.function_name.as_ref()) { return None; @@ -137,23 +141,20 @@ impl PluginModule { /// This structure represent a Pre-encoded event object (Useful to avoid /// reencoding for each module in every plugin) -pub struct PreparedEventQuery { +pub struct PreparedEventQuery<'a, T> { bytes: Vec, - function_name: Cow<'static, str>, + function_name: Cow<'a, str>, _phantom: PhantomData, } -impl PreparedEventQuery { +impl<'a, T: Event<'a>> PreparedEventQuery<'a, T> { /// Create a prepared query from an event reference (Encode to bytes the /// struct) This Prepared Query is used by the `try_execute` method in /// `PluginModule` - pub fn new(event: &T) -> Result - where - T: Event, - { + pub fn new(raw_event: &'a >::Raw) -> Result { Ok(Self { - bytes: bincode::serialize(&event).map_err(PluginError::Encoding)?, - function_name: event.get_event_name(), + bytes: bincode::serialize(raw_event).map_err(PluginError::Encoding)?, + function_name: T::get_handler_name(raw_event), _phantom: PhantomData::default(), }) } @@ -238,74 +239,58 @@ fn execute_raw( .unwrap()) } -fn retrieve_action( - ecs: &EcsAccessManager, - action: Retrieve, -) -> Result { - match action { - Retrieve::GetPlayerName(e) => { +fn request<'a>(ecs: &'a EcsAccessManager, req: RawRequest) -> Result, ()> { + match req { + RawRequest::EntityName(e) => { // Safety: No reference is leaked out the function so it is safe. let world = unsafe { - ecs.get().ok_or(RetrieveError::EcsAccessError( - EcsAccessError::EcsPointerNotAvailable, - ))? + 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)), + let entity = world.uid_allocator.retrieve_entity_internal(e.0).ok_or( + (), /* RetrieveError::EcsAccessError(EcsAccessError::EcsEntityNotFound(e)) */ )?; - Ok(RetrieveResult::GetPlayerName( - world + Ok(RawResponse::EntityName(Cow::Borrowed( + &world .player - .get(player) + .get(entity) .ok_or_else(|| { + /* RetrieveError::EcsAccessError(EcsAccessError::EcsComponentNotFound( e, "Player".to_owned(), )) + */ })? - .alias - .to_owned(), - )) + .alias, + ))) }, - Retrieve::GetEntityHealth(e) => { + RawRequest::EntityHealth(e) => { // Safety: No reference is leaked out the function so it is safe. let world = unsafe { - ecs.get().ok_or(RetrieveError::EcsAccessError( - EcsAccessError::EcsPointerNotAvailable, - ))? + 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)), + let entity = world.uid_allocator.retrieve_entity_internal(e.0).ok_or( + (), /* + RetrieveError::EcsAccessError(EcsAccessError::EcsEntityNotFound(e))*/ )?; - Ok(RetrieveResult::GetEntityHealth( - *world.health.get(player).ok_or_else(|| { + Ok(RawResponse::EntityHealth( + *world.health.get(entity).ok_or_else(|| { + /* RetrieveError::EcsAccessError(EcsAccessError::EcsComponentNotFound( e, "Health".to_owned(), )) + */ })?, )) }, - } -} - -fn handle_actions(actions: Vec) { - for action in actions { - match action { - Action::ServerClose => { - tracing::info!("Server closed by plugin"); - std::process::exit(-1); - }, - Action::Print(e) => { - tracing::info!("{}", e); - }, - Action::PlayerSendMessage(a, b) => { - tracing::info!("SendMessage {} -> {}", a, b); - }, - Action::KillEntity(e) => { - tracing::info!("Kill Entity {}", e); - }, - } + RawRequest::PlayerUidByName(_) => todo!(), } } diff --git a/common/state/src/plugin/wasm_env.rs b/common/state/src/plugin/wasm_env.rs index c64955d58e..210ece921b 100644 --- a/common/state/src/plugin/wasm_env.rs +++ b/common/state/src/plugin/wasm_env.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use serde::{de::DeserializeOwned, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use wasmer::{Function, HostEnvInitError, Instance, LazyInit, Memory, WasmerEnv}; use super::{ @@ -60,8 +60,8 @@ impl HostFunctionEnvironment { /// This function is a safe interface to WASM memory that reads memory from /// pointer and length returning an object - pub fn read_data( - &self, + pub fn read_data<'a, T: for<'b> Deserialize<'b>>( + &'a self, position: u64, length: u64, ) -> Result { diff --git a/common/state/src/state.rs b/common/state/src/state.rs index 6a7f628a1e..432cacfacc 100644 --- a/common/state/src/state.rs +++ b/common/state/src/state.rs @@ -31,7 +31,7 @@ use specs::{ storage::{MaskedStorage as EcsMaskedStorage, Storage as EcsStorage}, Component, DispatcherBuilder, Entity as EcsEntity, WorldExt, }; -use std::sync::Arc; +use std::{marker::PhantomData, sync::Arc}; use vek::*; /// How much faster should an in-game day be compared to a real day? @@ -238,11 +238,13 @@ impl State { uid_allocator: &ecs.read_resource::().into(), player: ecs.read_component().into(), }; - if let Err(e) = plugin_mgr - .execute_event(&ecs_world, &plugin_api::event::PluginLoadEvent { - game_mode, - }) - { + if let Err(e) = plugin_mgr.execute_event::( + &ecs_world, + &plugin_api::event::init::RawInit { + mode: game_mode, + phantom: PhantomData, + }, + ) { tracing::debug!(?e, "Failed to run plugin init"); tracing::info!("Plugins disabled, enable debug logging for more information."); PluginMgr::default() diff --git a/plugin/api/src/entity.rs b/plugin/api/src/entity.rs new file mode 100644 index 0000000000..1e8e4fff99 --- /dev/null +++ b/plugin/api/src/entity.rs @@ -0,0 +1,23 @@ +use super::*; + +#[derive(Copy, Clone)] +pub struct Entity<'a> { + pub(crate) game: &'a Game, + pub(crate) uid: Uid, +} + +impl<'a> Entity<'a> { + pub fn uid(&self) -> Uid { self.uid } + + pub fn send_chat_msg(&self, msg: T) { + self.game.emit(RawAction::EntitySendChatMessage( + self.uid, + Cow::Owned(msg.to_string()), + )) + } + + // TODO + pub fn get_name(&self) -> String { todo!() } + + pub fn get_health(&self) -> String { todo!() } +} diff --git a/plugin/api/src/event.rs b/plugin/api/src/event.rs new file mode 100644 index 0000000000..95c32bf216 --- /dev/null +++ b/plugin/api/src/event.rs @@ -0,0 +1,19 @@ +pub mod command; +pub mod init; +pub mod player_join; + +pub use self::{ + command::Command, + init::Init, + player_join::{PlayerJoin, PlayerJoinResponse}, +}; + +use super::*; + +pub trait Event<'a>: Send + Sync { + type Response: Serialize + DeserializeOwned + Send + Sync + 'a; + type Raw: Serialize + Deserialize<'a> + Send + Sync + 'a; + + fn from_raw(game: &'a Game, raw: Self::Raw) -> Self; + fn get_handler_name(raw: &Self::Raw) -> Cow<'_, str>; +} diff --git a/plugin/api/src/event/command.rs b/plugin/api/src/event/command.rs new file mode 100644 index 0000000000..d0b66f1e93 --- /dev/null +++ b/plugin/api/src/event/command.rs @@ -0,0 +1,39 @@ +use super::*; + +#[derive(Serialize, Deserialize)] +pub struct RawCommand<'a> { + pub entity: Uid, + pub cmd: &'a str, + pub args: Cow<'a, [&'a str]>, +} + +pub struct Command<'a> { + entity: Entity<'a>, + cmd: &'a str, + args: Cow<'a, [&'a str]>, +} + +impl<'a> Event<'a> for Command<'a> { + type Raw = RawCommand<'a>; + type Response = Result, String>; + + fn from_raw(game: &'a Game, raw: Self::Raw) -> Self { + Self { + entity: game.entity(raw.entity), + cmd: raw.cmd, + args: raw.args, + } + } + + fn get_handler_name(raw: &Self::Raw) -> Cow<'_, str> { + Cow::Owned(format!("on_command_{}", raw.cmd)) + } +} + +impl<'a> Command<'a> { + pub fn entity(&self) -> Entity<'_> { self.entity } + + pub fn cmd(&self) -> &str { self.cmd } + + pub fn args(&self) -> &[&str] { &self.args } +} diff --git a/plugin/api/src/event/init.rs b/plugin/api/src/event/init.rs new file mode 100644 index 0000000000..2ff53dced1 --- /dev/null +++ b/plugin/api/src/event/init.rs @@ -0,0 +1,30 @@ +use super::*; + +#[derive(Serialize, Deserialize)] +pub struct RawInit<'a> { + pub mode: GameMode, + pub phantom: PhantomData<&'a ()>, +} + +pub struct Init<'a> { + mode: GameMode, + pub phantom: PhantomData<&'a ()>, +} + +impl<'a> Event<'a> for Init<'a> { + type Raw = RawInit<'a>; + type Response = (); + + fn from_raw(game: &'a Game, raw: Self::Raw) -> Self { + Self { + mode: raw.mode, + phantom: PhantomData, + } + } + + fn get_handler_name(raw: &Self::Raw) -> Cow<'_, str> { Cow::Borrowed("on_init") } +} + +impl<'a> Init<'a> { + pub fn mode(&self) -> GameMode { self.mode } +} diff --git a/plugin/api/src/event/player_join.rs b/plugin/api/src/event/player_join.rs new file mode 100644 index 0000000000..2b33dc232c --- /dev/null +++ b/plugin/api/src/event/player_join.rs @@ -0,0 +1,40 @@ +use super::*; + +pub type Uuid = [u8; 16]; + +#[derive(Serialize, Deserialize)] +pub struct RawPlayerJoin<'a> { + pub uuid: Uuid, + pub alias: Cow<'a, str>, +} + +pub struct PlayerJoin<'a> { + uuid: Uuid, + alias: Cow<'a, str>, +} + +impl<'a> Event<'a> for PlayerJoin<'a> { + type Raw = RawPlayerJoin<'a>; + type Response = PlayerJoinResponse<'a>; + + fn from_raw(game: &'a Game, raw: Self::Raw) -> Self { + Self { + uuid: raw.uuid, + alias: raw.alias, + } + } + + fn get_handler_name(raw: &Self::Raw) -> Cow<'_, str> { Cow::Borrowed("on_player_join") } +} + +impl<'a> PlayerJoin<'a> { + pub fn uuid(&self) -> Uuid { self.uuid } + + pub fn alias(&self) -> &str { &self.alias } +} + +#[derive(Serialize, Deserialize)] +pub enum PlayerJoinResponse<'a> { + Accept, + Reject { reason: Cow<'a, str> }, +} diff --git a/plugin/api/src/lib.rs b/plugin/api/src/lib.rs index 2f871b86f2..29ddc8521f 100644 --- a/plugin/api/src/lib.rs +++ b/plugin/api/src/lib.rs @@ -1,14 +1,32 @@ //#![deny(missing_docs)] -pub use common::{comp::Health, resources::GameMode, uid::Uid}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::borrow::Cow; +pub mod entity; +pub mod event; +pub mod raw; mod errors; -pub use errors::*; -pub use event::*; +pub use self::{errors::*, event::Event}; +pub use common::{resources::GameMode, uid::Uid}; +use self::{entity::Entity, raw::RawAction}; +use common::comp::Health; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::{borrow::Cow, marker::PhantomData}; + +pub struct Game { + emit_action: fn(RawAction), +} + +impl Game { + pub fn __new(emit_action: fn(RawAction)) -> Self { Self { emit_action } } + + fn emit(&self, action: RawAction) { (self.emit_action)(action) } + + pub fn entity(&self, uid: Uid) -> Entity<'_> { Entity { game: self, uid } } +} + +/* /// The [`Action`] enum represents a push modification that will be made in the /// ECS in the next tick Note that all actions when sent are async and will not /// be executed in order like [`Retrieve`] that are sync. All actions sent will @@ -28,7 +46,7 @@ pub use event::*; /// // You can also use this to only send one action /// emit_action(Action::KillEntity(Uid(1))); /// ``` -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Debug, Clone)] pub enum Action { ServerClose, Print(String), @@ -223,3 +241,4 @@ pub mod event { // } // } } +*/ diff --git a/plugin/api/src/raw.rs b/plugin/api/src/raw.rs new file mode 100644 index 0000000000..eda8073eb6 --- /dev/null +++ b/plugin/api/src/raw.rs @@ -0,0 +1,24 @@ +use super::*; + +/// An action to be performed by the game. +#[derive(Serialize, Deserialize, Debug)] +pub enum RawAction<'a> { + ServerShutdown, + EntitySendChatMessage(Uid, Cow<'a, str>), + EntityKill(Uid), +} + +/// A request to the game for information. +#[derive(Serialize, Deserialize, Debug)] +pub enum RawRequest<'a> { + PlayerUidByName(Cow<'a, str>), + EntityName(Uid), + EntityHealth(Uid), +} + +/// A response to a [`RawRequest`] +#[derive(Serialize, Deserialize, Debug)] +pub enum RawResponse<'a> { + EntityName(Cow<'a, str>), + EntityHealth(Health), +} diff --git a/plugin/derive/src/lib.rs b/plugin/derive/src/lib.rs index ae4a985191..319e24234a 100644 --- a/plugin/derive/src/lib.rs +++ b/plugin/derive/src/lib.rs @@ -1,8 +1,10 @@ +#![feature(proc_macro_diagnostic)] + extern crate proc_macro; -use proc_macro::TokenStream; +use proc_macro::{Diagnostic, Level, TokenStream}; use quote::quote; -use syn::{parse_macro_input, ItemFn, ItemStruct}; +use syn::{parse_macro_input, spanned::Spanned, ItemFn, ItemStruct}; #[proc_macro_attribute] pub fn global_state(_args: TokenStream, item: TokenStream) -> TokenStream { @@ -28,47 +30,69 @@ pub fn event_handler(_args: TokenStream, item: TokenStream) -> TokenStream { let fn_args = sig.inputs; // comma separated args let fn_return = sig.output; // comma separated args - let out: proc_macro2::TokenStream = if fn_args.len() == 1 { - quote! { + let out: proc_macro2::TokenStream = match fn_args.len() { + 2 => quote! { #[allow(clippy::unnecessary_wraps)] #[no_mangle] pub fn #fn_name(intern__ptr: i64, intern__len: i64) -> i64 { - let input = ::veloren_plugin_rt::read_input(intern__ptr as _,intern__len as _).unwrap(); + /// Safety: `input` is not permitted to escape this function. + let input = unsafe { ::veloren_plugin_rt::read_input(intern__ptr as _,intern__len as _).unwrap() }; #[inline] fn inner(#fn_args) #fn_return { #fn_body } // Artificially force the event handler to be type-correct - fn force_event(event: E, inner: fn(E) -> E::Response) -> E::Response { - inner(event) + fn force_event<'a, E: ::veloren_plugin_rt::api::Event<'a>>( + game: &'a ::veloren_plugin_rt::api::Game, + raw_event: E::Raw, + inner: fn(&::veloren_plugin_rt::api::Game, E) -> E::Response, + ) -> E::Response { + inner( + &game, + E::from_raw(&game, raw_event), + ) } - ::veloren_plugin_rt::write_output(&force_event(input, inner)) + ::veloren_plugin_rt::write_output(&force_event(unsafe { &::veloren_plugin_rt::__game() }, input, inner)) } - } - } else { - quote! { + }, + 3 => quote! { #[allow(clippy::unnecessary_wraps)] #[no_mangle] pub fn #fn_name(intern__ptr: i64, intern__len: i64) -> i64 { - let input = ::veloren_plugin_rt::read_input(intern__ptr as _,intern__len as _).unwrap(); + /// Safety: `input` is not permitted to escape this function. + let input = unsafe { ::veloren_plugin_rt::read_input(intern__ptr as _,intern__len as _).unwrap() }; #[inline] fn inner(#fn_args) #fn_return { #fn_body } // Artificially force the event handler to be type-correct - fn force_event(event: E, inner: fn(E, &mut PLUGIN_STATE_TYPE) -> E::Response) -> E::Response { + fn force_event<'a, E: ::veloren_plugin_rt::api::Event<'a>>( + game: &'a ::veloren_plugin_rt::api::Game, + raw_event: E::Raw, + inner: fn(&::veloren_plugin_rt::api::Game, E, &mut PLUGIN_STATE_TYPE) -> E::Response, + ) -> E::Response { assert_eq!(PLUGIN_STATE_GUARD.swap(true, std::sync::atomic::Ordering::Acquire), false); let out = inner( - event, + &game, + E::from_raw(&game, raw_event), unsafe { PLUGIN_STATE.get_or_insert_with(core::default::Default::default) }, ); PLUGIN_STATE_GUARD.store(false, std::sync::atomic::Ordering::Release); out } - ::veloren_plugin_rt::write_output(&force_event(input, inner)) + ::veloren_plugin_rt::write_output(&force_event(unsafe { &::veloren_plugin_rt::__game() }, input, inner)) } - } + }, + n => { + Diagnostic::spanned( + fn_args.span().unwrap(), + Level::Error, + "Incorrect number of function parameters", + ) + .emit(); + quote! {} + }, }; out.into() } diff --git a/plugin/rt/Cargo.toml b/plugin/rt/Cargo.toml index ec216fb887..bf45accc12 100644 --- a/plugin/rt/Cargo.toml +++ b/plugin/rt/Cargo.toml @@ -13,6 +13,3 @@ bincode = "1.3.1" [[example]] name = "hello" crate-type = ["cdylib"] - -[dev-dependencies] -plugin-derive = { package = "veloren-plugin-derive", path = "../derive"} diff --git a/plugin/rt/examples/hello.rs b/plugin/rt/examples/hello.rs index aa8f0c1b71..ca0b0ce547 100644 --- a/plugin/rt/examples/hello.rs +++ b/plugin/rt/examples/hello.rs @@ -1,46 +1,49 @@ use veloren_plugin_rt::{ - api::{event::*, Action, GameMode}, + api::{event, GameMode, *}, *, }; #[event_handler] -pub fn on_load(load: PluginLoadEvent) { - match load.game_mode { - GameMode::Server => emit_action(Action::Print("Hello, server!".to_owned())), - GameMode::Client => emit_action(Action::Print("Hello, client!".to_owned())), - GameMode::Singleplayer => emit_action(Action::Print("Hello, singleplayer!".to_owned())), +pub fn on_load(game: &Game, init: event::Init) { + match init.mode() { + GameMode::Server => log!("Hello, server!"), + GameMode::Client => log!("Hello, client!"), + GameMode::Singleplayer => log!("Hello, singleplayer!"), } } #[event_handler] -pub fn on_command_testplugin(command: ChatCommandEvent) -> Result, String> { - Ok(vec![format!( - "Player of id {:?} named {} with {:?} sended command with args {:?}", - command.player.id, - command - .player - .get_player_name() - .expect("Can't get player name"), - command - .player - .get_entity_health() - .expect("Can't get player health"), - command.command_args - )]) +pub fn on_command_test(game: &Game, cmd: event::Command) -> Result, String> { + Ok(vec![ + format!( + "Entity with uid {:?} named {} with {:?} sent command with args {:?}", + cmd.entity().uid(), + cmd.entity().get_name(), + cmd.entity().get_health(), + cmd.args(), + ) + .into(), + ]) } #[global_state] #[derive(Default)] struct State { - counter: bool, + total_joined: u64, } #[event_handler] -pub fn on_join(input: PlayerJoinEvent, state: &mut State) -> PlayerJoinResult { - state.counter = !state.counter; - if !state.counter { - PlayerJoinResult::Kick(format!("You are a cheater {:?}", input)) +pub fn on_join( + game: &Game, + player_join: event::PlayerJoin, + state: &mut State, +) -> event::PlayerJoinResponse<'static> { + state.total_joined += 1; + if state.total_joined > 10 { + event::PlayerJoinResponse::Reject { + reason: "Too many people have joined!".into(), + } } else { - PlayerJoinResult::None + event::PlayerJoinResponse::Accept } } diff --git a/plugin/rt/src/lib.rs b/plugin/rt/src/lib.rs index c73f181876..d149f410b3 100644 --- a/plugin/rt/src/lib.rs +++ b/plugin/rt/src/lib.rs @@ -1,61 +1,47 @@ extern crate plugin_derive; -pub mod retrieve; - pub use plugin_api as api; pub use plugin_derive::{event_handler, global_state}; -pub use retrieve::*; -use api::RetrieveError; -use serde::{de::DeserializeOwned, Serialize}; -use std::{convert::TryInto, marker::PhantomData}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::{ + borrow::{Cow, ToOwned}, + convert::TryInto, + marker::PhantomData, +}; -pub struct Game { - phantom: PhantomData<()>, -} - -impl Game { - /// This is not strictly unsafe today, but it may become unsafe in the - /// future. No safety guarantees are listed because we don't intend this - /// to ever be manually called. Do not use this function! - pub unsafe fn __new() -> Self { - Self { - phantom: PhantomData, - } - } -} +pub fn __game() -> api::Game { api::Game::__new(|_| todo!()) } #[cfg(target_arch = "wasm32")] extern "C" { fn raw_emit_actions(ptr: i64, len: i64); - fn raw_retrieve_action(ptr: i64, len: i64) -> i64; + fn raw_request(ptr: i64, len: i64) -> i64; fn raw_print(ptr: i64, len: i64); } -pub fn retrieve_action(_actions: &api::Retrieve) -> Result { +pub fn request(req: api::raw::RawRequest) -> Result, ()> { #[cfg(target_arch = "wasm32")] { - let ret = bincode::serialize(&_actions).expect("Can't serialize action in emit"); - unsafe { - let ptr = raw_retrieve_action(to_i64(ret.as_ptr() as _), to_i64(ret.len() as _)); + let ret = bincode::serialize(&req).expect("Can't serialize action in emit"); + let bytes = unsafe { + let ptr = raw_request(to_i64(ret.as_ptr() as _), to_i64(ret.len() as _)); let ptr = from_i64(ptr); let len = u64::from_le_bytes(std::slice::from_raw_parts(ptr as _, 8).try_into().unwrap()); - let a = ::std::slice::from_raw_parts((ptr + 8) as _, len as _); - bincode::deserialize::>(&a) - .map_err(|x| RetrieveError::BincodeError(x.to_string()))? - } + ::std::slice::from_raw_parts((ptr + 8) as *const u8, len as _) + }; + bincode::deserialize::, ()>>(bytes).map_err(|_| ())? } #[cfg(not(target_arch = "wasm32"))] - unreachable!() + panic!("Requests are not implemented for non-WASM targets") } -pub fn emit_action(action: api::Action) { emit_actions(vec![action]) } +pub fn emit_action(action: api::raw::RawAction) { emit_actions(&[action]) } -pub fn emit_actions(_actions: Vec) { +pub fn emit_actions(actions: &[api::raw::RawAction]) { #[cfg(target_arch = "wasm32")] { - let ret = bincode::serialize(&_actions).expect("Can't serialize action in emit"); + let ret = bincode::serialize(actions.as_ref()).expect("Can't serialize action in emit"); unsafe { raw_emit_actions(to_i64(ret.as_ptr() as _), to_i64(ret.len() as _)); } @@ -75,9 +61,10 @@ macro_rules! log { ($($x:tt)*) => { $crate::print_str(&format!($($x)*)) }; } -pub fn read_input(ptr: i64, len: i64) -> Result +/// Safety: Data pointed to by `ptr` must outlive 'a +pub unsafe fn read_input<'a, T>(ptr: i64, len: i64) -> Result where - T: DeserializeOwned, + T: Deserialize<'a>, { let slice = unsafe { ::std::slice::from_raw_parts(from_i64(ptr) as _, from_i64(len) as _) }; bincode::deserialize(slice).map_err(|_| "Failed to deserialize function input") diff --git a/plugin/rt/src/retrieve.rs b/plugin/rt/src/retrieve.rs deleted file mode 100644 index 823e974f54..0000000000 --- a/plugin/rt/src/retrieve.rs +++ /dev/null @@ -1,35 +0,0 @@ -use plugin_api::{Health, RetrieveError}; - -use crate::api::{Retrieve, RetrieveResult}; - -pub trait GetPlayerName { - fn get_player_name(&self) -> Result; -} - -pub trait GetEntityHealth { - fn get_entity_health(&self) -> Result; -} - -impl GetEntityHealth for crate::api::event::Player { - fn get_entity_health(&self) -> Result { - if let RetrieveResult::GetEntityHealth(e) = - crate::retrieve_action(&Retrieve::GetEntityHealth(self.id))? - { - Ok(e) - } else { - Err(RetrieveError::InvalidType) - } - } -} - -impl GetPlayerName for crate::api::event::Player { - fn get_player_name(&self) -> Result { - if let RetrieveResult::GetPlayerName(e) = - crate::retrieve_action(&Retrieve::GetPlayerName(self.id))? - { - Ok(e) - } else { - Err(RetrieveError::InvalidType) - } - } -} diff --git a/server/src/lib.rs b/server/src/lib.rs index b7ca95298c..74c6b0baa9 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -94,6 +94,7 @@ use prometheus::Registry; use prometheus_hyper::Server as PrometheusServer; use specs::{join::Join, Builder, Entity as EcsEntity, SystemData, WorldExt}; use std::{ + borrow::Cow, i32, ops::{Deref, DerefMut}, sync::Arc, @@ -1017,12 +1018,12 @@ impl Server { ); return; }; - let rs = plugin_manager.execute_event( + let rs = plugin_manager.execute_event::( &ecs_world, - &plugin_api::event::ChatCommandEvent { - command: kwd.clone(), - command_args: args.split(' ').map(|x| x.to_owned()).collect(), - player: plugin_api::event::Player { id: uid }, + &plugin_api::event::command::RawCommand { + entity: uid, + cmd: &kwd, + args: Cow::Owned(args.split(' ').collect()), }, ); match rs { diff --git a/server/src/login_provider.rs b/server/src/login_provider.rs index 90eac64fa6..ff20a9b65b 100644 --- a/server/src/login_provider.rs +++ b/server/src/login_provider.rs @@ -8,10 +8,10 @@ use common_state::plugin::memory_manager::EcsWorld; #[cfg(feature = "plugins")] use common_state::plugin::PluginMgr; use hashbrown::HashMap; -use plugin_api::event::{PlayerJoinEvent, PlayerJoinResult}; +use plugin_api::event::player_join::{PlayerJoin, PlayerJoinResponse, RawPlayerJoin}; use specs::Component; use specs_idvs::IdvStorage; -use std::{str::FromStr, sync::Arc}; +use std::{borrow::Cow, str::FromStr, sync::Arc}; use tokio::{runtime::Runtime, sync::oneshot}; use tracing::{error, info}; @@ -141,15 +141,17 @@ impl LoginProvider { { // Plugin player join hooks execute for all players, but are only allowed to // filter non-admins. - match plugin_manager.execute_event(&world, &PlayerJoinEvent { - player_name: username.clone(), - player_id: *uuid.as_bytes(), + match plugin_manager.execute_event::(&world, &RawPlayerJoin { + uuid: *uuid.as_bytes(), + alias: Cow::Borrowed(&username), }) { Ok(e) => { if admin.is_none() { for i in e.into_iter() { - if let PlayerJoinResult::Kick(a) = i { - return Some(Err(RegisterError::Kicked(a))); + if let PlayerJoinResponse::Reject { reason } = i { + return Some(Err(RegisterError::Kicked( + reason.into_owned(), + ))); } } } diff --git a/server/src/sys/msg/register.rs b/server/src/sys/msg/register.rs index 99dda57b95..b0bef8f49e 100644 --- a/server/src/sys/msg/register.rs +++ b/server/src/sys/msg/register.rs @@ -5,7 +5,7 @@ use crate::{ EditableSettings, }; use common::{ - comp::{Admin, Player, Stats}, + comp::{Admin, Health, Player, Stats}, event::{EventBus, ServerEvent}, uid::{Uid, UidAllocator}, }; @@ -15,7 +15,6 @@ use common_net::msg::{ ServerGeneral, ServerRegisterAnswer, }; use hashbrown::HashMap; -use plugin_api::Health; use specs::{ storage::StorageEntry, Entities, Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage, };