From feffb5f030b527922f9971043e7fc6c2de330374 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Sat, 15 Apr 2023 14:34:42 +0200 Subject: [PATCH] Support for new error handling. --- Cargo.toml | 2 + src/dynamic_data/flow/choice_queue.rs | 90 ++++++++++--------- src/dynamic_data/flow/target_resolver.rs | 35 ++++---- src/dynamic_data/flow/turn_runner.rs | 52 +++++++---- src/dynamic_data/libraries/damage_library.rs | 34 ++++--- src/dynamic_data/libraries/dynamic_library.rs | 15 ++-- src/dynamic_data/libraries/script_resolver.rs | 11 +-- src/dynamic_data/models/battle.rs | 79 +++++++++------- src/dynamic_data/models/battle_random.rs | 38 +++++--- src/dynamic_data/models/battle_side.rs | 9 +- src/dynamic_data/models/executing_move.rs | 12 +-- src/dynamic_data/models/pokemon.rs | 30 ++++--- src/dynamic_data/models/pokemon_builder.rs | 7 +- .../script_handling/script_set.rs | 7 +- .../script_handling/volatile_scripts_owner.rs | 9 +- src/ffi/dynamic_data/models/battle.rs | 18 ++-- src/ffi/dynamic_data/models/pokemon.rs | 17 ++-- src/ffi/mod.rs | 64 +++++++++++++ src/ffi/static_data/growth_rate.rs | 9 +- .../libraries/growth_rate_library.rs | 5 +- .../static_data/libraries/library_settings.rs | 14 ++- src/lib.rs | 42 +-------- .../export_registry/dynamic_data/battle.rs | 2 +- .../dynamic_data/battle_random.rs | 6 +- .../dynamic_data/choice_queue.rs | 2 +- .../export_registry/dynamic_data/pokemon.rs | 2 +- src/script_implementations/wasm/mod.rs | 3 + .../wasm/script_resolver.rs | 26 +++--- src/static_data/growth_rates.rs | 13 +-- .../libraries/growth_rate_library.rs | 11 +-- src/static_data/libraries/library_settings.rs | 12 +-- src/static_data/libraries/nature_library.rs | 14 +++ src/static_data/libraries/static_data.rs | 2 +- tests/common/library_loader.rs | 2 +- tests/common/test_case.rs | 2 +- tests/integration.rs | 44 ++++++++- 36 files changed, 466 insertions(+), 274 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d4e3d48..19f310b 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,8 @@ uuid = "1.2.2" paste = { version = "1.0.8" } arcstr = { version = "1.1.4", features = ["std"] } enum-display-derive = "0.1.1" +anyhow = "1.0.69" +anyhow_ext = "0.2.1" [dev-dependencies] csv = "1.1.6" diff --git a/src/dynamic_data/flow/choice_queue.rs b/src/dynamic_data/flow/choice_queue.rs index a15f749..90d1b4a 100755 --- a/src/dynamic_data/flow/choice_queue.rs +++ b/src/dynamic_data/flow/choice_queue.rs @@ -2,6 +2,8 @@ use crate::dynamic_data::choices::TurnChoice; use crate::dynamic_data::script_handling::ScriptSource; use crate::dynamic_data::Pokemon; use crate::{script_hook, ValueIdentifiable, ValueIdentifier}; +use anyhow::Result; +use anyhow_ext::anyhow; use parking_lot::lock_api::MappedRwLockReadGuard; use parking_lot::{RawRwLock, RwLock, RwLockReadGuard}; @@ -48,12 +50,16 @@ impl ChoiceQueue { } /// This reads what the next choice to execute will be, without modifying state. - pub fn peek(&self) -> Option> { + pub fn peek(&self) -> Result>> { let read_lock = self.queue.read(); if self.current >= read_lock.len() { - None + Ok(None) } else { - Some(RwLockReadGuard::map(read_lock, |a| a[self.current].as_ref().unwrap())) + let v = RwLockReadGuard::try_map(read_lock, |a| a[self.current].as_ref()); + match v { + Ok(v) => Ok(Some(v)), + Err(_) => Err(anyhow!("Could not map choice")), + } } } @@ -81,7 +87,7 @@ impl ChoiceQueue { } /// This moves the choice of a specific Pokemon up to the next choice to be executed. - pub fn move_pokemon_choice_next(&self, pokemon: &Pokemon) -> bool { + pub fn move_pokemon_choice_next(&self, pokemon: &Pokemon) -> Result { let mut queue_lock = self.queue.write(); let mut desired_index = None; // Find the index for the choice we want to move up. @@ -93,26 +99,29 @@ impl ChoiceQueue { } } } - // If we couldn't find a choice, we can't execute, return. - if desired_index.is_none() { - return false; - } - let desired_index = desired_index.unwrap(); - // If the choice we want to move up is already the next choice, just return. - if desired_index == self.current { - return true; - } + let result = match desired_index { + Some(desired_index) => { + // If the choice we want to move up is already the next choice, just return. + if desired_index == self.current { + return Ok(true); + } - // Take the choice we want to move forward out of it's place. - let choice = queue_lock[desired_index].take().unwrap(); - // Iterate backwards from the spot before the choice we want to move up, push them all back - // by 1 spot. - for index in (self.current..desired_index).rev() { - queue_lock.swap(index, index + 1); - } - // Place the choice that needs to be next in the next to be executed position. - let _ = queue_lock[self.current].insert(choice); - true + // Take the choice we want to move forward out of it's place. + let choice = queue_lock[desired_index] + .take() + .ok_or(anyhow!("Choice was already taken"))?; + // Iterate backwards from the spot before the choice we want to move up, push them all back + // by 1 spot. + for index in (self.current..desired_index).rev() { + queue_lock.swap(index, index + 1); + } + // Place the choice that needs to be next in the next to be executed position. + let _ = queue_lock[self.current].insert(choice); + true + } + None => false, + }; + Ok(result) } /// Internal helper function to be easily able to iterate over the yet to be executed choices. @@ -140,7 +149,7 @@ mod tests { fn create_empty_queue() { let queue = ChoiceQueue::new(Vec::new()); assert!(!queue.has_next()); - assert!(queue.peek().is_none()); + assert!(queue.peek().unwrap().is_none()); } #[test] @@ -168,6 +177,7 @@ mod tests { 0, &"test_nature".into(), ) + .unwrap() } #[test] @@ -176,8 +186,8 @@ mod tests { let queue = ChoiceQueue::new(vec![Some(TurnChoice::Pass(PassChoice::new(user)))]); assert!(queue.has_next()); - assert!(queue.peek().is_some()); - assert_eq!(7, queue.peek().unwrap().speed()); + assert!(queue.peek().unwrap().is_some()); + assert_eq!(7, queue.peek().unwrap().unwrap().speed()); } #[test] @@ -188,7 +198,7 @@ mod tests { assert!(queue.has_next()); assert_eq!(7, queue.dequeue().unwrap().speed()); assert!(!queue.has_next()); - assert!(queue.peek().is_none()); + assert!(queue.peek().unwrap().is_none()); } #[test] @@ -201,8 +211,8 @@ mod tests { Some(TurnChoice::Pass(PassChoice::new(user2))), ]); assert!(queue.has_next()); - assert!(queue.peek().is_some()); - assert_eq!(7, queue.peek().unwrap().speed()); + assert!(queue.peek().unwrap().is_some()); + assert_eq!(7, queue.peek().unwrap().unwrap().speed()); } #[test] @@ -247,12 +257,12 @@ mod tests { user2.change_level_by(60); assert_eq!( user1.value_identifier(), - queue.peek().unwrap().user().value_identifier() + queue.peek().unwrap().unwrap().user().value_identifier() ); queue.resort(); assert_eq!( user2.value_identifier(), - queue.peek().unwrap().user().value_identifier() + queue.peek().unwrap().unwrap().user().value_identifier() ); } @@ -267,9 +277,9 @@ mod tests { ]); assert_eq!( user1.value_identifier(), - queue.peek().unwrap().user().value_identifier() + queue.peek().unwrap().unwrap().user().value_identifier() ); - assert!(queue.move_pokemon_choice_next(user2.as_ref())); + assert!(queue.move_pokemon_choice_next(user2.as_ref()).unwrap()); assert_eq!( user2.value_identifier(), @@ -294,13 +304,13 @@ mod tests { user2.value_identifier(), queue.dequeue().unwrap().user().value_identifier() ); - assert!(!queue.move_pokemon_choice_next(user2.as_ref())); + assert!(!queue.move_pokemon_choice_next(user2.as_ref()).unwrap()); assert_eq!( user1.value_identifier(), queue.dequeue().unwrap().user().value_identifier() ); - assert!(queue.peek().is_none()) + assert!(queue.peek().unwrap().is_none()) } #[test] @@ -314,12 +324,12 @@ mod tests { ]); assert_eq!( user1.value_identifier(), - queue.peek().unwrap().user().value_identifier() + queue.peek().unwrap().unwrap().user().value_identifier() ); - assert!(queue.move_pokemon_choice_next(user1.as_ref())); + assert!(queue.move_pokemon_choice_next(user1.as_ref()).unwrap()); assert_eq!( user1.value_identifier(), - queue.peek().unwrap().user().value_identifier() + queue.peek().unwrap().unwrap().user().value_identifier() ); } @@ -346,9 +356,9 @@ mod tests { ]); assert_eq!( users[0].value_identifier(), - queue.peek().unwrap().user().value_identifier() + queue.peek().unwrap().unwrap().user().value_identifier() ); - assert!(queue.move_pokemon_choice_next(users[4].as_ref())); + assert!(queue.move_pokemon_choice_next(users[4].as_ref()).unwrap()); assert_eq!( users[4].value_identifier(), diff --git a/src/dynamic_data/flow/target_resolver.rs b/src/dynamic_data/flow/target_resolver.rs index 0de5a50..a41688a 100755 --- a/src/dynamic_data/flow/target_resolver.rs +++ b/src/dynamic_data/flow/target_resolver.rs @@ -135,45 +135,48 @@ pub fn resolve_targets(side: u8, index: u8, target: MoveTarget, battle: &Battle) /// Checks whether a given side and index are valid for a given move target and user. pub fn is_valid_target(side: u8, index: u8, target: MoveTarget, user: &Pokemon) -> bool { - let user_side = user.get_battle_side_index(); - let user_index = user.get_battle_index(); // If the user is not on the field, nothing is a valid target - if user_side.is_none() || user_index.is_none() { - return false; - } + let user_side = match user.get_battle_side_index() { + Some(side) => side, + None => return false, + }; + let user_index = match user.get_battle_index() { + Some(index) => index, + None => return false, + }; match target { MoveTarget::Adjacent | MoveTarget::AllAdjacent => { - let diff = abs(index as i32 - user_index.unwrap() as i32); + let diff = abs(index as i32 - user_index as i32); if diff == 0 { - return user_side.unwrap() != side; + return user_side != side; } diff <= 1 } MoveTarget::AdjacentAlly => { - if user_side.unwrap() != side { + if user_side != side { false } else { - abs(index as i32 - user_index.unwrap() as i32) == 1 + abs(index as i32 - user_index as i32) == 1 } } MoveTarget::AdjacentAllySelf => { - if user_side.unwrap() != side { + if user_side != side { false } else { - abs(index as i32 - user_index.unwrap() as i32) <= 1 + abs(index as i32 - user_index as i32) <= 1 } } MoveTarget::AdjacentOpponent | MoveTarget::AllAdjacentOpponent => { - if user_side.unwrap() == side { + if user_side == side { false } else { - abs(index as i32 - user_index.unwrap() as i32) <= 1 + abs(index as i32 - user_index as i32) <= 1 } } MoveTarget::All | MoveTarget::Any | MoveTarget::RandomOpponent => true, - MoveTarget::AllAlly => user_side.unwrap() == side, - MoveTarget::AllOpponent => user_side.unwrap() != side, - MoveTarget::SelfUse => user_side.unwrap() == side && user_index.unwrap() == index, + MoveTarget::AllAlly => user_side == side, + MoveTarget::AllOpponent => user_side != side, + MoveTarget::SelfUse => user_side == side && user_index == index, } } diff --git a/src/dynamic_data/flow/turn_runner.rs b/src/dynamic_data/flow/turn_runner.rs index 1cef78e..882a7c3 100755 --- a/src/dynamic_data/flow/turn_runner.rs +++ b/src/dynamic_data/flow/turn_runner.rs @@ -1,3 +1,5 @@ +use anyhow::Result; +use anyhow_ext::anyhow; use std::ops::Deref; use std::sync::Arc; @@ -10,11 +12,21 @@ use crate::dynamic_data::DamageSource; use crate::dynamic_data::ExecutingMove; use crate::dynamic_data::Pokemon; use crate::static_data::MoveCategory; -use crate::{run_scripts, script_hook, PkmnResult}; +use crate::{run_scripts, script_hook}; + +/// Simple macro to get a read lock on the choice queue. +macro_rules! read_choice_queue { + ($choice_queue:ident) => { + $choice_queue + .read() + .as_ref() + .ok_or(anyhow!("Could not get a lock on choice queue"))? + }; +} impl Battle { /// Execute the entire turn after the choices are all set. - pub(crate) fn run_turn(&self) -> PkmnResult<()> { + pub(crate) fn run_turn(&self) -> Result<()> { let choice_queue = self.current_turn_queue(); // We are now at the very beginning of a turn. We have assigned speeds and priorities to all @@ -24,15 +36,19 @@ impl Battle { // is primarily intended to be used to reset variables on a script (for example scripts that need // to check whether a pokemon was hit this turn. By resetting here, and setting a variable to true // they can then know this later on.) - for choice in choice_queue.read().as_ref().unwrap().get_queue().iter().flatten() { + for choice in read_choice_queue!(choice_queue).get_queue().iter().flatten() { script_hook!(on_before_turn, choice, choice); } // Now we can properly begin executing choices. // One by one dequeue the turns, and run them. If the battle has ended we do not want to // continue running however. - while choice_queue.read().as_ref().unwrap().has_next() && !self.has_ended() { - let choice = choice_queue.write().as_mut().unwrap().dequeue(); + while read_choice_queue!(choice_queue).has_next() && !self.has_ended() { + let choice = choice_queue + .write() + .as_mut() + .ok_or(anyhow!("Failed to get a write lock on choice queue"))? + .dequeue(); if let Some(choice) = choice { self.execute_choice(&choice)?; } @@ -64,7 +80,7 @@ impl Battle { } /// Executes a single choice. - fn execute_choice(&self, choice: &TurnChoice) -> PkmnResult<()> { + fn execute_choice(&self, choice: &TurnChoice) -> Result<()> { // A pass turn choice means the user does not intend to do anything. As such, return. if let TurnChoice::Pass(..) = choice { return Ok(()); @@ -93,7 +109,7 @@ impl Battle { } /// Executes a move choice. - fn execute_move_choice<'func>(&'func self, choice: &'func TurnChoice) -> PkmnResult<()> { + fn execute_move_choice<'func>(&'func self, choice: &'func TurnChoice) -> Result<()> { let move_choice = choice.get_move_turn_data(); let used_move = move_choice.used_move(); let move_data = { @@ -101,7 +117,11 @@ impl Battle { let move_data = move_data_lock.move_data(); let mut move_name = move_data.name().clone(); script_hook!(change_move, choice, choice, &mut move_name); - self.library().static_data().moves().get(&move_name).unwrap() + self.library() + .static_data() + .moves() + .get(&move_name) + .ok_or(anyhow!("Move not found"))? }; // FIXME: also change the script on the choice if changed; let target_type = move_data.target(); @@ -150,7 +170,7 @@ impl Battle { } /// Executes a move turn choice on a single target. - fn handle_move_for_target(&self, executing_move: &mut ExecutingMove, target: &Arc) -> PkmnResult<()> { + fn handle_move_for_target(&self, executing_move: &mut ExecutingMove, target: &Arc) -> Result<()> { { let mut fail = false; script_hook!(fail_incoming_move, target, executing_move, target, &mut fail); @@ -228,7 +248,7 @@ impl Battle { let is_critical = self.library() .damage_calculator() - .is_critical(self, executing_move, target, hit_index); + .is_critical(self, executing_move, target, hit_index)?; hit_data.set_critical(is_critical); } let base_power = self.library().damage_calculator().get_base_power( @@ -243,7 +263,7 @@ impl Battle { target, hit_index, executing_move.get_hit_data(target, hit_index)?, - ); + )?; hit_data.set_damage(damage); let mut accuracy = executing_move.use_move().accuracy(); @@ -259,7 +279,9 @@ impl Battle { &mut accuracy ); } - if accuracy < 100 && self.random().get_max(100) as u8 >= accuracy { + + // TODO: Deal with accuracy/evasion stats. + if accuracy < 100 && self.random().get_max(100)? as u8 >= accuracy { script_hook!(on_move_miss, target, executing_move, target); self.event_hook().trigger(Event::Miss { user: executing_move.user().deref(), @@ -273,7 +295,7 @@ impl Battle { if secondary_effect_chance == -1.0 || self .random() - .effect_chance(secondary_effect_chance, executing_move, target, hit_index) + .effect_chance(secondary_effect_chance, executing_move, target, hit_index)? { script_hook!(on_secondary_effect, executing_move, executing_move, target, hit_index); // TODO: on fail @@ -287,7 +309,7 @@ impl Battle { hit_data.set_damage(damage); } if damage > 0 { - target.damage(damage, DamageSource::MoveDamage); + target.damage(damage, DamageSource::MoveDamage)?; if !target.is_fainted() { script_hook!(on_incoming_hit, target, executing_move, target, hit_index); } else { @@ -313,7 +335,7 @@ impl Battle { executing_move, target, hit_index, - ) + )? { script_hook!( on_secondary_effect, diff --git a/src/dynamic_data/libraries/damage_library.rs b/src/dynamic_data/libraries/damage_library.rs index 8bb2249..e9a21f0 100755 --- a/src/dynamic_data/libraries/damage_library.rs +++ b/src/dynamic_data/libraries/damage_library.rs @@ -1,3 +1,5 @@ +use anyhow::Result; +use anyhow_ext::anyhow; use std::sync::Arc; use crate::dynamic_data::script_handling::ScriptSource; @@ -16,7 +18,7 @@ pub trait DamageLibrary: std::fmt::Debug + ValueIdentifiable { target: &Arc, hit_number: u8, hit_data: &HitData, - ) -> u32; + ) -> Result; /// Calculate the base power for a given hit on a Pokemon. fn get_base_power( @@ -34,7 +36,7 @@ pub trait DamageLibrary: std::fmt::Debug + ValueIdentifiable { executing_move: &ExecutingMove, target: &Arc, hit_number: u8, - ) -> bool; + ) -> Result; } /// The implementation of a Damage Library for generation 7. @@ -174,9 +176,9 @@ impl DamageLibrary for Gen7DamageLibrary { target: &Arc, hit_number: u8, hit_data: &HitData, - ) -> u32 { + ) -> Result { if executing_move.use_move().category() == MoveCategory::Status { - return 0; + return Ok(0); } let level_modifier = ((2.0 * executing_move.user().level() as f32) / 5.0).floor() + 2.0; @@ -205,7 +207,13 @@ impl DamageLibrary for Gen7DamageLibrary { } if self.has_randomness { - let random_percentage = 85 + executing_move.user().get_battle().unwrap().random().get_between(0, 16); + let random_percentage = 85 + + executing_move + .user() + .get_battle() + .ok_or(anyhow!("Damage calculation was done for a Pokemon not in battle"))? + .random() + .get_between(0, 16)?; float_damage = (float_damage * (random_percentage as f32 / 100.0)).floor(); } @@ -247,7 +255,7 @@ impl DamageLibrary for Gen7DamageLibrary { hit_number, &mut damage ); - damage + Ok(damage) } fn get_base_power( @@ -279,10 +287,10 @@ impl DamageLibrary for Gen7DamageLibrary { executing_move: &ExecutingMove, target: &Arc, hit_number: u8, - ) -> bool { + ) -> Result { // Status moves can't be critical. if executing_move.use_move().category() == MoveCategory::Status { - return false; + return Ok(false); } // Get the critical stage from scripts. let mut crit_stage = 0; @@ -296,12 +304,12 @@ impl DamageLibrary for Gen7DamageLibrary { ); // Crit stage is an unsigned byte, so we only care about values of 0 or higher. // For a critical stage of 3+ we always return true. - match crit_stage { - 0 => battle.random().get_max(24) == 0, - 1 => battle.random().get_max(8) == 0, - 2 => battle.random().get_max(2) == 0, + Ok(match crit_stage { + 0 => battle.random().get_max(24)? == 0, + 1 => battle.random().get_max(8)? == 0, + 2 => battle.random().get_max(2)? == 0, _ => true, - } + }) } } diff --git a/src/dynamic_data/libraries/dynamic_library.rs b/src/dynamic_data/libraries/dynamic_library.rs index 063dea0..f26ba44 100755 --- a/src/dynamic_data/libraries/dynamic_library.rs +++ b/src/dynamic_data/libraries/dynamic_library.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use std::fmt::Debug; use std::sync::Arc; @@ -9,7 +10,7 @@ use crate::dynamic_data::{ItemScript, ScriptResolver}; use crate::dynamic_data::{Script, ScriptOwnerData}; use crate::static_data::Item; use crate::static_data::StaticData; -use crate::{PkmnResult, StringKey, ValueIdentifiable, ValueIdentifier}; +use crate::{StringKey, ValueIdentifiable, ValueIdentifier}; /// 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. @@ -32,11 +33,11 @@ pub trait DynamicLibrary: Debug + ValueIdentifiable { owner: ScriptOwnerData, _category: ScriptCategory, _key: &StringKey, - ) -> PkmnResult>>; + ) -> Result>>; /// Loads an item script with the given unique key. If no script can be created with this /// combinations, returns None. Note that ItemScripts are immutable, as their script should be /// shared between all different usages. - fn load_item_script(&self, _key: &Arc) -> PkmnResult>>; + fn load_item_script(&self, _key: &Arc) -> Result>>; } /// The dynamic library stores a static data library, as well as holding different libraries and @@ -107,13 +108,13 @@ impl DynamicLibrary for DynamicLibraryImpl { owner: ScriptOwnerData, _category: ScriptCategory, _key: &StringKey, - ) -> PkmnResult>> { + ) -> Result>> { self.script_resolver.load_script(owner, _category, _key) } /// Loads an item script with the given unique key. If no script can be created with this /// combinations, returns None. Note that ItemScripts are immutable, as their script should be /// shared between all different usages. - fn load_item_script(&self, _key: &Arc) -> PkmnResult>> { + fn load_item_script(&self, _key: &Arc) -> Result>> { todo!() } } @@ -145,8 +146,8 @@ pub mod test { owner: ScriptOwnerData, _category: ScriptCategory, _key: &StringKey, - ) -> PkmnResult>>; - fn load_item_script(&self, _key: &Arc) -> PkmnResult>>; + ) -> Result>>; + fn load_item_script(&self, _key: &Arc) -> Result>>; } impl ValueIdentifiable for DynamicLibrary{ fn value_identifier(&self) -> ValueIdentifier{ diff --git a/src/dynamic_data/libraries/script_resolver.rs b/src/dynamic_data/libraries/script_resolver.rs index a815321..f833736 100755 --- a/src/dynamic_data/libraries/script_resolver.rs +++ b/src/dynamic_data/libraries/script_resolver.rs @@ -1,9 +1,10 @@ +use anyhow::Result; use std::fmt::Debug; use std::sync::Arc; use crate::dynamic_data::{ItemScript, Script, ScriptOwnerData}; use crate::static_data::Item; -use crate::{PkmnResult, StringKey, ValueIdentifiable, ValueIdentifier}; +use crate::{StringKey, ValueIdentifiable, ValueIdentifier}; /// A script resolver deals with the resolving of scripts. These scripts are non-hardcoded /// implementations of different effects in Pokemon. This allows for things such as generational @@ -16,12 +17,12 @@ pub trait ScriptResolver: Debug + ValueIdentifiable { owner: ScriptOwnerData, category: ScriptCategory, script_key: &StringKey, - ) -> PkmnResult>>; + ) -> Result>>; /// Loads an item script with the given unique key. If no script can be created with this /// combinations, returns None. Note that ItemScripts are immutable, as their script should be /// shared between all different usages. - fn load_item_script(&self, _key: &dyn Item) -> PkmnResult>>; + fn load_item_script(&self, _key: &dyn Item) -> Result>>; } use std::fmt::Display; @@ -73,11 +74,11 @@ impl ScriptResolver for EmptyScriptResolver { _owner: ScriptOwnerData, _category: ScriptCategory, _script_key: &StringKey, - ) -> PkmnResult>> { + ) -> Result>> { Ok(None) } - fn load_item_script(&self, _key: &dyn Item) -> PkmnResult>> { + fn load_item_script(&self, _key: &dyn Item) -> Result>> { Ok(None) } } diff --git a/src/dynamic_data/models/battle.rs b/src/dynamic_data/models/battle.rs index 026a8f7..f346016 100755 --- a/src/dynamic_data/models/battle.rs +++ b/src/dynamic_data/models/battle.rs @@ -2,6 +2,8 @@ use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::Arc; +use anyhow::Result; +use anyhow_ext::anyhow; use atomig::Atomic; use parking_lot::RwLock; @@ -18,7 +20,7 @@ use crate::dynamic_data::VolatileScriptsOwner; use crate::dynamic_data::{is_valid_target, ScriptWrapper}; use crate::dynamic_data::{ChoiceQueue, ScriptContainer}; use crate::dynamic_data::{ScriptCategory, ScriptSource, ScriptSourceData}; -use crate::{script_hook, PkmnResult, StringKey, ValueIdentifiable, ValueIdentifier}; +use crate::{script_hook, StringKey, ValueIdentifiable, ValueIdentifier}; /// A pokemon battle, with any amount of sides and pokemon per side. #[derive(Debug)] @@ -169,11 +171,9 @@ impl Battle { /// Get a Pokemon on the battlefield, on a specific side and an index on that side. pub fn get_pokemon(&self, side: u8, index: u8) -> Option> { let side = self.sides.get(side as usize); - side?; - let pokemon_read_lock = side.unwrap().pokemon(); + let pokemon_read_lock = side?.pokemon(); let pokemon = pokemon_read_lock.get(index as usize); - pokemon?; - pokemon.unwrap().clone() + pokemon?.clone() } /// Returns whether a slot on the battlefield can still be filled. If no party is responsible @@ -190,10 +190,10 @@ impl Battle { /// Validates whether the battle is still in a non-ended state. If the battle has ended, this /// properly sets who has won etc. - pub fn validate_battle_state(&self) { + pub fn validate_battle_state(&self) -> Result<()> { // If we've already ended, we dont need to run this. if self.has_ended() { - return; + return Ok(()); } let mut surviving_side_exists = false; let mut winning_side = None; @@ -205,13 +205,13 @@ impl Battle { self.result.data_ptr().replace(BattleResult::Inconclusive); } self.has_ended.store(true, Ordering::SeqCst); - return; + return Ok(()); } // If the side is not defeated if !side.is_defeated() { // More than 1 surviving side. Battle is not ended if surviving_side_exists { - return; + return Ok(()); } surviving_side_exists = true; winning_side = Some(side_index as u8); @@ -228,12 +228,13 @@ impl Battle { else { let _w = self.result.write(); unsafe { - self.result - .data_ptr() - .replace(BattleResult::Conclusive(winning_side.unwrap())); + self.result.data_ptr().replace(BattleResult::Conclusive( + winning_side.ok_or(anyhow!("Winning side was not set"))?, + )); } } self.has_ended.store(true, Ordering::SeqCst); + Ok(()) } /// Checks whether a choice is actually possible. @@ -260,7 +261,7 @@ impl Battle { } /// Try and set the choice for the battle. If the choice is not valid, this returns false. - pub fn try_set_choice(&self, choice: TurnChoice) -> PkmnResult { + pub fn try_set_choice(&self, choice: TurnChoice) -> Result { if !self.can_use(&choice) { return Ok(false); } @@ -268,17 +269,19 @@ impl Battle { return Ok(false); } let side = choice.user().get_battle_side_index(); - if side.is_none() { - return Ok(false); + match side { + Some(side) => { + self.sides[side as usize].set_choice(choice); + self.check_choices_set_and_run()?; + Ok(true) + } + None => Ok(false), } - self.sides[side.unwrap() as usize].set_choice(choice); - self.check_choices_set_and_run()?; - Ok(true) } /// Checks to see whether all Pokemon on the field have set their choices. If so, we then run /// the turn. - fn check_choices_set_and_run(&self) -> PkmnResult<()> { + fn check_choices_set_and_run(&self) -> Result<()> { for side in &self.sides { if !side.all_choices_set() { return Ok(()); @@ -292,10 +295,9 @@ impl Battle { for side in &self.sides { let mut side_choices = side.choices().write(); for choice_opt in side_choices.deref_mut() { - if choice_opt.is_none() { - panic!("Choice was none, but all choices were set? Logic error."); - } - let mut choice = choice_opt.as_mut().unwrap(); + let mut choice = choice_opt + .as_mut() + .ok_or(anyhow!("Choice was none, but all choices were set? Logic error."))?; let c = choice.deref(); if let TurnChoice::Move(data) = c { let mut change_priority = data.priority(); @@ -312,7 +314,7 @@ impl Battle { script_hook!(change_speed, c, c, &mut speed); *choice.speed_mut() = speed; - choice.set_random_value(self.random.get() as u32); + choice.set_random_value(self.random.get()? as u32); choices.push(choice_opt.take()); } // Drop the lock guard, as we need to write into it in reset_choices. @@ -331,32 +333,41 @@ impl Battle { self.event_hook.trigger(Event::EndTurn); let end_time = chrono::Utc::now(); let time = end_time - start_time; - self.last_turn_time - .store(time.num_nanoseconds().unwrap() as u64, Ordering::SeqCst); + match time.num_nanoseconds() { + None => {} + Some(v) => { + self.last_turn_time.store(v as u64, Ordering::SeqCst); + } + } Ok(()) } /// Sets the current weather for the battle. If None is passed, this clears the weather. - pub fn set_weather(&self, weather: Option) { + pub fn set_weather(&self, weather: Option) -> Result<()> { if let Some(weather) = weather { let script = self .library() - .load_script(self.into(), ScriptCategory::Weather, &weather) - .unwrap() - .unwrap_or_else(|| panic!("Couldn't find weather script by name {}", weather)); + .load_script(self.into(), ScriptCategory::Weather, &weather)? + .ok_or(anyhow!("Couldn't find weather script by name {}", weather))?; self.weather.set(script); } else { self.weather.clear(); } + Ok(()) } /// Gets the current weather of the battle. If no weather is present, this returns None. - pub fn weather_name(&self) -> Option { + pub fn weather_name(&self) -> Result> { if let Some(script) = self.weather.get() { let lock = script.read(); - Some(lock.as_ref().unwrap().name().clone()) + Ok(Some( + lock.as_ref() + .ok_or(anyhow!("Failed to get a lock on weather"))? + .name() + .clone(), + )) } else { - None + Ok(None) } } } @@ -366,7 +377,7 @@ impl VolatileScriptsOwner for Battle { &self.volatile_scripts } - fn load_volatile_script(&self, key: &StringKey) -> PkmnResult>> { + fn load_volatile_script(&self, key: &StringKey) -> Result>> { self.library.load_script(self.into(), ScriptCategory::Battle, key) } } diff --git a/src/dynamic_data/models/battle_random.rs b/src/dynamic_data/models/battle_random.rs index 24c3690..fdf49f9 100755 --- a/src/dynamic_data/models/battle_random.rs +++ b/src/dynamic_data/models/battle_random.rs @@ -1,3 +1,5 @@ +use anyhow::Result; +use anyhow_ext::anyhow; use std::fmt::{Debug, Formatter}; use std::sync::{Arc, Mutex}; @@ -32,16 +34,25 @@ impl BattleRandom { } /// Get a random 32 bit integer. Can be any value between min int and max int. - pub fn get(&self) -> i32 { - return self.get_rng().lock().unwrap().get(); + pub fn get(&self) -> Result { + match self.get_rng().lock() { + Ok(mut l) => Ok(l.get()), + Err(_) => Err(anyhow!("Failed to get a RNG lock")), + } } /// Get a random 32 bit integer between 0 and max. - pub fn get_max(&self, max: i32) -> i32 { - return self.get_rng().lock().unwrap().get_max(max); + pub fn get_max(&self, max: i32) -> Result { + match self.get_rng().lock() { + Ok(mut l) => Ok(l.get_max(max)), + Err(_) => Err(anyhow!("Failed to get a RNG lock")), + } } /// Get a random 32 bit integer between min and max. - pub fn get_between(&self, min: i32, max: i32) -> i32 { - return self.get_rng().lock().unwrap().get_between(min, max); + pub fn get_between(&self, min: i32, max: i32) -> Result { + match self.get_rng().lock() { + Ok(mut l) => Ok(l.get_between(min, max)), + Err(_) => Err(anyhow!("Failed to get a RNG lock")), + } } /// Gets whether or not a move triggers its secondary effect. This takes its chance, and @@ -53,7 +64,7 @@ impl BattleRandom { executing_move: &ExecutingMove, target: &Arc, hit_number: u8, - ) -> bool { + ) -> Result { script_hook!( change_effect_chance, executing_move, @@ -72,12 +83,15 @@ impl BattleRandom { ); if chance < 100.0 { if chance > 0.0 { - self.get_rng().lock().unwrap().get_float() < (chance / 100.0) + match self.get_rng().lock() { + Ok(mut l) => Ok(l.get_float() < (chance / 100.0)), + Err(_) => Err(anyhow!("Failed to get a RNG lock")), + } } else { - false + Ok(false) } } else { - true + Ok(true) } } } @@ -92,6 +106,10 @@ impl Clone for BattleRandom { fn clone(&self) -> Self { Self { identifier: Default::default(), + + // As cloning when we can't get a lock on the randomness is completely impossible, we + // should unwrap here. + #[allow(clippy::unwrap_used)] random: Mutex::new(self.random.lock().unwrap().clone()), } } diff --git a/src/dynamic_data/models/battle_side.rs b/src/dynamic_data/models/battle_side.rs index fb44b52..bd21b72 100755 --- a/src/dynamic_data/models/battle_side.rs +++ b/src/dynamic_data/models/battle_side.rs @@ -2,6 +2,7 @@ use std::ops::Deref; use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::sync::Arc; +use anyhow::Result; use parking_lot::lock_api::RwLockReadGuard; use parking_lot::{RawRwLock, RwLock}; @@ -14,7 +15,7 @@ use crate::dynamic_data::script_handling::{ScriptSource, ScriptSourceData, Scrip use crate::dynamic_data::Script; use crate::dynamic_data::ScriptSet; use crate::dynamic_data::VolatileScriptsOwner; -use crate::{script_hook, PkmnResult, StringKey, ValueIdentifiable, ValueIdentifier}; +use crate::{script_hook, StringKey, ValueIdentifiable, ValueIdentifier}; /// A side on a battle. #[derive(Debug)] @@ -248,9 +249,9 @@ impl BattleSide { } /// Gets a random Pokemon on the given side. - pub fn get_random_creature_index(&self) -> u8 { + pub fn get_random_creature_index(&self) -> Result { // TODO: Consider adding parameter to only get index for available creatures. - self.battle().random().get_max(self.pokemon_per_side as i32) as u8 + Ok(self.battle().random().get_max(self.pokemon_per_side as i32)? as u8) } /// Swap two Pokemon on a single side around. @@ -299,7 +300,7 @@ impl VolatileScriptsOwner for BattleSide { &self.volatile_scripts } - fn load_volatile_script(&self, key: &StringKey) -> PkmnResult>> { + fn load_volatile_script(&self, key: &StringKey) -> Result>> { self.battle() .library() .load_script(self.into(), crate::ScriptCategory::Side, key) diff --git a/src/dynamic_data/models/executing_move.rs b/src/dynamic_data/models/executing_move.rs index 0ab0056..aa0485b 100755 --- a/src/dynamic_data/models/executing_move.rs +++ b/src/dynamic_data/models/executing_move.rs @@ -2,6 +2,8 @@ use std::ops::Deref; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU8, Ordering}; use std::sync::Arc; +use anyhow::Result; +use anyhow_ext::bail; use atomig::Atomic; use parking_lot::RwLock; @@ -11,7 +13,7 @@ use crate::dynamic_data::script_handling::{ScriptSource, ScriptSourceData, Scrip use crate::dynamic_data::ScriptContainer; use crate::dynamic_data::TargetList; use crate::static_data::{MoveData, TypeIdentifier}; -use crate::{PkmnResult, PokemonError, ValueIdentifiable, ValueIdentifier}; +use crate::{ValueIdentifiable, ValueIdentifier}; /// A hit data is the data for a single hit, on a single target. #[derive(Default, Debug)] @@ -164,7 +166,7 @@ impl ExecutingMove { } /// Gets a hit data for a target, with a specific index. - pub fn get_hit_data(&self, for_target: &Pokemon, hit: u8) -> PkmnResult<&HitData> { + pub fn get_hit_data(&self, for_target: &Pokemon, hit: u8) -> Result<&HitData> { for (index, target) in self.targets.iter().enumerate() { if let Some(target) = target { if std::ptr::eq(target.deref().deref(), for_target.deref().deref()) { @@ -173,7 +175,7 @@ impl ExecutingMove { } } } - Err(PokemonError::InvalidTargetRequested) + bail!("Invalid target requested"); } /// Checks whether a Pokemon is a target for this move. @@ -187,7 +189,7 @@ impl ExecutingMove { } /// Gets the index of the hits in this move where the hits for a specific target start. - pub(crate) fn get_index_of_target(&self, for_target: &Arc) -> PkmnResult { + pub(crate) fn get_index_of_target(&self, for_target: &Arc) -> Result { for (index, target) in self.targets.iter().enumerate() { if let Some(target) = target { if std::ptr::eq(target.deref().deref(), for_target.deref().deref()) { @@ -196,7 +198,7 @@ impl ExecutingMove { } } } - Err(PokemonError::InvalidTargetRequested) + bail!("Invalid target requested"); } /// Gets a hit based on its raw index. diff --git a/src/dynamic_data/models/pokemon.rs b/src/dynamic_data/models/pokemon.rs index 6d3f9e3..7de6298 100755 --- a/src/dynamic_data/models/pokemon.rs +++ b/src/dynamic_data/models/pokemon.rs @@ -22,7 +22,8 @@ use crate::static_data::TypeIdentifier; use crate::static_data::{Ability, Statistic}; use crate::static_data::{ClampedStatisticSet, StatisticSet}; use crate::utils::Random; -use crate::{script_hook, PkmnResult, StringKey, ValueIdentifiable, ValueIdentifier}; +use crate::{script_hook, StringKey, ValueIdentifiable, ValueIdentifier}; +use anyhow::Result; /// An individual Pokemon as we know and love them. #[derive(Debug)] @@ -129,12 +130,12 @@ impl Pokemon { gender: Gender, coloring: u8, nature: &StringKey, - ) -> Self { + ) -> Result { // Calculate experience from the level for the specified growth rate. let experience = library .static_data() .growth_rates() - .calculate_experience(species.growth_rate(), level); + .calculate_experience(species.growth_rate(), level)?; let weight = form.weight(); let height = form.height(); let nature = library @@ -183,7 +184,7 @@ impl Pokemon { let health = pokemon.flat_stats().hp(); pokemon.current_health = AtomicU32::new(health); - pokemon + Ok(pokemon) } /// The library data of the Pokemon. @@ -617,12 +618,12 @@ impl Pokemon { } /// Damages the Pokemon by a certain amount of damage, from a damage source. - pub fn damage(&self, mut damage: u32, source: DamageSource) { + pub fn damage(&self, mut damage: u32, source: DamageSource) -> Result<()> { if damage > self.current_health() { damage = self.current_health(); } if damage == 0 { - return; + return Ok(()); } let new_health = self.current_health() - damage; if let Some(battle_data) = &self.battle_data.read().deref() { @@ -641,12 +642,13 @@ impl Pokemon { self.current_health.store(new_health, Ordering::SeqCst); if self.is_fainted() && damage > 0 { - self.on_faint(source); + self.on_faint(source)?; } + Ok(()) } /// Triggers when the Pokemon faints. - fn on_faint(&self, source: DamageSource) { + fn on_faint(&self, source: DamageSource) -> Result<()> { let r = self.battle_data.read(); if let Some(battle_data) = r.deref() { if let Some(battle) = battle_data.battle() { @@ -659,9 +661,10 @@ impl Pokemon { .mark_slot_as_unfillable(battle_data.index()); } - battle.validate_battle_state(); + battle.validate_battle_state()?; } } + Ok(()) } /// Heals the Pokemon by a specific amount. Unless allow_revive is set to true, this will not @@ -803,7 +806,7 @@ impl VolatileScriptsOwner for Pokemon { &self.volatile } - fn load_volatile_script(&self, key: &StringKey) -> PkmnResult>> { + fn load_volatile_script(&self, key: &StringKey) -> Result>> { self.library.load_script(self.into(), ScriptCategory::Pokemon, key) } } @@ -866,7 +869,9 @@ pub mod test { static_lib.expect_species().return_const(Box::new(species_lib)); let mut growth_rate_lib = MockGrowthRateLibrary::new(); - growth_rate_lib.expect_calculate_experience().return_const(1000u32); + growth_rate_lib + .expect_calculate_experience() + .returning(|_, _| Ok(1000u32)); let mut nature_lib = MockNatureLibrary::new(); nature_lib.expect_get_nature().returning(|_| { @@ -907,7 +912,8 @@ pub mod test { Gender::Male, 0, &"test_nature".into(), - ); + ) + .unwrap(); assert_eq!(pokemon.species().name(), &"test_species".into()); assert_eq!(pokemon.form().name(), &"default".into()); } diff --git a/src/dynamic_data/models/pokemon_builder.rs b/src/dynamic_data/models/pokemon_builder.rs index 645b855..4673f29 100755 --- a/src/dynamic_data/models/pokemon_builder.rs +++ b/src/dynamic_data/models/pokemon_builder.rs @@ -6,6 +6,7 @@ use crate::dynamic_data::models::pokemon::Pokemon; use crate::dynamic_data::DynamicLibrary; use crate::static_data::{AbilityIndex, Gender}; use crate::{Random, StringKey}; +use anyhow::Result; /// This allows for the easy chain building of a Pokemon. pub struct PokemonBuilder { @@ -39,7 +40,7 @@ impl PokemonBuilder { } /// Finally turn the builder into an actual Pokemon. - pub fn build(self) -> Pokemon { + pub fn build(self) -> Result { let mut random = if let Some(seed) = self.random_seed { Random::new(seed) } else { @@ -61,11 +62,11 @@ impl PokemonBuilder { Gender::Male, 0, &"hardy".into(), - ); + )?; for learned_move in self.learned_moves { p.learn_move(&learned_move, MoveLearnMethod::Unknown); } - p + Ok(p) } } diff --git a/src/dynamic_data/script_handling/script_set.rs b/src/dynamic_data/script_handling/script_set.rs index 02aab3d..6d3dbdb 100755 --- a/src/dynamic_data/script_handling/script_set.rs +++ b/src/dynamic_data/script_handling/script_set.rs @@ -1,11 +1,12 @@ use std::ops::Deref; use std::sync::Arc; +use anyhow::Result; use indexmap::IndexMap; use parking_lot::RwLock; use crate::dynamic_data::script_handling::script::{Script, ScriptContainer}; -use crate::{PkmnResult, StringKey}; +use crate::StringKey; /// A collection of unique scripts. #[derive(Debug, Default)] @@ -36,9 +37,9 @@ impl ScriptSet { /// Adds a script with a name to the set. If the script with that name already exists in this /// set, this makes that script stack instead. The return value here is that script. - pub fn stack_or_add(&self, key: &StringKey, instantiation: &F) -> PkmnResult> + pub fn stack_or_add(&self, key: &StringKey, instantiation: &F) -> Result> where - F: Fn() -> PkmnResult>>, + F: Fn() -> Result>>, { if let Some(lock) = self.scripts.read().get(key) { if let Some(existing) = lock.get() { diff --git a/src/dynamic_data/script_handling/volatile_scripts_owner.rs b/src/dynamic_data/script_handling/volatile_scripts_owner.rs index fe620c9..ae71fe0 100755 --- a/src/dynamic_data/script_handling/volatile_scripts_owner.rs +++ b/src/dynamic_data/script_handling/volatile_scripts_owner.rs @@ -1,15 +1,16 @@ +use anyhow::Result; use std::sync::Arc; use crate::dynamic_data::script_handling::script::{Script, ScriptContainer}; use crate::dynamic_data::script_handling::script_set::ScriptSet; -use crate::{PkmnResult, StringKey}; +use crate::StringKey; /// This trait adds a bunch of helper functions to deal with volatile scripts on a struct. pub trait VolatileScriptsOwner { /// Return the [`ScriptSet`] that are our volatile scripts. fn volatile_scripts(&self) -> &Arc; /// Loads a volatile script by name. - fn load_volatile_script(&self, key: &StringKey) -> PkmnResult>>; + fn load_volatile_script(&self, key: &StringKey) -> Result>>; /// Check if a volatile script with given name exists. fn has_volatile_script(&self, key: &StringKey) -> bool { @@ -22,13 +23,13 @@ pub trait VolatileScriptsOwner { } /// Adds a volatile script by name. - fn add_volatile_script(&self, key: &StringKey) -> PkmnResult> { + fn add_volatile_script(&self, key: &StringKey) -> Result> { self.volatile_scripts() .stack_or_add(key, &|| self.load_volatile_script(key)) } /// Adds a volatile script by name. - fn add_volatile_script_with_script(&self, script: Arc) -> PkmnResult> { + fn add_volatile_script_with_script(&self, script: Arc) -> Result> { self.volatile_scripts() .stack_or_add(&script.name().clone(), &|| Ok(Some(script.clone()))) } diff --git a/src/ffi/dynamic_data/models/battle.rs b/src/ffi/dynamic_data/models/battle.rs index 69423fd..f027bee 100644 --- a/src/ffi/dynamic_data/models/battle.rs +++ b/src/ffi/dynamic_data/models/battle.rs @@ -2,7 +2,7 @@ use crate::dynamic_data::{ Battle, BattleParty, BattleRandom, BattleResult, BattleSide, DynamicLibrary, Pokemon, TurnChoice, }; use crate::ffi::dynamic_data::models::native_event_hook::NativeEventHook; -use crate::ffi::{ExternPointer, IdentifiablePointer, OwnedPtr}; +use crate::ffi::{ExternPointer, IdentifiablePointer, NativeResult, OwnedPtr}; use std::ffi::{c_char, CStr, CString}; use std::sync::Arc; @@ -191,20 +191,20 @@ extern "C" fn battle_try_set_choice(ptr: ExternPointer>, choice: Own /// Sets the current weather for the battle. If nullptr is passed, this clears the weather. #[no_mangle] -extern "C" fn battle_set_weather(ptr: ExternPointer>, weather: *const c_char) { +extern "C" fn battle_set_weather(ptr: ExternPointer>, weather: *const c_char) -> NativeResult<()> { if weather.is_null() { - ptr.as_ref().set_weather(None) + ptr.as_ref().set_weather(None).into() } else { - unsafe { ptr.as_ref().set_weather(Some(CStr::from_ptr(weather).into())) } + unsafe { ptr.as_ref().set_weather(Some(CStr::from_ptr(weather).into())).into() } } } /// Gets the current weather of the battle. If no weather is present, this returns nullptr. #[no_mangle] -extern "C" fn battle_weather_name(ptr: ExternPointer>) -> *mut c_char { - if let Some(w) = ptr.as_ref().weather_name() { - CString::new(w.str()).unwrap().into_raw() - } else { - std::ptr::null_mut() +extern "C" fn battle_weather_name(ptr: ExternPointer>) -> NativeResult<*mut c_char> { + match ptr.as_ref().weather_name() { + Ok(Some(w)) => CString::new(w.str()).unwrap().into_raw().into(), + Ok(None) => std::ptr::null_mut::().into(), + Err(e) => NativeResult::err(e), } } diff --git a/src/ffi/dynamic_data/models/pokemon.rs b/src/ffi/dynamic_data/models/pokemon.rs index e005623..2571456 100644 --- a/src/ffi/dynamic_data/models/pokemon.rs +++ b/src/ffi/dynamic_data/models/pokemon.rs @@ -1,6 +1,6 @@ use crate::defines::LevelInt; use crate::dynamic_data::{Battle, DamageSource, DynamicLibrary, LearnedMove, MoveLearnMethod, Pokemon}; -use crate::ffi::{ffi_arc_getter, ffi_vec_value_getters, ExternPointer, IdentifiablePointer, OwnedPtr}; +use crate::ffi::{ffi_arc_getter, ffi_vec_value_getters, ExternPointer, IdentifiablePointer, NativeResult, OwnedPtr}; use crate::static_data::{ Ability, AbilityIndex, Form, Gender, Item, Nature, Species, Statistic, StatisticSet, TypeIdentifier, }; @@ -21,9 +21,9 @@ extern "C" fn pokemon_new( gender: Gender, coloring: u8, nature: *const c_char, -) -> IdentifiablePointer> { +) -> NativeResult>> { let nature = unsafe { CStr::from_ptr(nature) }.into(); - Arc::new(Pokemon::new( + let pokemon = Pokemon::new( library.as_ref().clone(), species.as_ref().clone(), form.as_ref(), @@ -36,8 +36,11 @@ extern "C" fn pokemon_new( gender, coloring, &nature, - )) - .into() + ); + match pokemon { + Ok(pokemon) => NativeResult::ok(Arc::new(pokemon).into()), + Err(err) => NativeResult::err(err), + } } /// Drops an Arc reference held by the FFI. @@ -327,8 +330,8 @@ extern "C" fn pokemon_is_on_battlefield(ptr: ExternPointer>) -> u8 /// Damages the Pokemon by a certain amount of damage, from a damage source. #[no_mangle] -extern "C" fn pokemon_damage(ptr: ExternPointer>, damage: u32, source: DamageSource) { - ptr.as_ref().damage(damage, source) +extern "C" fn pokemon_damage(ptr: ExternPointer>, damage: u32, source: DamageSource) -> NativeResult<()> { + ptr.as_ref().damage(damage, source).into() } /// Heals the Pokemon by a specific amount. Unless allow_revive is set to true, this will not diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 2668c6d..9b11953 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -93,6 +93,7 @@ pub(self) use ffi_arc_getter; pub(self) use ffi_arc_stringkey_getter; pub(self) use ffi_vec_stringkey_getters; pub(self) use ffi_vec_value_getters; +use std::ffi::{c_char, CString}; use std::mem::transmute; use std::sync::Arc; @@ -131,6 +132,17 @@ pub(self) struct IdentifiablePointer { pub id: usize, } +impl Clone for IdentifiablePointer { + fn clone(&self) -> Self { + Self { + id: self.id, + ptr: self.ptr, + } + } +} + +impl Copy for IdentifiablePointer {} + impl IdentifiablePointer { /// Creates a new IdentifiablePointer. pub(self) fn new(ptr: *const T, id: ValueIdentifier) -> Self { @@ -244,3 +256,55 @@ impl IdentifiablePointer { } } } + +/// Helper utility class to give either the data or an error to a FFI. +#[repr(C)] +union ResultUnion { + /// If the result is ok, this contains the value. + ok: T, + /// If the result is an error, this contains the error message. + err: OwnedPtr, +} + +/// The result of a FFI call that can either be an error or a value. +#[repr(C)] +pub struct NativeResult { + /// If the result is ok, this is 1, otherwise 0. + ok: u8, + /// The value or error. + value: ResultUnion, +} + +impl NativeResult { + /// Creates a new NativeResult with the given value. + pub fn ok(value: T) -> Self { + Self { + ok: 1, + value: ResultUnion { ok: value }, + } + } + /// Creates a new NativeResult with the given error. + pub fn err(err: anyhow::Error) -> Self { + Self { + ok: 0, + value: ResultUnion { + err: CString::new(err.to_string()).unwrap().into_raw(), + }, + } + } +} + +impl From> for NativeResult { + fn from(value: anyhow::Result) -> Self { + match value { + Ok(v) => Self::ok(v), + Err(e) => Self::err(e), + } + } +} + +impl From for NativeResult { + fn from(value: T) -> Self { + Self::ok(value) + } +} diff --git a/src/ffi/static_data/growth_rate.rs b/src/ffi/static_data/growth_rate.rs index 18bd282..6e45ce9 100644 --- a/src/ffi/static_data/growth_rate.rs +++ b/src/ffi/static_data/growth_rate.rs @@ -1,5 +1,5 @@ use crate::defines::LevelInt; -use crate::ffi::{ExternPointer, OwnedPtr}; +use crate::ffi::{ExternPointer, NativeResult, OwnedPtr}; use crate::static_data::{GrowthRate, LookupGrowthRate}; use std::ptr::drop_in_place; @@ -25,6 +25,9 @@ extern "C" fn growth_rate_calculate_level(ptr: ExternPointer /// Calculate the experience something with this growth rate would have at a certain level. #[no_mangle] -extern "C" fn growth_rate_calculate_experience(ptr: ExternPointer>, level: LevelInt) -> u32 { - ptr.as_ref().calculate_experience(level) +extern "C" fn growth_rate_calculate_experience( + ptr: ExternPointer>, + level: LevelInt, +) -> NativeResult { + ptr.as_ref().calculate_experience(level).into() } diff --git a/src/ffi/static_data/libraries/growth_rate_library.rs b/src/ffi/static_data/libraries/growth_rate_library.rs index ffd9774..155f948 100644 --- a/src/ffi/static_data/libraries/growth_rate_library.rs +++ b/src/ffi/static_data/libraries/growth_rate_library.rs @@ -1,5 +1,5 @@ use crate::defines::LevelInt; -use crate::ffi::{BorrowedPtr, ExternPointer, IdentifiablePointer, OwnedPtr}; +use crate::ffi::{BorrowedPtr, ExternPointer, IdentifiablePointer, NativeResult, OwnedPtr}; use crate::static_data::{GrowthRate, GrowthRateLibrary, GrowthRateLibraryImpl}; use std::ffi::{c_char, CStr}; use std::ptr::drop_in_place; @@ -34,9 +34,10 @@ unsafe extern "C" fn growth_rate_library_calculate_experience( ptr: ExternPointer>, growth_rate: BorrowedPtr, level: LevelInt, -) -> u32 { +) -> NativeResult { ptr.as_ref() .calculate_experience(&CStr::from_ptr(growth_rate).into(), level) + .into() } /// Adds a new growth rate with a name and value. diff --git a/src/ffi/static_data/libraries/library_settings.rs b/src/ffi/static_data/libraries/library_settings.rs index bb2bf2e..ac2abad 100644 --- a/src/ffi/static_data/libraries/library_settings.rs +++ b/src/ffi/static_data/libraries/library_settings.rs @@ -1,5 +1,5 @@ use crate::defines::LevelInt; -use crate::ffi::{ExternPointer, IdentifiablePointer, OwnedPtr}; +use crate::ffi::{ExternPointer, IdentifiablePointer, NativeResult, OwnedPtr}; use crate::static_data::{LibrarySettings, LibrarySettingsImpl}; use std::ptr::drop_in_place; @@ -12,9 +12,15 @@ use std::ptr::drop_in_place; extern "C" fn library_settings_new( max_level: LevelInt, shiny_rate: u32, -) -> IdentifiablePointer> { - let b: Box = Box::new(LibrarySettingsImpl::new(max_level, shiny_rate)); - b.into() +) -> NativeResult>> { + match LibrarySettingsImpl::new(max_level, shiny_rate) { + Ok(settings) => { + let b: Box = Box::new(settings); + let p: IdentifiablePointer> = b.into(); + p.into() + } + Err(e) => NativeResult::err(e), + } } /// Drop a library settings object. diff --git a/src/lib.rs b/src/lib.rs index cffcea2..c557c73 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +// Linter modifications #![allow(clippy::too_many_arguments, clippy::needless_range_loop)] #![allow(clippy::not_unsafe_ptr_arg_deref)] #![allow(clippy::borrowed_box)] @@ -5,9 +6,11 @@ #![allow(ambiguous_glob_reexports)] #![deny(missing_docs)] #![deny(clippy::missing_docs_in_private_items)] +// #![deny(clippy::unwrap_used)] +// #![deny(clippy::expect_used)] +// Features #![feature(test)] #![feature(const_option)] -#![feature(is_some_and)] #![feature(new_uninit)] #![feature(get_mut_unchecked)] #![feature(strict_provenance)] @@ -27,7 +30,6 @@ extern crate core; #[macro_use] extern crate enum_display_derive; -use std::fmt::{Display, Formatter}; #[doc(hidden)] pub use utils::*; @@ -48,39 +50,3 @@ pub mod script_implementations; pub mod static_data; /// The utils module includes misc utils that are used within PkmnLib pub mod utils; - -/// The PokemonError enum holds all different error states that can be encountered in PkmnLib. -#[derive(Debug, Clone)] -pub enum PokemonError { - /// A script was requested, but we were unable to find it. - ScriptNotFound { - /// The category of the script we requested, - category: ScriptCategory, - /// The unique key of the requested script. - name: StringKey, - }, - /// We requested data for a specific target, but that target does not exist on the battle field. - InvalidTargetRequested, - - /// Misc errors. Use of this should be minimized, but it is useful for early development. - MiscError, -} - -/// A simple result type. -pub type PkmnResult = Result; - -impl Display for PokemonError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - PokemonError::ScriptNotFound { category, name } => { - write!(f, "No script found with category `{category}` and name `{name}`") - } - PokemonError::InvalidTargetRequested => { - write!(f, "Invalid target was requested") - } - PokemonError::MiscError => { - write!(f, "An unknown error occurred") - } - } - } -} diff --git a/src/script_implementations/wasm/export_registry/dynamic_data/battle.rs b/src/script_implementations/wasm/export_registry/dynamic_data/battle.rs index 6268c28..3f923fc 100755 --- a/src/script_implementations/wasm/export_registry/dynamic_data/battle.rs +++ b/src/script_implementations/wasm/export_registry/dynamic_data/battle.rs @@ -59,7 +59,7 @@ register! { env: FunctionEnvMut, battle: ExternRef, ) -> ExternRef { - let weather = battle.value_func(&env).unwrap().weather_name(); + let weather = battle.value_func(&env).unwrap().weather_name().unwrap(); if let Some(weather) = weather { ExternRef::func_new(&env, &weather) } else { diff --git a/src/script_implementations/wasm/export_registry/dynamic_data/battle_random.rs b/src/script_implementations/wasm/export_registry/dynamic_data/battle_random.rs index 308fa45..477c692 100755 --- a/src/script_implementations/wasm/export_registry/dynamic_data/battle_random.rs +++ b/src/script_implementations/wasm/export_registry/dynamic_data/battle_random.rs @@ -10,14 +10,14 @@ register! { env: FunctionEnvMut, battle_random: ExternRef, ) -> i32 { - battle_random.value_func(&env).unwrap().get() + battle_random.value_func(&env).unwrap().get().unwrap() } fn battle_random_get_max( env: FunctionEnvMut, battle_random: ExternRef, max: i32 ) -> i32 { - battle_random.value_func(&env).unwrap().get_max(max) + battle_random.value_func(&env).unwrap().get_max(max).unwrap() } fn battle_random_get_between( env: FunctionEnvMut, @@ -25,6 +25,6 @@ register! { min: i32, max: i32 ) -> i32 { - battle_random.value_func(&env).unwrap().get_between(min, max) + battle_random.value_func(&env).unwrap().get_between(min, max).unwrap() } } diff --git a/src/script_implementations/wasm/export_registry/dynamic_data/choice_queue.rs b/src/script_implementations/wasm/export_registry/dynamic_data/choice_queue.rs index 4df32ef..55c5e6b 100755 --- a/src/script_implementations/wasm/export_registry/dynamic_data/choice_queue.rs +++ b/src/script_implementations/wasm/export_registry/dynamic_data/choice_queue.rs @@ -10,6 +10,6 @@ register! { battle_random: ExternRef, pokemon: ExternRef ) -> u8 { - u8::from(battle_random.value_func(&env).unwrap().move_pokemon_choice_next(pokemon.value_func(&env).unwrap())) + u8::from(battle_random.value_func(&env).unwrap().move_pokemon_choice_next(pokemon.value_func(&env).unwrap()).unwrap()) } } diff --git a/src/script_implementations/wasm/export_registry/dynamic_data/pokemon.rs b/src/script_implementations/wasm/export_registry/dynamic_data/pokemon.rs index 7c23e9c..b840f52 100755 --- a/src/script_implementations/wasm/export_registry/dynamic_data/pokemon.rs +++ b/src/script_implementations/wasm/export_registry/dynamic_data/pokemon.rs @@ -108,7 +108,7 @@ register! { source: u8 ) { unsafe{ - pokemon.value_func(&env).unwrap().damage(damage, transmute(source)); + pokemon.value_func(&env).unwrap().damage(damage, transmute(source)).unwrap(); } } diff --git a/src/script_implementations/wasm/mod.rs b/src/script_implementations/wasm/mod.rs index 424fd3a..051c163 100755 --- a/src/script_implementations/wasm/mod.rs +++ b/src/script_implementations/wasm/mod.rs @@ -1,3 +1,6 @@ +// allow these for now until we have a good result interface defined. +#[allow(clippy::unwrap_used)] +#[allow(clippy::expect_used)] /// The export registry module deals with registering all functions we require in WebAssembly. mod export_registry; /// A hacky extern ref implementation to ensure the client does not do things it is not allowed to do. diff --git a/src/script_implementations/wasm/script_resolver.rs b/src/script_implementations/wasm/script_resolver.rs index 01d2486..37bb11e 100755 --- a/src/script_implementations/wasm/script_resolver.rs +++ b/src/script_implementations/wasm/script_resolver.rs @@ -3,6 +3,8 @@ use std::fmt::{Debug, Formatter}; use std::mem::{align_of, forget, size_of}; use std::sync::{Arc, Weak}; +use anyhow::Result; +use anyhow_ext::{anyhow, ensure}; use hashbrown::{HashMap, HashSet}; use parking_lot::lock_api::RwLockReadGuard; use parking_lot::{RawRwLock, RwLock}; @@ -19,7 +21,7 @@ use crate::script_implementations::wasm::script_function_cache::ScriptFunctionCa 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, ValueIdentifiable, ValueIdentifier}; +use crate::{ScriptCategory, StringKey, ValueIdentifiable, ValueIdentifier}; /// A WebAssembly script resolver implements the dynamic scripts functionality with WebAssembly. pub struct WebAssemblyScriptResolver { @@ -175,21 +177,20 @@ impl ScriptResolver for WebAssemblyScriptResolver { owner: ScriptOwnerData, category: ScriptCategory, script_key: &StringKey, - ) -> PkmnResult>> { + ) -> Result>> { let script = self .load_script_fn .as_ref() - .unwrap() + .ok_or(anyhow!("Load script function was not found"))? .call( &mut self.store_mut(), category as u8, ExternRef::new_with_resolver(self, script_key), - ) - .unwrap(); + )?; self.environment_data.setup_script(script, category, script_key, owner) } - fn load_item_script(&self, _key: &dyn Item) -> PkmnResult>> { + fn load_item_script(&self, _key: &dyn Item) -> Result>> { todo!() } } @@ -354,7 +355,7 @@ impl WebAssemblyEnvironmentData { .unwrap() .call( &mut self.store_mut(), - (size_of::() * v.len()) as u32, + (std::mem::size_of_val(v)) as u32, align_of::() as u32, ) .unwrap(); @@ -484,7 +485,7 @@ impl WebAssemblyEnvironmentData { category: ScriptCategory, script_key: &StringKey, owner: ScriptOwnerData, - ) -> PkmnResult>> { + ) -> Result>> { if script_ptr == 0 { return Ok(None); } @@ -502,13 +503,12 @@ impl WebAssemblyEnvironmentData { .read() .get::(&"get_script_capabilities".into()) { - let res = get_cap - .call(&mut self.store_mut(), &[Value::I32(script_ptr as i32)]) - .unwrap(); + let res = get_cap.call(&mut self.store_mut(), &[Value::I32(script_ptr as i32)])?; let ptr = (self.memory() as *const WebAssemblyScriptCapabilities).offset(res[0].i32().unwrap() as isize); - let length = res[1].i32().unwrap() as usize; - for i in 0..length { + let length = res[1].i32(); + ensure!(length.is_some(), "Script capabilities length was not a valid i32"); + for i in 0..length.unwrap() as usize { capabilities.insert(*ptr.add(i)); } } diff --git a/src/static_data/growth_rates.rs b/src/static_data/growth_rates.rs index 172afdf..dd035e5 100755 --- a/src/static_data/growth_rates.rs +++ b/src/static_data/growth_rates.rs @@ -1,11 +1,13 @@ use crate::defines::LevelInt; +use anyhow::Result; +use anyhow_ext::ensure; /// A growth rate defines how much experience is required per level. pub trait GrowthRate { /// Calculate the level something with this growth rate would have at a certain experience. fn calculate_level(&self, experience: u32) -> LevelInt; /// Calculate the experience something with this growth rate would have at a certain level. - fn calculate_experience(&self, level: LevelInt) -> u32; + fn calculate_experience(&self, level: LevelInt) -> Result; } /// An implementation of the growth rate that uses a lookup table for experience. @@ -32,11 +34,12 @@ impl GrowthRate for LookupGrowthRate { self.experience.len() as LevelInt } - fn calculate_experience(&self, level: LevelInt) -> u32 { + fn calculate_experience(&self, level: LevelInt) -> Result { + ensure!(level > 0, "Level must be greater than 0, but was {}", level); if level >= self.experience.len() as LevelInt { - *self.experience.last().unwrap() + Ok(*self.experience.last().unwrap()) } else { - self.experience[(level - 1) as usize] + Ok(self.experience[(level - 1) as usize]) } } } @@ -50,7 +53,7 @@ pub(crate) mod tests { pub GrowthRate {} impl GrowthRate for GrowthRate { fn calculate_level(&self, experience: u32) -> LevelInt; - fn calculate_experience(&self, level: LevelInt) -> u32; + fn calculate_experience(&self, level: LevelInt) -> Result; } } } diff --git a/src/static_data/libraries/growth_rate_library.rs b/src/static_data/libraries/growth_rate_library.rs index 6b5579e..3e7f810 100755 --- a/src/static_data/libraries/growth_rate_library.rs +++ b/src/static_data/libraries/growth_rate_library.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use std::fmt; use std::fmt::{Debug, Formatter}; @@ -12,7 +13,7 @@ pub trait GrowthRateLibrary: Debug + ValueIdentifiable { /// Calculates the level for a given growth key name and a certain experience. fn calculate_level(&self, growth_rate: &StringKey, experience: u32) -> LevelInt; /// Calculates the experience for a given growth key name and a certain level. - fn calculate_experience(&self, growth_rate: &StringKey, level: LevelInt) -> u32; + fn calculate_experience(&self, growth_rate: &StringKey, level: LevelInt) -> Result; /// Adds a new growth rate with a name and value. fn add_growth_rate(&mut self, key: &StringKey, value: Box); } @@ -41,7 +42,7 @@ impl GrowthRateLibrary for GrowthRateLibraryImpl { self.growth_rates[growth_rate].calculate_level(experience) } /// Calculates the experience for a given growth key name and a certain level. - fn calculate_experience(&self, growth_rate: &StringKey, level: LevelInt) -> u32 { + fn calculate_experience(&self, growth_rate: &StringKey, level: LevelInt) -> Result { self.growth_rates[growth_rate].calculate_experience(level) } @@ -89,7 +90,7 @@ pub mod tests { pub GrowthRateLibrary{} impl GrowthRateLibrary for GrowthRateLibrary { fn calculate_level(&self, growth_rate: &StringKey, experience: u32) -> LevelInt; - fn calculate_experience(&self, growth_rate: &StringKey, level: LevelInt) -> u32; + fn calculate_experience(&self, growth_rate: &StringKey, level: LevelInt) -> Result; fn add_growth_rate(&mut self, key: &StringKey, value: Box); } impl ValueIdentifiable for GrowthRateLibrary { @@ -109,7 +110,7 @@ pub mod tests { #[test] fn add_growth_rate_to_library_and_calculate_experience() { let lib = build(); - assert_eq!(lib.calculate_experience(&"test_growthrate".into(), 1), 0); - assert_eq!(lib.calculate_experience(&"test_growthrate".into(), 3), 10); + assert_eq!(lib.calculate_experience(&"test_growthrate".into(), 1).unwrap(), 0); + assert_eq!(lib.calculate_experience(&"test_growthrate".into(), 3).unwrap(), 10); } } diff --git a/src/static_data/libraries/library_settings.rs b/src/static_data/libraries/library_settings.rs index 56f19c3..e0752bc 100755 --- a/src/static_data/libraries/library_settings.rs +++ b/src/static_data/libraries/library_settings.rs @@ -1,5 +1,7 @@ use crate::defines::LevelInt; use crate::{ValueIdentifiable, ValueIdentifier}; +use anyhow::Result; +use anyhow_ext::ensure; use std::fmt::Debug; /// This library holds several misc settings for the library. @@ -30,14 +32,14 @@ impl LibrarySettingsImpl { /// - `shiny_rate` is the chance of a Pokemon being shiny, as the denominator of a fraction, where /// the nominator is 1. For example, if this is 1000, then the chance of a Pokemon being shiny is /// 1/1000. - pub fn new(maximum_level: LevelInt, shiny_rate: u32) -> Self { - assert!(shiny_rate >= 1); - assert!(maximum_level >= 1); - Self { + pub fn new(maximum_level: LevelInt, shiny_rate: u32) -> Result { + ensure!(shiny_rate >= 1); + ensure!(maximum_level >= 1); + Ok(Self { identifier: Default::default(), maximum_level, shiny_rate, - } + }) } } diff --git a/src/static_data/libraries/nature_library.rs b/src/static_data/libraries/nature_library.rs index 994f1f8..9701547 100644 --- a/src/static_data/libraries/nature_library.rs +++ b/src/static_data/libraries/nature_library.rs @@ -140,4 +140,18 @@ pub mod tests { let name2 = lib.get_nature_name(&n2); assert_eq!(name2, "bar".into()); } + + #[test] + fn create_nature_library_insert_single_and_retrieve_random() { + let mut lib = NatureLibraryImpl::new(2); + lib.load_nature( + "foo".into(), + NatureImpl::new(Statistic::HP, Statistic::Attack, 1.1, 0.9), + ); + let n1 = lib.get_random_nature(&mut Random::new(0)); + assert_eq!(n1.increased_stat(), Statistic::HP); + assert_eq!(n1.decreased_stat(), Statistic::Attack); + assert_eq!(n1.get_stat_modifier(n1.increased_stat()), 1.1); + assert_eq!(n1.get_stat_modifier(n1.decreased_stat()), 0.9); + } } diff --git a/src/static_data/libraries/static_data.rs b/src/static_data/libraries/static_data.rs index 7c98076..b1aa2af 100755 --- a/src/static_data/libraries/static_data.rs +++ b/src/static_data/libraries/static_data.rs @@ -199,7 +199,7 @@ pub mod test { pub fn build() -> StaticDataImpl { StaticDataImpl { identifier: Default::default(), - settings: Box::new(LibrarySettingsImpl::new(100, 100)), + settings: Box::new(LibrarySettingsImpl::new(100, 100).unwrap()), species: crate::static_data::libraries::species_library::tests::build(), moves: crate::static_data::libraries::move_library::tests::build(), items: crate::static_data::libraries::item_library::tests::build(), diff --git a/tests/common/library_loader.rs b/tests/common/library_loader.rs index bdba6f5..a511af2 100755 --- a/tests/common/library_loader.rs +++ b/tests/common/library_loader.rs @@ -65,7 +65,7 @@ pub fn load_library() -> LoadResult { let species_load_time = t2 - t1; let data = StaticDataImpl::new( - Box::new(LibrarySettingsImpl::new(100, 100)), + Box::new(LibrarySettingsImpl::new(100, 100).unwrap()), species, moves, items, diff --git a/tests/common/test_case.rs b/tests/common/test_case.rs index 01ca3de..378cd1d 100755 --- a/tests/common/test_case.rs +++ b/tests/common/test_case.rs @@ -79,6 +79,6 @@ impl TestPokemon { builder = builder.learn_move(StringKey::new(move_name)); } - builder.build() + builder.build().unwrap() } } diff --git a/tests/integration.rs b/tests/integration.rs index e86d46e..d7810e7 100755 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -45,18 +45,56 @@ fn validate_library_load() { ); } +#[test] +fn load_non_existing_wasm_script() { + let start_time = chrono::Utc::now(); + let result = library_loader::load_library(); + let end_time = chrono::Utc::now(); + println!( + "Built library in {} ms\ + \n\t- Types load time: {} ms\ + \n\t- Natures load time: {} ms\ + \n\t- Items load time: {} ms\ + \n\t- Growth Rate load time: {} ms\ + \n\t- Abilities load time: {} ms\ + \n\t- Moves load time: {} ms\ + \n\t- Species load time: {} ms\ + \n\t- WASM load time: {} ms\ + ", + (end_time - start_time).num_milliseconds(), + result.types_load_time.num_milliseconds(), + result.natures_load_time.num_milliseconds(), + result.items_load_time.num_milliseconds(), + result.growth_rate_load_time.num_milliseconds(), + result.abilities_load_time.num_milliseconds(), + result.moves_load_time.num_milliseconds(), + result.species_load_time.num_milliseconds(), + result.wasm_load_time.num_milliseconds(), + ); + let script = result + .library + .load_script(ScriptOwnerData::None, ScriptCategory::Move, &"_____non_existing".into()) + .unwrap(); + + assert!(script.is_none()); +} + /// Assurance has the interesting properties that it creates a special data script internally, and /// deletes that data script through the get_owner functionality. #[test] -#[cfg_attr(miri, ignore)] fn validate_assurance() { let lib = get_library(); let p1 = Arc::new( PokemonBuilder::new(lib.clone(), "charizard".into(), 100) .learn_move("assurance".into()) - .build(), + .build() + .unwrap(), + ); + let p2 = Arc::new( + PokemonBuilder::new(lib.clone(), "venusaur".into(), 100) + .build() + .unwrap(), ); - let p2 = Arc::new(PokemonBuilder::new(lib.clone(), "venusaur".into(), 100).build()); let party1 = BattleParty::new( Arc::new(PokemonParty::new_from_vec(vec![Some(p1.clone())])),