use std::{
fmt::{Debug, Display},
iter::Sum,
marker::PhantomData,
ops::{Add, AddAssign},
};
use crate::Sealed;
pub trait ResourceType: Sealed + Debug {
const NAME: &'static str;
}
#[macro_export]
macro_rules! resource_type {
($(#[$outer:meta])*
$name:ident) => {
$(#[$outer])*
#[derive(Debug)]
pub struct $name;
impl $crate::Sealed for $name {}
impl $crate::ResourceType for $name {
const NAME: &'static str = stringify!($name);
}
};
}
#[derive(Debug, Clone)]
pub struct InsufficientResourceError<Resource: ResourceType> {
pub requested_amount: u32,
pub available_amount: u32,
phantom: PhantomData<Resource>,
}
impl<Resource: ResourceType> InsufficientResourceError<Resource> {
pub const fn new(requested_amount: u32, available_amount: u32) -> Self {
Self {
requested_amount,
available_amount,
phantom: PhantomData,
}
}
}
impl<Resource: ResourceType> Display for InsufficientResourceError<Resource> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Insufficient {:?}: requested {}, but only {} available",
Resource::NAME,
self.requested_amount,
self.available_amount
)
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
#[must_use = "This resource is being dropped without being used. If this is intentional, use the `let _ = resource;` pattern to silence this warning."]
pub struct Resource<Content: ResourceType> {
pub(crate) amount: u32,
phantom: PhantomData<Content>,
}
pub const fn resource<Content: ResourceType>(amount: u32) -> Resource<Content> {
Resource::new(amount)
}
pub const fn resource_amount_mut<Content: ResourceType>(
resource: &mut Resource<Content>,
) -> &mut u32 {
resource.amount_mut()
}
impl<Content: ResourceType> Resource<Content> {
pub const fn new_empty() -> Self {
Self {
amount: 0,
phantom: PhantomData,
}
}
pub(crate) const fn new(amount: u32) -> Self {
Self {
amount,
phantom: PhantomData,
}
}
pub const fn amount(&self) -> u32 {
self.amount
}
const fn amount_mut(&mut self) -> &mut u32 {
&mut self.amount
}
pub const fn split(self, amount: u32) -> Result<(Self, Self), Self> {
if let Some(remaining) = self.amount.checked_sub(amount) {
Ok((Self::new(remaining), Self::new(amount)))
} else {
Err(self)
}
}
pub const fn split_off(
&mut self,
amount: u32,
) -> Result<Self, InsufficientResourceError<Content>> {
if let Some(remaining) = self.amount.checked_sub(amount) {
self.amount = remaining;
Ok(Resource::new(amount))
} else {
Err(InsufficientResourceError::new(amount, self.amount))
}
}
pub const fn split_off_max(&mut self, amount: u32) -> Self {
if let Some(remaining) = self.amount.checked_sub(amount) {
self.amount = remaining;
Resource::new(amount)
} else {
let all = self.amount;
self.amount = 0;
Resource::new(all)
}
}
pub const fn empty(&mut self) -> Self {
let amount = self.amount;
self.amount = 0;
Resource::new(amount)
}
pub const fn empty_except(&mut self, amount: u32) -> Self {
let to_empty = self.amount.saturating_sub(amount);
self.amount -= to_empty;
Resource::new(to_empty)
}
pub const fn empty_into(&mut self, other: &mut Self) {
other.amount += self.amount;
self.amount = 0;
}
pub fn add(&mut self, other: impl Into<Self>) {
self.amount += other.into().amount();
}
pub const fn add_bundle<const AMOUNT: u32>(&mut self, bundle: Bundle<Content, AMOUNT>) {
self.amount += bundle.amount();
}
pub const fn bundle<const AMOUNT: u32>(
&mut self,
) -> Result<Bundle<Content, AMOUNT>, InsufficientResourceError<Content>> {
if let Some(remaining) = self.amount.checked_sub(AMOUNT) {
self.amount = remaining;
Ok(Bundle::new())
} else {
Err(InsufficientResourceError::new(AMOUNT, self.amount))
}
}
}
impl<Content: ResourceType> Display for Resource<Content> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{amount} {content}",
amount = self.amount,
content = Content::NAME
)
}
}
impl<Content: ResourceType> PartialOrd<u32> for Resource<Content> {
fn partial_cmp(&self, other: &u32) -> Option<std::cmp::Ordering> {
Some(self.amount.cmp(other))
}
}
impl<Content: ResourceType> PartialEq<u32> for Resource<Content> {
fn eq(&self, other: &u32) -> bool {
self.amount == *other
}
}
impl<Content: ResourceType> PartialOrd<Resource<Content>> for u32 {
fn partial_cmp(&self, other: &Resource<Content>) -> Option<std::cmp::Ordering> {
Some(self.cmp(&other.amount))
}
}
impl<Content: ResourceType> PartialEq<Resource<Content>> for u32 {
fn eq(&self, other: &Resource<Content>) -> bool {
*self == other.amount
}
}
impl<Content: ResourceType> AddAssign for Resource<Content> {
fn add_assign(&mut self, rhs: Self) {
self.amount += rhs.amount
}
}
impl<Content: ResourceType> Add for Resource<Content> {
type Output = Self;
fn add(mut self, rhs: Self) -> Self::Output {
self += rhs;
self
}
}
impl<Content: ResourceType> Sum for Resource<Content> {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Resource::new_empty(), |cur, next| cur + next)
}
}
#[derive(Debug)]
#[must_use = "This bundle is being dropped without being used. If this is intentional, use the `let _ = bundle;` pattern to silence this warning."]
pub struct Bundle<Content: ResourceType, const AMOUNT: u32> {
dummy: PhantomData<Content>,
}
pub fn bundle<Content: ResourceType, const AMOUNT: u32>() -> Bundle<Content, AMOUNT> {
Bundle::new()
}
pub struct Assert<const OK: bool>;
pub trait IsTrue {}
impl IsTrue for Assert<true> {}
impl<Content: ResourceType, const AMOUNT: u32> Bundle<Content, AMOUNT> {
pub const AMOUNT: u32 = AMOUNT;
pub(crate) const fn new() -> Self {
Self { dummy: PhantomData }
}
pub const fn amount(&self) -> u32 {
AMOUNT
}
pub const fn split<const AMOUNT1: u32, const AMOUNT2: u32>(
self,
) -> (Bundle<Content, AMOUNT1>, Bundle<Content, AMOUNT2>)
where
Assert<{ AMOUNT1 + AMOUNT2 == AMOUNT }>: IsTrue,
{
(Bundle::new(), Bundle::new())
}
pub const fn to_resource(self) -> Resource<Content> {
Resource::new(AMOUNT)
}
}
impl<Content: ResourceType, const AMOUNT: u32> AddAssign<Bundle<Content, AMOUNT>>
for Resource<Content>
{
fn add_assign(&mut self, bundle: Bundle<Content, AMOUNT>) {
let _ = bundle;
self.amount += AMOUNT;
}
}
impl<Content: ResourceType, const AMOUNT: u32> Add<Bundle<Content, AMOUNT>> for Resource<Content> {
type Output = Self;
fn add(mut self, rhs: Bundle<Content, AMOUNT>) -> Self::Output {
self += rhs;
self
}
}
impl<Content: ResourceType, const AMOUNT: u32> Add<Resource<Content>> for Bundle<Content, AMOUNT> {
type Output = Resource<Content>;
fn add(self, mut rhs: Resource<Content>) -> Self::Output {
rhs += self;
rhs
}
}
impl<Content: ResourceType, const AMOUNT_LHS: u32, const AMOUNT_RHS: u32>
Add<Bundle<Content, AMOUNT_RHS>> for Bundle<Content, AMOUNT_LHS>
where
[(); { AMOUNT_LHS + AMOUNT_RHS } as usize]:,
{
type Output = Bundle<Content, { AMOUNT_LHS + AMOUNT_RHS }>;
fn add(self, rhs: Bundle<Content, AMOUNT_RHS>) -> Self::Output {
let _ = rhs;
Bundle::new()
}
}
impl<Content: ResourceType, const AMOUNT: u32> From<Bundle<Content, AMOUNT>> for Resource<Content> {
fn from(bundle: Bundle<Content, AMOUNT>) -> Self {
let _ = bundle;
Resource::new(AMOUNT)
}
}