use crate::abi::{ChangeId, ChangeOp, DataChange, DataSelect, Header, Status, Str};
use crate::content::Guid;
use crate::{Host, Load, Off};
use super::{Field, Items, Value, ValueError};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Target {
Entry(Guid),
Items(Items),
}
impl Target {
fn to_abi(self, field: Field, source: &str) -> DataChange {
let (entry, select, categories) = match self {
Self::Entry(entry) => (entry.0, DataSelect::ENTRY, 0),
Self::Items(items) => {
let (select, categories) = items.to_abi();
([0; 16], select, categories)
}
};
DataChange {
header: Header::of::<DataChange>(),
entry,
field: field.to_abi(),
op: ChangeOp::TIMES,
value: 1.0,
source: Str::new(source),
item: [0; 16],
select,
categories,
}
}
fn check(self, field: Field) -> Result<(), ValueError> {
match self {
Self::Items(_) if !field.selectable() => Err(ValueError::NotSelectable { field }),
_ if matches!(field, Field::Ingredient | Field::Station) => {
Err(ValueError::NotLive { field })
}
_ => Ok(()),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, thiserror::Error)]
#[non_exhaustive]
pub enum SetChangeError {
#[error(transparent)]
Value(#[from] ValueError),
#[error("the loader refused the value")]
Refused,
#[error("the value takes a restart")]
Restart,
}
#[derive(Clone, Copy, Debug)]
pub struct ChangeHandle {
host: Host,
id: ChangeId,
field: Field,
}
impl ChangeHandle {
#[must_use]
pub const fn field(&self) -> Field {
self.field
}
pub fn set(&self, value: Value) -> Result<(), SetChangeError> {
self.field.check(value)?;
let (op, raw) = match value {
Value::Set(v) => (ChangeOp::SET, v),
Value::Times(v) => (ChangeOp::TIMES, v),
};
self.send(op, raw)
}
pub fn reset(&self) -> Result<(), SetChangeError> {
self.send(ChangeOp::RESET, 0.0)
}
fn send(&self, op: ChangeOp, value: f64) -> Result<(), SetChangeError> {
match (self.host.api().set_change)(self.host.handle(), self.id, op, value) {
Status::OK => Ok(()),
Status::TOO_LATE => Err(SetChangeError::Restart),
_ => Err(SetChangeError::Refused),
}
}
}
impl Load<'_> {
pub fn declare_change(
&mut self,
target: Target,
field: Field,
source: &str,
) -> Result<ChangeHandle, Off> {
field.unstable_gate()?;
let what = || format!("the change of {field} of {target:?}");
target
.check(field)
.map_err(|why| Off::new(format!("{}: {why}", what())))?;
if field.seen_by_clients() && !self.joins() {
return Err(Off::new(format!(
"{}: players' games read it, so only a mod with `join: true` declares it",
what()
)));
}
let raw = target.to_abi(field, source);
let host = self.host();
let mut id = ChangeId(0);
let status = (host.api().declare_change)(host.handle(), &raw const raw, &raw mut id);
crate::load::ok(status, ChangeHandle { host, id, field }, what)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_ingredient_is_no_change_a_mod_sets_later() {
let target = Target::Entry(Guid([1; 16]));
assert_eq!(
target.check(Field::Ingredient),
Err(ValueError::NotLive {
field: Field::Ingredient
})
);
assert!(target.check(Field::Output).is_ok());
assert!(
Target::Items(Items::Stackable)
.check(Field::Output)
.is_err()
);
}
#[test]
fn a_target_names_its_entry_or_its_selection_in_the_abi() {
let one = Target::Entry(Guid([3; 16])).to_abi(Field::MaxStackSize, "x");
assert_eq!((one.entry, one.select), ([3; 16], DataSelect::ENTRY));
let many = Target::Items(Items::Containers).to_abi(Field::Slots, "x");
assert_eq!((many.entry, many.select), ([0; 16], DataSelect::CONTAINERS));
}
}