Major amounts of progress
This commit is contained in:
parent
c194c5d209
commit
310bf857d2
|
@ -0,0 +1,3 @@
|
|||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "/usr/bin/clang"
|
||||
rustflags = ["-Clink-arg=-fuse-ld=/usr/bin/mold"]
|
|
@ -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"
|
||||
maplit = "1.0.2"
|
||||
failure = "0.1.8"
|
||||
failure_derive = "0.1.8"
|
||||
lazy_static = "1.4.0"
|
|
@ -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<R>(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<T>, 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<T>: Send {});
|
||||
unsafe_impl!("The handle uses `ThreadBound` for synchronization" => impl<'a, T: ?Sized> Sync for HandleExclusive<'a, T> where &'a mut ThreadBound<T>: 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<R>(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()
|
||||
}
|
||||
}
|
|
@ -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<usize> = 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<ValueId, (UnsafeCell<*mut ()>, Box<dyn Fn(&UnsafeCell<*mut ()>)>)>);
|
||||
|
||||
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<Registry> = UnsafeCell::new(Registry(Default::default())));
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref GARBAGE: Mutex<HashMap<ThreadId, Vec<ValueId>>> = Mutex::new(HashMap::new());
|
||||
}
|
||||
|
||||
/**
|
||||
A value that's bound to the thread it's created on.
|
||||
*/
|
||||
pub struct ThreadBound<T: ?Sized> {
|
||||
thread_id: ThreadId,
|
||||
inner: UnsafeCell<T>,
|
||||
}
|
||||
|
||||
impl<T> ThreadBound<T> {
|
||||
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<T: Send> ThreadBound<T> {
|
||||
pub(super) fn into_inner(self) -> T {
|
||||
self.inner.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> ThreadBound<T> {
|
||||
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<T: ?Sized + UnwindSafe> UnwindSafe for ThreadBound<T> {}
|
||||
impl<T: ?Sized + RefUnwindSafe> RefUnwindSafe for ThreadBound<T> {}
|
||||
|
||||
unsafe_impl!("The inner value is safe to send to another thread" => impl<T: ?Sized + Send> Send for ThreadBound<T> {});
|
||||
unsafe_impl!("The inner value can't actually be accessed concurrently" => impl<T: ?Sized> Sync for ThreadBound<T> {});
|
||||
|
||||
/**
|
||||
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<T: 'static> {
|
||||
thread_id: ThreadId,
|
||||
value_id: ValueId,
|
||||
_m: PhantomData<*mut T>,
|
||||
}
|
||||
|
||||
impl<T: 'static> Drop for DeferredCleanup<T> {
|
||||
fn drop(&mut self) {
|
||||
if mem::needs_drop::<T>() {
|
||||
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<T: 'static> DeferredCleanup<T> {
|
||||
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<T> = Box::from_raw(*(cell.get() as *mut *mut T));
|
||||
mem::drop(b);
|
||||
}),
|
||||
),
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
DeferredCleanup {
|
||||
thread_id,
|
||||
value_id,
|
||||
_m: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn with_value<F: FnOnce(&UnsafeCell<Box<T>>) -> 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<T: 'static> Send for DeferredCleanup<T> {});
|
||||
|
||||
impl<T: 'static> Deref for DeferredCleanup<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
self.with_value(
|
||||
|value| unsafe_block!("The borrow of self protects the inner value" => &*value.get()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> DerefMut for DeferredCleanup<T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
self.with_value(|value| unsafe_block!("The borrow of self protects the inner value" => &mut *value.get()))
|
||||
}
|
||||
}
|
|
@ -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<T: ?Sized> IsNull for *const T {
|
||||
fn is_null(&self) -> bool {
|
||||
<*const T>::is_null(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> 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
|
||||
);
|
|
@ -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)*
|
||||
};
|
||||
}
|
|
@ -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;
|
|
@ -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<Option<LastResult>> = 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<R>(
|
||||
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<E> From<E> for PkmnResult
|
||||
where
|
||||
E: Fail,
|
||||
{
|
||||
fn from(e: E) -> Self {
|
||||
PkmnResult::internal_error().context(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct LastResult {
|
||||
value: PkmnResult,
|
||||
err: Option<String>,
|
||||
}
|
||||
|
||||
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<dyn Any + Send + 'static>) -> Option<String> {
|
||||
if let Some(err) = err.downcast_ref::<String>() {
|
||||
Some(err.clone())
|
||||
} else if let Some(err) = err.downcast_ref::<&'static str>() {
|
||||
Some((*err).to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
trait OptionMutExt<T> {
|
||||
/**
|
||||
Map and mutate an option in place.
|
||||
*/
|
||||
fn map_mut<F>(&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<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: FnOnce() -> Option<T>;
|
||||
}
|
||||
|
||||
impl<T> OptionMutExt<T> for Option<T> {
|
||||
fn map_mut<F>(&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<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: FnOnce() -> Option<T>,
|
||||
{
|
||||
if self.is_none() {
|
||||
*self = f();
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
|
@ -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<Item>,
|
||||
}
|
||||
|
||||
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<ItemHandle>
|
||||
) -> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
mod item;
|
|
@ -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<LearnedMove>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> TurnChoice<'a> {
|
||||
pub fn user(&self) -> &'a Pokemon<'a> {
|
||||
match self {
|
||||
TurnChoice::Move { user, .. } => user,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<fn(&Box<&Event>)>,
|
||||
}
|
||||
|
||||
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<Arc<Mutex<Pokemon<'a>>>>,
|
||||
},
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
pub mod event_hook;
|
|
@ -0,0 +1,2 @@
|
|||
#[derive(Debug)]
|
||||
pub struct ChoiceQueue {}
|
|
@ -0,0 +1 @@
|
|||
pub mod choice_queue;
|
|
@ -0,0 +1,2 @@
|
|||
#[derive(Debug)]
|
||||
pub struct HistoryHolder {}
|
|
@ -0,0 +1 @@
|
|||
pub mod history_holder;
|
|
@ -1,5 +1,3 @@
|
|||
use crate::dynamic_data::models::pokemon::Pokemon;
|
||||
|
||||
pub trait BattleStatCalculator {
|
||||
//fn is_critical(attack: &ExecutingMove, target: &Pokemon, hit: u8);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<BattleParty>,
|
||||
can_flee: bool,
|
||||
number_of_sides: u8,
|
||||
pokemon_per_side: u8,
|
||||
sides: Vec<BattleSide<'a>>,
|
||||
#[getter(skip)]
|
||||
random: BattleRandom,
|
||||
choice_queue: ChoiceQueue,
|
||||
has_ended: bool,
|
||||
result: BattleResult,
|
||||
event_hook: EventHook,
|
||||
history_holder: Box<HistoryHolder>,
|
||||
current_turn: u32,
|
||||
volatile: ScriptSet,
|
||||
last_turn_time: i64,
|
||||
}
|
||||
|
||||
impl<'a> Battle<'a> {
|
||||
pub fn new(
|
||||
library: &'a DynamicLibrary<'a>,
|
||||
parties: Vec<BattleParty>,
|
||||
can_flee: bool,
|
||||
number_of_sides: u8,
|
||||
pokemon_per_side: u8,
|
||||
random_seed: Option<u128>,
|
||||
) -> Arc<Mutex<Self>> {
|
||||
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!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
#[derive(Debug)]
|
||||
pub struct BattleParty {}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
#[derive(Debug)]
|
||||
pub enum BattleResult {
|
||||
Inconclusive,
|
||||
Conclusive(u8),
|
||||
}
|
|
@ -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<Option<Arc<Mutex<Pokemon<'a>>>>>,
|
||||
choices: Vec<Option<Arc<TurnChoice<'a>>>>,
|
||||
fillable_slots: Vec<bool>,
|
||||
choices_set: u8,
|
||||
battle: Weak<Mutex<Battle<'a>>>,
|
||||
has_fled: bool,
|
||||
}
|
||||
|
||||
impl<'a> BattleSide<'a> {
|
||||
pub fn new(index: u8, battle: Weak<Mutex<Battle<'a>>>, 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<Arc<Mutex<Pokemon<'a>>>>) {
|
||||
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<Pokemon<'a>>) -> 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!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
#[derive(Debug)]
|
||||
pub struct LearnedMove {}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Mutex<Battle<'a>>>,
|
||||
battle_side_index: u8,
|
||||
index: u8,
|
||||
on_battle_field: bool,
|
||||
seen_opponents: HashSet<Pokemon<'a>>,
|
||||
seen_opponents: HashSet<u32>,
|
||||
}
|
||||
|
||||
#[derive(Getters)]
|
||||
impl<'a> PokemonBattleData<'a> {
|
||||
pub fn battle(&'a mut self) -> &'a mut Weak<Mutex<Battle<'a>>> {
|
||||
&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<u32> {
|
||||
&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<i8>,
|
||||
flat_stats: StatisticSet<u16>,
|
||||
boosted_stats: StatisticSet<u16>,
|
||||
|
@ -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<Mutex<Battle<'a>>>, 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)]
|
||||
|
|
|
@ -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<dyn Script>),
|
||||
Set(&'a ScriptSet),
|
||||
}
|
||||
|
||||
pub struct ScriptAggregator<'a> {
|
||||
scripts: Vec<Option<ScriptWrapper<'a>>>,
|
||||
size: i32,
|
||||
index: i32,
|
||||
set_index: i32,
|
||||
}
|
||||
|
||||
impl<'a> ScriptAggregator<'a> {
|
||||
pub fn new(scripts: Vec<Option<ScriptWrapper<'a>>>) -> 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<dyn Script>> {
|
||||
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)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<dyn Script> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
11
src/lib.rs
11
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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::collections::HashMap;
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct ItemLibrary {
|
||||
map: HashMap<String, Item>,
|
||||
map: HashMap<String, Box<Item>>,
|
||||
list: Vec<String>,
|
||||
}
|
||||
|
||||
|
@ -17,8 +17,8 @@ impl ItemLibrary {
|
|||
}
|
||||
}
|
||||
|
||||
impl DataLibrary<'_, Item> for ItemLibrary {
|
||||
fn map(&self) -> &HashMap<String, Item> {
|
||||
impl DataLibrary<'_, Box<Item>> for ItemLibrary {
|
||||
fn map(&self) -> &HashMap<String, Box<Item>> {
|
||||
&self.map
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ impl DataLibrary<'_, Item> for ItemLibrary {
|
|||
&self.list
|
||||
}
|
||||
|
||||
fn get_modify(&mut self) -> (&mut HashMap<String, Item>, &mut Vec<String>) {
|
||||
fn get_modify(&mut self) -> (&mut HashMap<String, Box<Item>>, &mut Vec<String>) {
|
||||
(&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
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::collections::HashMap;
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct SpeciesLibrary<'a> {
|
||||
map: HashMap<String, Species<'a>>,
|
||||
map: HashMap<String, Box<Species<'a>>>,
|
||||
list: Vec<String>,
|
||||
}
|
||||
|
||||
|
@ -17,8 +17,8 @@ impl<'a> SpeciesLibrary<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> DataLibrary<'a, Species<'a>> for SpeciesLibrary<'a> {
|
||||
fn map(&self) -> &HashMap<String, Species<'a>> {
|
||||
impl<'a> DataLibrary<'a, Box<Species<'a>>> for SpeciesLibrary<'a> {
|
||||
fn map(&self) -> &HashMap<String, Box<Species<'a>>> {
|
||||
&self.map
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ impl<'a> DataLibrary<'a, Species<'a>> for SpeciesLibrary<'a> {
|
|||
&self.list
|
||||
}
|
||||
|
||||
fn get_modify(&mut self) -> (&mut HashMap<String, Species<'a>>, &mut Vec<String>) {
|
||||
fn get_modify(&mut self) -> (&mut HashMap<String, Box<Species<'a>>>, &mut Vec<String>) {
|
||||
(&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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -19,7 +19,7 @@ impl<T> StatisticSet<T>
|
|||
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,
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue