Initial work on wasm scripting backend
This commit is contained in:
parent
8eb1159d64
commit
7682704945
|
@ -12,9 +12,10 @@ crate_type = ["rlib"]
|
|||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
c_interface = []
|
||||
ffi = []
|
||||
serde = ["dep:serde"]
|
||||
default = ["serde"]
|
||||
wasm = ["dep:wasmer", "dep:unique-type-id", "dep:unique-type-id-derive"]
|
||||
default = ["serde", "wasm"]
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
|
@ -50,7 +51,11 @@ rand_pcg = "0.3.1"
|
|||
hashbrown = "0.12.1"
|
||||
indexmap = "1.8.2"
|
||||
parking_lot = "0.12.1"
|
||||
conquer-once = "0.3.2"
|
||||
serde = { version = "1.0.137", optional = true, features = ["derive"] }
|
||||
wasmer = { version = "2.3.0", optional = true, default-features = false, features = ["default-cranelift", "universal", "experimental-reference-types-extern-ref"] }
|
||||
unique-type-id = { version = "1.0.0", optional = true }
|
||||
unique-type-id-derive = { version = "1.0.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
csv = "1.1.6"
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use std::ffi::c_void;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -15,6 +14,7 @@ use crate::{PkmnResult, StringKey};
|
|||
/// The dynamic library stores a static data library, as well as holding different libraries and
|
||||
/// calculators that might be customized between different generations and implementations.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))]
|
||||
pub struct DynamicLibrary {
|
||||
/// The static data is the immutable storage data for this library.
|
||||
static_data: StaticData,
|
||||
|
@ -75,7 +75,7 @@ impl DynamicLibrary {
|
|||
/// can be created with this combination, returns None.
|
||||
pub fn load_script(
|
||||
&self,
|
||||
owner: *const c_void,
|
||||
owner: *const u8,
|
||||
_category: ScriptCategory,
|
||||
_key: &StringKey,
|
||||
) -> PkmnResult<Option<Arc<dyn Script>>> {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use std::ffi::c_void;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -14,7 +13,7 @@ pub trait ScriptResolver: Debug {
|
|||
/// can be created with this combination, returns None.
|
||||
fn load_script(
|
||||
&self,
|
||||
owner: *const c_void,
|
||||
owner: *const u8,
|
||||
category: ScriptCategory,
|
||||
script_key: &StringKey,
|
||||
) -> PkmnResult<Option<Arc<dyn Script>>>;
|
||||
|
@ -29,7 +28,8 @@ pub trait ScriptResolver: Debug {
|
|||
/// the same name, but a different script. It should be completely valid for a move to have the same
|
||||
/// name as an ability, or more commonly: for a script attached to a Pokemon to have the same name as
|
||||
/// a move that placed it there.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
#[repr(u8)]
|
||||
pub enum ScriptCategory {
|
||||
/// A script that belongs to a move. This generally is only the script that is attached to a
|
||||
/// [`MoveChoice`](crate::dynamic_data::MoveChoice) and [`ExecutingMove`](crate::dynamic_data::ExecutingMove)
|
||||
|
@ -58,7 +58,7 @@ pub struct EmptyScriptResolver {}
|
|||
impl ScriptResolver for EmptyScriptResolver {
|
||||
fn load_script(
|
||||
&self,
|
||||
_owner: *const c_void,
|
||||
_owner: *const u8,
|
||||
_category: ScriptCategory,
|
||||
_script_key: &StringKey,
|
||||
) -> PkmnResult<Option<Arc<dyn Script>>> {
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#![feature(once_cell)]
|
||||
#![feature(const_option)]
|
||||
#![feature(is_some_with)]
|
||||
#![feature(core_ffi_c)]
|
||||
#![feature(new_uninit)]
|
||||
|
||||
//! PkmnLib
|
||||
//! PkmnLib is a full featured implementation of Pokemon. while currently focused on implementing
|
||||
|
@ -27,6 +29,8 @@ pub mod defines;
|
|||
/// The dynamic data module holds data that can change during execution, and things that relate to
|
||||
/// this. This includes things as Pokemon themselves, battles, etc.
|
||||
pub mod dynamic_data;
|
||||
/// Script implementations handles the different ways that dynamic scripts get loaded during battle.
|
||||
pub mod script_implementations;
|
||||
/// The static data module holds data that can be set once, and then never change. This includes
|
||||
/// things such as data about Pokemon species, data about items, etc.
|
||||
pub mod static_data;
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
/// The WASM module handles loading dynamic scripts through WebAssembly.
|
||||
#[cfg(feature = "wasm")]
|
||||
pub mod wasm;
|
|
@ -0,0 +1,103 @@
|
|||
use core::ffi::c_char;
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::CString;
|
||||
use std::mem::{align_of, size_of};
|
||||
|
||||
use wasmer::wasmparser::Data;
|
||||
use wasmer::{Exports, Function, Store};
|
||||
|
||||
use crate::dynamic_data::DynamicLibrary;
|
||||
use crate::script_implementations::wasm::extern_ref::ExternRef;
|
||||
use crate::script_implementations::wasm::script_resolver::WebAssemblyEnv;
|
||||
use crate::static_data::{DataLibrary, MoveData, MoveLibrary, StaticData};
|
||||
use crate::StringKey;
|
||||
|
||||
macro_rules! register_func {
|
||||
($exports: ident, $store: ident, $func: ident) => {
|
||||
$exports.insert(stringify!($func), Function::new_native($store, $func));
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! register_func_with_env {
|
||||
($exports: ident, $store: ident, $func: ident, $env: expr) => {
|
||||
$exports.insert(
|
||||
stringify!($func),
|
||||
Function::new_native_with_env($store, $env.clone(), $func),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn register_webassembly_funcs(exports: &mut Exports, store: &Store, env: WebAssemblyEnv) {
|
||||
register_func_with_env!(exports, store, _print, env);
|
||||
register_func_with_env!(exports, store, _error, env);
|
||||
register_func_with_env!(exports, store, move_library_get_move_by_hash, env);
|
||||
register_func_with_env!(exports, store, move_data_get_name, env);
|
||||
register_func_with_env!(exports, store, move_data_get_base_power, env);
|
||||
register_func_with_env!(exports, store, const_string_get_hash, env);
|
||||
register_func_with_env!(exports, store, const_string_get_str, env);
|
||||
register_func_with_env!(exports, store, battle_library_get_data_library, env);
|
||||
register_func_with_env!(exports, store, data_library_get_move_library, env);
|
||||
}
|
||||
|
||||
fn _print(env: &WebAssemblyEnv, p: u32, len: u32) {
|
||||
unsafe {
|
||||
let mem: *mut u8 = env.resolver().memory().data_ptr().offset(p as isize);
|
||||
let s = String::from_raw_parts(mem, len as usize, len as usize);
|
||||
println!("{}", s);
|
||||
}
|
||||
}
|
||||
|
||||
fn _error(env: &WebAssemblyEnv, message: u32, message_len: u32, file: u32, file_len: u32, line: u32, position: u32) {
|
||||
unsafe {
|
||||
let mem: *mut u8 = env.resolver().memory().data_ptr().offset(message as isize);
|
||||
let message = String::from_raw_parts(mem, message_len as usize, message_len as usize);
|
||||
let mem: *mut u8 = env.resolver().memory().data_ptr().offset(file as isize);
|
||||
let file = String::from_raw_parts(mem, file_len as usize, file_len as usize);
|
||||
println!("Error: {} in file {}, line: {}:{}", message, file, line, position);
|
||||
}
|
||||
}
|
||||
|
||||
fn move_library_get_move_by_hash(env: &WebAssemblyEnv, lib: ExternRef<MoveLibrary>, hash: u32) -> ExternRef<MoveData> {
|
||||
let lib = lib.value(env);
|
||||
let m = lib.get_by_hash(hash);
|
||||
if let Some(v) = m {
|
||||
ExternRef::new(env, v)
|
||||
} else {
|
||||
ExternRef::null()
|
||||
}
|
||||
}
|
||||
|
||||
fn move_data_get_name(env: &WebAssemblyEnv, move_data: ExternRef<MoveData>) -> ExternRef<StringKey> {
|
||||
let move_data = move_data.value(env);
|
||||
ExternRef::new(env, move_data.name())
|
||||
}
|
||||
|
||||
fn move_data_get_base_power(env: &WebAssemblyEnv, move_data: ExternRef<MoveData>) -> u8 {
|
||||
move_data.value(env).base_power()
|
||||
}
|
||||
|
||||
fn const_string_get_hash(env: &WebAssemblyEnv, string_key: ExternRef<StringKey>) -> u32 {
|
||||
string_key.value(env).hash()
|
||||
}
|
||||
|
||||
fn const_string_get_str(env: &WebAssemblyEnv, string_key: ExternRef<StringKey>) -> u32 {
|
||||
let string_key = string_key.value(env).str();
|
||||
let mut s: CString = CString::new(string_key.as_bytes()).unwrap();
|
||||
let wasm_string_ptr = env
|
||||
.resolver()
|
||||
.allocate_mem(string_key.len() as u32, align_of::<CString>() as u32);
|
||||
let mut wasm_string = unsafe { CString::from_raw(wasm_string_ptr.0 as *mut c_char) };
|
||||
s.clone_into(&mut wasm_string);
|
||||
wasm_string_ptr.1
|
||||
}
|
||||
|
||||
fn battle_library_get_data_library(
|
||||
env: &WebAssemblyEnv,
|
||||
dynamic_lib: ExternRef<DynamicLibrary>,
|
||||
) -> ExternRef<StaticData> {
|
||||
ExternRef::new(env, dynamic_lib.value(env).static_data())
|
||||
}
|
||||
|
||||
fn data_library_get_move_library(env: &WebAssemblyEnv, data_library: ExternRef<StaticData>) -> ExternRef<MoveLibrary> {
|
||||
ExternRef::new(env, data_library.value(env).moves())
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use unique_type_id::UniqueTypeId;
|
||||
use wasmer::FromToNativeWasmType;
|
||||
|
||||
use crate::script_implementations::wasm::script_resolver::WebAssemblyEnv;
|
||||
|
||||
pub(crate) struct ExternRef<T: UniqueTypeId<u64>> {
|
||||
index: u32,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: UniqueTypeId<u64>> ExternRef<T> {
|
||||
pub fn new(env: &WebAssemblyEnv, value: &T) -> Self {
|
||||
Self {
|
||||
index: env.resolver().get_extern_ref_index(value),
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_index(index: u32) -> Self {
|
||||
Self {
|
||||
index,
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn null() -> Self {
|
||||
Self {
|
||||
index: 0,
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value<'a, 'b>(&'a self, env: &'b WebAssemblyEnv) -> &'b T {
|
||||
let ptr = env.resolver().get_extern_ref_value(self.index) as *const T;
|
||||
unsafe { ptr.as_ref().unwrap() }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: UniqueTypeId<u64>> FromToNativeWasmType for ExternRef<T> {
|
||||
type Native = i32;
|
||||
|
||||
fn from_native(native: Self::Native) -> Self {
|
||||
Self {
|
||||
index: native as u32,
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_native(self) -> Self::Native {
|
||||
self.index as i32
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/// The export registry module deals with registering all functions we require in WebAssembly.
|
||||
mod export_registry;
|
||||
pub(crate) mod extern_ref;
|
||||
/// The script module deals with the actual running of WASM functions.
|
||||
pub mod script;
|
||||
/// The script resolver deals with the loading of scripts.
|
||||
pub mod script_resolver;
|
||||
|
||||
/// The WebAssemblyScriptCapabilities define which functions are implemented on a script. This allows
|
||||
/// us to not call a function if we do not need to.
|
||||
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum WebAssemblyScriptCapabilities {
|
||||
None = 0,
|
||||
Initialize = 1,
|
||||
OnStack,
|
||||
OnRemove,
|
||||
OnBeforeTurn,
|
||||
ChangeAttack,
|
||||
ModifyNumberOfHits,
|
||||
PreventAttack,
|
||||
FailAttack,
|
||||
StopBeforeAttack,
|
||||
OnBeforeAttack,
|
||||
FailIncomingAttack,
|
||||
IsInvulnerable,
|
||||
OnAttackMiss,
|
||||
ChangeAttackType,
|
||||
ChangeEffectiveness,
|
||||
BlockCritical,
|
||||
OnIncomingHit,
|
||||
OnFaintingOpponent,
|
||||
PreventStatBoostChange,
|
||||
ModifyStatBoostChange,
|
||||
PreventSecondaryEffects,
|
||||
OnSecondaryEffect,
|
||||
OnAfterHits,
|
||||
PreventSelfSwitch,
|
||||
ModifyEffectChance,
|
||||
ModifyIncomingEffectChance,
|
||||
OverrideBasePower,
|
||||
ChangeDamageStatsUser,
|
||||
BypassDefensiveStat,
|
||||
BypassOffensiveStat,
|
||||
ModifyStatModifier,
|
||||
ModifyDamageModifier,
|
||||
OverrideDamage,
|
||||
OverrideIncomingDamage,
|
||||
ChangeSpeed,
|
||||
ChangePriority,
|
||||
OnFail,
|
||||
OnOpponentFail,
|
||||
PreventRunAway,
|
||||
PreventOpponentRunAway,
|
||||
PreventOpponentSwitch,
|
||||
OnEndTurn,
|
||||
OnDamage,
|
||||
OnFaint,
|
||||
OnAfterHeldItemConsume,
|
||||
PreventIncomingCritical,
|
||||
ModifyCriticalStage,
|
||||
OverrideCriticalModifier,
|
||||
OverrideSTABModifier,
|
||||
ModifyExperienceGain,
|
||||
DoesShareExperience,
|
||||
BlockWeather,
|
||||
OnSwitchIn,
|
||||
ModifyOffensiveStatValue,
|
||||
ModifyDefensiveStatValue,
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
use std::any::Any;
|
||||
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize};
|
||||
|
||||
use hashbrown::HashSet;
|
||||
|
||||
use crate::dynamic_data::Script;
|
||||
use crate::script_implementations::wasm::script_resolver::WebAssemblyScriptResolver;
|
||||
use crate::script_implementations::wasm::WebAssemblyScriptCapabilities;
|
||||
use crate::StringKey;
|
||||
|
||||
/// A WebAssemblyScript is there to implement the Script trait within WebAssemblyScript.
|
||||
pub struct WebAssemblyScript {
|
||||
/// The unique identifier of the script.
|
||||
name: StringKey,
|
||||
/// Returns an atomic bool for internal marking of deletion. This is currently only specifically
|
||||
/// used for deletion of a script while we are holding a reference to it (i.e. executing a script
|
||||
/// hook on it).
|
||||
marked_for_deletion: AtomicBool,
|
||||
/// A script can be suppressed by other scripts. If a script is suppressed by at least one script
|
||||
/// we will not execute its methods. This holds the number of suppressions on the script.
|
||||
suppressed_count: AtomicUsize,
|
||||
/// The owner of this script (where the script is attached to)
|
||||
owner_ptr: AtomicPtr<u8>,
|
||||
/// Pointer inside WebAssembly memory where the data is for this script.
|
||||
self_ptr: u32,
|
||||
/// Capabilities define which functions we actually implement.
|
||||
capabilities: AtomicPtr<HashSet<WebAssemblyScriptCapabilities>>,
|
||||
/// A reference back to our resolver.
|
||||
resolver: AtomicPtr<WebAssemblyScriptResolver>,
|
||||
}
|
||||
|
||||
impl WebAssemblyScript {
|
||||
/// Instantiates a new WebAssemblyScript.
|
||||
pub fn new(
|
||||
owner_ptr: *mut u8,
|
||||
self_ptr: u32,
|
||||
capabilities: *mut HashSet<WebAssemblyScriptCapabilities>,
|
||||
resolver: *mut WebAssemblyScriptResolver,
|
||||
name: StringKey,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
marked_for_deletion: Default::default(),
|
||||
suppressed_count: Default::default(),
|
||||
owner_ptr: AtomicPtr::new(owner_ptr),
|
||||
self_ptr,
|
||||
capabilities: AtomicPtr::new(capabilities),
|
||||
resolver: AtomicPtr::new(resolver),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Script for WebAssemblyScript {
|
||||
fn name(&self) -> &StringKey {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn get_marked_for_deletion(&self) -> &AtomicBool {
|
||||
&self.marked_for_deletion
|
||||
}
|
||||
|
||||
fn get_suppressed_count(&self) -> &AtomicUsize {
|
||||
&self.suppressed_count
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,253 @@
|
|||
use std::any::{Any, TypeId};
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use parking_lot::RwLock;
|
||||
use unique_type_id::UniqueTypeId;
|
||||
use wasmer::{
|
||||
Cranelift, Exports, Extern, Features, Function, ImportObject, Instance, Memory, Module, NativeFunc, Store,
|
||||
Universal, UniversalEngine, Value, WasmerEnv,
|
||||
};
|
||||
|
||||
use crate::dynamic_data::{ItemScript, Script, ScriptResolver};
|
||||
use crate::script_implementations::wasm::export_registry::register_webassembly_funcs;
|
||||
use crate::script_implementations::wasm::extern_ref::ExternRef;
|
||||
use crate::script_implementations::wasm::script::WebAssemblyScript;
|
||||
use crate::script_implementations::wasm::WebAssemblyScriptCapabilities;
|
||||
use crate::static_data::Item;
|
||||
use crate::{PkmnResult, ScriptCategory, StringKey};
|
||||
|
||||
/// A WebAssembly script resolver implements the dynamic scripts functionality with WebAssembly.
|
||||
pub struct WebAssemblyScriptResolver {
|
||||
engine: UniversalEngine,
|
||||
store: Store,
|
||||
module: Option<Module>,
|
||||
instance: Option<Instance>,
|
||||
memory: Option<Memory>,
|
||||
imports: HashMap<String, Function>,
|
||||
exports: Exports,
|
||||
exported_functions: HashMap<StringKey, Function>,
|
||||
load_script_fn: Option<NativeFunc<(u8, ExternRef<StringKey>), u32>>,
|
||||
allocate_mem_fn: Option<NativeFunc<(u32, u32), u32>>,
|
||||
script_capabilities: RwLock<HashMap<ScriptCapabilitiesKey, HashSet<WebAssemblyScriptCapabilities>>>,
|
||||
|
||||
extern_ref_pointers: RwLock<Vec<*const u8>>,
|
||||
extern_ref_pointers_lookup: RwLock<HashMap<*const u8, u32>>,
|
||||
extern_ref_type_lookup: RwLock<HashMap<*const u8, u64>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
struct ScriptCapabilitiesKey {
|
||||
category: ScriptCategory,
|
||||
script_key: StringKey,
|
||||
}
|
||||
|
||||
impl WebAssemblyScriptResolver {
|
||||
/// Instantiates a new WebAssemblyScriptResolver.
|
||||
pub fn new() -> Self {
|
||||
let config = Cranelift::default();
|
||||
let mut features = Features::new();
|
||||
features.multi_value = true;
|
||||
features.reference_types = true;
|
||||
let universal = Universal::new(config).features(features);
|
||||
let engine = universal.engine();
|
||||
let store = Store::new(&engine);
|
||||
Self {
|
||||
engine,
|
||||
store,
|
||||
module: Default::default(),
|
||||
instance: Default::default(),
|
||||
memory: Default::default(),
|
||||
imports: Default::default(),
|
||||
exports: Default::default(),
|
||||
exported_functions: Default::default(),
|
||||
load_script_fn: None,
|
||||
allocate_mem_fn: None,
|
||||
script_capabilities: Default::default(),
|
||||
extern_ref_pointers: Default::default(),
|
||||
extern_ref_pointers_lookup: Default::default(),
|
||||
extern_ref_type_lookup: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a compiled WASM module.
|
||||
pub fn load_wasm_from_bytes(&mut self, bytes: &[u8]) {
|
||||
// FIXME: Error handling
|
||||
let module = Module::new(&self.store, bytes).unwrap();
|
||||
self.module = Some(module);
|
||||
|
||||
self.finalize();
|
||||
}
|
||||
|
||||
/// Initialise all the data we need.
|
||||
fn finalize(&mut self) {
|
||||
let mut imports = ImportObject::new();
|
||||
let mut exports = Exports::new();
|
||||
|
||||
let env = WebAssemblyEnv {
|
||||
resolver: self as *const WebAssemblyScriptResolver,
|
||||
};
|
||||
register_webassembly_funcs(&mut exports, &self.store, env);
|
||||
imports.register("env", exports);
|
||||
|
||||
self.instance = Some(Instance::new(&self.module.as_ref().unwrap(), &imports).unwrap());
|
||||
let exports = &self.instance.as_ref().unwrap().exports;
|
||||
for export in exports.iter() {
|
||||
match export.1 {
|
||||
Extern::Function(f) => {
|
||||
self.exported_functions.insert(export.0.as_str().into(), f.clone());
|
||||
}
|
||||
Extern::Memory(m) => {
|
||||
self.memory = Some(m.clone());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some(m) = &self.memory {
|
||||
m.grow(32).unwrap();
|
||||
}
|
||||
if let Some(f) = self.exported_functions.get(&"load_script".into()) {
|
||||
self.load_script_fn = Some(f.native().unwrap())
|
||||
}
|
||||
if let Some(f) = self.exported_functions.get(&"allocate_mem".into()) {
|
||||
self.allocate_mem_fn = Some(f.native().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the internal WASM memory.
|
||||
pub fn memory(&self) -> &Memory {
|
||||
self.memory.as_ref().unwrap()
|
||||
}
|
||||
|
||||
/// Get a numeric value from any given value. This is not a true Extern Ref from WASM, as this
|
||||
/// is not supported by our current WASM platform (Rust). Instead, this is simply a way to not
|
||||
/// have to send arbitrary pointer values back and forth with WASM. Only values WASM can actually
|
||||
/// access can be touched through this, and we ensure the value is the correct type. In the future,
|
||||
/// when extern refs get actually properly implemented at compile time we might want to get rid
|
||||
/// of this code.
|
||||
pub fn get_extern_ref_index<T: UniqueTypeId<u64>>(&self, value: &T) -> u32 {
|
||||
let ptr = value as *const T as *const u8;
|
||||
if let Some(v) = self.extern_ref_pointers_lookup.read().get(&ptr) {
|
||||
return *v as u32;
|
||||
}
|
||||
let index = {
|
||||
let mut extern_ref_guard = self.extern_ref_pointers.write();
|
||||
extern_ref_guard.push(ptr);
|
||||
extern_ref_guard.len() as u32
|
||||
};
|
||||
self.extern_ref_pointers_lookup.write().insert(ptr, index);
|
||||
self.extern_ref_type_lookup.write().insert(ptr, T::id().0);
|
||||
index
|
||||
}
|
||||
|
||||
/// Gets a value from the extern ref lookup. This turns an earlier registered index back into
|
||||
/// its proper value, validates its type, and returns the value.
|
||||
pub fn get_extern_ref_value<T: UniqueTypeId<u64>>(&self, index: u32) -> &T {
|
||||
let read_guard = self.extern_ref_pointers.read();
|
||||
let ptr = read_guard.get(index as usize).unwrap();
|
||||
let expected_type_id = self.extern_ref_type_lookup.read()[&ptr];
|
||||
if expected_type_id != T::id().0 {
|
||||
panic!("Extern ref was accessed with wrong type");
|
||||
}
|
||||
|
||||
unsafe { (*ptr as *const T).as_ref().unwrap() }
|
||||
}
|
||||
|
||||
/// Allocates memory inside the WASM container with a given size and alignment. This memory is
|
||||
/// owned by WASM, and is how we can pass memory references that the host allocated to WASM.
|
||||
/// The return is a tuple containing both the actual pointer to the memory (usable by the host),
|
||||
/// and the WASM offset to the memory (usable by the client).
|
||||
pub fn allocate_mem(&self, size: u32, align: u32) -> (*const u8, u32) {
|
||||
let wasm_ptr = self.allocate_mem_fn.as_ref().unwrap().call(size, align).unwrap();
|
||||
unsafe {
|
||||
(
|
||||
self.memory.as_ref().unwrap().data_ptr().offset(wasm_ptr as isize),
|
||||
wasm_ptr,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScriptResolver for WebAssemblyScriptResolver {
|
||||
fn load_script(
|
||||
&self,
|
||||
owner: *const u8,
|
||||
category: ScriptCategory,
|
||||
script_key: &StringKey,
|
||||
) -> PkmnResult<Option<Arc<dyn Script>>> {
|
||||
let script = self
|
||||
.load_script_fn
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.call(
|
||||
category as u8,
|
||||
ExternRef::from_index(self.get_extern_ref_index(script_key)),
|
||||
)
|
||||
.unwrap();
|
||||
if script == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let key = ScriptCapabilitiesKey {
|
||||
category,
|
||||
script_key: script_key.clone(),
|
||||
};
|
||||
|
||||
if !self.script_capabilities.read().contains_key(&key) {
|
||||
let mut capabilities = HashSet::new();
|
||||
unsafe {
|
||||
if let Some(get_cap) = self.exported_functions.get(&"get_script_capabilities".into()) {
|
||||
let res = get_cap.call(&[Value::I32(script as i32)]).unwrap();
|
||||
let ptr = (self.memory.as_ref().unwrap().data_ptr() as *const WebAssemblyScriptCapabilities)
|
||||
.offset(res[0].i32().unwrap() as isize);
|
||||
let length = res[1].i32().unwrap() as usize;
|
||||
for i in 0..length {
|
||||
capabilities.insert(*ptr.offset(i as isize));
|
||||
}
|
||||
}
|
||||
}
|
||||
self.script_capabilities.write().insert(key.clone(), capabilities);
|
||||
}
|
||||
|
||||
let read_guard = self.script_capabilities.read();
|
||||
let capabilities = read_guard.get(&key).unwrap();
|
||||
|
||||
Ok(Some(Arc::new(WebAssemblyScript::new(
|
||||
owner as *mut u8,
|
||||
script,
|
||||
capabilities as *const HashSet<WebAssemblyScriptCapabilities>
|
||||
as *mut HashSet<WebAssemblyScriptCapabilities>,
|
||||
self as *const WebAssemblyScriptResolver as *mut WebAssemblyScriptResolver,
|
||||
script_key.clone(),
|
||||
))))
|
||||
}
|
||||
|
||||
fn load_item_script(&self, _key: &Item) -> PkmnResult<Option<Arc<dyn ItemScript>>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for WebAssemblyScriptResolver {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("WebAssemblyScriptResolver")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct WebAssemblyEnv {
|
||||
pub resolver: *const WebAssemblyScriptResolver,
|
||||
}
|
||||
|
||||
impl WebAssemblyEnv {
|
||||
pub fn resolver(&self) -> &WebAssemblyScriptResolver {
|
||||
unsafe { self.resolver.as_ref().unwrap() }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Sync for WebAssemblyEnv {}
|
||||
|
||||
unsafe impl Send for WebAssemblyEnv {}
|
||||
|
||||
impl WasmerEnv for WebAssemblyEnv {}
|
|
@ -26,6 +26,11 @@ pub trait DataLibrary<'a, T: 'a> {
|
|||
self.map().get::<StringKey>(key)
|
||||
}
|
||||
|
||||
/// Gets a value from the library.
|
||||
fn get_by_hash(&'a self, key: u32) -> Option<&'a T> {
|
||||
self.map().get::<u32>(&key)
|
||||
}
|
||||
|
||||
/// Gets a mutable value from the library.
|
||||
fn get_mut(&mut self, key: &StringKey) -> Option<&mut T> {
|
||||
self.get_modify().get_mut(key)
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::StringKey;
|
|||
|
||||
/// A library to store all data for moves.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))]
|
||||
pub struct MoveLibrary {
|
||||
/// The underlying map.
|
||||
map: IndexMap<StringKey, MoveData>,
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::static_data::TypeLibrary;
|
|||
|
||||
/// The storage for all different libraries.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))]
|
||||
pub struct StaticData {
|
||||
/// Several misc settings for the library.
|
||||
settings: LibrarySettings,
|
||||
|
|
|
@ -59,6 +59,7 @@ pub enum MoveTarget {
|
|||
|
||||
/// A move is the skill Pokémon primarily use in battle. This is the data related to that.
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))]
|
||||
pub struct MoveData {
|
||||
/// The name of the move.
|
||||
name: StringKey,
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::lazy::SyncLazy;
|
||||
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
|
||||
use crate::static_data::Form;
|
||||
|
@ -28,7 +26,11 @@ pub struct Species {
|
|||
}
|
||||
|
||||
/// A cached String Key to get the default form.
|
||||
static DEFAULT_KEY: SyncLazy<StringKey> = SyncLazy::new(|| StringKey::new("default"));
|
||||
static DEFAULT_KEY: conquer_once::OnceCell<StringKey> = conquer_once::OnceCell::uninit();
|
||||
|
||||
fn get_default_key() -> StringKey {
|
||||
DEFAULT_KEY.get_or_init(|| StringKey::new("default")).clone()
|
||||
}
|
||||
|
||||
impl Species {
|
||||
/// Creates a new species.
|
||||
|
@ -42,7 +44,7 @@ impl Species {
|
|||
flags: HashSet<StringKey>,
|
||||
) -> Species {
|
||||
let mut forms = HashMap::with_capacity(1);
|
||||
forms.insert_unique_unchecked(DEFAULT_KEY.clone(), default_form);
|
||||
forms.insert_unique_unchecked(get_default_key(), default_form);
|
||||
Species {
|
||||
id,
|
||||
name: name.clone(),
|
||||
|
@ -96,7 +98,7 @@ impl Species {
|
|||
|
||||
/// Gets the form the Pokemon will have by default, if no other form is specified.
|
||||
pub fn get_default_form(&self) -> &Form {
|
||||
self.forms.get(&DEFAULT_KEY).unwrap()
|
||||
self.forms.get(&get_default_key()).unwrap()
|
||||
}
|
||||
|
||||
/// Gets a random gender.
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
use std::fmt::{Display, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::lazy::SyncLazy;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
|
||||
use conquer_once::OnceCell;
|
||||
use hashbrown::HashMap;
|
||||
use indexmap::Equivalent;
|
||||
|
||||
/// StringKey is an immutable string that is used for indexing of hashmaps or equality a lot.
|
||||
/// By reference counting the string instead of copying, and caching the hash, we can get some
|
||||
/// free speed out of it. Note that StringKeys also compare case insensitive, so that for example
|
||||
/// `charmander` == `Charmander`.
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))]
|
||||
pub struct StringKey {
|
||||
/// The underlying reference counted string.
|
||||
str: Arc<str>,
|
||||
|
@ -19,9 +21,9 @@ pub struct StringKey {
|
|||
|
||||
/// A cache of all allocated strings. This allows us to re-use strings that are often used without
|
||||
/// allocation.
|
||||
static STRING_CACHE: SyncLazy<Mutex<HashMap<u32, Weak<str>>>> = SyncLazy::new(|| Mutex::new(HashMap::new()));
|
||||
static STRING_CACHE: OnceCell<Mutex<HashMap<u32, Weak<str>>>> = OnceCell::uninit();
|
||||
/// An empty StringKey
|
||||
static EMPTY: SyncLazy<StringKey> = SyncLazy::new(|| StringKey::new(""));
|
||||
static EMPTY: OnceCell<StringKey> = OnceCell::uninit();
|
||||
|
||||
impl StringKey {
|
||||
/// Calculates the hash of a string key in a const manner.
|
||||
|
@ -49,7 +51,7 @@ impl StringKey {
|
|||
/// that value.
|
||||
pub fn new(s: &str) -> Self {
|
||||
let hash = StringKey::get_hash(s);
|
||||
let mut cache = STRING_CACHE.lock().unwrap();
|
||||
let mut cache = STRING_CACHE.get_or_init(|| Mutex::new(HashMap::new())).lock().unwrap();
|
||||
let cached_value = cache.get(&hash);
|
||||
if let Some(cached_value) = cached_value {
|
||||
if let Some(cached_value) = cached_value.upgrade() {
|
||||
|
@ -66,7 +68,7 @@ impl StringKey {
|
|||
|
||||
/// Gets the empty StringKey.
|
||||
pub fn empty() -> Self {
|
||||
EMPTY.clone()
|
||||
EMPTY.get_or_init(|| StringKey::new("")).clone()
|
||||
}
|
||||
|
||||
/// Gets the underlying string for the StringKey.
|
||||
|
@ -100,6 +102,12 @@ impl From<&str> for StringKey {
|
|||
}
|
||||
}
|
||||
|
||||
impl Equivalent<StringKey> for u32 {
|
||||
fn equivalent(&self, key: &StringKey) -> bool {
|
||||
*self == key.hash
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for StringKey {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&*self.str)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::io::{BufReader, Read};
|
||||
|
||||
use hashbrown::HashSet;
|
||||
use num_traits::PrimInt;
|
||||
|
@ -13,6 +13,7 @@ use pkmn_lib::dynamic_data::Gen7BattleStatCalculator;
|
|||
use pkmn_lib::dynamic_data::Gen7DamageLibrary;
|
||||
use pkmn_lib::dynamic_data::Gen7MiscLibrary;
|
||||
use pkmn_lib::dynamic_data::{DynamicLibrary, EmptyScriptResolver};
|
||||
use pkmn_lib::script_implementations::wasm::script_resolver::WebAssemblyScriptResolver;
|
||||
use pkmn_lib::static_data::{
|
||||
Ability, AbilityLibrary, BattleItemCategory, DataLibrary, EffectParameter, Form, GrowthRateLibrary, Item,
|
||||
ItemLibrary, LearnableMoves, LibrarySettings, LookupGrowthRate, MoveData, MoveLibrary, Nature, NatureLibrary,
|
||||
|
@ -32,12 +33,15 @@ pub fn load_library() -> DynamicLibrary {
|
|||
load_abilities(&path, data.abilities_mut());
|
||||
load_moves(&path, &mut data);
|
||||
load_species(&path, &mut data);
|
||||
let mut resolver = WebAssemblyScriptResolver::new();
|
||||
load_wasm(&path, &mut resolver);
|
||||
|
||||
let dynamic = DynamicLibrary::new(
|
||||
data,
|
||||
Box::new(Gen7BattleStatCalculator {}),
|
||||
Box::new(Gen7DamageLibrary::new(false)),
|
||||
Box::new(Gen7MiscLibrary::new()),
|
||||
Box::new(EmptyScriptResolver {}),
|
||||
Box::new(resolver),
|
||||
);
|
||||
dynamic
|
||||
}
|
||||
|
@ -271,6 +275,14 @@ pub fn load_species(path: &String, library: &mut StaticData) {
|
|||
}
|
||||
}
|
||||
|
||||
fn load_wasm(path: &String, library: &mut WebAssemblyScriptResolver) {
|
||||
let mut file = File::open(path.to_string() + "gen7_scripts_rs.wasm").unwrap();
|
||||
let mut reader = BufReader::new(file);
|
||||
let mut buffer = Vec::new();
|
||||
reader.read_to_end(&mut buffer);
|
||||
library.load_wasm_from_bytes(&buffer);
|
||||
}
|
||||
|
||||
fn parse_form(name: StringKey, value: &Value, library: &mut StaticData) -> Form {
|
||||
let mut abilities = Vec::new();
|
||||
for a in value["abilities"].as_array().unwrap() {
|
||||
|
|
Binary file not shown.
|
@ -4,27 +4,35 @@
|
|||
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::lazy::SyncLazy;
|
||||
use std::path::Path;
|
||||
|
||||
use pkmn_lib::dynamic_data::DynamicLibrary;
|
||||
use conquer_once::OnceCell;
|
||||
|
||||
use pkmn_lib::dynamic_data::{DynamicLibrary, ScriptCategory};
|
||||
|
||||
use crate::common::{library_loader, TestCase};
|
||||
|
||||
pub mod common;
|
||||
|
||||
static LIBRARY: SyncLazy<DynamicLibrary> = SyncLazy::new(|| {
|
||||
let start_time = chrono::Utc::now();
|
||||
let lib = library_loader::load_library();
|
||||
let end_time = chrono::Utc::now();
|
||||
println!("Built library in {} ms", (end_time - start_time).num_milliseconds());
|
||||
lib
|
||||
});
|
||||
static LIBRARY: OnceCell<DynamicLibrary> = OnceCell::uninit();
|
||||
|
||||
fn get_library<'a>() -> &'a DynamicLibrary {
|
||||
LIBRARY.get_or_init(|| {
|
||||
let start_time = chrono::Utc::now();
|
||||
let lib = library_loader::load_library();
|
||||
let end_time = chrono::Utc::now();
|
||||
println!("Built library in {} ms", (end_time - start_time).num_milliseconds());
|
||||
lib
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn validate_library_load() {
|
||||
SyncLazy::force(&LIBRARY);
|
||||
let start_time = chrono::Utc::now();
|
||||
let lib = library_loader::load_library();
|
||||
let end_time = chrono::Utc::now();
|
||||
println!("Built library in {} ms", (end_time - start_time).num_milliseconds());
|
||||
}
|
||||
|
||||
#[datatest::files("tests/test_cases", {
|
||||
|
@ -37,5 +45,16 @@ fn integration_tests(input: &Path) {
|
|||
file.read_to_string(&mut str).unwrap();
|
||||
let test_case = serde_yaml::from_str::<TestCase>(&*str).unwrap();
|
||||
println!("\tRunning integration test {}", test_case.name);
|
||||
test_case.run_test(&LIBRARY);
|
||||
test_case.run_test(get_library());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn validate_script() {
|
||||
let lib = library_loader::load_library();
|
||||
let script = lib
|
||||
.load_script(0 as *const u8, ScriptCategory::Move, &"test".into())
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
script.on_initialize(&[]);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
MoveLibrary = 0
|
||||
MoveData = 1
|
||||
StringKey = 2
|
||||
DynamicLibrary = 3
|
||||
StaticData = 4
|
Loading…
Reference in New Issue