rustorio_engine/
machine.rs1use crate::{
12 recipe::{Recipe, RecipeEx},
13 tick::Tick,
14};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum BufferLocation {
19 Input,
21 Output,
23}
24
25#[derive(Debug)]
27pub struct MachineNotEmptyError<M> {
28 pub machine: M,
30 pub resource_type: &'static str,
32 pub amount: u32,
34 pub location: BufferLocation,
36}
37
38impl<M> MachineNotEmptyError<M> {
39 pub fn map_machine<F, M2>(self, f: F) -> MachineNotEmptyError<M2>
41 where
42 F: FnOnce(M) -> M2,
43 {
44 MachineNotEmptyError {
45 machine: f(self.machine),
46 resource_type: self.resource_type,
47 amount: self.amount,
48 location: self.location,
49 }
50 }
51}
52
53impl<R: Recipe> std::fmt::Display for MachineNotEmptyError<R> {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 write!(
56 f,
57 "Machine is not empty: machine has {} of resource {} in its {:?} buffer",
58 self.amount, self.resource_type, self.location
59 )
60 }
61}
62
63#[derive(Debug)]
65pub struct Machine<R: Recipe> {
66 inputs: R::Inputs,
67 outputs: R::Outputs,
68 tick: u64,
69 crafting_time: u64,
70}
71
72impl<R: RecipeEx> Machine<R> {
73 fn new_inner(tick: u64) -> Self {
74 Self {
75 inputs: R::new_inputs(),
76 outputs: R::new_outputs(),
77 tick,
78 crafting_time: 0,
79 }
80 }
81
82 pub fn new(tick: &Tick) -> Self {
84 Self::new_inner(tick.cur())
85 }
86
87 pub fn inputs(&mut self, tick: &Tick) -> &mut R::Inputs {
89 self.tick(tick);
90 &mut self.inputs
91 }
92
93 pub fn outputs(&mut self, tick: &Tick) -> &mut R::Outputs {
95 self.tick(tick);
96 &mut self.outputs
97 }
98
99 fn iter_inputs(&mut self) -> impl Iterator<Item = (&'static str, u32, &mut u32)> {
100 R::iter_inputs(&mut self.inputs)
101 }
102
103 fn iter_outputs(&mut self) -> impl Iterator<Item = (&'static str, u32, &mut u32)> {
104 R::iter_outputs(&mut self.outputs)
105 }
106
107 pub fn change_recipe<R2: RecipeEx>(
110 mut self,
111 recipe: R2,
112 ) -> Result<Machine<R2>, MachineNotEmptyError<Self>> {
113 let _ = recipe;
114 fn find_nonempty<'a>(
115 mut iter: impl Iterator<Item = (&'static str, u32, &'a mut u32)>,
116 location: BufferLocation,
117 ) -> Option<(&'static str, u32, BufferLocation)> {
118 iter.find_map(|(resource_name, _needed, &mut current)| {
119 (current > 0).then_some((resource_name, current, location))
120 })
121 }
122
123 if let Some((resource_type, amount, location)) =
124 find_nonempty(self.iter_inputs(), BufferLocation::Input)
125 .or_else(|| find_nonempty(self.iter_outputs(), BufferLocation::Output))
126 {
127 Err(MachineNotEmptyError {
128 machine: self,
129 resource_type,
130 amount,
131 location,
132 })
133 } else {
134 Ok(Machine::new_inner(self.tick))
135 }
136 }
137
138 fn tick(&mut self, tick: &Tick) {
139 assert!(tick.cur() >= self.tick, "Tick must be non-decreasing");
140
141 self.crafting_time += tick.cur() - self.tick;
142 let crafting_time = self.crafting_time;
143 let count = self
144 .iter_inputs()
145 .map(|(_, needed, current)| *current / needed)
146 .chain((R::TIME > 0).then(|| (crafting_time / R::TIME).try_into().unwrap()))
147 .min()
148 .unwrap();
149
150 for (_, needed, current) in self.iter_inputs() {
151 *current -= count * needed;
152 }
153 for (_, needed, current) in self.iter_outputs() {
154 *current += count * needed;
155 }
156 self.crafting_time -= u64::from(count) * R::TIME;
157
158 if self
159 .iter_inputs()
160 .any(|(_, needed, current)| *current < needed)
161 {
162 self.crafting_time = 0;
163 }
164
165 self.tick = tick.cur();
166 }
167}