use crate::{
recipe::{Recipe, RecipeEx},
tick::Tick,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BufferLocation {
Input,
Output,
}
#[derive(Debug)]
pub struct MachineNotEmptyError<M> {
pub machine: M,
pub resource_type: &'static str,
pub amount: u32,
pub location: BufferLocation,
}
impl<M> MachineNotEmptyError<M> {
pub fn map_machine<F, M2>(self, f: F) -> MachineNotEmptyError<M2>
where
F: FnOnce(M) -> M2,
{
MachineNotEmptyError {
machine: f(self.machine),
resource_type: self.resource_type,
amount: self.amount,
location: self.location,
}
}
}
impl<R: Recipe> std::fmt::Display for MachineNotEmptyError<R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Machine is not empty: machine has {} of resource {} in its {:?} buffer",
self.amount, self.resource_type, self.location
)
}
}
#[derive(Debug)]
pub struct Machine<R: Recipe> {
inputs: R::Inputs,
outputs: R::Outputs,
tick: u64,
crafting_time: u64,
}
impl<R: RecipeEx> Machine<R> {
fn new_inner(tick: u64) -> Self {
Self {
inputs: R::new_inputs(),
outputs: R::new_outputs(),
tick,
crafting_time: 0,
}
}
pub fn new(tick: &Tick) -> Self {
Self::new_inner(tick.cur())
}
pub fn inputs(&mut self, tick: &Tick) -> &mut R::Inputs {
self.tick(tick);
&mut self.inputs
}
pub fn outputs(&mut self, tick: &Tick) -> &mut R::Outputs {
self.tick(tick);
&mut self.outputs
}
fn iter_inputs(&mut self) -> impl Iterator<Item = (&'static str, u32, &mut u32)> {
R::iter_inputs(&mut self.inputs)
}
fn iter_outputs(&mut self) -> impl Iterator<Item = (&'static str, u32, &mut u32)> {
R::iter_outputs(&mut self.outputs)
}
pub fn change_recipe<R2: RecipeEx>(
mut self,
recipe: R2,
) -> Result<Machine<R2>, MachineNotEmptyError<Self>> {
let _ = recipe;
fn find_nonempty<'a>(
mut iter: impl Iterator<Item = (&'static str, u32, &'a mut u32)>,
location: BufferLocation,
) -> Option<(&'static str, u32, BufferLocation)> {
iter.find_map(|(resource_name, _needed, &mut current)| {
(current > 0).then_some((resource_name, current, location))
})
}
if let Some((resource_type, amount, location)) =
find_nonempty(self.iter_inputs(), BufferLocation::Input)
.or_else(|| find_nonempty(self.iter_outputs(), BufferLocation::Output))
{
Err(MachineNotEmptyError {
machine: self,
resource_type,
amount,
location,
})
} else {
Ok(Machine::new_inner(self.tick))
}
}
fn tick(&mut self, tick: &Tick) {
assert!(tick.cur() >= self.tick, "Tick must be non-decreasing");
self.crafting_time += tick.cur() - self.tick;
let crafting_time = self.crafting_time;
let count = self
.iter_inputs()
.map(|(_, needed, current)| *current / needed)
.chain((R::TIME > 0).then(|| (crafting_time / R::TIME).try_into().unwrap()))
.min()
.unwrap();
for (_, needed, current) in self.iter_inputs() {
*current -= count * needed;
}
for (_, needed, current) in self.iter_outputs() {
*current += count * needed;
}
self.crafting_time -= u64::from(count) * R::TIME;
if self
.iter_inputs()
.any(|(_, needed, current)| *current < needed)
{
self.crafting_time = 0;
}
self.tick = tick.cur();
}
}