mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Began overhauling plugin API
This commit is contained in:
parent
51e5b39f5c
commit
cd694b0d6c
@ -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> {
|
||||
|
@ -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> {
|
||||
|
@ -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!(),
|
||||
}
|
||||
}
|
||||
|
@ -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> {
|
||||
|
@ -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
23
plugin/api/src/entity.rs
Normal 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
19
plugin/api/src/event.rs
Normal 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>;
|
||||
}
|
39
plugin/api/src/event/command.rs
Normal file
39
plugin/api/src/event/command.rs
Normal 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 }
|
||||
}
|
30
plugin/api/src/event/init.rs
Normal file
30
plugin/api/src/event/init.rs
Normal 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 }
|
||||
}
|
40
plugin/api/src/event/player_join.rs
Normal file
40
plugin/api/src/event/player_join.rs
Normal 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> },
|
||||
}
|
@ -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
24
plugin/api/src/raw.rs
Normal 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),
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
@ -13,6 +13,3 @@ bincode = "1.3.1"
|
||||
[[example]]
|
||||
name = "hello"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dev-dependencies]
|
||||
plugin-derive = { package = "veloren-plugin-derive", path = "../derive"}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user