use crate::static_data::{ Ability, Form, GrowthRate, Item, LearnableMoves, MoveData, Nature, Parameter, SecondaryEffect, Species, StaticStatisticSet, StatisticSet, }; use anyhow::anyhow; use hashbrown::HashMap; use parking_lot::RwLock; use std::hash::Hash; use std::sync::atomic::AtomicUsize; use std::sync::{Arc, LazyLock}; /// This function can be called from the FFI to release a handle when it is no longer needed. This /// does not drop the object per se, but will reduce the reference count of the object. If the object /// is then no longer referenced, it will be dropped. #[no_mangle] extern "C" fn ffi_release_handle(handle: usize) { let mut write_lock = FFI_OBJECTS.write(); let mut write_lock_inverse = FFI_OBJECTS_INVERSE.write(); let obj = write_lock.remove(&handle); if let Some(obj) = obj { write_lock_inverse.remove(&obj); } } /// A handle of an object that can be passed over FFI. We use this to avoid passing pointers over the /// FFI boundary. This allows us to be able to move the data around in memory without having to worry /// about the pointers being invalidated. It also allows us to have a type-safe interface. #[repr(C)] pub(super) struct FFIHandle { /// An incrementing handle that is unique for each object. handle: usize, /// The type of the object. _marker: std::marker::PhantomData, } impl Clone for FFIHandle { fn clone(&self) -> Self { Self { handle: self.handle, _marker: std::marker::PhantomData, } } } impl Copy for FFIHandle {} impl Default for FFIHandle { fn default() -> Self { Self { handle: 0, _marker: std::marker::PhantomData, } } } /// An FFIObject we can fetch from the FFIHandle. We store this in a hashmap to be able to fetch /// the object from the handle, and to be able to drop the object when the FFI is done with the handle #[derive(Clone)] #[allow(clippy::missing_docs_in_private_items)] // I'm not documenting these items. pub(super) enum FFIObject { Ability(Arc), EffectParameter(Arc), StatisticSetU8(Arc>), StatisticSetI8(Arc>), StatisticSetU32(Arc>), StaticStatisticSetU16(Arc>), Form(Arc), LearnableMoves(Arc), GrowthRate(Arc), Item(Arc), SecondaryEffect(Arc), MoveData(Arc), Nature(Arc), Species(Arc), SpeciesLibrary(Arc), MoveLibrary(Arc), AbilityLibrary(Arc), ItemLibrary(Arc), GrowthRateLibrary(Arc), LibrarySettings(Arc), NatureLibrary(Arc), TypeLibrary(Arc), StaticData(Arc), // DynamicData TurnChoice(Arc), Pokemon(crate::dynamic_data::Pokemon), LearnedMove(Arc), PokemonParty(Arc), BattleParty(Arc), Battle(crate::dynamic_data::Battle), BattleSide(crate::dynamic_data::BattleSide), BattleRandom(Arc), // DynamicLibrary BattleStatCalculator(Arc), DamageLibrary(Arc), MiscLibrary(Arc), ScriptResolver(Arc), DynamicLibrary(Arc), // Events Event(Arc), } unsafe impl Send for FFIObject {} unsafe impl Sync for FFIObject {} /// The next handle to be used. static NEXT_HANDLE: AtomicUsize = AtomicUsize::new(1); /// A lookup to get an actual object from a handle. static FFI_OBJECTS: LazyLock>> = LazyLock::new(|| RwLock::new(HashMap::new())); /// A lookup to get a handle from an object. static FFI_OBJECTS_INVERSE: LazyLock>> = LazyLock::new(|| RwLock::new(HashMap::new())); impl FFIHandle { /// Get a handle from an object. pub fn get_handle(o: FFIObject) -> Self { if let Some(handle) = FFI_OBJECTS_INVERSE.read().get(&o) { return Self { handle: *handle, _marker: std::marker::PhantomData, }; } let handle = NEXT_HANDLE.fetch_add(1, std::sync::atomic::Ordering::SeqCst); FFI_OBJECTS.write().insert(handle, o.clone()); FFI_OBJECTS_INVERSE.write().insert(o, handle); Self { handle, _marker: std::marker::PhantomData, } } /// An empty handle. This is used when no value is returned. Represented as handle 0 pub fn none() -> Self { Self { handle: 0, _marker: std::marker::PhantomData, } } /// Get the object from the handle. #[allow(clippy::unwrap_used)] // Unwrap used, as it is a potential security bug if this fails pub fn resolve(&self) -> FFIObject { FFI_OBJECTS .read() .get(&self.handle) .ok_or(anyhow!("Unable to get handle {} from FFI_OBJECTS", self.handle)) .unwrap() .clone() } /// Check if the handle is empty. pub fn is_none(&self) -> bool { self.handle == 0 } } #[allow(clippy::from_over_into)] impl Into> for FFIHandle { fn into(self) -> anyhow_ext::Result { Ok(FFI_OBJECTS .read() .get(&self.handle) .ok_or(anyhow!("Unable to get handle"))? .clone()) } } impl Eq for FFIObject {} /// A trait to convert a handle into an object. #[allow(clippy::wrong_self_convention)] pub(super) trait FromFFIHandle { /// Convert a handle into an object. fn from_ffi_handle(&self) -> T; /// Convert a handle into an object, returning `None` if the handle is empty. fn from_ffi_handle_opt(&self) -> Option; } impl Hash for FFIObject { fn hash(&self, state: &mut H) { match self { Self::Ability(a) => Arc::as_ptr(a).hash(state), Self::EffectParameter(a) => Arc::as_ptr(a).hash(state), Self::StatisticSetU8(a) => Arc::as_ptr(a).hash(state), Self::StatisticSetI8(a) => Arc::as_ptr(a).hash(state), Self::StatisticSetU32(a) => Arc::as_ptr(a).hash(state), Self::StaticStatisticSetU16(a) => Arc::as_ptr(a).hash(state), Self::Form(a) => Arc::as_ptr(a).hash(state), Self::LearnableMoves(a) => Arc::as_ptr(a).hash(state), Self::GrowthRate(a) => Arc::as_ptr(a).hash(state), Self::Item(a) => Arc::as_ptr(a).hash(state), Self::SecondaryEffect(a) => Arc::as_ptr(a).hash(state), Self::MoveData(a) => Arc::as_ptr(a).hash(state), Self::Nature(a) => Arc::as_ptr(a).hash(state), Self::Species(a) => Arc::as_ptr(a).hash(state), Self::SpeciesLibrary(a) => Arc::as_ptr(a).hash(state), Self::MoveLibrary(a) => Arc::as_ptr(a).hash(state), Self::AbilityLibrary(a) => Arc::as_ptr(a).hash(state), Self::ItemLibrary(a) => Arc::as_ptr(a).hash(state), Self::GrowthRateLibrary(a) => Arc::as_ptr(a).hash(state), Self::LibrarySettings(a) => Arc::as_ptr(a).hash(state), Self::NatureLibrary(a) => Arc::as_ptr(a).hash(state), Self::TypeLibrary(a) => Arc::as_ptr(a).hash(state), Self::StaticData(a) => Arc::as_ptr(a).hash(state), Self::TurnChoice(a) => Arc::as_ptr(a).hash(state), Self::Pokemon(a) => a.as_ptr().hash(state), Self::LearnedMove(a) => Arc::as_ptr(a).hash(state), Self::PokemonParty(a) => Arc::as_ptr(a).hash(state), Self::BattleParty(a) => Arc::as_ptr(a).hash(state), Self::Battle(a) => a.as_ptr().hash(state), Self::BattleSide(a) => a.as_ptr().hash(state), Self::BattleRandom(a) => Arc::as_ptr(a).hash(state), Self::BattleStatCalculator(a) => Arc::as_ptr(a).hash(state), Self::DamageLibrary(a) => Arc::as_ptr(a).hash(state), Self::MiscLibrary(a) => Arc::as_ptr(a).hash(state), Self::ScriptResolver(a) => Arc::as_ptr(a).hash(state), Self::DynamicLibrary(a) => Arc::as_ptr(a).hash(state), FFIObject::Event(a) => Arc::as_ptr(a).hash(state), } } } impl PartialEq for FFIObject { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Ability(a), Self::Ability(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::EffectParameter(a), Self::EffectParameter(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::StatisticSetU8(a), Self::StatisticSetU8(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::StatisticSetI8(a), Self::StatisticSetI8(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::StatisticSetU32(a), Self::StatisticSetU32(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::StaticStatisticSetU16(a), Self::StaticStatisticSetU16(b)) => { Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr() } (Self::Form(a), Self::Form(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::LearnableMoves(a), Self::LearnableMoves(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::GrowthRate(a), Self::GrowthRate(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::Item(a), Self::Item(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::SecondaryEffect(a), Self::SecondaryEffect(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::MoveData(a), Self::MoveData(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::Nature(a), Self::Nature(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::Species(a), Self::Species(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::SpeciesLibrary(a), Self::SpeciesLibrary(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::MoveLibrary(a), Self::MoveLibrary(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::AbilityLibrary(a), Self::AbilityLibrary(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::ItemLibrary(a), Self::ItemLibrary(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::GrowthRateLibrary(a), Self::GrowthRateLibrary(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::LibrarySettings(a), Self::LibrarySettings(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::NatureLibrary(a), Self::NatureLibrary(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::TypeLibrary(a), Self::TypeLibrary(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::StaticData(a), Self::StaticData(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::TurnChoice(a), Self::TurnChoice(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::Pokemon(a), Self::Pokemon(b)) => a.eq(b), (Self::LearnedMove(a), Self::LearnedMove(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::PokemonParty(a), Self::PokemonParty(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::BattleParty(a), Self::BattleParty(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::Battle(a), Self::Battle(b)) => a.eq(b), (Self::BattleSide(a), Self::BattleSide(b)) => a.eq(b), (Self::BattleRandom(a), Self::BattleRandom(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::BattleStatCalculator(a), Self::BattleStatCalculator(b)) => { Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr() } (Self::DamageLibrary(a), Self::DamageLibrary(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::MiscLibrary(a), Self::MiscLibrary(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::ScriptResolver(a), Self::ScriptResolver(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::DynamicLibrary(a), Self::DynamicLibrary(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), (Self::Event(a), Self::Event(b)) => Arc::as_ptr(a).addr() == Arc::as_ptr(b).addr(), _ => false, } } } /// Helper macro to implement the `From` trait for a `FFIObject` enum variant, and the `FromFFIHandle` /// trait for a `FFIHandle` enum variant. macro_rules! ffi_obj_conversions { ($res_type:ty, $ffi_obj_name:ident) => { impl From<$res_type> for FFIObject { fn from(a: $res_type) -> Self { Self::$ffi_obj_name(a) } } impl FromFFIHandle<$res_type> for FFIHandle<$res_type> { fn from_ffi_handle(&self) -> $res_type { match self.resolve() { FFIObject::$ffi_obj_name(a) => a.clone(), _ => panic!("Invalid handle"), } } fn from_ffi_handle_opt(&self) -> Option<$res_type> { if (self.is_none()) { return None; } match self.resolve() { FFIObject::$ffi_obj_name(a) => Some(a.clone()), _ => panic!("Invalid handle"), } } } }; } ffi_obj_conversions!(Arc, Ability); ffi_obj_conversions!(Arc, EffectParameter); ffi_obj_conversions!(Arc>, StatisticSetI8); ffi_obj_conversions!(Arc>, StatisticSetU8); ffi_obj_conversions!(Arc>, StatisticSetU32); ffi_obj_conversions!(Arc>, StaticStatisticSetU16); ffi_obj_conversions!(Arc, Form); ffi_obj_conversions!(Arc, LearnableMoves); ffi_obj_conversions!(Arc, GrowthRate); ffi_obj_conversions!(Arc, Item); ffi_obj_conversions!(Arc, SecondaryEffect); ffi_obj_conversions!(Arc, MoveData); ffi_obj_conversions!(Arc, Nature); ffi_obj_conversions!(Arc, Species); ffi_obj_conversions!(Arc, SpeciesLibrary); ffi_obj_conversions!(Arc, MoveLibrary); ffi_obj_conversions!(Arc, AbilityLibrary); ffi_obj_conversions!(Arc, ItemLibrary); ffi_obj_conversions!(Arc, GrowthRateLibrary); ffi_obj_conversions!(Arc, LibrarySettings); ffi_obj_conversions!(Arc, NatureLibrary); ffi_obj_conversions!(Arc, TypeLibrary); ffi_obj_conversions!(Arc, StaticData); ffi_obj_conversions!(Arc, TurnChoice); ffi_obj_conversions!(crate::dynamic_data::Pokemon, Pokemon); ffi_obj_conversions!(Arc, LearnedMove); ffi_obj_conversions!(Arc, PokemonParty); ffi_obj_conversions!(Arc, BattleParty); ffi_obj_conversions!(crate::dynamic_data::Battle, Battle); ffi_obj_conversions!(crate::dynamic_data::BattleSide, BattleSide); ffi_obj_conversions!(Arc, BattleRandom); ffi_obj_conversions!(Arc, BattleStatCalculator); ffi_obj_conversions!(Arc, DamageLibrary); ffi_obj_conversions!(Arc, MiscLibrary); ffi_obj_conversions!(Arc, ScriptResolver); ffi_obj_conversions!(Arc, DynamicLibrary); ffi_obj_conversions!(Arc, Event);