A state refactor question

Saman Bemel Benrud • Sep 30 2021

I’m working through a big performance oriented refactor right now and I’m trying to decide on the best shape for wilderplace’s state. I figured I’d share my thinking so far in public in case anyone has any advice for me.

What are the goals of Wilderplace’s state?

  • Perform easy and efficient individual entity lookups and updates, both when updating position and when updating entity properties.
  • Easily iterate through all entities in order to render them.
  • Easily perform spatial transformations of state. I need to be able to run algorithms that change the position or properties of many entities based on their position.
  • Minimize React re-renders.
  • Easily serializable.

Here are the options I’m looking at. In all examples, the objects with an ‘id’ would also have other properties like ‘disabled’ and ‘hidden’ ect.

Option 1

const state = {
  entityIds: ['a', 'b', 'c'],
  entityPositionData: {
    a: { boardPosition: [0, 0, 0], screenPosition: [1, -1] },
    b: { boardPosition: [0, 1, 0], screenPosition: [1, 0] },
    c: { boardPosition: [1, 0, 0], screenPosition: [2, -1] },
  },
  boardWithEntities: [
    [[{ id: 'a', type: 'MONSTR'}], [{ id: 'c', type: 'WARDEN'}]],
    [[{ id: 'b', type: 'PLAYER'}]]
  ]
}

I like this option because it’s straightforward to read/update entities while traversing the board. The downside is that I need to do two lookups to get an entity’s properties based on it’s ID:

Option 2

const state = {
  entityIds: ['a', 'b', 'c'],
  entitiesWithPositionData: {
    a: { id: 'a', type: 'MONSTR', boardPosition: [0, 0, 0], screenPosition: [1, -1] },
    b: { id: 'b', type: 'PLAYER', boardPosition: [0, 1, 0], screenPosition: [1, 0] },
    c: { id: 'c', type: 'WARDEN', boardPosition: [1, 0, 0], screenPosition: [2, -1] },
  },
  board: [
    [['a'], ['c']],
    [['b']]
  ]
}

I like this option because it’s straightforward to lookup entities from their IDs. The downside is that it’s more awkward to read or update entities while traversing the board:

Option 3

const state = {
  entityIds: ['a', 'b', 'c'],
  entitiesWithPositionData: {
    a: { id: 'a', type: 'MONSTR', boardPosition: [0, 0, 0], screenPosition: [1, -1] },
    b: { id: 'b', type: 'PLAYER', boardPosition: [0, 1, 0], screenPosition: [1, 0] },
    c: { id: 'c', type: 'WARDEN', boardPosition: [1, 0, 0], screenPosition: [2, -1] },
  },
  boardWithEntities: [
    [[{ id: 'a', type: 'MONSTR' }], [{ id: 'c', type: 'WARDEN' }]],
    [[{ id: 'b', type: 'PLAYER' }]]
  ]
}

This is the kitchen sink option. It’s easy to lookup entities either by position or by id. The downside is that, because I’m using Immutable.js (which I regret using but that ship has sailed), I can’t save entities by reference and mutate them. I’d need to ensure I synchronized the copies of entities across both data structures whenever making updates:

Current leanings

I am leaning toward option 1, but I’m curious what the rest of the world thinks. Should I be pre-calculating and storing even more data, like neighbors, so my base data structure is more graph-like? Any option will be better than what I have now, which is just a multidimensional board array with entities inside it. The downside to that: I have to derive both my entity list and position data from the board on all state changes. I think any of the options above will lead to less work to update state and many fewer React re-renders.


👋 Thanks!

Wilderplace is now on Steam! The best way to keep up with the game’s development is to join the discord.