Major amounts of progress

This commit is contained in:
Deukhoofd 2022-06-03 16:35:18 +02:00
parent c194c5d209
commit 310bf857d2
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
37 changed files with 1558 additions and 29 deletions

3
.cargo/config.toml Normal file
View File

@ -0,0 +1,3 @@
[target.x86_64-unknown-linux-gnu]
linker = "/usr/bin/clang"
rustflags = ["-Clink-arg=-fuse-ld=/usr/bin/mold"]

View File

@ -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"

View File

@ -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()
}
}

View File

@ -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()))
}
}

View File

@ -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
);

86
src/c_interface/macros.rs Normal file
View File

@ -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)*
};
}

7
src/c_interface/mod.rs Normal file
View File

@ -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;

View File

@ -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
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1 @@
mod item;

View File

@ -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,
}
}
}

View File

@ -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>>>>,
},
}

View File

@ -0,0 +1 @@
pub mod event_hook;

View File

@ -0,0 +1,2 @@
#[derive(Debug)]
pub struct ChoiceQueue {}

View File

@ -0,0 +1 @@
pub mod choice_queue;

View File

@ -0,0 +1,2 @@
#[derive(Debug)]
pub struct HistoryHolder {}

View File

@ -0,0 +1 @@
pub mod history_holder;

View File

@ -1,5 +1,3 @@
use crate::dynamic_data::models::pokemon::Pokemon;
pub trait BattleStatCalculator {
//fn is_critical(attack: &ExecutingMove, target: &Pokemon, hit: u8);
}

View File

@ -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;

View File

@ -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!()
}
}

View File

@ -0,0 +1,2 @@
#[derive(Debug)]
pub struct BattleParty {}

View File

@ -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
}
}

View File

@ -0,0 +1,5 @@
#[derive(Debug)]
pub enum BattleResult {
Inconclusive,
Conclusive(u8),
}

View File

@ -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!()
}
}

View File

@ -1 +1,2 @@
#[derive(Debug)]
pub struct LearnedMove {}

View File

@ -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;

View File

@ -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)]

View File

@ -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)),
};
}
}

View File

@ -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(())
}
}

View File

@ -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!()
}
}

View File

@ -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;

View File

@ -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,

View File

@ -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

View File

@ -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);
}

View File

@ -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,

View File

@ -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,

View File

@ -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));
}
});
}
}