A user submitted a performance bug report today and attached a map that was slow on my computer, as well as theirs.
This was an absolutely golden opportunity for me, because usually when users have games that are running slowly I’ll load them up and they’ll run quickly on my computer. But my performance analysis tools only work well if what I’m analyzing runs slowly on my computer as well.
Anyways, I’ve just landed some (hopefully) very impactful changes. Large maps especially should see quite a speedup. Notably the map @KraftHP sent me now has manageable performance. I wouldn’t call it fast yet, but it’s way faster.
I was only able to do some relatively-basic optimizations. The refactoring I have planned in the April 2020 Roadmap will allow me to go much further.
Notably maps with a huge amount of tokens are likely to still be fairly slow, and I won’t be able to optimize them much until that refactoring is done. Lots of tokens with labels are even worse .
I’ll touch on the specific optimizations I just landed, but it’s fairly technically nitty-gritty.
Cache computation of map bounds
Shmeppy needs to know the borders of the map for various things (like how far to let the user pan). It does this by going through all the objects in the map (each cell that’s filled, each edge, each token, etc) and keeping track of the smallest rectangle that encompasses them all.
Well that ends up being surprisingly slow for large maps.
I didn’t speed up the computation itself (though that is possible and I plan to as part of the performance work) but I did make it so that we won’t redo the computation if we’ve already done it. It ended up that we were redoing that work a lot so this ended up being quite a speedup.
Faster detection of null operations
If you draw over a cell with the same color that the cell already is, the resulting “map state” is still the same. You didn’t actually change anything. I call this kind of “do nothing” operation a “null operation” (actually I call it a noop, but “null operation” is what I’m calling it right now).
Previously I detected null operations by “applying” the operation to the current map state, and then comparing the old and new map states. This is a simple, clean, and always correct approach. However, for very big maps, it takes an absurdly long time for your computer to check whether the old and new map states are correct. On the order of a second or two.
This could make it very very slow to make changes to the map.
How I fixed this is slightly more nitty-gritty than I want to go into, but we don’t do that super expensive comparison anymore and we can detect null operations in basically no time at all now.
Only draw things that are visible
This sounds kind of obvious doesn’t it? But if something is offscreen, maybe don’t draw it…
Many drawing systems will automatically detect whether they’re drawing offscreen, and just not do the work. But it seems browsers do not do this. Indeed, it’s quite expensive (in terms of time spent rendering) to draw offscreen. So now Shmeppy doesn’t draw things that are off screen.
Tokens and token labels are the notable exceptions. Determining whether a token label is visible or not is complicated, because the anti-label collision code is complicated and needs to be run if I want to know where the labels are going to end up. So for the moment, those are still drawn even if they’re offscreen.
The way the drawing code will be structured after I’m done with the refactoring in the April 2020 Roadmap will make it easier for me to handle this tricky token and token labeling case, so I’m probably just going to wait until then to do this. But it’ll be almost a month until that’s ready so I might break and just do it sooner within the current Shmeppy code, we’ll see.