Began overhauling plugin API

This commit is contained in:
Joshua Barretto 2021-06-12 03:26:16 +01:00
parent 51e5b39f5c
commit cd694b0d6c
20 changed files with 387 additions and 228 deletions

View File

@ -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<T: DeserializeOwned>(
memory: &Memory,
pub fn read_data<'a, T: for<'b> Deserialize<'b>>(
memory: &'a Memory,
position: u64,
length: u64,
) -> Result<T, bincode::Error> {

View File

@ -81,13 +81,13 @@ impl Plugin {
})
}
pub fn execute_prepared<T>(
pub fn execute_prepared<'a, T>(
&self,
ecs: &EcsWorld,
event: &PreparedEventQuery<T>,
) -> Result<Vec<T::Response>, PluginError>
event: &PreparedEventQuery<'a, T>,
) -> Result<Vec<<T as Event<'a>>::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<T>(
pub fn execute_prepared<'a, T>(
&self,
ecs: &EcsWorld,
event: &PreparedEventQuery<T>,
) -> Result<Vec<T::Response>, PluginError>
event: &PreparedEventQuery<'a, T>,
) -> Result<Vec<<T as Event<'a>>::Response>, PluginError>
where
T: Event,
T: Event<'a>,
{
Ok(self
.plugins
@ -137,15 +137,15 @@ impl PluginMgr {
.collect())
}
pub fn execute_event<T>(
pub fn execute_event<'a, T>(
&self,
ecs: &EcsWorld,
event: &T,
) -> Result<Vec<T::Response>, PluginError>
raw_event: &'a T::Raw,
) -> Result<Vec<<T as Event<'a>>::Response>, PluginError>
where
T: Event,
T: Event<'a>,
{
self.execute_prepared(ecs, &PreparedEventQuery::new(event)?)
self.execute_prepared::<T>(ecs, &PreparedEventQuery::new(raw_event)?)
}
pub fn from_dir<P: AsRef<Path>>(path: P) -> Result<Self, PluginError> {

View File

@ -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::<Vec<RawAction>>(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<T>(
pub fn try_execute<'a, T>(
&self,
ecs: &EcsWorld,
request: &PreparedEventQuery<T>,
) -> Option<Result<T::Response, PluginModuleError>>
) -> Option<Result<<T as Event<'a>>::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<T> {
pub struct PreparedEventQuery<'a, T> {
bytes: Vec<u8>,
function_name: Cow<'static, str>,
function_name: Cow<'a, str>,
_phantom: PhantomData<T>,
}
impl<T: Event> PreparedEventQuery<T> {
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<Self, PluginError>
where
T: Event,
{
pub fn new(raw_event: &'a <T as Event<'a>>::Raw) -> Result<Self, PluginError> {
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<RetrieveResult, RetrieveError> {
match action {
Retrieve::GetPlayerName(e) => {
fn request<'a>(ecs: &'a EcsAccessManager, req: RawRequest) -> Result<RawResponse<'a>, ()> {
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<Action>) {
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!(),
}
}

View File

@ -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<T: DeserializeOwned>(
&self,
pub fn read_data<'a, T: for<'b> Deserialize<'b>>(
&'a self,
position: u64,
length: u64,
) -> Result<T, bincode::Error> {

View File

@ -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::<UidAllocator>().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::<plugin_api::event::Init>(
&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()

23
plugin/api/src/entity.rs Normal file
View File

@ -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<T: ToString>(&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!() }
}

19
plugin/api/src/event.rs Normal file
View File

@ -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>;
}

View File

@ -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<Vec<String>, 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 }
}

View File

@ -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 }
}

View File

@ -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> },
}

View File

@ -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 {
// }
// }
}
*/

24
plugin/api/src/raw.rs Normal file
View File

@ -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),
}

View File

@ -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<E: ::veloren_plugin_rt::api::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<E: ::veloren_plugin_rt::api::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()
}

View File

@ -13,6 +13,3 @@ bincode = "1.3.1"
[[example]]
name = "hello"
crate-type = ["cdylib"]
[dev-dependencies]
plugin-derive = { package = "veloren-plugin-derive", path = "../derive"}

View File

@ -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<Vec<String>, 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<Vec<String>, 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
}
}

View File

@ -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<T: DeserializeOwned>(_actions: &api::Retrieve) -> Result<T, RetrieveError> {
pub fn request(req: api::raw::RawRequest) -> Result<api::raw::RawResponse<'static>, ()> {
#[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::<Result<T, RetrieveError>>(&a)
.map_err(|x| RetrieveError::BincodeError(x.to_string()))?
}
::std::slice::from_raw_parts((ptr + 8) as *const u8, len as _)
};
bincode::deserialize::<Result<api::raw::RawResponse<'static>, ()>>(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<api::Action>) {
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<T>(ptr: i64, len: i64) -> Result<T, &'static str>
/// Safety: Data pointed to by `ptr` must outlive 'a
pub unsafe fn read_input<'a, T>(ptr: i64, len: i64) -> Result<T, &'static str>
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")

View File

@ -1,35 +0,0 @@
use plugin_api::{Health, RetrieveError};
use crate::api::{Retrieve, RetrieveResult};
pub trait GetPlayerName {
fn get_player_name(&self) -> Result<String, RetrieveError>;
}
pub trait GetEntityHealth {
fn get_entity_health(&self) -> Result<Health, RetrieveError>;
}
impl GetEntityHealth for crate::api::event::Player {
fn get_entity_health(&self) -> Result<Health, RetrieveError> {
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<String, RetrieveError> {
if let RetrieveResult::GetPlayerName(e) =
crate::retrieve_action(&Retrieve::GetPlayerName(self.id))?
{
Ok(e)
} else {
Err(RetrieveError::InvalidType)
}
}
}

View File

@ -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::<plugin_api::event::Command>(
&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 {

View File

@ -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::<PlayerJoin>(&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(),
)));
}
}
}

View File

@ -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,
};