diff --git a/Cargo.toml b/Cargo.toml index 4c8d8a6..d11a670 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ 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 } +paste = { version = "1.0.8" } [dev-dependencies] csv = "1.1.6" diff --git a/src/dynamic_data/choices.rs b/src/dynamic_data/choices.rs index 5304896..2e1c04f 100644 --- a/src/dynamic_data/choices.rs +++ b/src/dynamic_data/choices.rs @@ -29,6 +29,7 @@ struct CommonChoiceData<'user, 'library> { /// This enum defines a single choice for a Pokemon for a battle turn. #[derive(Debug)] +#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))] pub enum TurnChoice<'user, 'library> { /// A move choice tells a Pokemon to use a move on a target for this turn. Move(MoveChoice<'user, 'library>), diff --git a/src/dynamic_data/event_hooks.rs b/src/dynamic_data/event_hooks.rs index fde6c1e..9d6c6f9 100644 --- a/src/dynamic_data/event_hooks.rs +++ b/src/dynamic_data/event_hooks.rs @@ -91,6 +91,15 @@ pub enum Event<'own, 'battle, 'library> { /// The health of the Pokemon after the damage. new_health: u32, }, + /// This event happens when a Pokemon gets healed + Heal { + /// The Pokemon that gets healed. + pokemon: &'own Pokemon<'battle, 'library>, + /// The health of the Pokemon before the heal. + original_health: u32, + /// The health of the Pokemon after the heal. + new_health: u32, + }, /// This event happens when a Pokemon faints. Faint { /// The pokemon that has fainted. diff --git a/src/dynamic_data/libraries/misc_library.rs b/src/dynamic_data/libraries/misc_library.rs index 811507d..b94e0a9 100644 --- a/src/dynamic_data/libraries/misc_library.rs +++ b/src/dynamic_data/libraries/misc_library.rs @@ -68,7 +68,7 @@ impl<'library> Default for Gen7MiscLibrary<'library> { impl<'library> Drop for Gen7MiscLibrary<'library> { fn drop(&mut self) { unsafe { - Box::from_raw(self.struggle_data as *mut MoveData); + let _ = Box::from_raw(self.struggle_data as *mut MoveData); } } } diff --git a/src/dynamic_data/models/learned_move.rs b/src/dynamic_data/models/learned_move.rs index a86ca12..781e8fe 100644 --- a/src/dynamic_data/models/learned_move.rs +++ b/src/dynamic_data/models/learned_move.rs @@ -5,6 +5,7 @@ use crate::static_data::MoveData; /// A learned move is the data attached to a Pokemon for a move it has learned. It has information /// such as the remaining amount of users, how it has been learned, etc. #[derive(Debug)] +#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))] pub struct LearnedMove<'library> { /// The immutable move information of the move. move_data: &'library MoveData, @@ -18,6 +19,7 @@ pub struct LearnedMove<'library> { /// The different ways a move can be learned. #[derive(Copy, Clone, Debug, Default)] +#[repr(u8)] pub enum MoveLearnMethod { /// We do not know the learn method. #[default] diff --git a/src/dynamic_data/models/pokemon.rs b/src/dynamic_data/models/pokemon.rs index f7c065c..dc61d4c 100644 --- a/src/dynamic_data/models/pokemon.rs +++ b/src/dynamic_data/models/pokemon.rs @@ -25,6 +25,7 @@ use crate::{script_hook, PkmnResult, StringKey}; /// An individual Pokemon as we know and love them. #[derive(Debug)] +#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))] pub struct Pokemon<'own, 'library> where 'own: 'library, @@ -306,6 +307,12 @@ impl<'own, 'library> Pokemon<'own, 'library> { pub fn flat_stats(&self) -> &StatisticSet { &self.flat_stats } + + /// The stats of the Pokemon including the stat boosts + pub fn stat_boosts(&self) -> &ClampedStatisticSet { + &self.stat_boost + } + /// The stats of the Pokemon including the stat boosts pub fn boosted_stats(&self) -> &StatisticSet { &self.boosted_stats @@ -613,7 +620,6 @@ impl<'own, 'library> Pokemon<'own, 'library> { original_health: self.current_health(), new_health, }); - // TODO: register history } } if self.battle_data.read().is_some_and(|a| a.on_battle_field()) { @@ -645,6 +651,33 @@ impl<'own, 'library> Pokemon<'own, 'library> { } } + /// Heals the Pokemon by a specific amount. Unless allow_revive is set to true, this will not + /// heal if the Pokemon has 0 health. If the amount healed is 0, this will return false. + pub fn heal(&self, mut amount: u32, allow_revive: bool) -> bool { + if self.current_health() == 0 && !allow_revive { + return false; + } + let max_amount = self.max_health() - self.current_health(); + if amount > max_amount { + amount = max_amount; + } + if amount == 0 { + return false; + } + let new_health = self.current_health() + max_amount; + if let Some(battle_data) = &self.battle_data.read().deref() { + if let Some(battle) = battle_data.battle() { + battle.event_hook().trigger(Event::Heal { + pokemon: self, + original_health: self.current_health(), + new_health, + }); + } + } + self.current_health.store(new_health, Ordering::SeqCst); + true + } + /// Learn a move. pub fn learn_move(&self, move_name: &StringKey, learn_method: MoveLearnMethod) { let mut learned_moves = self.learned_moves().write(); @@ -745,6 +778,7 @@ impl<'own, 'library> VolatileScriptsOwner<'own> for Pokemon<'own, 'library> { /// A source of damage. This should be as unique as possible. #[derive(Debug, Clone, Copy)] +#[repr(u8)] pub enum DamageSource { /// The damage is done by a move. MoveDamage = 0, diff --git a/src/lib.rs b/src/lib.rs index 0964544..71100fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,11 +5,9 @@ #![deny(clippy::missing_docs_in_private_items)] #![feature(test)] #![feature(bench_black_box)] -#![feature(let_chains)] #![feature(once_cell)] #![feature(const_option)] #![feature(is_some_with)] -#![feature(core_ffi_c)] #![feature(new_uninit)] #![feature(get_mut_unchecked)] @@ -18,7 +16,6 @@ //! generation 7, this library tries to offload generational differences such as move effects //! to a scripting library. //! -extern crate core; #[doc(hidden)] pub use utils::*; diff --git a/src/script_implementations/wasm/export_registry/dynamic_data/learned_move.rs b/src/script_implementations/wasm/export_registry/dynamic_data/learned_move.rs new file mode 100644 index 0000000..b0f8cb3 --- /dev/null +++ b/src/script_implementations/wasm/export_registry/dynamic_data/learned_move.rs @@ -0,0 +1,25 @@ +use std::intrinsics::transmute; + +use crate::dynamic_data::{LearnedMove, MoveLearnMethod, Pokemon, TurnChoice}; +use crate::script_implementations::wasm::export_registry::register; +use crate::script_implementations::wasm::extern_ref::ExternRef; +use crate::script_implementations::wasm::script_resolver::WebAssemblyEnv; +use crate::static_data::MoveData; + +register! { + fn learned_move_get_learn_method<'a>( + env: &WebAssemblyEnv, + turn_choice: ExternRef, + ) -> u8 { + unsafe { + transmute(turn_choice.value(env).unwrap().learn_method()) + } + } + + fn learned_move_get_move_data<'a>( + env: &WebAssemblyEnv, + turn_choice: ExternRef, + ) -> ExternRef { + ExternRef::new(env.data().as_ref(), turn_choice.value(env).unwrap().move_data()) + } +} diff --git a/src/script_implementations/wasm/export_registry/dynamic_data/mod.rs b/src/script_implementations/wasm/export_registry/dynamic_data/mod.rs new file mode 100644 index 0000000..bb8b0b0 --- /dev/null +++ b/src/script_implementations/wasm/export_registry/dynamic_data/mod.rs @@ -0,0 +1,13 @@ +use wasmer::{Exports, Store}; + +use crate::script_implementations::wasm::script_resolver::WebAssemblyEnv; + +mod learned_move; +mod pokemon; +mod turn_choice; + +pub(crate) fn register(exports: &mut Exports, store: &Store, env: WebAssemblyEnv) { + turn_choice::register(exports, store, env.clone()); + pokemon::register(exports, store, env.clone()); + learned_move::register(exports, store, env.clone()); +} diff --git a/src/script_implementations/wasm/export_registry/dynamic_data/pokemon.rs b/src/script_implementations/wasm/export_registry/dynamic_data/pokemon.rs new file mode 100644 index 0000000..4f4912d --- /dev/null +++ b/src/script_implementations/wasm/export_registry/dynamic_data/pokemon.rs @@ -0,0 +1,77 @@ +use std::mem::transmute; + +use crate::dynamic_data::{DamageSource, DynamicLibrary, Pokemon}; +use crate::script_implementations::wasm::export_registry::register; +use crate::script_implementations::wasm::extern_ref::ExternRef; +use crate::script_implementations::wasm::script_resolver::WebAssemblyEnv; +use crate::static_data::StatisticSet; +use crate::static_data::{ClampedStatisticSet, Species}; + +register! { + fn pokemon_get_library( + env: &WebAssemblyEnv, + pokemon: ExternRef, + ) -> ExternRef { + let lib = pokemon.value(env).unwrap().library(); + ExternRef::new(env.data().as_ref(), lib) + } + + fn pokemon_get_boosted_stats( + env: &WebAssemblyEnv, + pokemon: ExternRef, + ) -> ExternRef> { + let statistic_set = pokemon.value(env).unwrap().boosted_stats(); + ExternRef::new(env.data().as_ref(), statistic_set) + } + + fn pokemon_get_flat_stats( + env: &WebAssemblyEnv, + pokemon: ExternRef, + ) -> ExternRef> { + let statistic_set = pokemon.value(env).unwrap().flat_stats(); + ExternRef::new(env.data().as_ref(), statistic_set) + } + + fn pokemon_get_stat_boosts( + env: &WebAssemblyEnv, + pokemon: ExternRef, + ) -> ExternRef> { + let statistic_set = pokemon.value(env).unwrap().stat_boosts(); + ExternRef::new(env.data().as_ref(), statistic_set) + } + + fn pokemon_get_individual_values( + env: &WebAssemblyEnv, + pokemon: ExternRef, + ) -> ExternRef> { + let statistic_set = pokemon.value(env).unwrap().individual_values(); + ExternRef::new(env.data().as_ref(), statistic_set) + } + + fn pokemon_get_effort_values( + env: &WebAssemblyEnv, + pokemon: ExternRef, + ) -> ExternRef> { + let statistic_set = pokemon.value(env).unwrap().effort_values(); + ExternRef::new(env.data().as_ref(), statistic_set) + } + + fn pokemon_get_species( + env: &WebAssemblyEnv, + pokemon: ExternRef, + ) -> ExternRef { + let species = pokemon.value(env).unwrap().species(); + ExternRef::new(env.data().as_ref(), species) + } + + fn pokemon_damage( + env: &WebAssemblyEnv, + pokemon: ExternRef, + damage: u32, + source: u8 + ) { + unsafe{ + pokemon.value(env).unwrap().damage(damage, transmute(source)); + } + } +} diff --git a/src/script_implementations/wasm/export_registry/dynamic_data/turn_choice.rs b/src/script_implementations/wasm/export_registry/dynamic_data/turn_choice.rs new file mode 100644 index 0000000..7bbcd51 --- /dev/null +++ b/src/script_implementations/wasm/export_registry/dynamic_data/turn_choice.rs @@ -0,0 +1,61 @@ +use std::ops::Deref; + +use crate::dynamic_data::{LearnedMove, Pokemon, TurnChoice}; +use crate::script_implementations::wasm::export_registry::register; +use crate::script_implementations::wasm::extern_ref::ExternRef; +use crate::script_implementations::wasm::script_resolver::WebAssemblyEnv; + +register! { + + fn turn_choice_get_user<'a>( + env: &WebAssemblyEnv, + turn_choice: ExternRef>, + ) -> ExternRef> { + let turn_choice = turn_choice.value(env).unwrap(); + ExternRef::new(env.data().as_ref(), turn_choice.user().as_ref().deref()) + } + + fn turn_choice_get_kind( + env: &WebAssemblyEnv, + turn_choice: ExternRef, + ) -> u8 { + match turn_choice.value(env).unwrap() { + TurnChoice::Move(_) => 0, + TurnChoice::Item(_) => 1, + TurnChoice::Switch(_) => 2, + TurnChoice::Flee(_) => 3, + TurnChoice::Pass(_) => 4, + } + } + + fn turn_choice_move_used_move<'a>( + env: &WebAssemblyEnv, + turn_choice: ExternRef>, + ) -> ExternRef> { + if let TurnChoice::Move(d) = turn_choice.value(env).unwrap() { + return ExternRef::new(env.data().as_ref(), d.used_move().as_ref()); + } + panic!("Invalid turn choice"); + } + + fn turn_choice_move_target_side( + env: &WebAssemblyEnv, + turn_choice: ExternRef, + ) -> u8 { + if let TurnChoice::Move(d) = turn_choice.value(env).unwrap() { + return d.target_side(); + } + panic!("Invalid turn choice"); + } + + fn turn_choice_move_target_index( + env: &WebAssemblyEnv, + turn_choice: ExternRef, + ) -> u8 { + if let TurnChoice::Move(d) = turn_choice.value(env).unwrap() { + return d.target_index(); + } + panic!("Invalid turn choice"); + } + +} diff --git a/src/script_implementations/wasm/export_registry/mod.rs b/src/script_implementations/wasm/export_registry/mod.rs index cf4842a..be356cb 100644 --- a/src/script_implementations/wasm/export_registry/mod.rs +++ b/src/script_implementations/wasm/export_registry/mod.rs @@ -1,14 +1,21 @@ use std::ffi::CString; use std::mem::{align_of, forget}; +use std::process::exit; -use wasmer::{Exports, Function, Store}; +use wasmer::{Exports, Store}; + +pub(crate) use register; +pub(crate) use register_func_with_env; 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::static_data::{EffectParameter, StaticData}; use crate::StringKey; +mod dynamic_data; +mod static_data; + #[allow(unused_macros)] macro_rules! register_func { ($exports: ident, $store: ident, $func: ident) => { @@ -20,21 +27,50 @@ 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), + wasmer::Function::new_native_with_env($store, $env.clone(), $func), ); }; } +macro_rules! register { + ( + $( + fn $name:ident$(<$($lt:lifetime)*>)?($($par:ident: $par_type:ty),*$(,)?) $(-> $return:ty)? $block:block + )* + ) => { + pub(crate) fn register(exports: &mut crate::script_implementations::wasm::export_registry::Exports, + store: &crate::script_implementations::wasm::export_registry::Store, + env: crate::script_implementations::wasm::script_resolver::WebAssemblyEnv) { + $( + + fn $name<$($($lt)*)*>( + $( + $par: $par_type, + )* + ) $(-> $return)* $block + crate::script_implementations::wasm::export_registry::register_func_with_env!(exports, store, $name, env); + )* + + } + }; +} + 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); + register_func_with_env!(exports, store, _vec_extern_ref_get_value, env); + + static_data::register(exports, store, env.clone()); + dynamic_data::register(exports, store, env.clone()); + + register_func_with_env!(exports, store, string_key_get_hash, env); + register_func_with_env!(exports, store, string_key_get_str, env); + register_func_with_env!(exports, store, dynamic_library_get_static_data, env); + register_func_with_env!(exports, store, effect_parameter_get_type, env); + register_func_with_env!(exports, store, effect_parameter_as_bool, env); + register_func_with_env!(exports, store, effect_parameter_as_int, env); + register_func_with_env!(exports, store, effect_parameter_as_float, env); + register_func_with_env!(exports, store, effect_parameter_as_string, env); } fn _print(env: &WebAssemblyEnv, p: u32, len: u32) { @@ -46,42 +82,29 @@ fn _print(env: &WebAssemblyEnv, p: u32, len: u32) { } } +#[track_caller] fn _error(env: &WebAssemblyEnv, message: u32, message_len: u32, file: u32, file_len: u32, line: u32, position: u32) { unsafe { let mem: *mut u8 = env.data().memory().data_ptr().offset(message as isize); - let message = String::from_raw_parts(mem, message_len as usize, message_len as usize); + let message_str = String::from_raw_parts(mem, message_len as usize, message_len as usize); let mem: *mut u8 = env.data().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); - forget(message); - forget(file); + let file_str = String::from_raw_parts(mem, file_len as usize, file_len as usize); + panic!( + "Error: {} in file {}, line: {}:{}", + message_str, file_str, line, position + ); } } -fn move_library_get_move_by_hash(env: &WebAssemblyEnv, lib: ExternRef, hash: u32) -> ExternRef { - let lib = lib.value(env).unwrap(); - let m = lib.get_by_hash(hash); - if let Some(v) = m { - ExternRef::new(env.data().as_ref(), v) - } else { - ExternRef::null() - } +fn _vec_extern_ref_get_value(env: &WebAssemblyEnv, reference: u32, index: u32) -> u32 { + env.data().get_extern_vec_ref_extern_ref(reference, index) } -fn move_data_get_name(env: &WebAssemblyEnv, move_data: ExternRef) -> ExternRef { - let move_data = move_data.value(env).unwrap(); - ExternRef::new(env.data().as_ref(), move_data.name()) -} - -fn move_data_get_base_power(env: &WebAssemblyEnv, move_data: ExternRef) -> u8 { - move_data.value(env).unwrap().base_power() -} - -fn const_string_get_hash(env: &WebAssemblyEnv, string_key: ExternRef) -> u32 { +fn string_key_get_hash(env: &WebAssemblyEnv, string_key: ExternRef) -> u32 { string_key.value(env).unwrap().hash() } -fn const_string_get_str(env: &WebAssemblyEnv, string_key: ExternRef) -> u32 { +fn string_key_get_str(env: &WebAssemblyEnv, string_key: ExternRef) -> u32 { let string_key = string_key.value(env).unwrap().str(); let wasm_string_ptr = env .data() @@ -95,13 +118,57 @@ fn const_string_get_str(env: &WebAssemblyEnv, string_key: ExternRef) wasm_string_ptr.1 } -fn battle_library_get_data_library( +fn dynamic_library_get_static_data( env: &WebAssemblyEnv, dynamic_lib: ExternRef, ) -> ExternRef { ExternRef::new(env.data().as_ref(), dynamic_lib.value(env).unwrap().static_data()) } -fn data_library_get_move_library(env: &WebAssemblyEnv, data_library: ExternRef) -> ExternRef { - ExternRef::new(env.data().as_ref(), data_library.value(env).unwrap().moves()) +fn effect_parameter_get_type(env: &WebAssemblyEnv, parameter: ExternRef) -> u8 { + let v = parameter.value(env).unwrap(); + match v { + EffectParameter::Bool(_) => 1, + EffectParameter::Int(_) => 2, + EffectParameter::Float(_) => 3, + EffectParameter::String(_) => 4, + } +} + +fn effect_parameter_as_bool(env: &WebAssemblyEnv, parameter: ExternRef) -> u8 { + let v = parameter.value(env).unwrap(); + match v { + EffectParameter::Bool(b) => { + if *b { + 1 + } else { + 0 + } + } + _ => panic!("Unexpected parameter type!"), + } +} + +fn effect_parameter_as_int(env: &WebAssemblyEnv, parameter: ExternRef) -> i64 { + let v = parameter.value(env).unwrap(); + match v { + EffectParameter::Int(i) => *i, + _ => panic!("Unexpected parameter type!"), + } +} + +fn effect_parameter_as_float(env: &WebAssemblyEnv, parameter: ExternRef) -> f32 { + let v = parameter.value(env).unwrap(); + match v { + EffectParameter::Float(f) => *f, + _ => panic!("Unexpected parameter type!"), + } +} + +fn effect_parameter_as_string(env: &WebAssemblyEnv, parameter: ExternRef) -> ExternRef { + let v = parameter.value(env).unwrap(); + match v { + EffectParameter::String(s) => ExternRef::new(env.data().as_ref(), s), + _ => panic!("Unexpected parameter type!"), + } } diff --git a/src/script_implementations/wasm/export_registry/static_data/mod.rs b/src/script_implementations/wasm/export_registry/static_data/mod.rs new file mode 100644 index 0000000..22d83c3 --- /dev/null +++ b/src/script_implementations/wasm/export_registry/static_data/mod.rs @@ -0,0 +1,52 @@ +use wasmer::{Exports, Store}; + +use crate::defines::LevelInt; +use crate::script_implementations::wasm::export_registry::register_func_with_env; +use crate::script_implementations::wasm::extern_ref::ExternRef; +use crate::script_implementations::wasm::script_resolver::WebAssemblyEnv; +use crate::static_data::{ItemLibrary, LibrarySettings, MoveLibrary, SpeciesLibrary, StaticData, TypeLibrary}; + +mod moves; +mod species; + +pub(crate) fn register(exports: &mut Exports, store: &Store, env: WebAssemblyEnv) { + register_func_with_env!(exports, store, static_data_get_move_library, env); + register_func_with_env!(exports, store, static_data_get_species_library, env); + register_func_with_env!(exports, store, static_data_get_item_library, env); + register_func_with_env!(exports, store, static_data_get_type_library, env); + register_func_with_env!(exports, store, static_data_get_library_settings, env); + register_func_with_env!(exports, store, library_settings_get_maximum_level, env); + + moves::register(exports, store, env.clone()); + species::register(exports, store, env.clone()); +} + +fn static_data_get_move_library(env: &WebAssemblyEnv, data_library: ExternRef) -> ExternRef { + ExternRef::new(env.data().as_ref(), data_library.value(env).unwrap().moves()) +} + +fn static_data_get_species_library( + env: &WebAssemblyEnv, + data_library: ExternRef, +) -> ExternRef { + ExternRef::new(env.data().as_ref(), data_library.value(env).unwrap().species()) +} + +fn static_data_get_item_library(env: &WebAssemblyEnv, data_library: ExternRef) -> ExternRef { + ExternRef::new(env.data().as_ref(), data_library.value(env).unwrap().items()) +} + +fn static_data_get_type_library(env: &WebAssemblyEnv, data_library: ExternRef) -> ExternRef { + ExternRef::new(env.data().as_ref(), data_library.value(env).unwrap().types()) +} + +fn static_data_get_library_settings( + env: &WebAssemblyEnv, + data_library: ExternRef, +) -> ExternRef { + ExternRef::new(env.data().as_ref(), data_library.value(env).unwrap().settings()) +} + +fn library_settings_get_maximum_level(env: &WebAssemblyEnv, data_library: ExternRef) -> LevelInt { + data_library.value(env).unwrap().maximum_level() +} diff --git a/src/script_implementations/wasm/export_registry/static_data/moves.rs b/src/script_implementations/wasm/export_registry/static_data/moves.rs new file mode 100644 index 0000000..5e3a34e --- /dev/null +++ b/src/script_implementations/wasm/export_registry/static_data/moves.rs @@ -0,0 +1,71 @@ +use crate::script_implementations::wasm::export_registry::register; +use crate::script_implementations::wasm::extern_ref::ExternRef; +use crate::script_implementations::wasm::script_resolver::WebAssemblyEnv; +use crate::static_data::{DataLibrary, MoveData, MoveLibrary}; +use crate::StringKey; + +register! { +fn move_library_get_move( + env: &WebAssemblyEnv, + lib: ExternRef, + string_key: ExternRef, +) -> ExternRef { + let lib = lib.value(env).unwrap(); + let m = lib.get(string_key.value(env).unwrap()); + if let Some(v) = m { + ExternRef::new(env.data().as_ref(), v) + } else { + ExternRef::null() + } +} + +fn move_library_get_move_by_hash(env: &WebAssemblyEnv, lib: ExternRef, hash: u32) -> ExternRef { + let lib = lib.value(env).unwrap(); + let m = lib.get_by_hash(hash); + if let Some(v) = m { + ExternRef::new(env.data().as_ref(), v) + } else { + ExternRef::null() + } +} + +fn move_data_get_name(env: &WebAssemblyEnv, move_data: ExternRef) -> ExternRef { + ExternRef::new(env.data().as_ref(), move_data.value(env).unwrap().name()) +} + +fn move_data_get_type(env: &WebAssemblyEnv, move_data: ExternRef) -> u8 { + move_data.value(env).unwrap().move_type().into() +} +fn move_data_get_category(env: &WebAssemblyEnv, move_data: ExternRef) -> u8 { + move_data.value(env).unwrap().category() as u8 +} +fn move_data_get_base_power(env: &WebAssemblyEnv, move_data: ExternRef) -> u8 { + move_data.value(env).unwrap().base_power() +} +fn move_data_get_accuracy(env: &WebAssemblyEnv, move_data: ExternRef) -> u8 { + move_data.value(env).unwrap().accuracy() +} +fn move_data_get_base_usages(env: &WebAssemblyEnv, move_data: ExternRef) -> u8 { + move_data.value(env).unwrap().base_usages() +} +fn move_data_get_target(env: &WebAssemblyEnv, move_data: ExternRef) -> u8 { + move_data.value(env).unwrap().target() as u8 +} +fn move_data_get_priority(env: &WebAssemblyEnv, move_data: ExternRef) -> i8 { + move_data.value(env).unwrap().priority() +} +fn move_data_has_flag(env: &WebAssemblyEnv, move_data: ExternRef, flag: ExternRef) -> u8 { + if move_data.value(env).unwrap().has_flag(flag.value(env).unwrap()) { + 1 + } else { + 0 + } +} +fn move_data_has_flag_by_hash(env: &WebAssemblyEnv, move_data: ExternRef, flag_hash: u32) -> u8 { + if move_data.value(env).unwrap().has_flag_by_hash(flag_hash) { + 1 + } else { + 0 + } +} +} diff --git a/src/script_implementations/wasm/export_registry/static_data/species.rs b/src/script_implementations/wasm/export_registry/static_data/species.rs new file mode 100644 index 0000000..253b36f --- /dev/null +++ b/src/script_implementations/wasm/export_registry/static_data/species.rs @@ -0,0 +1,61 @@ +use crate::script_implementations::wasm::export_registry::register; +use crate::script_implementations::wasm::extern_ref::ExternRef; +use crate::script_implementations::wasm::script_resolver::WebAssemblyEnv; +use crate::static_data::{DataLibrary, Species, SpeciesLibrary}; +use crate::StringKey; + +register! { + +fn species_library_get_species( + env: &WebAssemblyEnv, + lib: ExternRef, + string_key: ExternRef, +) -> ExternRef { + let lib = lib.value(env).unwrap(); + let m = lib.get(string_key.value(env).unwrap()); + if let Some(v) = m { + ExternRef::new(env.data().as_ref(), v) + } else { + ExternRef::null() + } +} + +fn species_get_capture_rate( + env: &WebAssemblyEnv, + species: ExternRef, +) -> u8 { + species.value(env).unwrap().capture_rate() +} + +fn species_get_growth_rate( + env: &WebAssemblyEnv, + species: ExternRef, +) -> ExternRef { + let species = species.value(env).unwrap(); + ExternRef::new(env.data().as_ref(), species.growth_rate()) +} + +fn species_get_gender_rate( + env: &WebAssemblyEnv, + species: ExternRef, +) -> f32 { + species.value(env).unwrap().gender_rate() +} + +fn species_get_name( + env: &WebAssemblyEnv, + species: ExternRef, +) -> ExternRef { + let species = species.value(env).unwrap(); + ExternRef::new(env.data().as_ref(), species.name()) +} + +fn species_get_id( + env: &WebAssemblyEnv, + species: ExternRef, +) -> u16 { + species.value(env).unwrap().id() +} + + +} diff --git a/src/script_implementations/wasm/extern_ref.rs b/src/script_implementations/wasm/extern_ref.rs index e4a3197..a6883b6 100644 --- a/src/script_implementations/wasm/extern_ref.rs +++ b/src/script_implementations/wasm/extern_ref.rs @@ -1,4 +1,5 @@ use std::marker::PhantomData; +use std::mem::transmute; use unique_type_id::UniqueTypeId; use wasmer::FromToNativeWasmType; @@ -7,7 +8,7 @@ use crate::script_implementations::wasm::script_resolver::{ WebAssemblyEnv, WebAssemblyEnvironmentData, WebAssemblyScriptResolver, }; -pub(crate) struct ExternRef + ?Sized> { +pub(crate) struct ExternRef> { index: u32, _phantom: PhantomData, } @@ -20,6 +21,10 @@ impl> ExternRef { } } + pub(crate) fn index(&self) -> u32 { + self.index + } + /// Creates an ExternRef with a given resolver. This can be used in cases where we do not have an environment variable. pub(crate) fn new_with_resolver(resolver: &WebAssemblyScriptResolver, value: &T) -> Self { Self { @@ -58,3 +63,37 @@ unsafe impl> FromToNativeWasmType for ExternRef { self.index as i32 } } + +pub(crate) struct VecExternRef { + index: u32, + size: u32, + _phantom: PhantomData, +} + +impl> VecExternRef { + pub fn new(env: &WebAssemblyEnvironmentData, value: &[T]) -> Self { + Self { + index: env.get_extern_vec_ref_index(value), + size: value.len() as u32, + _phantom: Default::default(), + } + } +} + +unsafe impl> FromToNativeWasmType for VecExternRef { + type Native = i64; + + fn from_native(native: Self::Native) -> Self { + let split: (u32, u32) = unsafe { transmute(native) }; + Self { + index: split.0, + size: split.1, + _phantom: Default::default(), + } + } + + fn to_native(self) -> Self::Native { + let v: i64 = unsafe { transmute((self.index, self.size)) }; + v + } +} diff --git a/src/script_implementations/wasm/mod.rs b/src/script_implementations/wasm/mod.rs index 2ce7f35..dff4e0d 100644 --- a/src/script_implementations/wasm/mod.rs +++ b/src/script_implementations/wasm/mod.rs @@ -5,6 +5,8 @@ pub(crate) mod extern_ref; pub mod script; /// The script resolver deals with the loading of scripts. pub mod script_resolver; +mod temp_wasm_allocator; +mod script_function_cache; /// The WebAssemblyScriptCapabilities define which functions are implemented on a script. This allows /// us to not call a function if we do not need to. diff --git a/src/script_implementations/wasm/script.rs b/src/script_implementations/wasm/script.rs index 7ff5b65..a0f53cb 100644 --- a/src/script_implementations/wasm/script.rs +++ b/src/script_implementations/wasm/script.rs @@ -1,12 +1,13 @@ use std::any::Any; +use std::mem::{align_of, size_of}; use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize}; -use std::sync::Weak; +use std::sync::{Arc, Weak}; use hashbrown::HashSet; use wasmer::NativeFunc; -use crate::dynamic_data::{DynamicLibrary, Script}; -use crate::script_implementations::wasm::extern_ref::ExternRef; +use crate::dynamic_data::{DynamicLibrary, Pokemon, Script, TurnChoice}; +use crate::script_implementations::wasm::extern_ref::{ExternRef, VecExternRef}; use crate::script_implementations::wasm::script_resolver::WebAssemblyEnvironmentData; use crate::script_implementations::wasm::WebAssemblyScriptCapabilities; use crate::static_data::EffectParameter; @@ -24,11 +25,11 @@ pub struct WebAssemblyScript { /// 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, + _owner_ptr: AtomicPtr, /// Pointer inside WebAssembly memory where the data is for this script. self_ptr: u32, /// Capabilities define which functions we actually implement. - capabilities: AtomicPtr>, + capabilities: Arc>, /// The global runtime environment data. environment: Weak, } @@ -38,7 +39,7 @@ impl WebAssemblyScript { pub fn new( owner_ptr: *mut u8, self_ptr: u32, - capabilities: *mut HashSet, + capabilities: Arc>, environment: Weak, name: StringKey, ) -> Self { @@ -46,9 +47,9 @@ impl WebAssemblyScript { name, marked_for_deletion: Default::default(), suppressed_count: Default::default(), - owner_ptr: AtomicPtr::new(owner_ptr), + _owner_ptr: AtomicPtr::new(owner_ptr), self_ptr, - capabilities: AtomicPtr::new(capabilities), + capabilities, environment, } } @@ -67,20 +68,56 @@ impl Script for WebAssemblyScript { &self.suppressed_count } - fn on_initialize(&self, library: &DynamicLibrary, _pars: &[EffectParameter]) { + fn on_initialize(&self, library: &DynamicLibrary, pars: &[EffectParameter]) { + if !self.capabilities.contains(&WebAssemblyScriptCapabilities::Initialize) { + return; + } + + let env = self.environment.upgrade().unwrap(); + let func = env.script_function_cache().on_initialize(&env); + if let Some(func) = func { + func.call( + self.self_ptr, + ExternRef::new(env.as_ref(), library), + VecExternRef::new(env.as_ref(), pars), + ) + .unwrap(); + } + } + + fn on_before_turn(&self, choice: &TurnChoice) { + if !self.capabilities.contains(&WebAssemblyScriptCapabilities::OnBeforeTurn) { + return; + } + let env = self.environment.upgrade().unwrap(); + let func = env.script_function_cache().on_before_turn(&env); + if let Some(func) = func { + func.call(self.self_ptr, ExternRef::new(env.as_ref(), choice).index()) + .unwrap(); + } + } + + fn change_speed(&self, choice: &TurnChoice, speed: &mut u32) { + if !self.capabilities.contains(&WebAssemblyScriptCapabilities::ChangeSpeed) { + return; + } + let env = self.environment.upgrade().unwrap(); let exported = env.exported_functions(); - if let Some(f) = exported.get(&"script_on_initialize".into()) { - let func: NativeFunc<(u32, ExternRef, u32), ()> = f.native().unwrap(); - func.call(self.self_ptr, ExternRef::new(env.as_ref(), library), 0) + if let Some(f) = exported.get::(&"script_change_speed".into()) { + let func: NativeFunc<(u32, ExternRef, u32), ()> = f.native().unwrap(); + let ptr = env.temp_allocate_mem_typed::(); + func.call(self.self_ptr, ExternRef::new(env.as_ref(), choice), ptr.wasm_pointer) .unwrap(); + unsafe { + *speed = *ptr.ptr; + } } } fn as_any(&self) -> &dyn Any { self } - fn as_any_mut(&mut self) -> &mut dyn Any { self } diff --git a/src/script_implementations/wasm/script_function_cache.rs b/src/script_implementations/wasm/script_function_cache.rs new file mode 100644 index 0000000..bc845d3 --- /dev/null +++ b/src/script_implementations/wasm/script_function_cache.rs @@ -0,0 +1,62 @@ +use std::sync::Arc; + +use parking_lot::RwLock; +use paste::paste; +use wasmer::NativeFunc; + +use crate::dynamic_data::{DynamicLibrary, TurnChoice}; +use crate::script_implementations::wasm::extern_ref::{ExternRef, VecExternRef}; +use crate::script_implementations::wasm::script_resolver::WebAssemblyEnvironmentData; +use crate::static_data::EffectParameter; +use crate::StringKey; + +macro_rules! script_function_cache { + ( + $( + $name:ident -> $return:ty + )* + ) => { + #[derive(Default)] + pub struct ScriptFunctionCache { + $( + $name: RwLock>, + )* + } + + impl ScriptFunctionCache { + $( + paste! { + #[cold] + fn [](&self, env: &Arc) { + let exported = env.exported_functions(); + let f = exported.get::(&stringify!([< script_ $name >]).into()); + if let Some(f) = f { + let func: $return = f.native().unwrap(); + let _ = self.$name.write().insert(func); + } + } + + + pub(crate) fn [<$name>]( + &self, + env: &Arc, + ) -> Option<$return> { + { + let read_lock = self.$name.read(); + if let Some(f) = read_lock.as_ref() { + return Some(f.clone()); + } + } + self.[](env); + self.$name.read().as_ref().cloned() + } + } + )* + } + } +} + +script_function_cache! { + on_initialize -> NativeFunc<(u32, ExternRef, VecExternRef), ()> + on_before_turn -> NativeFunc<(u32, u32), ()> +} diff --git a/src/script_implementations/wasm/script_resolver.rs b/src/script_implementations/wasm/script_resolver.rs index 917b4f3..c373169 100644 --- a/src/script_implementations/wasm/script_resolver.rs +++ b/src/script_implementations/wasm/script_resolver.rs @@ -1,4 +1,5 @@ use std::fmt::{Debug, Formatter}; +use std::mem::{align_of, size_of}; use std::sync::{Arc, Weak}; use hashbrown::{HashMap, HashSet}; @@ -14,6 +15,8 @@ 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::script_function_cache::ScriptFunctionCache; +use crate::script_implementations::wasm::temp_wasm_allocator::{AllocatedObject, TempWasmAllocator}; use crate::script_implementations::wasm::WebAssemblyScriptCapabilities; use crate::static_data::Item; use crate::{PkmnResult, ScriptCategory, StringKey}; @@ -33,7 +36,7 @@ pub struct WebAssemblyScriptResolver { /// Script capabilities tell us which functions are implemented on a given script. This allows us to skip unneeded /// WASM calls. - script_capabilities: RwLock>>, + script_capabilities: RwLock>>>, environment_data: Arc, } @@ -85,8 +88,26 @@ impl WebAssemblyScriptResolver { imports.register("env", exports); for module in &self.modules { + for import in module.imports() { + if imports.get_export("env", import.name()).is_none() { + println!( + "\x1b[91mMissing import: \"{}\" with type: {:?} \x1b[0m", + import.name(), + import.ty() + ); + } + } + let instance = Instance::new(module, &imports).unwrap(); let exports = &instance.exports; + + let init_fn = exports.get_extern("_init"); + if let Some(init_fn) = init_fn { + if let Extern::Function(init_fn) = init_fn { + init_fn.call(&[]).unwrap(); + } + } + let mut exported_functions = self.environment_data.exported_functions.write(); for export in exports.iter() { match export.1 { @@ -102,15 +123,22 @@ impl WebAssemblyScriptResolver { if let Some(m) = &self.environment_data.memory.read().as_ref() { m.grow(32).unwrap(); } - if let Some(f) = exported_functions.get(&"load_script".into()) { + if let Some(f) = exported_functions.get::(&"load_script".into()) { self.load_script_fn = Some(f.native().unwrap()) } - if let Some(f) = exported_functions.get(&"allocate_mem".into()) { + if let Some(f) = exported_functions.get::(&"allocate_mem".into()) { let _ = self .environment_data .allocate_mem_fn .write() .insert(f.native().unwrap()); + + let temp_memory_slab = self.environment_data.allocate_mem(128, 1); + let _ = self + .environment_data + .temp_allocator + .write() + .insert(TempWasmAllocator::new(temp_memory_slab.0, temp_memory_slab.1)); } self.instances.push(instance); } @@ -151,7 +179,7 @@ impl ScriptResolver for WebAssemblyScriptResolver { .environment_data .exported_functions .read() - .get(&"get_script_capabilities".into()) + .get::(&"get_script_capabilities".into()) { let res = get_cap.call(&[Value::I32(script as i32)]).unwrap(); let ptr = (self.environment_data.memory.read().as_ref().unwrap().data_ptr() @@ -163,7 +191,9 @@ impl ScriptResolver for WebAssemblyScriptResolver { } } } - self.script_capabilities.write().insert(key.clone(), capabilities); + self.script_capabilities + .write() + .insert(key.clone(), Arc::new(capabilities)); } let read_guard = self.script_capabilities.read(); @@ -172,8 +202,7 @@ impl ScriptResolver for WebAssemblyScriptResolver { Ok(Some(Arc::new(WebAssemblyScript::new( owner as *mut u8, script, - capabilities as *const HashSet - as *mut HashSet, + capabilities.clone(), Arc::downgrade(&self.environment_data), script_key.clone(), )))) @@ -207,19 +236,28 @@ pub struct WebAssemblyEnvironmentData { /// allow for modifying memory we might not want to. If we get a type mismatch, we will panic, preventing this. extern_ref_type_lookup: RwLock>, + /// Additional security for data slices passed to WASM. + extern_vec_ref_lookup: RwLock>>, + /// The memory inside of the WASM container. memory: RwLock>, /// This is a map of all the functions that WASM gives us. exported_functions: RwLock>, + script_function_cache: ScriptFunctionCache, + /// This is the WASM function to allocate memory inside the WASM container. allocate_mem_fn: RwLock>>, + + /// An allocator for quick short lifetime allocations within WASM. + temp_allocator: RwLock>, } #[derive(Clone, Eq, PartialEq, Hash)] struct ExternRefLookupKey { pub ptr: *const u8, + pub is_vec: bool, pub t: u64, } @@ -234,6 +272,11 @@ impl WebAssemblyEnvironmentData { self.exported_functions.read() } + /// + pub fn script_function_cache(&self) -> &ScriptFunctionCache { + &self.script_function_cache + } + /// 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), @@ -253,19 +296,79 @@ impl WebAssemblyEnvironmentData { } } + /// 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_typed(&self) -> (*mut u8, u32) { + let wasm_ptr = self + .allocate_mem_fn + .read() + .as_ref() + .unwrap() + .call(size_of::() as u32, align_of::() as u32) + .unwrap(); + unsafe { + ( + self.memory + .read() + .as_ref() + .unwrap() + .data_ptr() + .offset(wasm_ptr as isize), + wasm_ptr, + ) + } + } + + /// Allocate a piece of memory inside WASM with a very short lifespan. This is mainly used for + /// rapid allocation of script function parameters, where WASM needs to write to a specific + /// pointer. + pub fn temp_allocate_mem_typed(&self) -> AllocatedObject { + self.temp_allocator.read().as_ref().unwrap().alloc::() + } + /// 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>(&self, value: &T) -> u32 { - let ptr = value as *const T as *const u8; - if let Some(v) = self - .extern_ref_pointers_lookup - .read() - .get(&ExternRefLookupKey { ptr, t: T::id().0 }) - { + pub fn get_extern_ref_index + ?Sized>(&self, value: &T) -> u32 { + self.get_extern_ref_from_ptr(value as *const T as *const u8, T::id().0, false) + } + + /// 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_vec_ref_index>(&self, value: &[T]) -> u32 { + let mut vec = Vec::with_capacity(value.len()); + for v in value { + vec.push(self.get_extern_ref_index(v)); + } + let p = self.get_extern_ref_from_ptr(value as *const [T] as *const u8, T::id().0, true); + self.extern_vec_ref_lookup.write().insert(p, vec); + p + } + + /// Get an extern ref belonging to a vector we have passed to WASM. + pub fn get_extern_vec_ref_extern_ref(&self, extern_vec_ref: u32, index: u32) -> u32 { + let r = self.extern_vec_ref_lookup.read(); + let v = r.get(&extern_vec_ref).unwrap(); + v[index as usize] + } + + /// Gets the extern ref index belonging to a specific pointer. If none exists, this will create + /// a new one. + fn get_extern_ref_from_ptr(&self, ptr: *const u8, type_id: u64, is_vec: bool) -> u32 { + if let Some(v) = self.extern_ref_pointers_lookup.read().get(&ExternRefLookupKey { + ptr, + is_vec, + t: type_id, + }) { return *v as u32; } let index = { @@ -273,12 +376,19 @@ impl WebAssemblyEnvironmentData { extern_ref_guard.push(ptr); extern_ref_guard.len() as u32 }; - self.extern_ref_pointers_lookup - .write() - .insert(ExternRefLookupKey { ptr, t: T::id().0 }, index); - self.extern_ref_type_lookup - .write() - .insert(ExternRefLookupKey { ptr, t: T::id().0 }); + self.extern_ref_pointers_lookup.write().insert( + ExternRefLookupKey { + ptr, + is_vec, + t: type_id, + }, + index, + ); + self.extern_ref_type_lookup.write().insert(ExternRefLookupKey { + ptr, + is_vec, + t: type_id, + }); index } @@ -292,6 +402,7 @@ impl WebAssemblyEnvironmentData { .read() .get(&ExternRefLookupKey { ptr: *ptr, + is_vec: false, t: T::id().0, }) .is_none() diff --git a/src/script_implementations/wasm/temp_wasm_allocator.rs b/src/script_implementations/wasm/temp_wasm_allocator.rs new file mode 100644 index 0000000..f72a554 --- /dev/null +++ b/src/script_implementations/wasm/temp_wasm_allocator.rs @@ -0,0 +1,53 @@ +use std::mem::size_of; +use std::sync::atomic::{AtomicUsize, Ordering}; + +pub(super) struct TempWasmAllocator { + data: *mut u8, + wasm_pointer: u32, + currently_allocated: AtomicUsize, + offset_high: AtomicUsize, +} + +impl TempWasmAllocator { + pub(super) fn new(data: *mut u8, wasm_pointer: u32) -> Self { + Self { + data, + wasm_pointer, + currently_allocated: AtomicUsize::new(0), + offset_high: AtomicUsize::new(0), + } + } + + pub fn alloc(&self) -> AllocatedObject { + self.currently_allocated.fetch_add(size_of::(), Ordering::SeqCst); + let ptr_offset = self.offset_high.fetch_add(size_of::(), Ordering::SeqCst); + let ptr = unsafe { self.data.add(ptr_offset) } as *mut T; + AllocatedObject:: { + ptr, + wasm_pointer: self.wasm_pointer + ptr_offset as u32, + allocator: self as *const TempWasmAllocator, + } + } + + pub fn drop(&self) { + self.currently_allocated.fetch_sub(size_of::(), Ordering::SeqCst); + // As soon as we've no longer allocated anything, we reset our allocating back to the start. + if self.currently_allocated.load(Ordering::SeqCst) == 0 { + self.offset_high.store(0, Ordering::SeqCst); + } + } +} + +pub struct AllocatedObject { + pub ptr: *mut T, + pub wasm_pointer: u32, + allocator: *const TempWasmAllocator, +} + +impl Drop for AllocatedObject { + fn drop(&mut self) { + unsafe { + self.allocator.as_ref().unwrap().drop::(); + } + } +} diff --git a/src/static_data/libraries/item_library.rs b/src/static_data/libraries/item_library.rs index adbe946..6cfc66e 100644 --- a/src/static_data/libraries/item_library.rs +++ b/src/static_data/libraries/item_library.rs @@ -6,6 +6,7 @@ use crate::StringKey; /// A library to store all items. #[derive(Debug)] +#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))] pub struct ItemLibrary { /// The underlying data structure. map: IndexMap>, diff --git a/src/static_data/libraries/library_settings.rs b/src/static_data/libraries/library_settings.rs index 9565bb7..ade746b 100644 --- a/src/static_data/libraries/library_settings.rs +++ b/src/static_data/libraries/library_settings.rs @@ -2,6 +2,7 @@ use crate::defines::LevelInt; /// This library holds several misc settings for the library. #[derive(Debug)] +#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))] pub struct LibrarySettings { /// The highest level a Pokemon can be. maximum_level: LevelInt, diff --git a/src/static_data/libraries/species_library.rs b/src/static_data/libraries/species_library.rs index 514918c..b95822f 100644 --- a/src/static_data/libraries/species_library.rs +++ b/src/static_data/libraries/species_library.rs @@ -6,6 +6,7 @@ use crate::StringKey; /// A library to store all data for Pokemon species. #[derive(Debug)] +#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))] pub struct SpeciesLibrary { /// The underlying map. map: IndexMap>, diff --git a/src/static_data/libraries/type_library.rs b/src/static_data/libraries/type_library.rs index 731599c..c6c7af9 100644 --- a/src/static_data/libraries/type_library.rs +++ b/src/static_data/libraries/type_library.rs @@ -17,8 +17,15 @@ impl From for TypeIdentifier { } } +impl Into for TypeIdentifier { + fn into(self) -> u8 { + self.val + } +} + /// All data related to types and effectiveness. #[derive(Debug)] +#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))] pub struct TypeLibrary { /// A list of types types: HashMap, diff --git a/src/static_data/moves/move_data.rs b/src/static_data/moves/move_data.rs index 3612f11..2061935 100644 --- a/src/static_data/moves/move_data.rs +++ b/src/static_data/moves/move_data.rs @@ -153,6 +153,11 @@ impl MoveData { /// Arbitrary flags that can be applied to the move. pub fn has_flag(&self, key: &StringKey) -> bool { - self.flags.contains(key) + self.flags.contains::(key) + } + + /// Arbitrary flags that can be applied to the move. + pub fn has_flag_by_hash(&self, key_hash: u32) -> bool { + self.flags.contains::(&key_hash) } } diff --git a/src/static_data/moves/secondary_effect.rs b/src/static_data/moves/secondary_effect.rs index 36e41ed..e7010b5 100644 --- a/src/static_data/moves/secondary_effect.rs +++ b/src/static_data/moves/secondary_effect.rs @@ -3,6 +3,7 @@ use crate::StringKey; /// A parameter for an effect. This is basically a simple way to dynamically store multiple different /// primitives on data. #[derive(PartialEq, Debug)] +#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))] pub enum EffectParameter { /// A boolean value. Bool(bool), @@ -11,7 +12,7 @@ pub enum EffectParameter { /// A float value. Stored as a 32 bit float. Float(f32), /// A string value. - String(String), + String(StringKey), } /// A secondary effect is an effect on a move that happens after it hits. diff --git a/src/static_data/species_data/ability.rs b/src/static_data/species_data/ability.rs index dbe54b8..5110427 100644 --- a/src/static_data/species_data/ability.rs +++ b/src/static_data/species_data/ability.rs @@ -39,6 +39,7 @@ impl Ability { /// An ability index allows us to find an ability on a form. It combines a bool for whether the /// ability is hidden or not, and then an index of the ability. #[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(C)] pub struct AbilityIndex { /// Whether or not the ability we're referring to is a hidden ability. pub hidden: bool, diff --git a/src/static_data/species_data/species.rs b/src/static_data/species_data/species.rs index 38d94bb..5a767bf 100644 --- a/src/static_data/species_data/species.rs +++ b/src/static_data/species_data/species.rs @@ -7,6 +7,7 @@ use crate::StringKey; /// The data belonging to a Pokemon with certain characteristics. #[derive(Debug)] +#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))] pub struct Species { /// The national dex identifier of the Pokemon. id: u16, diff --git a/src/static_data/statistic_set.rs b/src/static_data/statistic_set.rs index 9356059..522f60e 100644 --- a/src/static_data/statistic_set.rs +++ b/src/static_data/statistic_set.rs @@ -11,6 +11,7 @@ use super::statistics::Statistic; /// /// As all data in this type is atomic, threaded access to this struct is completely legal. #[derive(Default, Debug)] +#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))] pub struct StatisticSet where T: PrimitiveAtom, @@ -206,6 +207,7 @@ where /// A clamped statistic set holds the 6 normal stats for a Pokemon, but ensures it always remains /// between two values (inclusive on the two values). #[derive(Default, Debug)] +#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))] pub struct ClampedStatisticSet where T: PrimitiveAtom, diff --git a/src/static_data/statistics.rs b/src/static_data/statistics.rs index c0ce8e3..8dd071f 100644 --- a/src/static_data/statistics.rs +++ b/src/static_data/statistics.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; /// Stats are numerical values on Pokemon that are used in battle. #[derive(Debug, PartialEq, Eq, Copy, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] pub enum Statistic { /// Health Points determine how much damage a Pokemon can receive before fainting. HP, diff --git a/src/utils/string_key.rs b/src/utils/string_key.rs index b8437ab..0949bb0 100644 --- a/src/utils/string_key.rs +++ b/src/utils/string_key.rs @@ -1,3 +1,4 @@ +use std::borrow::Borrow; use std::fmt::{Display, Formatter}; use std::hash::{Hash, Hasher}; use std::ops::Deref; @@ -5,7 +6,6 @@ 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 @@ -103,9 +103,9 @@ impl From<&str> for StringKey { } } -impl Equivalent for u32 { - fn equivalent(&self, key: &StringKey) -> bool { - *self == key.hash +impl Borrow for StringKey { + fn borrow(&self) -> &u32 { + &self.hash } } diff --git a/tests/common/library_loader.rs b/tests/common/library_loader.rs index f28577f..c1d72c0 100644 --- a/tests/common/library_loader.rs +++ b/tests/common/library_loader.rs @@ -276,7 +276,7 @@ pub fn load_species(path: &String, library: &mut StaticData) { } fn load_wasm(path: &String, library: &mut WebAssemblyScriptResolver) { - let file = File::open(path.to_string() + "gen7_scripts_rs.wasm").unwrap(); + let file = File::open(path.to_string() + "gen7_scripts.wasm").unwrap(); let mut reader = BufReader::new(file); let mut buffer = Vec::new(); reader.read_to_end(&mut buffer).unwrap(); @@ -392,7 +392,7 @@ fn parse_effect_parameter(value: &Value) -> EffectParameter { EffectParameter::Int(n.as_i64().unwrap()) } } - Value::String(s) => EffectParameter::String(s.clone()), + Value::String(s) => EffectParameter::String(StringKey::new(s.as_str())), Value::Array(_) => { panic!("Unexpected type") } diff --git a/tests/data/gen7_scripts.wasm b/tests/data/gen7_scripts.wasm new file mode 100755 index 0000000..69d83d4 Binary files /dev/null and b/tests/data/gen7_scripts.wasm differ diff --git a/tests/data/gen7_scripts_rs.wasm b/tests/data/gen7_scripts_rs.wasm deleted file mode 100755 index 5884598..0000000 Binary files a/tests/data/gen7_scripts_rs.wasm and /dev/null differ diff --git a/tests/main.rs b/tests/main.rs index 917845c..69dbe69 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -5,10 +5,12 @@ use std::fs::File; use std::io::Read; use std::path::Path; +use std::sync::Arc; use conquer_once::OnceCell; -use pkmn_lib::dynamic_data::{DynamicLibrary, ScriptCategory}; +use pkmn_lib::dynamic_data::{DynamicLibrary, MoveChoice, PokemonBuilder, ScriptCategory, TurnChoice}; +use pkmn_lib::static_data::EffectParameter; use crate::common::{library_loader, TestCase}; @@ -51,10 +53,47 @@ fn integration_tests(input: &Path) { #[test] #[cfg_attr(miri, ignore)] fn validate_script() { - let lib = library_loader::load_library(); + let lib = get_library(); let script = lib .load_script(0 as *const u8, ScriptCategory::Move, &"test".into()) .unwrap() .unwrap(); - script.on_initialize(&lib, &[]); + let parameters = [EffectParameter::String("foo".into())]; + script.on_initialize(&lib, ¶meters); + script.on_initialize(&lib, ¶meters); + script.on_initialize(&lib, ¶meters); +} + +#[test] +#[cfg_attr(miri, ignore)] +fn validate_script_2() { + let lib = get_library(); + let script = lib + .load_script(0 as *const u8, ScriptCategory::Move, &"test".into()) + .unwrap() + .unwrap(); + let user = Arc::new( + PokemonBuilder::new(&lib, "charizard".into(), 100) + .learn_move("fire_blast".into()) + .build(), + ); + script.on_before_turn(&TurnChoice::Move(MoveChoice::new( + user.clone(), + user.learned_moves().read().get(0).unwrap().as_ref().unwrap().clone(), + 0, + 0, + ))); + assert_eq!(user.current_health(), user.max_health() - 50); + + let mut speed: u32 = 100; + script.change_speed( + &TurnChoice::Move(MoveChoice::new( + user.clone(), + user.learned_moves().read().get(0).unwrap().as_ref().unwrap().clone(), + 0, + 0, + )), + &mut speed, + ); + assert_eq!(speed, 684); }