Mutation via Events

<
>
September 13, 2021

Previously editors handled click events and depending on those mutated the Blueprint or Planet directly.
They had direct, mutable access to (parts) of the game’s state.

match event {
    UIEvent::Click(x, y) => {
        ...
        match self.cursor_item.clone() {
            None => self.planet.react(StructureAction::Remove(pos)),
            Some(structure) => self.add(PosAnchor(pos), structure.clone()),
        }
    }
    ...
}

This makes it quite hard to implement multiplayer, since it’s not really clear what the sum of all commited changes is.
It’s also difficult to exchange sources of events, or to ‘listen’ to multiple sources.

I therefore defined a new event type:

pub enum ClientEvent {
    StructurePlanet(StructureAction),
    StructureBP(StructureAction),
    MarkResearched(Tech),
    ...
}

And refactored all editors. Instead of directly mutating the state they now emit this new event type:

impl<'a> EventConverter<UIEvent, ClientEvent> for PlanetUI<'a> {
    fn convert(&self, event: UIEvent, c: &mut dyn Collector<ClientEvent>) {
        match event {
            UIEvent::Click(x, y) => {
                ...
                match self.cursor_item.clone() {
                    None => c.collect(CE::StructurePlanet(SA::Remove(pos))),
                    Some(structure) => {
                        c.collect(CE::StructurePlanet(SA::Add(PosAnchor(pos), structure)))
                    }
                }
            }
            ...
}

The core game logic now only has to collect those events from all editors and then mutate the state accordingly.

fn process_event_queue(&mut self) {
    ...
    for event in self.event_queue.drain(..) {
        match event {
            CE::StructurePlanet(x) => self.planet.react(x),
            CE::StructureBP(x) => BlueprintEditor { bp: &mut self.bp }.react(x),
            CE::Blueprint(x) => BlueprintEditor { bp: &mut self.bp }.react(x),
            ...

This way it’s easy to change the sources of events in the future or instead of applying them to the state send them to a server for processing.