ItemHandler Abstraction

<
>
May 19, 2021

There’s been quite a long break in the development of Factor Y. I’ve been working on the release of Spritify and some other side projects.

Previously to ItemHandler, every interaction between Structure pairs had to be explicitly implemented. Many of them did nothing, but it was generally a quadratic problem and already caused a lot of work whenever a new Structure type was added.

...
match (&mut source, &mut target) {
    // belts just move to other belts
    (Structure::Belt(bs), Structure::Belt(bt)) => {
        if !bs.on_cooldown() && !bt.on_cooldown() {
            match (&mut bs.content, &mut bt.content) {
                (Some(_), None) => {
                    changed = true;
                    std::mem::swap(&mut bs.content, &mut bt.content);
                    bt.set_cooldown();
                }
                _ => (),
            }
        }
    }

    // arms can take from belts
    (Structure::Belt(b), Structure::Arm(a)) => {
        if !b.on_cooldown() && !a.on_cooldown() {
            match (&mut b.content, &mut a.content) {
                (Some(_), None) => {
                    changed = true;
                    std::mem::swap(&mut b.content, &mut a.content);
                    a.set_cooldown();
                }
                _ => (),
            }
        }
    }

    // arms take from furnaces
    (Structure::Furnace(f), Structure::Arm(a)) => match (&mut f.product, &mut a.content) {
        (Some(plate), None) => {
            changed = true;
            a.content = Some(Material::Plate(*plate));
            f.product = None;
        }
        _ => (),
    },
    ...

ItemHandler defines the different, abstract action types and allows for not having to check for all the possible interaction pairs during the simulation.
Below some of the trait’s functions:

pub trait ItemHandler {
    fn accepts(&self, drop_pos_rel: &Pos, x: &Item) -> bool;
    fn accept(&mut self, drop_pos_rel: &Pos, x: Item);
    fn offer(&mut self, take_pos_rel: &Pos) -> Option<Item>;
    ...
}

And part of the simplified usage within the simulation that does not need to care about the specific Structure type anymore:

fn interact_one_way(
    mut a: Structure,
    mut b: Structure,
    pos_a: Pos,
    pos_b: Pos,
) -> ... {
    ...
    // a might actively place on b
    if let Some((pos, item)) = a.place() {
        let pos = Self::transformed(pos_a.clone(), pos_b.clone(), pos);
        if b.accepts(&pos, &item) {
            b.accept(&pos, item);
            changed = true
        } else {
            a.undo_place(item)
        }
    }
    ...

The entire interaction code remains quite complex, but isn’t affected by the number of Structure types anymore.