danprince / midas

🫅 Traditional roguelike where everything you touch turns to gold.
https://danprince.itch.io/midas
2 stars 0 forks source link

UI Architecture #22

Open danprince opened 4 years ago

danprince commented 4 years ago

Need to figure out how the game is going to talk to the UI. Don't want the imperative jQuery mess, so probably going to throw in React/Preact for the view.

Each screen can have a render method which renders the content for the screen. Want to keep screens as classes rather than components because it's quite useful to have them modelled as objects for the screen stack / input handling.

Screens can update the UI once per turn or once per frame, but neither are particularly good models. Vaguely remember reading about Hauberk queuing UI events during each time anything interesting happens, then giving the UI a chance to read those events and update once per frame.

Those events should probably flow through UI reducers that can live anywhere in the component tree. Or would that be better as a central store? E.g. just one place to think about each value?

danprince commented 4 years ago

Initial version is just going to force a re-render every frame and hope that the reconciler can keep up at 60FPS. Could experiment with a debounce too.

Once the UI gets more complex and it becomes a significant performance hit, then there's a hook that could be used to

function useForceUpdate() {
  let [_, forceUpdate] = useReducer(n => n + 1, 0);
  return forceUpdate;
}

function useUpdateOnEvent(callback, dependencies = []) {
  let forceUpdate = useForceUpdate();

  useEffect(() => {
    function handler(event) {
      // force the component to update if the event is a sync event (done to refresh the whole ui)
      // or if the callback returns true
      if (event.type === "sync" || callback(event)) {
        forceUpdate();
      }
    }
    systems.ui.addEventListener(handler);
    return () => systems.ui.removeEventListener(handler);
  }, dependencies);
}

// Hook in use making sure the component only updates if the player receives a set-hp or set-sanity event
useUpdateOnEvent(event => {
  return (
    event.type === "set-hp" && event.targetId === game.player.id ||
    event.type === "set-sanity" && event.targetId === game.player.id
  );
}, []);
danprince commented 4 years ago

Pretty happy with where this ended up. Might still need the event system in future but for now there's a useSync hook which allows a component to declare dependencies on game state.

useSync(() => [
  game.player.x // this component will update whenever game.player.x updates
]);

Currently uses shallow equality but wondering about increasing that to a deeper variant so that the UI could 'watch' objects instead.

useDeepSync(() => [
  game.player // update whenever game.player updates
]);