Fri May 17 2019
Pixmap Release: Launching Rewind
Article size is 3.8 kB - 22 MB and is a 3 min read
Today I'm excited to announce a new feature for Pixmap. While I had planned on mostly focusing on the native iOS port in Swift a couple friends brought up an idea that sounded too fun to implement, so here it is.
Think version control for pixel art, except it's just linear. It's useful and fun to use. If you buy Pixmap Ultimate you can use rewind to download snapshots or rewind an image.
When you rewind an image it modifies the history like this:
How it works is pretty simple. Luckily we chose to store every change everyone makes which made this possible. When you start a rewind the application simply paginates the event history from the backend and when you rewind an image we use the same architecture for drawing. All the changes get added to the same queue, processed by the same servers, etc. The only real difference is that a lock is created on the image and then released once the rewind is done so that no editing can happen during the rewind.
The main challenge client side was performance.
If you drag the bar all the way to the beginning of time and start dragging it forward that is very "light" since each "tick" of the bar is simply applying changes to the scene from the last tick. However, when you drag backwards you have to re-render the whole scene from the beginning of time to where the user dragged the bar. Doing this on the UI thread resulted in lots of lag, so I had to implement an "observer" thread that waits for things to render, does the processing, and pushes the result to the UI thread. I also ended up changing how concurrency worked in the renderer to reduce CPU usage, making both rewind and drawing smoother.
There were three challenges server side.
The first was that since I'm using websockets the original plan was for the client to send an event and then the server would paginate through the data and send the pages to the client. This didn't work with websockets because no matter the frame size or delay between messages the connection could get clogged resulting in high memory server side, delay in WS handshakes, and ultimately the connection closing. The solution was to implement traditional pagination over websockets where the client says "give me the next 10k after this id". At that point you don't even need websockets but I already have the infrastructure for it for other features.
For sorting the change list server-side for pagination we follow a "start after this _id" approach Mongo's
ids are naturally sortable in a non-sharded environment (assuming you let the DB create the id, and you don't create it application side). This drops
cpu usage from around 80% during a rewind of "Mid Peninsula" to a negligible amount. Also, we can just use the default index for _id
instead of
having to create more indexes which would take disk space etc. The only downside is we have to scan some documents before the sort, but at current draw
volume this works fine.
Anyway, I had fun building it and hope people enjoy it. Cheers.