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"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
c_interface = []
|
ffi = []
|
||||||
serde = ["dep:serde"]
|
serde = ["dep:serde"]
|
||||||
default = ["serde"]
|
wasm = ["dep:wasmer", "dep:unique-type-id", "dep:unique-type-id-derive"]
|
||||||
|
default = ["serde", "wasm"]
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 0
|
opt-level = 0
|
||||||
|
@ -50,7 +51,11 @@ rand_pcg = "0.3.1"
|
||||||
hashbrown = "0.12.1"
|
hashbrown = "0.12.1"
|
||||||
indexmap = "1.8.2"
|
indexmap = "1.8.2"
|
||||||
parking_lot = "0.12.1"
|
parking_lot = "0.12.1"
|
||||||
|
conquer-once = "0.3.2"
|
||||||
serde = { version = "1.0.137", optional = true, features = ["derive"] }
|
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]
|
[dev-dependencies]
|
||||||
csv = "1.1.6"
|
csv = "1.1.6"
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use std::ffi::c_void;
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
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
|
/// 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.
|
/// calculators that might be customized between different generations and implementations.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))]
|
||||||
pub struct DynamicLibrary {
|
pub struct DynamicLibrary {
|
||||||
/// The static data is the immutable storage data for this library.
|
/// The static data is the immutable storage data for this library.
|
||||||
static_data: StaticData,
|
static_data: StaticData,
|
||||||
|
@ -75,7 +75,7 @@ impl DynamicLibrary {
|
||||||
/// can be created with this combination, returns None.
|
/// can be created with this combination, returns None.
|
||||||
pub fn load_script(
|
pub fn load_script(
|
||||||
&self,
|
&self,
|
||||||
owner: *const c_void,
|
owner: *const u8,
|
||||||
_category: ScriptCategory,
|
_category: ScriptCategory,
|
||||||
_key: &StringKey,
|
_key: &StringKey,
|
||||||
) -> PkmnResult<Option<Arc<dyn Script>>> {
|
) -> PkmnResult<Option<Arc<dyn Script>>> {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use std::ffi::c_void;
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -14,7 +13,7 @@ pub trait ScriptResolver: Debug {
|
||||||
/// can be created with this combination, returns None.
|
/// can be created with this combination, returns None.
|
||||||
fn load_script(
|
fn load_script(
|
||||||
&self,
|
&self,
|
||||||
owner: *const c_void,
|
owner: *const u8,
|
||||||
category: ScriptCategory,
|
category: ScriptCategory,
|
||||||
script_key: &StringKey,
|
script_key: &StringKey,
|
||||||
) -> PkmnResult<Option<Arc<dyn Script>>>;
|
) -> 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
|
/// 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
|
/// 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.
|
/// a move that placed it there.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
#[repr(u8)]
|
||||||
pub enum ScriptCategory {
|
pub enum ScriptCategory {
|
||||||
/// A script that belongs to a move. This generally is only the script that is attached to a
|
/// 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)
|
/// [`MoveChoice`](crate::dynamic_data::MoveChoice) and [`ExecutingMove`](crate::dynamic_data::ExecutingMove)
|
||||||
|
@ -58,7 +58,7 @@ pub struct EmptyScriptResolver {}
|
||||||
impl ScriptResolver for EmptyScriptResolver {
|
impl ScriptResolver for EmptyScriptResolver {
|
||||||
fn load_script(
|
fn load_script(
|
||||||
&self,
|
&self,
|
||||||
_owner: *const c_void,
|
_owner: *const u8,
|
||||||
_category: ScriptCategory,
|
_category: ScriptCategory,
|
||||||
_script_key: &StringKey,
|
_script_key: &StringKey,
|
||||||
) -> PkmnResult<Option<Arc<dyn Script>>> {
|
) -> PkmnResult<Option<Arc<dyn Script>>> {
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
#![feature(once_cell)]
|
#![feature(once_cell)]
|
||||||
#![feature(const_option)]
|
#![feature(const_option)]
|
||||||
#![feature(is_some_with)]
|
#![feature(is_some_with)]
|
||||||
|
#![feature(core_ffi_c)]
|
||||||
|
#![feature(new_uninit)]
|
||||||
|
|
||||||
//! PkmnLib
|
//! PkmnLib
|
||||||
//! PkmnLib is a full featured implementation of Pokemon. while currently focused on implementing
|
//! 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
|
/// 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.
|
/// this. This includes things as Pokemon themselves, battles, etc.
|
||||||
pub mod dynamic_data;
|
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
|
/// 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.
|
/// things such as data about Pokemon species, data about items, etc.
|
||||||
pub mod static_data;
|
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)
|
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.
|
/// Gets a mutable value from the library.
|
||||||
fn get_mut(&mut self, key: &StringKey) -> Option<&mut T> {
|
fn get_mut(&mut self, key: &StringKey) -> Option<&mut T> {
|
||||||
self.get_modify().get_mut(key)
|
self.get_modify().get_mut(key)
|
||||||
|
|
|
@ -6,6 +6,7 @@ use crate::StringKey;
|
||||||
|
|
||||||
/// A library to store all data for moves.
|
/// A library to store all data for moves.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))]
|
||||||
pub struct MoveLibrary {
|
pub struct MoveLibrary {
|
||||||
/// The underlying map.
|
/// The underlying map.
|
||||||
map: IndexMap<StringKey, MoveData>,
|
map: IndexMap<StringKey, MoveData>,
|
||||||
|
|
|
@ -9,6 +9,7 @@ use crate::static_data::TypeLibrary;
|
||||||
|
|
||||||
/// The storage for all different libraries.
|
/// The storage for all different libraries.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))]
|
||||||
pub struct StaticData {
|
pub struct StaticData {
|
||||||
/// Several misc settings for the library.
|
/// Several misc settings for the library.
|
||||||
settings: LibrarySettings,
|
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.
|
/// A move is the skill Pokémon primarily use in battle. This is the data related to that.
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))]
|
||||||
pub struct MoveData {
|
pub struct MoveData {
|
||||||
/// The name of the move.
|
/// The name of the move.
|
||||||
name: StringKey,
|
name: StringKey,
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use std::lazy::SyncLazy;
|
|
||||||
|
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
|
|
||||||
use crate::static_data::Form;
|
use crate::static_data::Form;
|
||||||
|
@ -28,7 +26,11 @@ pub struct Species {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A cached String Key to get the default form.
|
/// 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 {
|
impl Species {
|
||||||
/// Creates a new species.
|
/// Creates a new species.
|
||||||
|
@ -42,7 +44,7 @@ impl Species {
|
||||||
flags: HashSet<StringKey>,
|
flags: HashSet<StringKey>,
|
||||||
) -> Species {
|
) -> Species {
|
||||||
let mut forms = HashMap::with_capacity(1);
|
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 {
|
Species {
|
||||||
id,
|
id,
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
|
@ -96,7 +98,7 @@ impl Species {
|
||||||
|
|
||||||
/// Gets the form the Pokemon will have by default, if no other form is specified.
|
/// Gets the form the Pokemon will have by default, if no other form is specified.
|
||||||
pub fn get_default_form(&self) -> &Form {
|
pub fn get_default_form(&self) -> &Form {
|
||||||
self.forms.get(&DEFAULT_KEY).unwrap()
|
self.forms.get(&get_default_key()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a random gender.
|
/// Gets a random gender.
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::lazy::SyncLazy;
|
|
||||||
use std::sync::{Arc, Mutex, Weak};
|
use std::sync::{Arc, Mutex, Weak};
|
||||||
|
|
||||||
|
use conquer_once::OnceCell;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
|
use indexmap::Equivalent;
|
||||||
|
|
||||||
/// StringKey is an immutable string that is used for indexing of hashmaps or equality a lot.
|
/// 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
|
/// 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
|
/// free speed out of it. Note that StringKeys also compare case insensitive, so that for example
|
||||||
/// `charmander` == `Charmander`.
|
/// `charmander` == `Charmander`.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))]
|
||||||
pub struct StringKey {
|
pub struct StringKey {
|
||||||
/// The underlying reference counted string.
|
/// The underlying reference counted string.
|
||||||
str: Arc<str>,
|
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
|
/// A cache of all allocated strings. This allows us to re-use strings that are often used without
|
||||||
/// allocation.
|
/// 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
|
/// An empty StringKey
|
||||||
static EMPTY: SyncLazy<StringKey> = SyncLazy::new(|| StringKey::new(""));
|
static EMPTY: OnceCell<StringKey> = OnceCell::uninit();
|
||||||
|
|
||||||
impl StringKey {
|
impl StringKey {
|
||||||
/// Calculates the hash of a string key in a const manner.
|
/// Calculates the hash of a string key in a const manner.
|
||||||
|
@ -49,7 +51,7 @@ impl StringKey {
|
||||||
/// that value.
|
/// that value.
|
||||||
pub fn new(s: &str) -> Self {
|
pub fn new(s: &str) -> Self {
|
||||||
let hash = StringKey::get_hash(s);
|
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);
|
let cached_value = cache.get(&hash);
|
||||||
if let Some(cached_value) = cached_value {
|
if let Some(cached_value) = cached_value {
|
||||||
if let Some(cached_value) = cached_value.upgrade() {
|
if let Some(cached_value) = cached_value.upgrade() {
|
||||||
|
@ -66,7 +68,7 @@ impl StringKey {
|
||||||
|
|
||||||
/// Gets the empty StringKey.
|
/// Gets the empty StringKey.
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
EMPTY.clone()
|
EMPTY.get_or_init(|| StringKey::new("")).clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the underlying string for the StringKey.
|
/// 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 {
|
impl Display for StringKey {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str(&*self.str)
|
f.write_str(&*self.str)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::{BufReader, Read};
|
||||||
|
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
use num_traits::PrimInt;
|
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::Gen7DamageLibrary;
|
||||||
use pkmn_lib::dynamic_data::Gen7MiscLibrary;
|
use pkmn_lib::dynamic_data::Gen7MiscLibrary;
|
||||||
use pkmn_lib::dynamic_data::{DynamicLibrary, EmptyScriptResolver};
|
use pkmn_lib::dynamic_data::{DynamicLibrary, EmptyScriptResolver};
|
||||||
|
use pkmn_lib::script_implementations::wasm::script_resolver::WebAssemblyScriptResolver;
|
||||||
use pkmn_lib::static_data::{
|
use pkmn_lib::static_data::{
|
||||||
Ability, AbilityLibrary, BattleItemCategory, DataLibrary, EffectParameter, Form, GrowthRateLibrary, Item,
|
Ability, AbilityLibrary, BattleItemCategory, DataLibrary, EffectParameter, Form, GrowthRateLibrary, Item,
|
||||||
ItemLibrary, LearnableMoves, LibrarySettings, LookupGrowthRate, MoveData, MoveLibrary, Nature, NatureLibrary,
|
ItemLibrary, LearnableMoves, LibrarySettings, LookupGrowthRate, MoveData, MoveLibrary, Nature, NatureLibrary,
|
||||||
|
@ -32,12 +33,15 @@ pub fn load_library() -> DynamicLibrary {
|
||||||
load_abilities(&path, data.abilities_mut());
|
load_abilities(&path, data.abilities_mut());
|
||||||
load_moves(&path, &mut data);
|
load_moves(&path, &mut data);
|
||||||
load_species(&path, &mut data);
|
load_species(&path, &mut data);
|
||||||
|
let mut resolver = WebAssemblyScriptResolver::new();
|
||||||
|
load_wasm(&path, &mut resolver);
|
||||||
|
|
||||||
let dynamic = DynamicLibrary::new(
|
let dynamic = DynamicLibrary::new(
|
||||||
data,
|
data,
|
||||||
Box::new(Gen7BattleStatCalculator {}),
|
Box::new(Gen7BattleStatCalculator {}),
|
||||||
Box::new(Gen7DamageLibrary::new(false)),
|
Box::new(Gen7DamageLibrary::new(false)),
|
||||||
Box::new(Gen7MiscLibrary::new()),
|
Box::new(Gen7MiscLibrary::new()),
|
||||||
Box::new(EmptyScriptResolver {}),
|
Box::new(resolver),
|
||||||
);
|
);
|
||||||
dynamic
|
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 {
|
fn parse_form(name: StringKey, value: &Value, library: &mut StaticData) -> Form {
|
||||||
let mut abilities = Vec::new();
|
let mut abilities = Vec::new();
|
||||||
for a in value["abilities"].as_array().unwrap() {
|
for a in value["abilities"].as_array().unwrap() {
|
||||||
|
|
Binary file not shown.
|
@ -4,27 +4,35 @@
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::lazy::SyncLazy;
|
|
||||||
use std::path::Path;
|
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};
|
use crate::common::{library_loader, TestCase};
|
||||||
|
|
||||||
pub mod common;
|
pub mod common;
|
||||||
|
|
||||||
static LIBRARY: SyncLazy<DynamicLibrary> = SyncLazy::new(|| {
|
static LIBRARY: OnceCell<DynamicLibrary> = OnceCell::uninit();
|
||||||
let start_time = chrono::Utc::now();
|
|
||||||
let lib = library_loader::load_library();
|
fn get_library<'a>() -> &'a DynamicLibrary {
|
||||||
let end_time = chrono::Utc::now();
|
LIBRARY.get_or_init(|| {
|
||||||
println!("Built library in {} ms", (end_time - start_time).num_milliseconds());
|
let start_time = chrono::Utc::now();
|
||||||
lib
|
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]
|
#[test]
|
||||||
#[cfg_attr(miri, ignore)]
|
#[cfg_attr(miri, ignore)]
|
||||||
fn validate_library_load() {
|
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", {
|
#[datatest::files("tests/test_cases", {
|
||||||
|
@ -37,5 +45,16 @@ fn integration_tests(input: &Path) {
|
||||||
file.read_to_string(&mut str).unwrap();
|
file.read_to_string(&mut str).unwrap();
|
||||||
let test_case = serde_yaml::from_str::<TestCase>(&*str).unwrap();
|
let test_case = serde_yaml::from_str::<TestCase>(&*str).unwrap();
|
||||||
println!("\tRunning integration test {}", test_case.name);
|
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