The core work for Tabletop RPG's virtual tabletop took place from October - December 2019. Of which, the majority of the tabletop logic (Grid, Scale, Zoom, Draw) was written in the first week.

The majority of challenges and unknowns came later. Except for one issue, once the initial tabletop geometry finished I never touched it again.

I am primarily a web developer and spend most of my time around JavaScript. There is nothing surprising in the tech stack - Node, Mongo, React, Redis, Express, Socket.io. Everything related to the tabletop is built from the ground up. I chose not to use existing canvas libraries in favor of an original work.

The tabletop is the primary focus from the context of usability. I wanted it to be full screen. It needed limitless scrolling in any direction with point zoom and real time updates.

Like a real tabletop is for RPGs and board games, the virtual tabletop should be the foundation for the entire application.

HTML5 Canvas

Foregoing a canvas library meant doing my own Math. It was very intimidating. I have no professional experience with graphics. My most valuable resource was a course from college six years ago. To graduate on time I took a summer course taught by a great Carnegie Mellon professor on computer graphics.

My first git commit is everything I remembered from that class that Stack Overflow could glue together. I had functional infinite click panning, image resize, image rotate, and point zoom.

The swearing began when two features combined like a twisted and malevolent Voltron. Resizing a previously rotated image would become a lingering glitch that would frustrate me over the next two months.

It ultimately led to spending a week experimenting with anchor/corner tracking and some of the strangest visual bugs I've encountered. So much so that I began a 'greatest hits' video collection and will give it a separate blog post. Unfortunately, early glitches were lost before recording began.

Managing State

The first real challenge came in designing the application state and canvas state. It did not make sense to setState every time the Canvas draws. Especially with real-time updates being a goal in the near future.

This became a great lesson in using state vs instance properties. Instance properties allowed for a total divorce in tabletop rendering and React rendering. I don't consider this an early optimization and here's why:

Dragging an image across the canvas can trigger thousands of canvas renders. If the only thing changing is an x,y coordinate on each canvas render, then React doesn't need to know or run its own render which would be unlikely to change much. Instead, the mouseUp event triggers a 'commit' saving the user action and then updating the React state.

In sticking with my 'the tabletop is the foundation' goal it means that the majority of the app is driven by a Tabletop component with lots of instance variables. E.g. instead of this.state there is a lot of  this.gridSize and patterns that started out like:

setGridSize = (gridSize) => {
  this.gridSize = gridSize;
}

This also set up socket work that would come later, although it was pure serendipity and luck, not design.