From 310bf857d211e269d13316d715f77fd909e8e9f2 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Fri, 3 Jun 2022 16:35:18 +0200 Subject: [PATCH] Major amounts of progress --- .cargo/config.toml | 3 + Cargo.toml | 8 +- src/c_interface/handle/mod.rs | 209 ++++++++++++++ src/c_interface/handle/thread_bound.rs | 262 ++++++++++++++++++ src/c_interface/is_null.rs | 55 ++++ src/c_interface/macros.rs | 86 ++++++ src/c_interface/mod.rs | 7 + src/c_interface/pkmn_result.rs | 233 ++++++++++++++++ src/c_interface/static_data/item.rs | 48 ++++ src/c_interface/static_data/mod.rs | 1 + src/dynamic_data/choices/mod.rs | 18 ++ src/dynamic_data/event_hooks/event_hook.rs | 43 +++ src/dynamic_data/event_hooks/mod.rs | 1 + src/dynamic_data/flow/choice_queue.rs | 2 + src/dynamic_data/flow/mod.rs | 1 + src/dynamic_data/history/history_holder.rs | 2 + src/dynamic_data/history/mod.rs | 1 + .../libraries/battle_stat_calculator.rs | 2 - src/dynamic_data/mod.rs | 4 + src/dynamic_data/models/battle.rs | 82 +++++- src/dynamic_data/models/battle_party.rs | 2 + src/dynamic_data/models/battle_random.rs | 30 ++ src/dynamic_data/models/battle_result.rs | 5 + src/dynamic_data/models/battle_side.rs | 147 +++++++++- src/dynamic_data/models/learned_move.rs | 1 + src/dynamic_data/models/mod.rs | 3 + src/dynamic_data/models/pokemon.rs | 118 +++++++- src/dynamic_data/script_handling/mod.rs | 84 ++++++ src/dynamic_data/script_handling/script.rs | 64 ++++- .../script_handling/script_set.rs | 13 + src/lib.rs | 11 +- src/static_data/items/item_category.rs | 2 + src/static_data/libraries/item_library.rs | 10 +- src/static_data/libraries/species_library.rs | 12 +- src/static_data/species_data/gender.rs | 2 +- src/static_data/statistic_set.rs | 2 +- src/utils/random.rs | 13 + 37 files changed, 1558 insertions(+), 29 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 src/c_interface/handle/mod.rs create mode 100644 src/c_interface/handle/thread_bound.rs create mode 100644 src/c_interface/is_null.rs create mode 100644 src/c_interface/macros.rs create mode 100644 src/c_interface/mod.rs create mode 100644 src/c_interface/pkmn_result.rs create mode 100644 src/c_interface/static_data/item.rs create mode 100644 src/c_interface/static_data/mod.rs create mode 100644 src/dynamic_data/choices/mod.rs create mode 100644 src/dynamic_data/event_hooks/event_hook.rs create mode 100644 src/dynamic_data/event_hooks/mod.rs create mode 100644 src/dynamic_data/flow/choice_queue.rs create mode 100644 src/dynamic_data/flow/mod.rs create mode 100644 src/dynamic_data/history/history_holder.rs create mode 100644 src/dynamic_data/history/mod.rs create mode 100644 src/dynamic_data/models/battle_party.rs create mode 100644 src/dynamic_data/models/battle_random.rs create mode 100644 src/dynamic_data/models/battle_result.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..327a62c --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +[target.x86_64-unknown-linux-gnu] +linker = "/usr/bin/clang" +rustflags = ["-Clink-arg=-fuse-ld=/usr/bin/mold"] \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 412082c..e90e655 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,9 @@ edition = "2018" name = "pkmn_lib" crate_type = ["cdylib"] +[features] +c_interface = [] + [profile.dev] opt-level = 0 debug = true @@ -43,4 +46,7 @@ chrono = "0.4.19" rand = "0.8.3" rand_pcg = "0.3.0" # Used for the hashmap!{} macro, creating a simple initializer pattern for hashmaps. -maplit = "1.0.2" \ No newline at end of file +maplit = "1.0.2" +failure = "0.1.8" +failure_derive = "0.1.8" +lazy_static = "1.4.0" \ No newline at end of file diff --git a/src/c_interface/handle/mod.rs b/src/c_interface/handle/mod.rs new file mode 100644 index 0000000..4f36223 --- /dev/null +++ b/src/c_interface/handle/mod.rs @@ -0,0 +1,209 @@ +#![allow(dead_code)] + +use std::{ + marker::PhantomData, + panic::{RefUnwindSafe, UnwindSafe}, + ptr, slice, +}; + +pub(crate) mod thread_bound; + +use self::thread_bound::ThreadBound; +use super::is_null::IsNull; + +/* +The handles here are wrappers for a shared `&T` and an exclusive `&mut T`. + +They protect from data races, but don't protect from use-after-free bugs. +The caller is expected to maintain that invariant, which in .NET can be +achieved using `SafeHandle`s. +*/ + +/** +A shared handle that can be accessed concurrently by multiple threads. + +The interior value can be treated like `&T`. + +Consumers must ensure a handle is not used again after it has been deallocated. +*/ +#[repr(transparent)] +pub struct HandleShared<'a, T: ?Sized>(*const T, PhantomData<&'a T>); + +unsafe_impl!("The handle is semantically `&T`" => impl<'a, T: ?Sized> Send for HandleShared<'a, T> where &'a T: Send {}); +unsafe_impl!("The handle is semantically `&T`" => impl<'a, T: ?Sized> Sync for HandleShared<'a, T> where &'a T: Sync {}); + +impl<'a, T: ?Sized + RefUnwindSafe> UnwindSafe for HandleShared<'a, T> {} + +impl<'a, T> HandleShared<'a, T> +where + HandleShared<'a, T>: Send + Sync, +{ + pub(super) fn alloc(value: T) -> Self + where + T: 'static, + { + let v = Box::new(value); + HandleShared(Box::into_raw(v), PhantomData) + } + + pub(super) fn as_ref(&self) -> &T { + unsafe_block!("We own the interior value" => &*self.0) + } + + unsafe_fn!("There are no other live references and the handle won't be used again" => + pub(super) fn dealloc(handle: Self, f: impl FnOnce(T) -> R) -> R { + let v = Box::from_raw(handle.0 as *mut T); + f(*v) + }); +} + +/** +A non-shared handle that cannot be accessed by multiple threads. + +The interior value can be treated like `&mut T`. + +The handle is bound to the thread that it was created on to ensure +there's no possibility for data races. Note that, if reverse PInvoke is supported +then it's possible to mutably alias the handle from the same thread if the reverse +call can re-enter the FFI using the same handle. This is technically undefined behaviour. + +The handle _can_ be deallocated from a different thread than the one that created it. + +Consumers must ensure a handle is not used again after it has been deallocated. +*/ +#[repr(transparent)] +pub struct HandleExclusive<'a, T: ?Sized>(*mut ThreadBound, PhantomData<&'a mut T>); + +unsafe_impl!("The handle is semantically `&mut T`" => impl<'a, T: ?Sized> Send for HandleExclusive<'a, T> where &'a mut ThreadBound: Send {}); +unsafe_impl!("The handle uses `ThreadBound` for synchronization" => impl<'a, T: ?Sized> Sync for HandleExclusive<'a, T> where &'a mut ThreadBound: Sync {}); + +impl<'a, T: ?Sized + RefUnwindSafe> UnwindSafe for HandleExclusive<'a, T> {} + +impl<'a, T> HandleExclusive<'a, T> +where + HandleExclusive<'a, T>: Send + Sync, +{ + pub(super) fn alloc(value: T) -> Self + where + T: 'static, + { + let v = Box::new(ThreadBound::new(value)); + HandleExclusive(Box::into_raw(v), PhantomData) + } + + pub(super) fn as_mut(&mut self) -> &mut T { + unsafe_block!("We own the interior value" => &mut *(*self.0).get_raw()) + } + + unsafe_fn!("There are no other live references and the handle won't be used again" => + pub(super) fn dealloc(handle: Self, f: impl FnOnce(T) -> R) -> R + where + T: Send, + { + let v = Box::from_raw(handle.0); + f(v.into_inner()) + }); +} + +/** +An initialized parameter passed by shared reference. +*/ +#[repr(transparent)] +pub struct Ref<'a, T: ?Sized>(*const T, PhantomData<&'a T>); + +impl<'a, T: ?Sized + RefUnwindSafe> UnwindSafe for Ref<'a, T> {} + +unsafe_impl!("The handle is semantically `&mut T`" => impl<'a, T: ?Sized> Send for Ref<'a, T> where &'a T: Send {}); +unsafe_impl!("The handle uses `ThreadBound` for synchronization" => impl<'a, T: ?Sized> Sync for Ref<'a, T> where &'a T: Sync {}); + +impl<'a, T: ?Sized> Ref<'a, T> { + unsafe_fn!("The pointer must be nonnull and will remain valid" => pub fn as_ref(&self) -> &T { + &*self.0 + }); +} + +impl<'a> Ref<'a, u8> { + unsafe_fn!("The pointer must be nonnull, the length is correct, and will remain valid" => pub fn as_bytes(&self, len: usize) -> &[u8] { + slice::from_raw_parts(self.0, len) + }); +} + +/** +An initialized parameter passed by exclusive reference. +*/ +#[repr(transparent)] +pub struct RefMut<'a, T: ?Sized>(*mut T, PhantomData<&'a mut T>); + +impl<'a, T: ?Sized + RefUnwindSafe> UnwindSafe for RefMut<'a, T> {} + +unsafe_impl!("The handle is semantically `&mut T`" => impl<'a, T: ?Sized> Send for RefMut<'a, T> where &'a mut T: Send {}); +unsafe_impl!("The handle uses `ThreadBound` for synchronization" => impl<'a, T: ?Sized> Sync for RefMut<'a, T> where &'a mut T: Sync {}); + +impl<'a, T: ?Sized> RefMut<'a, T> { + unsafe_fn!("The pointer must be nonnull and will remain valid" => pub fn as_mut(&mut self) -> &mut T { + &mut *self.0 + }); +} + +impl<'a> RefMut<'a, u8> { + unsafe_fn!("The pointer must be nonnull, the length is correct, and will remain valid" => pub fn as_bytes_mut(&mut self, len: usize) -> &mut [u8] { + slice::from_raw_parts_mut(self.0, len) + }); +} + +/** +An uninitialized, assignable out parameter. +*/ +#[repr(transparent)] +pub struct Out<'a, T: ?Sized>(*mut T, PhantomData<&'a mut T>); + +impl<'a, T: ?Sized + RefUnwindSafe> UnwindSafe for Out<'a, T> {} + +unsafe_impl!("The handle is semantically `&mut T`" => impl<'a, T: ?Sized> Send for Out<'a, T> where &'a mut T: Send {}); +unsafe_impl!("The handle uses `ThreadBound` for synchronization" => impl<'a, T: ?Sized> Sync for Out<'a, T> where &'a mut T: Sync {}); + +impl<'a, T> Out<'a, T> { + unsafe_fn!("The pointer must be nonnull and valid for writes" => pub fn init(&mut self, value: T) { + ptr::write(self.0, value); + }); +} + +impl<'a> Out<'a, u8> { + unsafe_fn!("The pointer must be nonnull, not overlap the slice, must be valid for the length of the slice, and valid for writes" => pub fn init_bytes(&mut self, value: &[u8]) { + ptr::copy_nonoverlapping(value.as_ptr(), self.0, value.len()); + }); + + unsafe_fn!("The slice must never be read from and must be valid for the length of the slice" => pub fn as_uninit_bytes_mut(&mut self, len: usize) -> &mut [u8] { + slice::from_raw_parts_mut(self.0, len) + }); +} + +impl<'a, T: ?Sized> IsNull for HandleExclusive<'a, T> { + fn is_null(&self) -> bool { + self.0.is_null() + } +} + +impl<'a, T: ?Sized + Sync> IsNull for HandleShared<'a, T> { + fn is_null(&self) -> bool { + self.0.is_null() + } +} + +impl<'a, T: ?Sized> IsNull for Ref<'a, T> { + fn is_null(&self) -> bool { + self.0.is_null() + } +} + +impl<'a, T: ?Sized + Sync> IsNull for RefMut<'a, T> { + fn is_null(&self) -> bool { + self.0.is_null() + } +} + +impl<'a, T: ?Sized> IsNull for Out<'a, T> { + fn is_null(&self) -> bool { + self.0.is_null() + } +} diff --git a/src/c_interface/handle/thread_bound.rs b/src/c_interface/handle/thread_bound.rs new file mode 100644 index 0000000..917422f --- /dev/null +++ b/src/c_interface/handle/thread_bound.rs @@ -0,0 +1,262 @@ +use std::{ + cell::UnsafeCell, + collections::HashMap, + marker::PhantomData, + mem, + ops::{Deref, DerefMut}, + panic::{RefUnwindSafe, UnwindSafe}, + sync::{ + atomic::{AtomicUsize, Ordering}, + Mutex, + }, +}; + +type ThreadId = usize; +type ValueId = usize; + +static GLOBAL_ID: AtomicUsize = AtomicUsize::new(0); +thread_local!(static THREAD_ID: usize = next_thread_id()); + +fn next_thread_id() -> usize { + GLOBAL_ID.fetch_add(1, Ordering::SeqCst) +} + +fn get_thread_id() -> usize { + THREAD_ID.with(|x| *x) +} + +thread_local!(static VALUE_ID: UnsafeCell = UnsafeCell::new(0)); + +fn next_value_id() -> usize { + VALUE_ID.with(|x| { + unsafe_block!("The value never has overlapping mutable aliases" => { + let x = x.get(); + let next = *x; + *x += 1; + + next + }) + }) +} + +struct Registry(HashMap, Box)>)>); + +impl Drop for Registry { + fn drop(&mut self) { + // Remove this thread from the garbage list + let thread_id = get_thread_id(); + { + let mut garbage = GARBAGE.lock().expect("failed to lock garbage queue"); + let _ = garbage.remove(&thread_id); + } + + // Drop any remaining values in the registry + for (_, value) in self.0.iter() { + (value.1)(&value.0); + } + } +} + +thread_local!(static REGISTRY: UnsafeCell = UnsafeCell::new(Registry(Default::default()))); + +lazy_static::lazy_static! { + static ref GARBAGE: Mutex>> = Mutex::new(HashMap::new()); +} + +/** +A value that's bound to the thread it's created on. +*/ +pub struct ThreadBound { + thread_id: ThreadId, + inner: UnsafeCell, +} + +impl ThreadBound { + pub(super) fn new(inner: T) -> Self { + ThreadBound { + thread_id: get_thread_id(), + inner: UnsafeCell::new(inner), + } + } +} + +/* +We don't need to check the thread id when moving out of the inner +value so long as the inner value is itself `Send`. This allows +the .NET runtime to potentially finalize a value on another thread. +*/ +impl ThreadBound { + pub(super) fn into_inner(self) -> T { + self.inner.into_inner() + } +} + +impl ThreadBound { + fn check(&self) { + let current = get_thread_id(); + + if self.thread_id != current { + panic!("attempted to access resource from a different thread"); + } + } + + pub(super) fn get_raw(&self) -> *mut T { + self.check(); + self.inner.get() + } +} + +impl UnwindSafe for ThreadBound {} +impl RefUnwindSafe for ThreadBound {} + +unsafe_impl!("The inner value is safe to send to another thread" => impl Send for ThreadBound {}); +unsafe_impl!("The inner value can't actually be accessed concurrently" => impl Sync for ThreadBound {}); + +/** +A thread-bound value that can be safely dropped from a different thread. + +The value is allocated in thread-local storage. When dropping, if the value +is being accessed from a different thread it will be put onto a garbage queue +for cleanup instead of being moved onto the current thread. +*/ +// NOTE: We require `T: 'static` because the value may live as long +// as the current thread +pub(crate) struct DeferredCleanup { + thread_id: ThreadId, + value_id: ValueId, + _m: PhantomData<*mut T>, +} + +impl Drop for DeferredCleanup { + fn drop(&mut self) { + if mem::needs_drop::() { + if self.is_valid() { + unsafe_block!("The value exists on the current thread" => { + self.into_inner_unchecked(); + }); + } else { + let mut garbage = GARBAGE.lock().expect("failed to lock garbage queue"); + let garbage = garbage.entry(self.thread_id).or_insert_with(|| Vec::new()); + + garbage.push(self.value_id); + } + } + } +} + +impl DeferredCleanup { + pub fn new(value: T) -> Self { + let thread_id = get_thread_id(); + let value_id = next_value_id(); + + // Check for any garbage that needs cleaning up + // If we can't acquire a lock to the global queue + // then we just continue on. + let garbage = { + GARBAGE + .try_lock() + .ok() + .and_then(|mut garbage| garbage.remove(&thread_id)) + }; + + if let Some(garbage) = garbage { + let remove = |value_id: ValueId| { + REGISTRY.with(|registry| { + unsafe_block!("The value never has overlapping mutable aliases" => { + let registry = &mut (*registry.get()).0; + registry.remove(&value_id) + }) + }) + }; + + for value_id in garbage { + if let Some((data, drop)) = remove(value_id) { + drop(&data); + } + } + } + + REGISTRY.with(|registry| { + unsafe_block!("The value never has overlapping mutable aliases" => { + (*registry.get()).0.insert( + value_id, + ( + UnsafeCell::new(Box::into_raw(Box::new(value)) as *mut _), + Box::new(|cell| { + let b: Box = Box::from_raw(*(cell.get() as *mut *mut T)); + mem::drop(b); + }), + ), + ); + }) + }); + + DeferredCleanup { + thread_id, + value_id, + _m: PhantomData, + } + } + + fn with_value>) -> R, R>(&self, f: F) -> R { + let current_thread = get_thread_id(); + + if current_thread != self.thread_id { + panic!("attempted to access resource from a different thread"); + } + + REGISTRY.with(|registry| { + unsafe_block!("There are no active mutable references" => { + let registry = &(*registry.get()).0; + + if let Some(item) = registry.get(&self.value_id) { + f(mem::transmute(&item.0)) + } else { + panic!("attempted to access resource from a different thread"); + } + }) + }) + } + + fn is_valid(&self) -> bool { + let current_thread = get_thread_id(); + let has_value = unsafe_block!("There are no active mutable references" => { + REGISTRY + .try_with(|registry| (*registry.get()).0.contains_key(&self.value_id)) + .unwrap_or(false) + }); + + self.thread_id == current_thread && has_value + } + + unsafe_fn!("The value must originate on the current thread" => fn into_inner_unchecked(&mut self) -> T { + let ptr = REGISTRY + .with(|registry| (*registry.get()).0.remove(&self.value_id)) + .unwrap() + .0 + .into_inner(); + let value = Box::from_raw(ptr as *mut T); + *value + }); +} + +unsafe_impl!( + "The inner value is pinned to the current thread and isn't actually sent. \ + Dropping from another thread will signal cleanup on the original" => + impl Send for DeferredCleanup {}); + +impl Deref for DeferredCleanup { + type Target = T; + + fn deref(&self) -> &T { + self.with_value( + |value| unsafe_block!("The borrow of self protects the inner value" => &*value.get()), + ) + } +} + +impl DerefMut for DeferredCleanup { + fn deref_mut(&mut self) -> &mut T { + self.with_value(|value| unsafe_block!("The borrow of self protects the inner value" => &mut *value.get())) + } +} diff --git a/src/c_interface/is_null.rs b/src/c_interface/is_null.rs new file mode 100644 index 0000000..6fbd1bf --- /dev/null +++ b/src/c_interface/is_null.rs @@ -0,0 +1,55 @@ +use failure_derive::*; + +#[derive(Debug, Fail)] +#[fail(display = "argument `{}` was null", arg)] +pub(super) struct Error { + pub(super) arg: &'static str, +} + +/** +Whether or not a value passed across an FFI boundary is null. +*/ +pub(super) trait IsNull { + fn is_null(&self) -> bool; +} + +macro_rules! never_null { + ($($t:ty),*) => { + $( + impl IsNull for $t { + fn is_null(&self) -> bool { + false + } + } + )* + } +} + +impl IsNull for *const T { + fn is_null(&self) -> bool { + <*const T>::is_null(*self) + } +} + +impl IsNull for *mut T { + fn is_null(&self) -> bool { + <*mut T>::is_null(*self) + } +} + +never_null!( + usize, + isize, + u8, + u16, + u32, + u64, + u128, + i8, + i16, + i32, + i64, + i128, + crate::static_data::items::item_category::ItemCategory, + crate::static_data::items::item_category::BattleItemCategory +); diff --git a/src/c_interface/macros.rs b/src/c_interface/macros.rs new file mode 100644 index 0000000..37e517a --- /dev/null +++ b/src/c_interface/macros.rs @@ -0,0 +1,86 @@ +macro_rules! ffi { + ($(fn $name:ident ( $( $arg_ident:ident : $arg_ty:ty),* ) -> PkmnResult $body:expr)*) => { + $( + #[allow(unsafe_code, unused_attributes)] + #[no_mangle] + pub unsafe extern "C" fn $name( $($arg_ident : $arg_ty),* ) -> PkmnResult { + #[allow(unused_mut)] + fn call( $(mut $arg_ident: $arg_ty),* ) -> PkmnResult { + $( + if $crate::c_interface::is_null::IsNull::is_null(&$arg_ident) { + return PkmnResult::argument_null().context($crate::c_interface::is_null::Error { arg: stringify!($arg_ident) }); + } + )* + + $body + PkmnResult::ok() + } + + PkmnResult::catch(move || call( $($arg_ident),* )) + } + )* + }; +} + +// macro_rules! ffi_no_catch { +// ($(fn $name:ident ( $( $arg_ident:ident : $arg_ty:ty),* ) -> PkmnResult $body:expr)*) => { +// $( +// #[allow(unsafe_code, unused_attributes)] +// #[no_mangle] +// pub unsafe extern "cdecl" fn $name( $($arg_ident : $arg_ty),* ) -> PkmnResult { +// #[allow(unused_mut)] +// fn call( $(mut $arg_ident: $arg_ty),* ) -> PkmnResult { +// $( +// if $crate::c_interface::is_null::IsNull::is_null(&$arg_ident) { +// return PkmnResult::argument_null().context($crate::c_interface::is_null::Error { arg: stringify!($arg_ident) }); +// } +// )* +// +// $body +// } +// +// call( $($arg_ident),* ) +// } +// )* +// }; +// } + +/** +Allow a block of `unsafe` code with a reason. + +The macro will expand to an `unsafe` block. +*/ +macro_rules! unsafe_block { + ($reason:tt => $body:expr) => {{ + #[allow(unsafe_code)] + let __result = unsafe { $body }; + __result + }}; +} + +/** +Allow an `unsafe` function with a reason. + +The macro will expand to an `unsafe fn`. +*/ +macro_rules! unsafe_fn { + ($reason: tt => fn $name:ident $($body:tt)*) => { + unsafe_fn!($reason => pub(self) fn $name $($body)*); + }; + ($reason: tt => $publicity:vis fn $name:ident $($body:tt)*) => { + #[allow(unsafe_code)] + $publicity unsafe fn $name $($body)* + }; +} + +/** +Allow an `unsafe` trait implementation with a reason. + +The macro will expand to an `unsafe impl`. +*/ +macro_rules! unsafe_impl { + ($reason: tt => impl $($body:tt)*) => { + #[allow(unsafe_code)] + unsafe impl $($body)* + }; +} diff --git a/src/c_interface/mod.rs b/src/c_interface/mod.rs new file mode 100644 index 0000000..ccaf058 --- /dev/null +++ b/src/c_interface/mod.rs @@ -0,0 +1,7 @@ +#[macro_use] +mod macros; + +pub(super) mod handle; +pub(super) mod is_null; +pub(super) mod pkmn_result; +mod static_data; diff --git a/src/c_interface/pkmn_result.rs b/src/c_interface/pkmn_result.rs new file mode 100644 index 0000000..17edc5e --- /dev/null +++ b/src/c_interface/pkmn_result.rs @@ -0,0 +1,233 @@ +#![allow(dead_code)] + +use failure::Fail; +use std::{ + any::Any, + cell::RefCell, + fmt::Write, + panic::{catch_unwind, UnwindSafe}, + sync::atomic::{AtomicU32, Ordering}, +}; + +static LAST_ERR_ID: AtomicU32 = AtomicU32::new(0); + +fn next_err_id() -> u32 { + LAST_ERR_ID.fetch_add(1, Ordering::SeqCst) +} + +thread_local! { + static LAST_RESULT: RefCell> = RefCell::new(None); +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PkmnResult { + kind: Kind, + id: u32, +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Kind { + Ok, + ArgumentNull, + InternalError, +} + +impl PkmnResult { + pub(super) fn ok() -> Self { + PkmnResult { + kind: Kind::Ok, + id: 0, + } + } + + pub(super) fn argument_null() -> Self { + PkmnResult { + kind: Kind::ArgumentNull, + id: next_err_id(), + } + } + + pub(super) fn internal_error() -> Self { + PkmnResult { + kind: Kind::InternalError, + id: next_err_id(), + } + } + + pub fn as_err(&self) -> Option<&'static str> { + match self.kind { + Kind::Ok => None, + Kind::ArgumentNull => Some("a required argument was null"), + Kind::InternalError => Some("an internal error occurred"), + } + } + + pub(super) fn context(self, e: impl Fail) -> Self { + assert!( + self.as_err().is_some(), + "context can only be attached to errors" + ); + + let err = Some(format_error(&e)); + + LAST_RESULT.with(|last_result| { + *last_result.borrow_mut() = Some(LastResult { value: self, err }); + }); + + self + } + + pub(super) fn catch(f: impl FnOnce() -> Self + UnwindSafe) -> Self { + LAST_RESULT.with(|last_result| { + { + *last_result.borrow_mut() = None; + } + + match catch_unwind(f) { + Ok(pkmn_result) => { + let extract_err = || pkmn_result.as_err().map(Into::into); + + // Always set the last result so it matches what's returned. + // This `Ok` branch doesn't necessarily mean the result is ok, + // only that there wasn't a panic. + last_result + .borrow_mut() + .map_mut(|last_result| { + last_result.value = pkmn_result; + last_result.err.or_else_mut(extract_err); + }) + .get_or_insert_with(|| LastResult { + value: pkmn_result, + err: extract_err(), + }) + .value + } + Err(e) => { + let extract_panic = + || extract_panic(&e).map(|s| format!("internal panic with '{}'", s)); + + // Set the last error to the panic message if it's not already set + last_result + .borrow_mut() + .map_mut(|last_result| { + last_result.err.or_else_mut(extract_panic); + }) + .get_or_insert_with(|| LastResult { + value: PkmnResult::internal_error(), + err: extract_panic(), + }) + .value + } + } + }) + } + + pub(super) fn with_last_result( + f: impl FnOnce(Option<(PkmnResult, Option<&str>)>) -> R, + ) -> R { + LAST_RESULT.with(|last_result| { + let last_result = last_result.borrow(); + + let last_result = last_result.as_ref().map(|last_result| { + let msg = last_result + .value + .as_err() + .and_then(|_| last_result.err.as_ref().map(|msg| msg.as_ref())); + + (last_result.value, msg) + }); + + f(last_result) + }) + } +} + +impl From for PkmnResult +where + E: Fail, +{ + fn from(e: E) -> Self { + PkmnResult::internal_error().context(e) + } +} + +#[derive(Debug)] +struct LastResult { + value: PkmnResult, + err: Option, +} + +fn format_error(err: &dyn Fail) -> String { + let mut error_string = String::new(); + + let mut causes = Some(err).into_iter().chain(err.iter_causes()); + + if let Some(cause) = causes.next() { + let _ = writeln!(error_string, "{}.", cause); + } + + let mut next = causes.next(); + while next.is_some() { + let cause = next.unwrap(); + let _ = writeln!(error_string, " caused by: {}", cause); + next = causes.next(); + } + + if let Some(backtrace) = err.backtrace() { + let _ = writeln!(error_string, "backtrace: {}", backtrace); + } + + error_string +} + +fn extract_panic(err: &Box) -> Option { + if let Some(err) = err.downcast_ref::() { + Some(err.clone()) + } else if let Some(err) = err.downcast_ref::<&'static str>() { + Some((*err).to_owned()) + } else { + None + } +} + +trait OptionMutExt { + /** + Map and mutate an option in place. + */ + fn map_mut(&mut self, f: F) -> &mut Self + where + F: FnOnce(&mut T); + + /** + Replace an option if it doesn't contain a value. + */ + fn or_else_mut(&mut self, f: F) -> &mut Self + where + F: FnOnce() -> Option; +} + +impl OptionMutExt for Option { + fn map_mut(&mut self, f: F) -> &mut Self + where + F: FnOnce(&mut T), + { + if let Some(ref mut t) = *self { + f(t) + } + + self + } + + fn or_else_mut(&mut self, f: F) -> &mut Self + where + F: FnOnce() -> Option, + { + if self.is_none() { + *self = f(); + } + + self + } +} diff --git a/src/c_interface/static_data/item.rs b/src/c_interface/static_data/item.rs new file mode 100644 index 0000000..eca9d1e --- /dev/null +++ b/src/c_interface/static_data/item.rs @@ -0,0 +1,48 @@ +use super::super::handle::Out; +use super::super::pkmn_result::PkmnResult; +use crate::c_interface::handle::{thread_bound, HandleExclusive}; +use crate::static_data::items::item::Item; +use std::collections::HashSet; +use std::ffi::CStr; +use std::os::raw::c_char; +use std::slice; +use thread_bound::DeferredCleanup; + +#[repr(C)] +pub struct ItemC { + inner: DeferredCleanup, +} + +type ItemHandle<'a> = HandleExclusive<'a, ItemC>; + +ffi! { + fn pkmnlib_item_new( + name: *const c_char, + category: u8, + battle_category: u8, + price: i32, + flags: *mut *const c_char, + flags_length: usize, + out: Out + ) -> PkmnResult { + unsafe { + let v = slice::from_raw_parts(flags, flags_length as usize).to_vec(); + let mut flags_map = HashSet::new(); + for flag in v { + flags_map.insert(CStr::from_ptr(flag).to_str().unwrap().to_string()); + } + + let handle = ItemHandle::alloc(ItemC { + inner: thread_bound::DeferredCleanup::new(Item::new( + CStr::from_ptr(name).to_str().unwrap(), + std::mem::transmute(category), + std::mem::transmute(battle_category), + price, + flags_map, + )), + }); + + out.init(handle); + } + } +} diff --git a/src/c_interface/static_data/mod.rs b/src/c_interface/static_data/mod.rs new file mode 100644 index 0000000..f326829 --- /dev/null +++ b/src/c_interface/static_data/mod.rs @@ -0,0 +1 @@ +mod item; diff --git a/src/dynamic_data/choices/mod.rs b/src/dynamic_data/choices/mod.rs new file mode 100644 index 0000000..0ba0d79 --- /dev/null +++ b/src/dynamic_data/choices/mod.rs @@ -0,0 +1,18 @@ +use crate::dynamic_data::models::learned_move::LearnedMove; +use crate::dynamic_data::models::pokemon::Pokemon; + +#[derive(Debug)] +pub enum TurnChoice<'a> { + Move { + user: &'a Pokemon<'a>, + used_move: Box, + }, +} + +impl<'a> TurnChoice<'a> { + pub fn user(&self) -> &'a Pokemon<'a> { + match self { + TurnChoice::Move { user, .. } => user, + } + } +} diff --git a/src/dynamic_data/event_hooks/event_hook.rs b/src/dynamic_data/event_hooks/event_hook.rs new file mode 100644 index 0000000..5575e0f --- /dev/null +++ b/src/dynamic_data/event_hooks/event_hook.rs @@ -0,0 +1,43 @@ +use crate::dynamic_data::models::pokemon::Pokemon; +use std::fmt::{Debug, Formatter}; +use std::sync::{Arc, Mutex}; + +pub struct EventHook { + evt_hook_function: Vec)>, +} + +impl EventHook { + pub fn register_listener(&mut self, func: fn(&Box<&Event>)) { + self.evt_hook_function.push(func); + } + + pub fn trigger(&self, evt: Event) { + let b = Box::new(&evt); + for f in &self.evt_hook_function { + f(&b); + } + } +} + +impl Default for EventHook { + fn default() -> Self { + EventHook { + evt_hook_function: vec![], + } + } +} + +impl Debug for EventHook { + fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result { + Ok(()) + } +} + +#[derive(Debug)] +pub enum Event<'a> { + Switch { + side_index: u8, + index: u8, + pokemon: Option>>>, + }, +} diff --git a/src/dynamic_data/event_hooks/mod.rs b/src/dynamic_data/event_hooks/mod.rs new file mode 100644 index 0000000..2181c6e --- /dev/null +++ b/src/dynamic_data/event_hooks/mod.rs @@ -0,0 +1 @@ +pub mod event_hook; diff --git a/src/dynamic_data/flow/choice_queue.rs b/src/dynamic_data/flow/choice_queue.rs new file mode 100644 index 0000000..7b0bb0a --- /dev/null +++ b/src/dynamic_data/flow/choice_queue.rs @@ -0,0 +1,2 @@ +#[derive(Debug)] +pub struct ChoiceQueue {} diff --git a/src/dynamic_data/flow/mod.rs b/src/dynamic_data/flow/mod.rs new file mode 100644 index 0000000..ac4fa1e --- /dev/null +++ b/src/dynamic_data/flow/mod.rs @@ -0,0 +1 @@ +pub mod choice_queue; diff --git a/src/dynamic_data/history/history_holder.rs b/src/dynamic_data/history/history_holder.rs new file mode 100644 index 0000000..897ce34 --- /dev/null +++ b/src/dynamic_data/history/history_holder.rs @@ -0,0 +1,2 @@ +#[derive(Debug)] +pub struct HistoryHolder {} diff --git a/src/dynamic_data/history/mod.rs b/src/dynamic_data/history/mod.rs new file mode 100644 index 0000000..702aa8f --- /dev/null +++ b/src/dynamic_data/history/mod.rs @@ -0,0 +1 @@ +pub mod history_holder; diff --git a/src/dynamic_data/libraries/battle_stat_calculator.rs b/src/dynamic_data/libraries/battle_stat_calculator.rs index fc2cff7..4d218aa 100644 --- a/src/dynamic_data/libraries/battle_stat_calculator.rs +++ b/src/dynamic_data/libraries/battle_stat_calculator.rs @@ -1,5 +1,3 @@ -use crate::dynamic_data::models::pokemon::Pokemon; - pub trait BattleStatCalculator { //fn is_critical(attack: &ExecutingMove, target: &Pokemon, hit: u8); } diff --git a/src/dynamic_data/mod.rs b/src/dynamic_data/mod.rs index bf17f0d..9a463d1 100644 --- a/src/dynamic_data/mod.rs +++ b/src/dynamic_data/mod.rs @@ -1,3 +1,7 @@ +pub mod choices; +pub mod event_hooks; +pub mod flow; +pub mod history; pub mod libraries; pub mod models; pub mod script_handling; diff --git a/src/dynamic_data/models/battle.rs b/src/dynamic_data/models/battle.rs index 455a8c4..9e0850f 100644 --- a/src/dynamic_data/models/battle.rs +++ b/src/dynamic_data/models/battle.rs @@ -1,2 +1,80 @@ -#[derive(Debug)] -pub struct Battle {} +use crate::dynamic_data::event_hooks::event_hook::EventHook; +use crate::dynamic_data::flow::choice_queue::ChoiceQueue; +use crate::dynamic_data::history::history_holder::HistoryHolder; +use crate::dynamic_data::libraries::dynamic_library::DynamicLibrary; +use crate::dynamic_data::models::battle_party::BattleParty; +use crate::dynamic_data::models::battle_random::BattleRandom; +use crate::dynamic_data::models::battle_result::BattleResult; +use crate::dynamic_data::models::battle_side::BattleSide; +use crate::dynamic_data::script_handling::script_set::ScriptSet; +use derive_getters::Getters; +use std::sync::{Arc, Mutex}; + +#[derive(Getters, Debug)] +pub struct Battle<'a> { + library: &'a DynamicLibrary<'a>, + parties: Vec, + can_flee: bool, + number_of_sides: u8, + pokemon_per_side: u8, + sides: Vec>, + #[getter(skip)] + random: BattleRandom, + choice_queue: ChoiceQueue, + has_ended: bool, + result: BattleResult, + event_hook: EventHook, + history_holder: Box, + current_turn: u32, + volatile: ScriptSet, + last_turn_time: i64, +} + +impl<'a> Battle<'a> { + pub fn new( + library: &'a DynamicLibrary<'a>, + parties: Vec, + can_flee: bool, + number_of_sides: u8, + pokemon_per_side: u8, + random_seed: Option, + ) -> Arc> { + let random = if let Some(seed) = random_seed { + BattleRandom::new_with_seed(seed) + } else { + BattleRandom::new() + }; + let sides = Vec::with_capacity(number_of_sides as usize); + let battle = Arc::new(Mutex::new(Self { + library, + parties, + can_flee, + number_of_sides, + pokemon_per_side, + sides, + random, + choice_queue: ChoiceQueue {}, + has_ended: false, + result: BattleResult::Inconclusive, + event_hook: Default::default(), + history_holder: Box::new(HistoryHolder {}), + current_turn: 0, + volatile: ScriptSet {}, + last_turn_time: 0, + })); + + for i in 0..number_of_sides { + battle.lock().unwrap().sides[i as usize] = + BattleSide::new(i, Arc::downgrade(&battle), pokemon_per_side); + } + battle + } + + pub fn random(&mut self) -> &mut BattleRandom { + &mut self.random + } + + pub fn can_slot_be_filled(&self) -> bool { + todo!() + } +} diff --git a/src/dynamic_data/models/battle_party.rs b/src/dynamic_data/models/battle_party.rs new file mode 100644 index 0000000..8dbef20 --- /dev/null +++ b/src/dynamic_data/models/battle_party.rs @@ -0,0 +1,2 @@ +#[derive(Debug)] +pub struct BattleParty {} diff --git a/src/dynamic_data/models/battle_random.rs b/src/dynamic_data/models/battle_random.rs new file mode 100644 index 0000000..e03adac --- /dev/null +++ b/src/dynamic_data/models/battle_random.rs @@ -0,0 +1,30 @@ +use crate::utils::random::Random; +use std::fmt::{Debug, Formatter}; + +pub struct BattleRandom { + random: Random, +} + +impl Debug for BattleRandom { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("BattleRandom").finish() + } +} + +impl BattleRandom { + pub fn new() -> Self { + return BattleRandom { + random: Default::default(), + }; + } + + pub fn new_with_seed(seed: u128) -> Self { + return BattleRandom { + random: Random::new(seed), + }; + } + + pub fn get_rng(&mut self) -> &mut Random { + &mut self.random + } +} diff --git a/src/dynamic_data/models/battle_result.rs b/src/dynamic_data/models/battle_result.rs new file mode 100644 index 0000000..b0cd5fe --- /dev/null +++ b/src/dynamic_data/models/battle_result.rs @@ -0,0 +1,5 @@ +#[derive(Debug)] +pub enum BattleResult { + Inconclusive, + Conclusive(u8), +} diff --git a/src/dynamic_data/models/battle_side.rs b/src/dynamic_data/models/battle_side.rs index ac78762..074e325 100644 --- a/src/dynamic_data/models/battle_side.rs +++ b/src/dynamic_data/models/battle_side.rs @@ -1,2 +1,145 @@ -#[derive(Debug)] -pub struct BattleSide {} +use crate::dynamic_data::choices::TurnChoice; +use crate::dynamic_data::event_hooks::event_hook::Event; +use crate::dynamic_data::models::battle::Battle; +use crate::dynamic_data::models::pokemon::Pokemon; +use crate::dynamic_data::script_handling::ScriptSource; +use crate::script_hook; +use derive_getters::Getters; +use std::sync::{Arc, Mutex, Weak}; + +#[derive(Getters, Debug)] +pub struct BattleSide<'a> { + index: u8, + pokemon_per_side: u8, + pokemon: Vec>>>>, + choices: Vec>>>, + fillable_slots: Vec, + choices_set: u8, + battle: Weak>>, + has_fled: bool, +} + +impl<'a> BattleSide<'a> { + pub fn new(index: u8, battle: Weak>>, pokemon_per_side: u8) -> Self { + let mut pokemon = Vec::with_capacity(pokemon_per_side as usize); + let mut choices = Vec::with_capacity(pokemon_per_side as usize); + let mut fillable_slots = Vec::with_capacity(pokemon_per_side as usize); + + for i in 0..pokemon_per_side { + pokemon[i as usize] = None; + choices[i as usize] = None; + fillable_slots[i as usize] = true; + } + + Self { + index, + pokemon_per_side, + pokemon, + choices, + fillable_slots, + choices_set: 0, + battle, + has_fled: false, + } + } + + pub fn all_choices_set(&self) -> bool { + self.choices_set == self.pokemon_per_side + } + + /// Returns true if there are slots that need to be filled with a new pokemon, that have parties + /// responsible for them. Returns false if all slots are filled with usable pokemon, or slots are + /// empty, but can't be filled by any party anymore. + pub fn all_slots_filled(&self) -> bool { + for pokemon in &self.pokemon { + if (!pokemon.is_none() || !pokemon.as_ref().unwrap().lock().unwrap().is_usable()) + && self + .battle + .upgrade() + .unwrap() + .lock() + .unwrap() + .can_slot_be_filled() + { + return false; + } + } + true + } + + pub fn set_choice(&mut self, choice: TurnChoice<'a>) { + for (index, pokemon_slot) in self.pokemon.iter().enumerate() { + if let Some(pokemon) = pokemon_slot { + if pokemon.lock().unwrap().unique_identifier() == choice.user().unique_identifier() + { + self.choices[index] = Some(Arc::new(choice)); + self.choices_set += 1; + return; + } + } + } + } + + pub fn force_clear_pokemon(&mut self, index: u8) { + self.pokemon[index as usize] = None; + } + + pub fn set_pokemon(&mut self, index: u8, pokemon: Option>>>) { + let old = &mut self.pokemon[index as usize]; + if let Some(old_pokemon) = old { + let mut p = old_pokemon.lock().unwrap(); + script_hook!(on_remove, p,); + p.set_on_battlefield(false); + } + self.pokemon[index as usize] = pokemon; + let pokemon = &self.pokemon[index as usize]; + if let Some(pokemon_mutex) = pokemon { + let mut pokemon = pokemon_mutex.lock().unwrap(); + pokemon.set_battle_data(self.battle.clone(), self.index); + pokemon.set_on_battlefield(true); + pokemon.set_battle_index(index); + + let battle = self.battle.upgrade().unwrap(); + let battle = battle.lock().unwrap(); + for side in battle.sides() { + if *side.index() == self.index { + continue; + } + for opponent in side.pokemon().iter().flatten() { + let mut opponent = opponent.lock().unwrap(); + opponent.mark_opponent_as_seen(*pokemon.unique_identifier()); + pokemon.mark_opponent_as_seen(*opponent.unique_identifier()); + } + } + battle.event_hook().trigger(Event::Switch { + side_index: self.index, + index, + pokemon: Some(pokemon_mutex.clone()), + }); + script_hook!(on_switch_in, pokemon, &pokemon); + } else { + let battle = self.battle.upgrade().unwrap(); + let battle = battle.lock().unwrap(); + battle.event_hook().trigger(Event::Switch { + side_index: self.index, + index, + pokemon: None, + }); + } + } + + pub fn is_pokemon_on_side(&self, pokemon: Arc>) -> bool { + for p in self.pokemon.iter().flatten() { + if p.lock().unwrap().unique_identifier() == pokemon.unique_identifier() { + return true; + } + } + false + } +} + +impl<'a> ScriptSource for BattleSide<'a> { + fn get_script_count(&self) { + todo!() + } +} diff --git a/src/dynamic_data/models/learned_move.rs b/src/dynamic_data/models/learned_move.rs index d944840..40d36d3 100644 --- a/src/dynamic_data/models/learned_move.rs +++ b/src/dynamic_data/models/learned_move.rs @@ -1 +1,2 @@ +#[derive(Debug)] pub struct LearnedMove {} diff --git a/src/dynamic_data/models/mod.rs b/src/dynamic_data/models/mod.rs index e230b40..197af53 100644 --- a/src/dynamic_data/models/mod.rs +++ b/src/dynamic_data/models/mod.rs @@ -1,4 +1,7 @@ pub mod battle; +pub mod battle_party; +pub mod battle_random; +pub mod battle_result; pub mod battle_side; pub mod learned_move; pub mod pokemon; diff --git a/src/dynamic_data/models/pokemon.rs b/src/dynamic_data/models/pokemon.rs index 7fc23da..f02be5c 100644 --- a/src/dynamic_data/models/pokemon.rs +++ b/src/dynamic_data/models/pokemon.rs @@ -1,10 +1,10 @@ use crate::defines::{LevelInt, MAX_MOVES}; use crate::dynamic_data::libraries::dynamic_library::DynamicLibrary; use crate::dynamic_data::models::battle::Battle; -use crate::dynamic_data::models::battle_side::BattleSide; use crate::dynamic_data::models::learned_move::LearnedMove; use crate::dynamic_data::script_handling::script::Script; use crate::dynamic_data::script_handling::script_set::ScriptSet; +use crate::dynamic_data::script_handling::ScriptSource; use crate::static_data::items::item::Item; use crate::static_data::species_data::ability_index::AbilityIndex; use crate::static_data::species_data::form::Form; @@ -12,18 +12,36 @@ use crate::static_data::species_data::gender::Gender; use crate::static_data::species_data::species::Species; use crate::static_data::statistic_set::StatisticSet; use crate::static_data::statistics::Statistic; +use crate::utils::random::Random; use derive_getters::Getters; use std::collections::HashSet; +use std::sync::{Mutex, Weak}; -#[derive(Getters)] +#[derive(Debug)] pub struct PokemonBattleData<'a> { - battle: &'a Battle, - battle_side: &'a BattleSide, + battle: Weak>>, + battle_side_index: u8, + index: u8, on_battle_field: bool, - seen_opponents: HashSet>, + seen_opponents: HashSet, } -#[derive(Getters)] +impl<'a> PokemonBattleData<'a> { + pub fn battle(&'a mut self) -> &'a mut Weak>> { + &mut self.battle + } + pub fn battle_side_index(&self) -> u8 { + self.battle_side_index + } + pub fn on_battle_field(&'a mut self) -> &mut bool { + &mut self.on_battle_field + } + pub fn seen_opponents(&'a mut self) -> &'a mut HashSet { + &mut self.seen_opponents + } +} + +#[derive(Getters, Debug)] pub struct Pokemon<'a> { library: &'a DynamicLibrary<'a>, species: &'a Species<'a>, @@ -40,6 +58,9 @@ pub struct Pokemon<'a> { held_item: Option<&'a Item>, health: u32, + weight: f32, + height: f32, + stat_boost: StatisticSet, flat_stats: StatisticSet, boosted_stats: StatisticSet, @@ -79,6 +100,8 @@ impl<'a> Pokemon<'a> { .growth_rates() .calculate_experience(species.growth_rate(), level); let health = form.get_base_stat(Statistic::HP) as u32; + let weight = *form.weight(); + let height = *form.height(); Pokemon { library, species, @@ -92,6 +115,8 @@ impl<'a> Pokemon<'a> { coloring, held_item: None, health, + weight, + height, stat_boost: Default::default(), flat_stats: *form.base_stats(), boosted_stats: *form.base_stats(), @@ -108,6 +133,87 @@ impl<'a> Pokemon<'a> { volatile: ScriptSet {}, } } + + pub fn change_species(&mut self, species: &'a Species, form: &'a Form) { + self.species = species; + self.form = form; + + // If the pokemon is genderless, but it's new species is not, we want to set its gender + if self.gender != Gender::Genderless && *species.gender_rate() < 0.0 { + if self.battle_data.is_some() { + let battle = self.battle_data.as_mut().unwrap(); + self.gender = species.get_random_gender( + battle + .battle + .upgrade() + .unwrap() + .lock() + .unwrap() + .random() + .get_rng(), + ); + } else { + self.gender = species.get_random_gender(&mut Random::default()); + } + } + // Else if the new species is genderless, but the pokemon has a gender, make the creature genderless. + else if *species.gender_rate() < 0.0 && self.gender != Gender::Genderless { + self.gender = Gender::Genderless; + } + // TODO: Battle Event trigger + } + + pub fn is_usable(&self) -> bool { + todo!() + } + + pub fn set_battle_data(&mut self, battle: Weak>>, battle_side_index: u8) { + if let Some(battle_data) = &mut self.battle_data { + battle_data.battle = battle; + battle_data.battle_side_index = battle_side_index; + } else { + self.battle_data = Some(PokemonBattleData { + battle, + battle_side_index, + index: 0, + on_battle_field: false, + seen_opponents: Default::default(), + }) + } + } + + pub fn set_on_battlefield(&mut self, value: bool) { + if let Some(data) = &mut self.battle_data { + data.on_battle_field = value; + if !value { + self.reset_active_scripts(); + self.weight = *self.form.weight(); + self.height = *self.form.height(); + } + } + } + + pub fn set_battle_index(&mut self, index: u8) { + if let Some(data) = &mut self.battle_data { + data.index = index; + } + } + + pub fn mark_opponent_as_seen(&mut self, unique_identifier: u32) { + if let Some(battle_data) = &mut self.battle_data { + battle_data.seen_opponents.insert(unique_identifier); + } + } + + pub fn reset_active_scripts(&mut self) { + todo!() + } +} + +impl<'a> ScriptSource for Pokemon<'a> { + fn get_script_count(&self) { + todo!() + } } #[cfg(test)] diff --git a/src/dynamic_data/script_handling/mod.rs b/src/dynamic_data/script_handling/mod.rs index 5821ee2..b875d6b 100644 --- a/src/dynamic_data/script_handling/mod.rs +++ b/src/dynamic_data/script_handling/mod.rs @@ -1,2 +1,86 @@ +use crate::dynamic_data::script_handling::script::Script; +use crate::dynamic_data::script_handling::script_set::ScriptSet; + pub mod script; pub mod script_set; + +#[macro_export] +macro_rules! script_hook { + ($hook_name: ident, $source: ident, $($parameters: expr),*) => { + let mut aggregator = $source.get_script_iterator(); + while let Some(script) = aggregator.get_next() { + if script.is_suppressed() { + continue; + } + script.$hook_name($($parameters),*); + } + }; +} + +pub trait ScriptSource { + fn get_script_iterator(&self) -> ScriptAggregator { + todo!() + } + fn get_script_count(&self); +} + +pub enum ScriptWrapper<'a> { + Script(&'a Box), + Set(&'a ScriptSet), +} + +pub struct ScriptAggregator<'a> { + scripts: Vec>>, + size: i32, + index: i32, + set_index: i32, +} + +impl<'a> ScriptAggregator<'a> { + pub fn new(scripts: Vec>>) -> Self { + let len = scripts.len(); + Self { + scripts, + size: len as i32, + index: -1, + set_index: -1, + } + } + + fn increment_to_next_value(&mut self) -> bool { + if self.index != -1 { + if let Some(wrapper) = &self.scripts[self.index as usize] { + if let ScriptWrapper::Set(set) = wrapper { + self.set_index += 1; + if self.set_index as usize >= set.count() { + self.set_index = -1; + } else { + return true; + } + } + } + } + self.index += 1; + for index in self.index..self.size { + self.index = index; + if let Some(wrapper) = &self.scripts[index as usize] { + if let ScriptWrapper::Set(..) = wrapper { + self.set_index = 0; + } + return true; + } + } + + false + } + + pub fn get_next(&mut self) -> Option<&Box> { + if !self.increment_to_next_value() { + return None; + } + return match self.scripts[self.index as usize].as_ref().unwrap() { + ScriptWrapper::Script(script) => Some(script), + ScriptWrapper::Set(set) => Some(set.at(self.set_index as usize)), + }; + } +} diff --git a/src/dynamic_data/script_handling/script.rs b/src/dynamic_data/script_handling/script.rs index 3f2da9d..4f1a1bc 100644 --- a/src/dynamic_data/script_handling/script.rs +++ b/src/dynamic_data/script_handling/script.rs @@ -1 +1,63 @@ -pub trait Script {} +use crate::dynamic_data::models::pokemon::Pokemon; +use std::fmt::{Debug, Formatter}; + +pub trait Script { + fn is_suppressed(&self) -> bool { + self.get_suppressed_count() > 0 + } + fn get_suppressed_count(&self) -> usize; + fn add_suppression(&self); + fn remove_suppression(&self); + + // FIXME: add missing parameters + fn stack(&self); + fn on_remove(&self); + fn on_initialize(&self); + fn on_before_turn(&self); + fn change_speed(&self); + fn change_priority(&self); + fn change_attack(&self); + fn change_number_of_hits(&self); + fn prevent_attack(&self); + fn fail_attack(&self); + fn stop_before_attack(&self); + fn on_before_attack(&self); + fn fail_incoming_attack(&self); + fn is_invulnerable(&self); + fn on_attack_miss(&self); + fn change_attack_type(&self); + fn block_critical(&self); + fn override_base_power(&self); + fn change_damage_stats_user(&self); + fn bypass_defensive_stat(&self); + fn bypass_offensive_stat(&self); + fn change_stat_modifier(&self); + fn change_damage_modifier(&self); + fn change_damage(&self); + fn change_incoming_damage(&self); + fn on_incoming_hit(&self); + fn on_opponent_faints(&self); + fn prevent_stat_boost_change(&self); + fn change_stat_boost_change(&self); + fn on_secondary_effect(&self); + fn on_after_hits(&self); + fn prevent_self_switch(&self); + fn prevent_opponent_switch(&self); + fn modify_effect_chance(&self); + fn modify_incoming_effect_change(&self); + fn on_fail(&self); + fn on_opponent_fail(&self); + fn prevent_self_run_away(&self); + fn prevent_opponent_run_away(&self); + fn on_end_turn(&self); + fn on_damage(&self); + fn on_faint(&self); + fn on_switch_in<'b>(&self, pokemon: &'b Pokemon); + fn on_after_held_item_consume(&self); +} + +impl Debug for dyn Script { + fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result { + Ok(()) + } +} diff --git a/src/dynamic_data/script_handling/script_set.rs b/src/dynamic_data/script_handling/script_set.rs index 8a1a6b4..6c8b5ee 100644 --- a/src/dynamic_data/script_handling/script_set.rs +++ b/src/dynamic_data/script_handling/script_set.rs @@ -1 +1,14 @@ +use crate::dynamic_data::script_handling::script::Script; + +#[derive(Debug)] pub struct ScriptSet {} + +impl ScriptSet { + pub fn count(&self) -> usize { + todo!() + } + + pub fn at(&self, _index: usize) -> &Box { + todo!() + } +} diff --git a/src/lib.rs b/src/lib.rs index 7fc7187..9b8ec54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,18 @@ // The too many arguments is annoying, especially for when we create constructors, disable. #![allow(clippy::too_many_arguments, clippy::needless_range_loop)] -#![feature(nll)] -#![feature(in_band_lifetimes)] +#![feature(test)] +#![feature(bench_black_box)] +#![feature(let_chains)] #[macro_use] extern crate maplit; +#[cfg(feature = "c_interface")] +#[macro_use] +extern crate lazy_static; + +mod c_interface; + pub mod defines; pub mod dynamic_data; pub mod static_data; diff --git a/src/static_data/items/item_category.rs b/src/static_data/items/item_category.rs index b1167a5..60dcaa3 100644 --- a/src/static_data/items/item_category.rs +++ b/src/static_data/items/item_category.rs @@ -1,4 +1,5 @@ #[derive(Debug)] +#[repr(u8)] pub enum ItemCategory { MiscItem, Pokeball, @@ -11,6 +12,7 @@ pub enum ItemCategory { } #[derive(Debug)] +#[repr(u8)] pub enum BattleItemCategory { Healing, StatusHealing, diff --git a/src/static_data/libraries/item_library.rs b/src/static_data/libraries/item_library.rs index 470fbea..22c190f 100644 --- a/src/static_data/libraries/item_library.rs +++ b/src/static_data/libraries/item_library.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; #[derive(Debug)] pub struct ItemLibrary { - map: HashMap, + map: HashMap>, list: Vec, } @@ -17,8 +17,8 @@ impl ItemLibrary { } } -impl DataLibrary<'_, Item> for ItemLibrary { - fn map(&self) -> &HashMap { +impl DataLibrary<'_, Box> for ItemLibrary { + fn map(&self) -> &HashMap> { &self.map } @@ -26,7 +26,7 @@ impl DataLibrary<'_, Item> for ItemLibrary { &self.list } - fn get_modify(&mut self) -> (&mut HashMap, &mut Vec) { + fn get_modify(&mut self) -> (&mut HashMap>, &mut Vec) { (&mut self.map, &mut self.list) } } @@ -54,7 +54,7 @@ pub mod tests { let m = build_item(); // Borrow as mut so we can insert let w = &mut lib; - w.add("foo", m); + w.add("foo", Box::from(m)); // Drops borrow as mut lib diff --git a/src/static_data/libraries/species_library.rs b/src/static_data/libraries/species_library.rs index 2be91d1..1292ed7 100644 --- a/src/static_data/libraries/species_library.rs +++ b/src/static_data/libraries/species_library.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; #[derive(Debug)] pub struct SpeciesLibrary<'a> { - map: HashMap>, + map: HashMap>>, list: Vec, } @@ -17,8 +17,8 @@ impl<'a> SpeciesLibrary<'a> { } } -impl<'a> DataLibrary<'a, Species<'a>> for SpeciesLibrary<'a> { - fn map(&self) -> &HashMap> { +impl<'a> DataLibrary<'a, Box>> for SpeciesLibrary<'a> { + fn map(&self) -> &HashMap>> { &self.map } @@ -26,7 +26,7 @@ impl<'a> DataLibrary<'a, Species<'a>> for SpeciesLibrary<'a> { &self.list } - fn get_modify(&mut self) -> (&mut HashMap>, &mut Vec) { + fn get_modify(&mut self) -> (&mut HashMap>>, &mut Vec) { (&mut self.map, &mut self.list) } } @@ -69,7 +69,7 @@ pub mod tests { let species = build_species(); // Borrow as mut so we can insert let w = &mut lib; - w.add("foo", species); + w.add("foo", Box::from(species)); // Drops borrow as mut lib @@ -84,7 +84,7 @@ pub mod tests { let mon = r.get("foo"); assert!(mon.is_some()); assert_eq!(*mon.unwrap().id(), 0_u16); - assert_eq!(mon.unwrap().name(), "foo"); + assert_eq!(mon.unwrap().as_ref().name(), "foo"); assert_eq!(r.len(), 1); } diff --git a/src/static_data/species_data/gender.rs b/src/static_data/species_data/gender.rs index 3e0624b..b7a7397 100644 --- a/src/static_data/species_data/gender.rs +++ b/src/static_data/species_data/gender.rs @@ -1,6 +1,6 @@ // Required for standard pokemon functions, but somewhat controversial nowadays. Consider adding a feature // that allows for a more progressive gender system for those that want it? -#[derive(Debug)] +#[derive(Eq, PartialEq, Debug)] pub enum Gender { Male, Female, diff --git a/src/static_data/statistic_set.rs b/src/static_data/statistic_set.rs index 77ef274..a9969df 100644 --- a/src/static_data/statistic_set.rs +++ b/src/static_data/statistic_set.rs @@ -19,7 +19,7 @@ impl StatisticSet where T: PrimInt, { - pub fn get_stat(&self, stat: Statistic) -> T { + pub const fn get_stat(&self, stat: Statistic) -> T { match stat { Statistic::HP => self.hp, Statistic::Attack => self.attack, diff --git a/src/utils/random.rs b/src/utils/random.rs index a86e1ae..2c8530d 100644 --- a/src/utils/random.rs +++ b/src/utils/random.rs @@ -71,6 +71,9 @@ impl Random { #[cfg(test)] mod tests { use crate::utils::random::Random; + extern crate test; + use std::hint::black_box; + use test::Bencher; #[test] fn create_random() { @@ -176,4 +179,14 @@ mod tests { assert_approx_eq::assert_approx_eq!(num_zeros as f32 / num_twos as f32, 1.0, 0.01); assert_approx_eq::assert_approx_eq!(num_ones as f32 / num_twos as f32, 1.0, 0.01); } + + #[bench] + fn bench_1000_random_ints_between(b: &mut Bencher) { + b.iter(|| { + let mut random = Random::default(); + for _ in 0..1000 { + black_box(random.get_between(0, 100)); + } + }); + } }