mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'ccgauche/plugin-retreive' into 'master'
ccgauche/plugin retreive See merge request veloren/veloren!1788
This commit is contained in:
commit
da3edf54fb
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- Plugin can now retrieve data from ECS
|
||||
- Added chat commands for inviting, kicking, leaving, and promoting in groups
|
||||
- Aura system
|
||||
- Campfire resting heal
|
||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -5637,6 +5637,7 @@ dependencies = [
|
||||
"indexmap",
|
||||
"rand 0.8.3",
|
||||
"rayon",
|
||||
"scopeguard",
|
||||
"serde",
|
||||
"slab",
|
||||
"specs",
|
||||
@ -5698,6 +5699,7 @@ dependencies = [
|
||||
name = "veloren-plugin-api"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"serde",
|
||||
"veloren-common",
|
||||
]
|
||||
|
@ -13,6 +13,11 @@ bin_csv = ["csv", "structopt"]
|
||||
default = ["simd"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
# Serde
|
||||
serde = { version = "1.0.110", features = ["derive", "rc"] }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
approx = "0.4.0"
|
||||
arraygen = "0.1.13"
|
||||
crossbeam-utils = "0.8.1"
|
||||
@ -22,20 +27,34 @@ lazy_static = "1.4.0"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
ordered-float = { version = "2.0.1", default-features = false }
|
||||
rand = "0.8"
|
||||
rayon = "1.5"
|
||||
roots = "0.0.6"
|
||||
spin_sleep = "1.0"
|
||||
tracing = { version = "0.1", default-features = false }
|
||||
vek = { version = "=0.12.0", features = ["serde"] }
|
||||
uuid = { version = "0.8.1", default-features = false, features = ["serde", "v4"] }
|
||||
rand = "0.8"
|
||||
|
||||
# Assets
|
||||
assets_manager = {version = "0.4.2", features = ["bincode", "ron", "json", "hot-reloading"]}
|
||||
directories-next = "2.0"
|
||||
dot_vox = "4.0"
|
||||
image = { version = "0.23.12", default-features = false, features = ["png"] }
|
||||
|
||||
# Assets
|
||||
assets_manager = {version = "0.4.2", features = ["bincode", "ron", "json", "hot-reloading"]}
|
||||
# Serde
|
||||
ron = { version = "0.6", default-features = false }
|
||||
serde_json = "1.0.50"
|
||||
serde_repr = "0.1.6"
|
||||
|
||||
# esv export
|
||||
csv = { version = "1.1.3", optional = true }
|
||||
structopt = { version = "0.3.13", optional = true }
|
||||
|
||||
# Tracy
|
||||
tracy-client = { version = "0.10.0", optional = true }
|
||||
|
||||
|
||||
# Data structures
|
||||
hashbrown = { version = "0.9", features = ["rayon", "serde", "nightly"] }
|
||||
slotmap = { version = "1.0", features = ["serde"] }
|
||||
@ -45,18 +64,6 @@ slab = "0.4.2"
|
||||
# ECS
|
||||
specs = { git = "https://github.com/amethyst/specs.git", features = ["serde", "storage-event-control", "nightly"], rev = "d4435bdf496cf322c74886ca09dd8795984919b4" }
|
||||
specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", rev = "9fab7b396acd6454585486e50ae4bfe2069858a9" }
|
||||
# Serde
|
||||
ron = { version = "0.6", default-features = false }
|
||||
serde = { version = "1.0.110", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.50"
|
||||
serde_repr = "0.1.6"
|
||||
|
||||
#esv export
|
||||
csv = { version = "1.1.3", optional = true }
|
||||
structopt = { version = "0.3.13", optional = true }
|
||||
|
||||
# Tracy
|
||||
tracy-client = { version = "0.10.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
#bench
|
||||
|
@ -1,6 +1,8 @@
|
||||
use crate::comp::buff::BuffKind;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::{
|
||||
comp::{
|
||||
buff::{Buff, BuffChange, BuffData, BuffKind, BuffSource},
|
||||
buff::{Buff, BuffChange, BuffData, BuffSource},
|
||||
inventory::{
|
||||
item::{
|
||||
armor::Protection,
|
||||
@ -18,18 +20,26 @@ use crate::{
|
||||
uid::Uid,
|
||||
util::Dir,
|
||||
};
|
||||
use rand::{thread_rng, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::Entity as EcsEntity;
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use specs::Entity as EcsEntity;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::time::Duration;
|
||||
#[cfg(not(target_arch = "wasm32"))] use vek::*;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum GroupTarget {
|
||||
InGroup,
|
||||
OutOfGroup,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct AttackerInfo<'a> {
|
||||
pub entity: EcsEntity,
|
||||
@ -37,6 +47,7 @@ pub struct AttackerInfo<'a> {
|
||||
pub energy: Option<&'a Energy>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)] // TODO: Yeet clone derive
|
||||
pub struct Attack {
|
||||
damages: Vec<AttackDamage>,
|
||||
@ -45,6 +56,7 @@ pub struct Attack {
|
||||
crit_multiplier: f32,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Default for Attack {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@ -56,6 +68,7 @@ impl Default for Attack {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Attack {
|
||||
pub fn with_damage(mut self, damage: AttackDamage) -> Self {
|
||||
self.damages.push(damage);
|
||||
@ -296,6 +309,7 @@ impl Attack {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AttackDamage {
|
||||
damage: Damage,
|
||||
@ -303,6 +317,7 @@ pub struct AttackDamage {
|
||||
effects: Vec<CombatEffect>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl AttackDamage {
|
||||
pub fn new(damage: Damage, target: Option<GroupTarget>) -> Self {
|
||||
Self {
|
||||
@ -318,6 +333,7 @@ impl AttackDamage {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AttackEffect {
|
||||
target: Option<GroupTarget>,
|
||||
@ -325,6 +341,7 @@ pub struct AttackEffect {
|
||||
requirement: Option<CombatRequirement>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl AttackEffect {
|
||||
pub fn new(target: Option<GroupTarget>, effect: CombatEffect) -> Self {
|
||||
Self {
|
||||
@ -342,6 +359,7 @@ impl AttackEffect {
|
||||
pub fn effect(&self) -> &CombatEffect { &self.effect }
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum CombatEffect {
|
||||
Heal(f32),
|
||||
@ -352,6 +370,7 @@ pub enum CombatEffect {
|
||||
Poise(f32),
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum CombatRequirement {
|
||||
AnyDamage,
|
||||
@ -370,12 +389,14 @@ pub enum DamageSource {
|
||||
Other,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Damage {
|
||||
pub source: DamageSource,
|
||||
pub value: f32,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Damage {
|
||||
/// Returns the total damage reduction provided by all equipped items
|
||||
pub fn compute_damage_reduction(inventory: &Inventory) -> f32 {
|
||||
@ -520,12 +541,14 @@ impl Damage {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Knockback {
|
||||
pub direction: KnockbackDir,
|
||||
pub strength: f32,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum KnockbackDir {
|
||||
Away,
|
||||
@ -534,6 +557,7 @@ pub enum KnockbackDir {
|
||||
TowardsUp,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Knockback {
|
||||
pub fn calculate_impulse(self, dir: Dir) -> Vec3<f32> {
|
||||
match self.direction {
|
||||
@ -554,6 +578,7 @@ impl Knockback {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct CombatBuff {
|
||||
pub kind: BuffKind,
|
||||
@ -562,12 +587,14 @@ pub struct CombatBuff {
|
||||
pub chance: f32,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub enum CombatBuffStrength {
|
||||
DamageFraction(f32),
|
||||
Value(f32),
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl CombatBuffStrength {
|
||||
fn to_strength(self, damage: f32) -> f32 {
|
||||
match self {
|
||||
@ -577,6 +604,7 @@ impl CombatBuffStrength {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl CombatBuff {
|
||||
fn to_buff(self, uid: Option<Uid>, damage: f32) -> Buff {
|
||||
// TODO: Generate BufCategoryId vec (probably requires damage overhaul?)
|
||||
@ -606,6 +634,7 @@ impl CombatBuff {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn equipped_item_and_tool(inv: &Inventory, slot: EquipSlot) -> Option<(&Item, &Tool)> {
|
||||
inv.equipped(slot).and_then(|i| {
|
||||
if let ItemKind::Tool(tool) = &i.kind() {
|
||||
@ -616,6 +645,7 @@ fn equipped_item_and_tool(inv: &Inventory, slot: EquipSlot) -> Option<(&Item, &T
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn get_weapons(inv: &Inventory) -> (Option<ToolKind>, Option<ToolKind>) {
|
||||
(
|
||||
equipped_item_and_tool(inv, EquipSlot::Mainhand).map(|(_, tool)| tool.kind),
|
||||
@ -623,6 +653,7 @@ pub fn get_weapons(inv: &Inventory) -> (Option<ToolKind>, Option<ToolKind>) {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn offensive_rating(inv: &Inventory, skillset: &SkillSet) -> f32 {
|
||||
let active_damage =
|
||||
equipped_item_and_tool(inv, EquipSlot::Mainhand).map_or(0.0, |(item, tool)| {
|
||||
@ -639,6 +670,7 @@ fn offensive_rating(inv: &Inventory, skillset: &SkillSet) -> f32 {
|
||||
active_damage.max(second_damage)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn combat_rating(inventory: &Inventory, health: &Health, stats: &Stats, body: Body) -> f32 {
|
||||
let defensive_weighting = 1.0;
|
||||
let offensive_weighting = 1.0;
|
||||
|
@ -1,8 +1,13 @@
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::uid::Uid;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use specs::{Component, DerefFlaggedStorage};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use specs_idvs::IdvStorage;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::{cmp::Ordering, time::Duration};
|
||||
|
||||
/// De/buff Kind.
|
||||
@ -28,6 +33,7 @@ pub enum BuffKind {
|
||||
IncreaseMaxHealth,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl BuffKind {
|
||||
/// Checks if buff is buff or debuff
|
||||
pub fn is_buff(self) -> bool {
|
||||
@ -48,12 +54,14 @@ impl BuffKind {
|
||||
}
|
||||
|
||||
// Struct used to store data relevant to a buff
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct BuffData {
|
||||
pub strength: f32,
|
||||
pub duration: Option<Duration>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl BuffData {
|
||||
pub fn new(strength: f32, duration: Option<Duration>) -> Self { Self { strength, duration } }
|
||||
}
|
||||
@ -61,6 +69,7 @@ impl BuffData {
|
||||
/// De/buff category ID.
|
||||
/// Similar to `BuffKind`, but to mark a category (for more generic usage, like
|
||||
/// positive/negative buffs).
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub enum BuffCategory {
|
||||
Natural,
|
||||
@ -70,6 +79,7 @@ pub enum BuffCategory {
|
||||
PersistOnDeath,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ModifierKind {
|
||||
Additive,
|
||||
@ -77,6 +87,7 @@ pub enum ModifierKind {
|
||||
}
|
||||
|
||||
/// Data indicating and configuring behaviour of a de/buff.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum BuffEffect {
|
||||
/// Periodically damages or heals entity
|
||||
@ -101,6 +112,7 @@ pub enum BuffEffect {
|
||||
///
|
||||
/// To provide more classification info when needed,
|
||||
/// buff can be in one or more buff category.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Buff {
|
||||
pub kind: BuffKind,
|
||||
@ -113,6 +125,7 @@ pub struct Buff {
|
||||
|
||||
/// Information about whether buff addition or removal was requested.
|
||||
/// This to implement "on_add" and "on_remove" hooks for constant buffs.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum BuffChange {
|
||||
/// Adds this buff.
|
||||
@ -134,6 +147,7 @@ pub enum BuffChange {
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Buff {
|
||||
/// Builder function for buffs
|
||||
pub fn new(
|
||||
@ -200,6 +214,7 @@ impl Buff {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl PartialOrd for Buff {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
if self == other {
|
||||
@ -218,10 +233,12 @@ impl PartialOrd for Buff {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn compare_duration(a: Option<Duration>, b: Option<Duration>) -> bool {
|
||||
a.map_or(true, |dur_a| b.map_or(false, |dur_b| dur_a > dur_b))
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl PartialEq for Buff {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.data.strength == other.data.strength && self.time == other.time
|
||||
@ -229,6 +246,7 @@ impl PartialEq for Buff {
|
||||
}
|
||||
|
||||
/// Source of the de/buff
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub enum BuffSource {
|
||||
/// Applied by a character
|
||||
@ -256,6 +274,7 @@ pub enum BuffSource {
|
||||
/// and undone on removal of the buff (by the specs system).
|
||||
/// Example could be decreasing max health, which, if repeated each tick,
|
||||
/// would be probably an undesired effect).
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
|
||||
pub struct Buffs {
|
||||
/// Uid used for synchronization
|
||||
@ -266,6 +285,7 @@ pub struct Buffs {
|
||||
pub buffs: HashMap<BuffId, Buff>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Buffs {
|
||||
fn sort_kind(&mut self, kind: BuffKind) {
|
||||
if let Some(buff_order) = self.kinds.get_mut(&kind) {
|
||||
@ -339,8 +359,10 @@ impl Buffs {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub type BuffId = u64;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Component for Buffs {
|
||||
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
@ -1,6 +1,11 @@
|
||||
use crate::{comp::Body, uid::Uid, DamageSource};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::comp::Body;
|
||||
use crate::{uid::Uid, DamageSource};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use specs::{Component, DerefFlaggedStorage};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use specs_idvs::IdvStorage;
|
||||
|
||||
/// Specifies what and how much changed current health
|
||||
@ -40,6 +45,7 @@ pub struct Health {
|
||||
}
|
||||
|
||||
impl Health {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn new(body: Body, level: u16) -> Self {
|
||||
let mut health = Health::empty();
|
||||
|
||||
@ -67,6 +73,7 @@ impl Health {
|
||||
|
||||
pub fn maximum(&self) -> u32 { self.maximum }
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn set_to(&mut self, amount: u32, cause: HealthSource) {
|
||||
let amount = amount.min(self.maximum);
|
||||
self.last_change = (0.0, HealthChange {
|
||||
@ -76,6 +83,7 @@ impl Health {
|
||||
self.current = amount;
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn change_by(&mut self, change: HealthChange) {
|
||||
self.current = ((self.current as i32 + change.amount).max(0) as u32).min(self.maximum);
|
||||
self.last_change = (0.0, change);
|
||||
@ -90,6 +98,7 @@ impl Health {
|
||||
}
|
||||
|
||||
// This is private because max hp is based on the level
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn set_base_max(&mut self, amount: u32) {
|
||||
self.base_max = amount;
|
||||
self.current = self.current.min(self.maximum);
|
||||
@ -97,12 +106,14 @@ impl Health {
|
||||
|
||||
pub fn should_die(&self) -> bool { self.current == 0 }
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn revive(&mut self) {
|
||||
self.set_to(self.maximum(), HealthSource::Revive);
|
||||
self.is_dead = false;
|
||||
}
|
||||
|
||||
// TODO: Delete this once stat points will be a thing
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn update_max_hp(&mut self, body: Option<Body>, level: u16) {
|
||||
if let Some(body) = body {
|
||||
self.set_base_max(body.base_health() + body.base_health_increase() * level as u32);
|
||||
@ -114,14 +125,17 @@ impl Health {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn with_max_health(mut self, amount: u32) -> Self {
|
||||
self.maximum = amount;
|
||||
self.current = amount;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn last_set(&mut self) { self.last_max = self.maximum }
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn reset_max(&mut self) {
|
||||
self.maximum = self.base_max;
|
||||
if self.current > self.last_max {
|
||||
@ -131,6 +145,8 @@ impl Health {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Component for Health {
|
||||
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
@ -140,6 +156,7 @@ pub struct Dead {
|
||||
pub cause: HealthSource,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Component for Dead {
|
||||
type Storage = IdvStorage<Self>;
|
||||
}
|
||||
|
@ -1,76 +1,91 @@
|
||||
mod ability;
|
||||
mod admin;
|
||||
pub mod agent;
|
||||
pub mod aura;
|
||||
pub mod beam;
|
||||
pub mod body;
|
||||
#[cfg(not(target_arch = "wasm32"))] mod ability;
|
||||
#[cfg(not(target_arch = "wasm32"))] mod admin;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod agent;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod aura;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod beam;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod body;
|
||||
pub mod buff;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod character_state;
|
||||
pub mod chat;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod chat;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod controller;
|
||||
mod energy;
|
||||
pub mod group;
|
||||
#[cfg(not(target_arch = "wasm32"))] mod energy;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod group;
|
||||
mod health;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod home_chunk;
|
||||
mod inputs;
|
||||
#[cfg(not(target_arch = "wasm32"))] mod inputs;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod inventory;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod invite;
|
||||
mod last;
|
||||
mod location;
|
||||
mod misc;
|
||||
pub mod ori;
|
||||
mod phys;
|
||||
mod player;
|
||||
pub mod poise;
|
||||
#[cfg(not(target_arch = "wasm32"))] mod last;
|
||||
#[cfg(not(target_arch = "wasm32"))] mod location;
|
||||
#[cfg(not(target_arch = "wasm32"))] mod misc;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod ori;
|
||||
#[cfg(not(target_arch = "wasm32"))] mod phys;
|
||||
#[cfg(not(target_arch = "wasm32"))] mod player;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod poise;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod projectile;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod shockwave;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod skills;
|
||||
mod stats;
|
||||
#[cfg(not(target_arch = "wasm32"))] mod stats;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod visual;
|
||||
|
||||
// Reexports
|
||||
pub use ability::{CharacterAbility, CharacterAbilityType};
|
||||
pub use admin::Admin;
|
||||
pub use agent::{Agent, Alignment};
|
||||
pub use aura::{Aura, AuraChange, AuraKind, Auras};
|
||||
pub use beam::{Beam, BeamSegment};
|
||||
pub use body::{
|
||||
biped_large, bird_medium, bird_small, dragon, fish_medium, fish_small, golem, humanoid, object,
|
||||
quadruped_low, quadruped_medium, quadruped_small, theropod, AllBodies, Body, BodyData,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use self::{
|
||||
ability::{CharacterAbility, CharacterAbilityType},
|
||||
admin::Admin,
|
||||
agent::{Agent, Alignment},
|
||||
aura::{Aura, AuraChange, AuraKind, Auras},
|
||||
beam::{Beam, BeamSegment},
|
||||
body::{
|
||||
biped_large, bird_medium, bird_small, dragon, fish_medium, fish_small, golem, humanoid,
|
||||
object, quadruped_low, quadruped_medium, quadruped_small, theropod, AllBodies, Body,
|
||||
BodyData,
|
||||
},
|
||||
buff::{
|
||||
Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffId, BuffKind, BuffSource, Buffs,
|
||||
ModifierKind,
|
||||
},
|
||||
character_state::{CharacterState, Melee, StateUpdate},
|
||||
chat::{
|
||||
ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg,
|
||||
},
|
||||
controller::{
|
||||
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, Input,
|
||||
InventoryManip, LoadoutManip, MountState, Mounting, SlotManip,
|
||||
},
|
||||
energy::{Energy, EnergyChange, EnergySource},
|
||||
group::Group,
|
||||
home_chunk::HomeChunk,
|
||||
inputs::CanBuild,
|
||||
inventory::{
|
||||
item,
|
||||
item::{Item, ItemConfig, ItemDrop},
|
||||
slot, Inventory, InventoryUpdate, InventoryUpdateEvent,
|
||||
},
|
||||
last::Last,
|
||||
location::{Waypoint, WaypointArea},
|
||||
misc::Object,
|
||||
ori::Ori,
|
||||
phys::{
|
||||
Collider, ForceUpdate, Gravity, Mass, PhysicsState, Pos, PreviousPhysCache, Scale, Sticky,
|
||||
Vel,
|
||||
},
|
||||
player::Player,
|
||||
poise::{Poise, PoiseChange, PoiseSource, PoiseState},
|
||||
projectile::{Projectile, ProjectileConstructor},
|
||||
shockwave::{Shockwave, ShockwaveHitEntities},
|
||||
skills::{Skill, SkillGroup, SkillGroupKind, SkillSet},
|
||||
stats::Stats,
|
||||
visual::{LightAnimation, LightEmitter},
|
||||
};
|
||||
pub use buff::{
|
||||
Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffId, BuffKind, BuffSource, Buffs,
|
||||
ModifierKind,
|
||||
};
|
||||
pub use character_state::{CharacterState, Melee, StateUpdate};
|
||||
pub use chat::{
|
||||
ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg,
|
||||
};
|
||||
pub use controller::{
|
||||
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, Input,
|
||||
InventoryManip, LoadoutManip, MountState, Mounting, SlotManip,
|
||||
};
|
||||
pub use energy::{Energy, EnergyChange, EnergySource};
|
||||
pub use group::Group;
|
||||
|
||||
pub use health::{Health, HealthChange, HealthSource};
|
||||
pub use home_chunk::HomeChunk;
|
||||
pub use inputs::CanBuild;
|
||||
pub use inventory::{
|
||||
item,
|
||||
item::{Item, ItemConfig, ItemDrop},
|
||||
slot, Inventory, InventoryUpdate, InventoryUpdateEvent,
|
||||
};
|
||||
pub use last::Last;
|
||||
pub use location::{Waypoint, WaypointArea};
|
||||
pub use misc::Object;
|
||||
pub use ori::Ori;
|
||||
pub use phys::{
|
||||
Collider, ForceUpdate, Gravity, Mass, PhysicsState, Pos, PreviousPhysCache, Scale, Sticky, Vel,
|
||||
};
|
||||
pub use player::Player;
|
||||
pub use poise::{Poise, PoiseChange, PoiseSource, PoiseState};
|
||||
pub use projectile::{Projectile, ProjectileConstructor};
|
||||
pub use shockwave::{Shockwave, ShockwaveHitEntities};
|
||||
pub use skills::{Skill, SkillGroup, SkillGroupKind, SkillSet};
|
||||
pub use stats::Stats;
|
||||
pub use visual::{LightAnimation, LightEmitter};
|
||||
|
@ -18,45 +18,66 @@
|
||||
trait_alias,
|
||||
type_alias_impl_trait
|
||||
)]
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod assets;
|
||||
pub mod astar;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod astar;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod character;
|
||||
pub mod clock;
|
||||
pub mod cmd;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod clock;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod cmd;
|
||||
pub mod combat;
|
||||
pub mod comp;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod consts;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod effect;
|
||||
pub mod event;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod event;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod explosion;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod figure;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod generation;
|
||||
pub mod grid;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod grid;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod lottery;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod metrics;
|
||||
pub mod npc;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod npc;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod outcome;
|
||||
pub mod path;
|
||||
pub mod ray;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod path;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod ray;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod recipe;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod region;
|
||||
pub mod resources;
|
||||
pub mod rtsim;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod rtsim;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod skillset_builder;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod spiral;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod states;
|
||||
pub mod store;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod store;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod terrain;
|
||||
pub mod time;
|
||||
pub mod trade;
|
||||
pub mod typed;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod time;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod trade;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod typed;
|
||||
pub mod uid;
|
||||
pub mod util;
|
||||
pub mod vol;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod util;
|
||||
#[cfg(not(target_arch = "wasm32"))] pub mod vol;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod volumes;
|
||||
|
||||
pub use combat::{Damage, DamageSource, GroupTarget, Knockback, KnockbackDir};
|
||||
pub use combat::DamageSource;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use combat::{Damage, GroupTarget, Knockback, KnockbackDir};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use comp::inventory::loadout_builder::LoadoutBuilder;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use explosion::{Explosion, RadiusEffect};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use skillset_builder::SkillSetBuilder;
|
||||
|
@ -1,5 +1,7 @@
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use specs::{
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
world::EntitiesRes,
|
||||
@ -22,10 +24,12 @@ impl fmt::Display for Uid {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) }
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Component for Uid {
|
||||
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Marker for Uid {
|
||||
type Allocator = UidAllocator;
|
||||
type Identifier = u64;
|
||||
@ -37,11 +41,14 @@ impl Marker for Uid {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Debug)]
|
||||
pub struct UidAllocator {
|
||||
index: u64,
|
||||
mapping: HashMap<u64, Entity>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl UidAllocator {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@ -55,10 +62,12 @@ impl UidAllocator {
|
||||
pub fn remove_entity(&mut self, id: u64) -> Option<Entity> { self.mapping.remove(&id) }
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Default for UidAllocator {
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl MarkerAllocator<Uid> for UidAllocator {
|
||||
fn allocate(&mut self, entity: Entity, id: Option<u64>) -> Uid {
|
||||
let id = id.unwrap_or_else(|| {
|
||||
|
@ -35,6 +35,7 @@ serde = { version = "1.0.110", features = ["derive"] }
|
||||
tracy-client = { version = "0.10.0", optional = true }
|
||||
|
||||
# Plugins
|
||||
scopeguard = "1.1.0"
|
||||
toml = { version = "0.5.7", optional = true }
|
||||
tar = { version = "0.4.30", optional = true }
|
||||
wasmer = { version = "1.0.0", optional = true, default-features = false, features = ["wat", "default-cranelift", "default-jit"] }
|
||||
|
@ -25,6 +25,7 @@ pub enum PluginModuleError {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MemoryAllocationError {
|
||||
InvalidReturnType,
|
||||
AllocatorNotFound(ExportError),
|
||||
CantAllocate(RuntimeError),
|
||||
}
|
||||
|
143
common/sys/src/plugin/memory_manager.rs
Normal file
143
common/sys/src/plugin/memory_manager.rs
Normal file
@ -0,0 +1,143 @@
|
||||
use std::sync::atomic::{AtomicI32, AtomicPtr, AtomicU32, Ordering};
|
||||
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use specs::World;
|
||||
use wasmer::{Function, Memory, Value};
|
||||
|
||||
use super::errors::{MemoryAllocationError, PluginModuleError};
|
||||
|
||||
/// This structure wraps the ECS pointer to ensure safety
|
||||
pub struct EcsAccessManager {
|
||||
ecs_pointer: AtomicPtr<World>,
|
||||
}
|
||||
|
||||
impl Default for EcsAccessManager {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ecs_pointer: AtomicPtr::new(std::ptr::null_mut()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EcsAccessManager {
|
||||
// This function take a World reference and a function to execute ensuring the
|
||||
// pointer will never be corrupted during the execution of the function!
|
||||
pub fn execute_with<T>(&self, world: &World, func: impl FnOnce() -> T) -> T {
|
||||
let _guard = scopeguard::guard((), |_| {
|
||||
// ensure the pointer is cleared in any case
|
||||
self.ecs_pointer
|
||||
.store(std::ptr::null_mut(), Ordering::Relaxed);
|
||||
});
|
||||
self.ecs_pointer
|
||||
.store(world as *const _ as *mut _, Ordering::Relaxed);
|
||||
func()
|
||||
}
|
||||
|
||||
/// 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
|
||||
self.ecs_pointer.load(Ordering::Relaxed).as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MemoryManager {
|
||||
pub pointer: AtomicI32,
|
||||
pub length: AtomicU32,
|
||||
}
|
||||
|
||||
impl Default for MemoryManager {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pointer: AtomicI32::new(0),
|
||||
length: AtomicU32::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryManager {
|
||||
/// This function check if the buffer is wide enough if not it realloc the
|
||||
/// buffer calling the `wasm_prepare_buffer` function Note: There is
|
||||
/// probably optimizations that can be done using less restrictive
|
||||
/// ordering
|
||||
pub fn get_pointer(
|
||||
&self,
|
||||
object_length: u32,
|
||||
allocator: &Function,
|
||||
) -> Result<i32, MemoryAllocationError> {
|
||||
if self.length.load(Ordering::SeqCst) >= object_length {
|
||||
return Ok(self.pointer.load(Ordering::SeqCst));
|
||||
}
|
||||
let pointer = allocator
|
||||
.call(&[Value::I32(object_length as i32)])
|
||||
.map_err(MemoryAllocationError::CantAllocate)?;
|
||||
let pointer = pointer[0]
|
||||
.i32()
|
||||
.ok_or(MemoryAllocationError::InvalidReturnType)?;
|
||||
self.length.store(object_length, Ordering::SeqCst);
|
||||
self.pointer.store(pointer, Ordering::SeqCst);
|
||||
Ok(pointer)
|
||||
}
|
||||
|
||||
/// This function writes an object to WASM memory returning a pointer and a
|
||||
/// length. Will realloc the buffer is not wide enough
|
||||
pub fn write_data<T: Serialize>(
|
||||
&self,
|
||||
memory: &Memory,
|
||||
allocator: &Function,
|
||||
object: &T,
|
||||
) -> Result<(i32, u32), PluginModuleError> {
|
||||
self.write_bytes(
|
||||
memory,
|
||||
allocator,
|
||||
&bincode::serialize(object).map_err(PluginModuleError::Encoding)?,
|
||||
)
|
||||
}
|
||||
|
||||
/// This function writes an raw bytes to WASM memory returning a pointer and
|
||||
/// a length. Will realloc the buffer is not wide enough
|
||||
pub fn write_bytes(
|
||||
&self,
|
||||
memory: &Memory,
|
||||
allocator: &Function,
|
||||
array: &[u8],
|
||||
) -> Result<(i32, u32), PluginModuleError> {
|
||||
let len = array.len();
|
||||
let mem_position = self
|
||||
.get_pointer(len as u32, allocator)
|
||||
.map_err(PluginModuleError::MemoryAllocation)? as usize;
|
||||
memory.view()[mem_position..mem_position + len]
|
||||
.iter()
|
||||
.zip(array.iter())
|
||||
.for_each(|(cell, byte)| cell.set(*byte));
|
||||
Ok((mem_position as i32, len as u32))
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
position: i32,
|
||||
length: u32,
|
||||
) -> Result<T, bincode::Error> {
|
||||
bincode::deserialize(&read_bytes(memory, position, 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> {
|
||||
memory.view()[(position as usize)..(position as usize) + length as usize]
|
||||
.iter()
|
||||
.map(|x| x.get())
|
||||
.collect()
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
pub mod errors;
|
||||
pub mod memory_manager;
|
||||
pub mod module;
|
||||
pub mod wasm_env;
|
||||
|
||||
use common::assets::ASSETS_PATH;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::World;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fs,
|
||||
@ -80,6 +83,7 @@ impl Plugin {
|
||||
|
||||
pub fn execute_prepared<T>(
|
||||
&self,
|
||||
ecs: &World,
|
||||
event_name: &str,
|
||||
event: &PreparedEventQuery<T>,
|
||||
) -> Result<Vec<T::Response>, PluginError>
|
||||
@ -89,7 +93,7 @@ impl Plugin {
|
||||
self.modules
|
||||
.iter()
|
||||
.flat_map(|module| {
|
||||
module.try_execute(event_name, event).map(|x| {
|
||||
module.try_execute(ecs, event_name, event).map(|x| {
|
||||
x.map_err(|e| {
|
||||
PluginError::PluginModuleError(
|
||||
self.data.name.to_owned(),
|
||||
@ -118,6 +122,7 @@ impl PluginMgr {
|
||||
|
||||
pub fn execute_prepared<T>(
|
||||
&self,
|
||||
ecs: &World,
|
||||
event_name: &str,
|
||||
event: &PreparedEventQuery<T>,
|
||||
) -> Result<Vec<T::Response>, PluginError>
|
||||
@ -127,7 +132,7 @@ impl PluginMgr {
|
||||
Ok(self
|
||||
.plugins
|
||||
.par_iter()
|
||||
.map(|plugin| plugin.execute_prepared(event_name, event))
|
||||
.map(|plugin| plugin.execute_prepared(ecs, event_name, event))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
@ -136,13 +141,14 @@ impl PluginMgr {
|
||||
|
||||
pub fn execute_event<T>(
|
||||
&self,
|
||||
ecs: &World,
|
||||
event_name: &str,
|
||||
event: &T,
|
||||
) -> Result<Vec<T::Response>, PluginError>
|
||||
where
|
||||
T: Event,
|
||||
{
|
||||
self.execute_prepared(event_name, &PreparedEventQuery::new(event)?)
|
||||
self.execute_prepared(ecs, event_name, &PreparedEventQuery::new(event)?)
|
||||
}
|
||||
|
||||
pub fn from_dir<P: AsRef<Path>>(path: P) -> Result<Self, PluginError> {
|
||||
|
@ -1,29 +1,39 @@
|
||||
use std::{
|
||||
cell::Cell,
|
||||
collections::HashSet,
|
||||
convert::TryInto,
|
||||
marker::PhantomData,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use wasmer::{
|
||||
imports, Cranelift, Function, HostEnvInitError, Instance, LazyInit, Memory, MemoryView, Module,
|
||||
Store, Value, WasmerEnv, JIT,
|
||||
use common::{
|
||||
comp::{Health, Player},
|
||||
uid::UidAllocator,
|
||||
};
|
||||
use specs::{saveload::MarkerAllocator, World, WorldExt};
|
||||
use wasmer::{imports, Cranelift, Function, Instance, Memory, Module, Store, Value, JIT};
|
||||
|
||||
use super::{
|
||||
errors::{PluginError, PluginModuleError},
|
||||
memory_manager::{self, EcsAccessManager, MemoryManager},
|
||||
wasm_env::HostFunctionEnvironement,
|
||||
};
|
||||
|
||||
use super::errors::{MemoryAllocationError, PluginError, PluginModuleError};
|
||||
|
||||
use plugin_api::{Action, Event};
|
||||
use plugin_api::{Action, EcsAccessError, Event, Retrieve, RetrieveError, RetrieveResult};
|
||||
|
||||
#[derive(Clone)]
|
||||
// This structure represent the WASM State of the plugin.
|
||||
/// This structure represent the WASM State of the plugin.
|
||||
pub struct PluginModule {
|
||||
wasm_state: Arc<Mutex<WasmState>>,
|
||||
ecs: Arc<EcsAccessManager>,
|
||||
wasm_state: Arc<Mutex<Instance>>,
|
||||
memory_manager: Arc<MemoryManager>,
|
||||
events: HashSet<String>,
|
||||
allocator: Function,
|
||||
memory: Memory,
|
||||
name: String,
|
||||
}
|
||||
|
||||
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> {
|
||||
// This is creating the engine is this case a JIT based on Cranelift
|
||||
let engine = JIT::new(Cranelift::default()).engine();
|
||||
@ -33,21 +43,8 @@ impl PluginModule {
|
||||
let module = Module::new(&store, &wasm_data).expect("Can't compile");
|
||||
|
||||
// This is the function imported into the wasm environement
|
||||
fn raw_emit_actions(env: &EmitActionEnv, ptr: u32, len: u32) {
|
||||
let memory: &Memory = if let Some(e) = env.memory.get_ref() {
|
||||
e
|
||||
} else {
|
||||
// This should not be possible but I prefer be safer!
|
||||
tracing::error!("Can't get memory from: `{}` plugin", env.name);
|
||||
return;
|
||||
};
|
||||
let memory: MemoryView<u8> = memory.view();
|
||||
|
||||
let str_slice = &memory[ptr as usize..(ptr + len) as usize];
|
||||
|
||||
let bytes: Vec<u8> = str_slice.iter().map(|x| x.get()).collect();
|
||||
|
||||
handle_actions(match bincode::deserialize(&bytes) {
|
||||
fn raw_emit_actions(env: &HostFunctionEnvironement, ptr: u32, len: u32) {
|
||||
handle_actions(match env.read_data(ptr as i32, len) {
|
||||
Ok(e) => e,
|
||||
Err(e) => {
|
||||
tracing::error!(?e, "Can't decode action");
|
||||
@ -56,10 +53,31 @@ impl PluginModule {
|
||||
});
|
||||
}
|
||||
|
||||
fn raw_retrieve_action(env: &HostFunctionEnvironement, ptr: u32, len: u32) -> i64 {
|
||||
let out = match env.read_data(ptr as _, len) {
|
||||
Ok(data) => retrieve_action(&env.ecs, data),
|
||||
Err(e) => Err(RetrieveError::BincodeError(e.to_string())),
|
||||
};
|
||||
|
||||
// If an error happen set the i64 to 0 so the WASM side can tell an error
|
||||
// occured
|
||||
let (ptr, len) = env.write_data(&out).unwrap();
|
||||
to_i64(ptr, len as _)
|
||||
}
|
||||
|
||||
fn dbg(a: i32) {
|
||||
println!("WASM DEBUG: {}", a);
|
||||
}
|
||||
|
||||
let ecs = Arc::new(EcsAccessManager::default());
|
||||
let memory_manager = Arc::new(MemoryManager::default());
|
||||
|
||||
// Create an import object.
|
||||
let import_object = imports! {
|
||||
"env" => {
|
||||
"raw_emit_actions" => Function::new_native_with_env(&store, EmitActionEnv::new(name.clone()), raw_emit_actions),
|
||||
"raw_emit_actions" => Function::new_native_with_env(&store, HostFunctionEnvironement::new(name.clone(), ecs.clone(),memory_manager.clone()), raw_emit_actions),
|
||||
"raw_retrieve_action" => Function::new_native_with_env(&store, HostFunctionEnvironement::new(name.clone(), ecs.clone(),memory_manager.clone()), raw_retrieve_action),
|
||||
"dbg" => Function::new_native(&store, dbg),
|
||||
}
|
||||
};
|
||||
|
||||
@ -67,20 +85,33 @@ impl PluginModule {
|
||||
let instance = Instance::new(&module, &import_object)
|
||||
.map_err(PluginModuleError::InstantiationError)?;
|
||||
Ok(Self {
|
||||
memory_manager,
|
||||
ecs,
|
||||
memory: instance
|
||||
.exports
|
||||
.get_memory("memory")
|
||||
.map_err(PluginModuleError::MemoryUninit)?
|
||||
.clone(),
|
||||
allocator: instance
|
||||
.exports
|
||||
.get_function("wasm_prepare_buffer")
|
||||
.map_err(PluginModuleError::MemoryUninit)?
|
||||
.clone(),
|
||||
events: instance
|
||||
.exports
|
||||
.iter()
|
||||
.map(|(name, _)| name.to_string())
|
||||
.collect(),
|
||||
wasm_state: Arc::new(Mutex::new(WasmState::new(instance))),
|
||||
wasm_state: Arc::new(Mutex::new(instance)),
|
||||
name,
|
||||
})
|
||||
}
|
||||
|
||||
// This function tries to execute an event for the current module. Will return
|
||||
// None if the event doesn't exists
|
||||
/// 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>(
|
||||
&self,
|
||||
ecs: &World,
|
||||
event_name: &str,
|
||||
request: &PreparedEventQuery<T>,
|
||||
) -> Option<Result<T::Response, PluginModuleError>>
|
||||
@ -90,74 +121,29 @@ impl PluginModule {
|
||||
if !self.events.contains(event_name) {
|
||||
return None;
|
||||
}
|
||||
let bytes = {
|
||||
// Store the ECS Pointer for later use in `retreives`
|
||||
let bytes = match self.ecs.execute_with(ecs, || {
|
||||
let mut state = self.wasm_state.lock().unwrap();
|
||||
match execute_raw(&mut state, event_name, &request.bytes) {
|
||||
Ok(e) => e,
|
||||
Err(e) => return Some(Err(e)),
|
||||
}
|
||||
execute_raw(self, &mut state, event_name, &request.bytes)
|
||||
}) {
|
||||
Ok(e) => e,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
Some(bincode::deserialize(&bytes).map_err(PluginModuleError::Encoding))
|
||||
}
|
||||
}
|
||||
|
||||
/// This is an internal struct used to represent the WASM state when the
|
||||
/// emit_action function is called
|
||||
#[derive(Clone)]
|
||||
struct EmitActionEnv {
|
||||
memory: LazyInit<Memory>,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl EmitActionEnv {
|
||||
fn new(name: String) -> Self {
|
||||
Self {
|
||||
memory: LazyInit::new(),
|
||||
name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmerEnv for EmitActionEnv {
|
||||
fn init_with_instance(&mut self, instance: &Instance) -> Result<(), HostEnvInitError> {
|
||||
let memory = instance.exports.get_memory("memory").unwrap();
|
||||
self.memory.initialize(memory.clone());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WasmMemoryContext {
|
||||
memory_buffer_size: usize,
|
||||
memory_pointer: i32,
|
||||
}
|
||||
|
||||
pub struct WasmState {
|
||||
instance: Instance,
|
||||
memory: WasmMemoryContext,
|
||||
}
|
||||
|
||||
impl WasmState {
|
||||
fn new(instance: Instance) -> Self {
|
||||
Self {
|
||||
instance,
|
||||
memory: WasmMemoryContext {
|
||||
memory_buffer_size: 0,
|
||||
memory_pointer: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This structure represent a Pre-encoded event object (Useful to avoid
|
||||
// reencoding for each module in every plugin)
|
||||
/// This structure represent a Pre-encoded event object (Useful to avoid
|
||||
/// reencoding for each module in every plugin)
|
||||
pub struct PreparedEventQuery<T> {
|
||||
bytes: Vec<u8>,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Event> PreparedEventQuery<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`
|
||||
/// 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,
|
||||
@ -169,75 +155,126 @@ impl<T: Event> PreparedEventQuery<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn from_i64(i: i64) -> (i32, i32) {
|
||||
let i = i.to_le_bytes();
|
||||
(
|
||||
i32::from_le_bytes(i[0..4].try_into().unwrap()),
|
||||
i32::from_le_bytes(i[4..8].try_into().unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_i64(a: i32, b: i32) -> i64 {
|
||||
let a = a.to_le_bytes();
|
||||
let b = b.to_le_bytes();
|
||||
i64::from_le_bytes([a[0], a[1], a[2], a[3], b[0], b[1], b[2], b[3]])
|
||||
}
|
||||
|
||||
// This function is not public because this function should not be used without
|
||||
// an interface to limit unsafe behaviours
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
fn execute_raw(
|
||||
instance: &mut WasmState,
|
||||
module: &PluginModule,
|
||||
instance: &mut Instance,
|
||||
event_name: &str,
|
||||
bytes: &[u8],
|
||||
) -> Result<Vec<u8>, PluginModuleError> {
|
||||
let len = bytes.len();
|
||||
// This write into memory `bytes` using allocation if necessary returning a
|
||||
// pointer and a length
|
||||
|
||||
let mem_position = reserve_wasm_memory_buffer(len, &instance.instance, &mut instance.memory)
|
||||
.map_err(PluginModuleError::MemoryAllocation)? as usize;
|
||||
let (mem_position, len) =
|
||||
module
|
||||
.memory_manager
|
||||
.write_bytes(&module.memory, &module.allocator, bytes)?;
|
||||
|
||||
let memory = instance
|
||||
.instance
|
||||
.exports
|
||||
.get_memory("memory")
|
||||
.map_err(PluginModuleError::MemoryUninit)?;
|
||||
|
||||
memory.view()[mem_position..mem_position + len]
|
||||
.iter()
|
||||
.zip(bytes.iter())
|
||||
.for_each(|(cell, byte)| cell.set(*byte));
|
||||
// This gets the event function from module exports
|
||||
|
||||
let func = instance
|
||||
.instance
|
||||
.exports
|
||||
.get_function(event_name)
|
||||
.map_err(PluginModuleError::MemoryUninit)?;
|
||||
|
||||
let mem_position = func
|
||||
// We call the function with the pointer and the length
|
||||
|
||||
let function_result = func
|
||||
.call(&[Value::I32(mem_position as i32), Value::I32(len as i32)])
|
||||
.map_err(PluginModuleError::RunFunction)?[0]
|
||||
.i32()
|
||||
.ok_or_else(PluginModuleError::InvalidArgumentType)? as usize;
|
||||
.map_err(PluginModuleError::RunFunction)?;
|
||||
|
||||
let view: MemoryView<u8> = memory.view();
|
||||
// Waiting for `multi-value` to be added to LLVM. So we encode the two i32 as an
|
||||
// i64
|
||||
|
||||
let mut new_len_bytes = [0u8; 4];
|
||||
// TODO: It is probably better to dirrectly make the new_len_bytes
|
||||
for i in 0..4 {
|
||||
new_len_bytes[i] = view.get(i + 1).map(Cell::get).unwrap_or(0);
|
||||
}
|
||||
let (pointer, length) = from_i64(
|
||||
function_result[0]
|
||||
.i64()
|
||||
.ok_or_else(PluginModuleError::InvalidArgumentType)?,
|
||||
);
|
||||
|
||||
let len = u32::from_ne_bytes(new_len_bytes) as usize;
|
||||
// We read the return object and deserialize it
|
||||
|
||||
Ok(view[mem_position..mem_position + len]
|
||||
.iter()
|
||||
.map(|x| x.get())
|
||||
.collect())
|
||||
Ok(memory_manager::read_bytes(
|
||||
&module.memory,
|
||||
pointer,
|
||||
length as u32,
|
||||
))
|
||||
}
|
||||
|
||||
fn reserve_wasm_memory_buffer(
|
||||
size: usize,
|
||||
instance: &Instance,
|
||||
context: &mut WasmMemoryContext,
|
||||
) -> Result<i32, MemoryAllocationError> {
|
||||
if context.memory_buffer_size >= size {
|
||||
return Ok(context.memory_pointer);
|
||||
fn retrieve_action(
|
||||
ecs: &EcsAccessManager,
|
||||
action: Retrieve,
|
||||
) -> Result<RetrieveResult, RetrieveError> {
|
||||
match action {
|
||||
Retrieve::GetPlayerName(e) => {
|
||||
// Safety: No reference is leaked out the function so it is safe.
|
||||
let world = unsafe {
|
||||
ecs.get().ok_or(RetrieveError::EcsAccessError(
|
||||
EcsAccessError::EcsPointerNotAvailable,
|
||||
))?
|
||||
};
|
||||
let player = world
|
||||
.read_resource::<UidAllocator>()
|
||||
.retrieve_entity_internal(e.0)
|
||||
.ok_or(RetrieveError::EcsAccessError(
|
||||
EcsAccessError::EcsEntityNotFound(e),
|
||||
))?;
|
||||
Ok(RetrieveResult::GetPlayerName(
|
||||
world
|
||||
.read_component::<Player>()
|
||||
.get(player)
|
||||
.ok_or_else(|| {
|
||||
RetrieveError::EcsAccessError(EcsAccessError::EcsComponentNotFound(
|
||||
e,
|
||||
"Player".to_owned(),
|
||||
))
|
||||
})?
|
||||
.alias
|
||||
.to_owned(),
|
||||
))
|
||||
},
|
||||
Retrieve::GetEntityHealth(e) => {
|
||||
// Safety: No reference is leaked out the function so it is safe.
|
||||
let world = unsafe {
|
||||
ecs.get().ok_or(RetrieveError::EcsAccessError(
|
||||
EcsAccessError::EcsPointerNotAvailable,
|
||||
))?
|
||||
};
|
||||
let player = world
|
||||
.read_resource::<UidAllocator>()
|
||||
.retrieve_entity_internal(e.0)
|
||||
.ok_or(RetrieveError::EcsAccessError(
|
||||
EcsAccessError::EcsEntityNotFound(e),
|
||||
))?;
|
||||
Ok(RetrieveResult::GetEntityHealth(
|
||||
*world
|
||||
.read_component::<Health>()
|
||||
.get(player)
|
||||
.ok_or_else(|| {
|
||||
RetrieveError::EcsAccessError(EcsAccessError::EcsComponentNotFound(
|
||||
e,
|
||||
"Health".to_owned(),
|
||||
))
|
||||
})?,
|
||||
))
|
||||
},
|
||||
}
|
||||
let pointer = instance
|
||||
.exports
|
||||
.get_function("wasm_prepare_buffer")
|
||||
.map_err(MemoryAllocationError::AllocatorNotFound)?
|
||||
.call(&[Value::I32(size as i32)])
|
||||
.map_err(MemoryAllocationError::CantAllocate)?;
|
||||
context.memory_buffer_size = size;
|
||||
context.memory_pointer = pointer[0].i32().unwrap();
|
||||
Ok(context.memory_pointer)
|
||||
}
|
||||
|
||||
fn handle_actions(actions: Vec<Action>) {
|
||||
|
70
common/sys/src/plugin/wasm_env.rs
Normal file
70
common/sys/src/plugin/wasm_env.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use wasmer::{Function, HostEnvInitError, Instance, LazyInit, Memory, WasmerEnv};
|
||||
|
||||
use super::{
|
||||
errors::PluginModuleError,
|
||||
memory_manager::{self, EcsAccessManager, MemoryManager},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HostFunctionEnvironement {
|
||||
pub ecs: Arc<EcsAccessManager>, /* This represent the pointer to the ECS object (set to
|
||||
* i32::MAX if to ECS is
|
||||
* availible) */
|
||||
pub memory: LazyInit<Memory>, // This object represent the WASM Memory
|
||||
pub allocator: LazyInit<Function>, // Linked to: wasm_prepare_buffer
|
||||
pub memory_manager: Arc<MemoryManager>, /* This object represent the current buffer size and
|
||||
* pointer */
|
||||
pub name: String, // This represent the plugin name
|
||||
}
|
||||
|
||||
impl HostFunctionEnvironement {
|
||||
pub fn new(
|
||||
name: String,
|
||||
ecs: Arc<EcsAccessManager>,
|
||||
memory_manager: Arc<MemoryManager>,
|
||||
) -> Self {
|
||||
Self {
|
||||
memory_manager,
|
||||
ecs,
|
||||
allocator: LazyInit::new(),
|
||||
memory: LazyInit::new(),
|
||||
name,
|
||||
}
|
||||
}
|
||||
|
||||
/// This function is a safe interface to WASM memory that writes data to the
|
||||
/// memory returning a pointer and length
|
||||
pub fn write_data<T: Serialize>(&self, object: &T) -> Result<(i32, u32), PluginModuleError> {
|
||||
self.memory_manager.write_data(
|
||||
self.memory.get_ref().unwrap(),
|
||||
self.allocator.get_ref().unwrap(),
|
||||
object,
|
||||
)
|
||||
}
|
||||
|
||||
/// 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,
|
||||
position: i32,
|
||||
length: u32,
|
||||
) -> Result<T, bincode::Error> {
|
||||
memory_manager::read_data(self.memory.get_ref().unwrap(), position, length)
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmerEnv for HostFunctionEnvironement {
|
||||
fn init_with_instance(&mut self, instance: &Instance) -> Result<(), HostEnvInitError> {
|
||||
let memory = instance.exports.get_memory("memory").unwrap();
|
||||
self.memory.initialize(memory.clone());
|
||||
let allocator = instance
|
||||
.exports
|
||||
.get_function("wasm_prepare_buffer")
|
||||
.expect("Can't get allocator");
|
||||
self.allocator.initialize(allocator.clone());
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -208,8 +208,10 @@ impl State {
|
||||
#[cfg(feature = "plugins")]
|
||||
ecs.insert(match PluginMgr::from_assets() {
|
||||
Ok(plugin_mgr) => {
|
||||
if let Err(e) = plugin_mgr
|
||||
.execute_event("on_load", &plugin_api::event::PluginLoadEvent { game_mode })
|
||||
if let Err(e) =
|
||||
plugin_mgr.execute_event(&ecs, "on_load", &plugin_api::event::PluginLoadEvent {
|
||||
game_mode,
|
||||
})
|
||||
{
|
||||
tracing::error!(?e, "Failed to run plugin init");
|
||||
tracing::info!(
|
||||
|
@ -5,5 +5,6 @@ authors = ["ccgauche <gaucheron.laurent@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
common = { package = "veloren-common", path = "../../common", features = ["no-assets"] }
|
||||
serde = { version = "1.0.118", features = ["derive"] }
|
||||
common = { package = "veloren-common", path = "../../common", features = ["no-assets"] }
|
||||
bincode = "1.3.1"
|
71
plugin/api/src/errors.rs
Normal file
71
plugin/api/src/errors.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use common::uid::Uid;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum RetrieveError {
|
||||
EcsAccessError(EcsAccessError),
|
||||
OtherError(String),
|
||||
DataReadError,
|
||||
BincodeError(String),
|
||||
InvalidType,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for RetrieveError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
RetrieveError::EcsAccessError(e) => {
|
||||
write!(f, "RetrieveError: {}", e)
|
||||
},
|
||||
RetrieveError::OtherError(e) => {
|
||||
write!(f, "RetrieveError: Unknown error: {}", e)
|
||||
},
|
||||
RetrieveError::DataReadError => {
|
||||
write!(
|
||||
f,
|
||||
"RetrieveError: Can't pass data through WASM FFI: WASM Memory is corrupted"
|
||||
)
|
||||
},
|
||||
RetrieveError::BincodeError(e) => {
|
||||
write!(f, "RetrieveError: Bincode error: {}", e)
|
||||
},
|
||||
RetrieveError::InvalidType => {
|
||||
write!(
|
||||
f,
|
||||
"RetrieveError: This type wasn't expected as the result for this Retrieve"
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum EcsAccessError {
|
||||
EcsPointerNotAvailable,
|
||||
EcsComponentNotFound(Uid, String),
|
||||
EcsResourceNotFound(String),
|
||||
EcsEntityNotFound(Uid),
|
||||
}
|
||||
|
||||
impl core::fmt::Display for EcsAccessError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
EcsAccessError::EcsPointerNotAvailable => {
|
||||
write!(f, "EcsAccessError can't read the ECS pointer")
|
||||
},
|
||||
EcsAccessError::EcsComponentNotFound(a, b) => {
|
||||
write!(
|
||||
f,
|
||||
"EcsAccessError can't find component {} for entity from UID {}",
|
||||
b, a
|
||||
)
|
||||
},
|
||||
EcsAccessError::EcsResourceNotFound(a) => {
|
||||
write!(f, "EcsAccessError can't find resource {}", a)
|
||||
},
|
||||
EcsAccessError::EcsEntityNotFound(a) => {
|
||||
write!(f, "EcsAccessError can't find entity from UID {}", a)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,14 @@
|
||||
use common::uid::Uid;
|
||||
pub extern crate common;
|
||||
|
||||
pub use common::comp::Health;
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
|
||||
pub use common::{resources::GameMode, uid::Uid};
|
||||
|
||||
mod errors;
|
||||
|
||||
pub use errors::*;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub enum Action {
|
||||
ServerClose,
|
||||
@ -9,12 +17,22 @@ pub enum Action {
|
||||
KillEntity(Uid),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub enum Retrieve {
|
||||
GetPlayerName(Uid),
|
||||
GetEntityHealth(Uid),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum RetrieveResult {
|
||||
GetPlayerName(String),
|
||||
GetEntityHealth(Health),
|
||||
}
|
||||
|
||||
pub trait Event: Serialize + DeserializeOwned + Send + Sync {
|
||||
type Response: Serialize + DeserializeOwned + Send + Sync;
|
||||
}
|
||||
|
||||
pub use common::resources::GameMode;
|
||||
|
||||
pub mod event {
|
||||
use super::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -16,8 +16,8 @@ pub fn event_handler(_args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let out: proc_macro2::TokenStream = quote! {
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
#[no_mangle]
|
||||
pub fn #fn_name(intern__ptr: i32, intern__len: u32) -> i32 {
|
||||
let input = ::veloren_plugin_rt::read_input(intern__ptr,intern__len).unwrap();
|
||||
pub fn #fn_name(intern__ptr: i32, intern__len: i32) -> i64 {
|
||||
let input = ::veloren_plugin_rt::read_input(intern__ptr,intern__len as u32).unwrap();
|
||||
#[inline]
|
||||
fn inner(#fn_args) #fn_return {
|
||||
#fn_body
|
||||
|
@ -3,7 +3,7 @@ use veloren_plugin_rt::{
|
||||
*,
|
||||
};
|
||||
|
||||
#[veloren_plugin_rt::event_handler]
|
||||
#[event_handler]
|
||||
pub fn on_load(load: PluginLoadEvent) {
|
||||
match load.game_mode {
|
||||
GameMode::Server => emit_action(Action::Print("Hello, server!".to_owned())),
|
||||
@ -15,8 +15,17 @@ pub fn on_load(load: PluginLoadEvent) {
|
||||
#[event_handler]
|
||||
pub fn on_command_testplugin(command: ChatCommandEvent) -> Result<Vec<String>, String> {
|
||||
Ok(vec![format!(
|
||||
"Player of id {:?} sended command with args {:?}",
|
||||
command.player, command.command_args
|
||||
"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
|
||||
)])
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,15 @@
|
||||
|
||||
pub extern crate plugin_derive;
|
||||
|
||||
pub mod retrieve;
|
||||
|
||||
use api::RetrieveError;
|
||||
pub use retrieve::*;
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
pub use retrieve::*;
|
||||
|
||||
pub use plugin_api as api;
|
||||
pub use plugin_derive::*;
|
||||
|
||||
@ -10,6 +19,23 @@ use serde::{de::DeserializeOwned, Serialize};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
extern "C" {
|
||||
fn raw_emit_actions(ptr: *const u8, len: usize);
|
||||
fn raw_retrieve_action(ptr: *const u8, len: usize) -> i64;
|
||||
pub fn dbg(i: i32);
|
||||
}
|
||||
|
||||
pub fn retrieve_action<T: DeserializeOwned>(_actions: &api::Retrieve) -> Result<T, RetrieveError> {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let ret = bincode::serialize(&_actions).expect("Can't serialize action in emit");
|
||||
unsafe {
|
||||
let (ptr, len) = from_i64(raw_retrieve_action(ret.as_ptr(), ret.len()));
|
||||
let a = ::std::slice::from_raw_parts(ptr as _, len as _);
|
||||
bincode::deserialize::<Result<T, RetrieveError>>(&a)
|
||||
.map_err(|x| RetrieveError::BincodeError(x.to_string()))?
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn emit_action(action: api::Action) { emit_actions(vec![action]) }
|
||||
@ -32,13 +58,23 @@ where
|
||||
bincode::deserialize(slice).map_err(|_| "Failed to deserialize function input")
|
||||
}
|
||||
|
||||
pub fn write_output(value: impl Serialize) -> i32 {
|
||||
pub fn from_i64(i: i64) -> (i32, i32) {
|
||||
let i = i.to_le_bytes();
|
||||
(
|
||||
i32::from_le_bytes(i[0..4].try_into().unwrap()),
|
||||
i32::from_le_bytes(i[4..8].try_into().unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_i64(a: i32, b: i32) -> i64 {
|
||||
let a = a.to_le_bytes();
|
||||
let b = b.to_le_bytes();
|
||||
i64::from_le_bytes([a[0], a[1], a[2], a[3], b[0], b[1], b[2], b[3]])
|
||||
}
|
||||
|
||||
pub fn write_output(value: impl Serialize) -> i64 {
|
||||
let ret = bincode::serialize(&value).expect("Can't serialize event output");
|
||||
let len = ret.len() as u32;
|
||||
unsafe {
|
||||
::std::ptr::write(1 as _, len);
|
||||
}
|
||||
ret.as_ptr() as _
|
||||
to_i64(ret.as_ptr() as _, ret.len() as _)
|
||||
}
|
||||
|
||||
static mut BUFFERS: Vec<u8> = Vec::new();
|
||||
|
35
plugin/rt/src/retrieve.rs
Normal file
35
plugin/rt/src/retrieve.rs
Normal file
@ -0,0 +1,35 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@ -1031,17 +1031,21 @@ impl Server {
|
||||
use common::uid::Uid;
|
||||
let plugin_manager = self.state.ecs().read_resource::<PluginMgr>();
|
||||
let rs = plugin_manager.execute_event(
|
||||
self.state.ecs(),
|
||||
&format!("on_command_{}", &kwd),
|
||||
&plugin_api::event::ChatCommandEvent {
|
||||
command: kwd.clone(),
|
||||
command_args: args.split(' ').map(|x| x.to_owned()).collect(),
|
||||
player: plugin_api::event::Player {
|
||||
id: *(self
|
||||
.state
|
||||
.ecs()
|
||||
.read_storage::<Uid>()
|
||||
.get(entity)
|
||||
.expect("Can't get player UUID [This should never appen]")),
|
||||
id: plugin_api::Uid(
|
||||
(self
|
||||
.state
|
||||
.ecs()
|
||||
.read_storage::<Uid>()
|
||||
.get(entity)
|
||||
.expect("Can't get player UUID [This should never appen]"))
|
||||
.0,
|
||||
),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user