Published on

Devlog #64 - PxSfsClient

Previous: https://www.patreon.com/posts/devlog-63-151418457

Last time we learned all about garbage and garbage collectors and how even small amounts of temporary memory can cause noticeable stutter in a game. Since then I’ve been digging into where the biggest sources of garbage are still coming from in FO2, and unsurprisingly one of the main offenders is still the SmartFoxServer JavaScript API.

https://www.npmjs.com/package/sfs2x-api

This is the library that handles all of the networking in the game. Every packet that comes in from the server gets decoded into JavaScript objects so it is easy to work with. The downside is that this process creates a lot of temporary data. Objects, arrays, and strings get created, used briefly, and then thrown away. In an MMORPG where updates from potentially thousands of players are constantly streaming in, that turns into a steady flow of garbage.

If you remember Devlog #52, we moved the entire SmartFox API into a Web Worker to push all networking and garbage collection work off the main thread.

https://www.patreon.com/posts/devlog-52-web-105415767

That worked really well at the time. Movement got noticeably smoother because the main thread was no longer getting interrupted by garbage collection caused by all the allocations during packet decoding. But it did not actually reduce the amount of garbage being created, it just moved it to another thread. The data coming out of the worker is still new JavaScript objects on the main thread, so we are still creating temporary data for every message that has to be cleaned up.

So instead of continuing to work around the problem, I decided it was time to replace the sfs2x-api entirely and write our own TypeScript SmartFoxServer client. The first step was reaching out to the SmartFoxServer developers to get access to the original source code. The version that ships publicly is minified and obfuscated, which makes it extremely difficult to understand or optimize. Having the real source made it possible to actually see how everything works internally and start rebuilding it in a way that fits our new PxEngine.

From there the focus was cutting everything down to only what FO2 actually uses and nothing more. The official API is built to cover every feature SmartFox supports, but most of that never applies to the game, so removing it made the entire networking layer much smaller and more direct. What used to be a large collection of old JavaScript files is now a small set of TypeScript modules built specifically for the engine, handling connection, handshake, login, and message processing directly against the raw protocol.

Another piece of this was removing the need for external compression libraries. SmartFox uses zlib for packet compression, so instead of pulling in a full dependency we implemented just the decompression side needed to read incoming data, keeping it consistent with the rest of the engine’s zero garbage approach.

The next step was figuring out how to actually reduce the packet deserialization garbage instead of just shifting where it gets cleaned up.

The core problem with the original API is that it fully decodes every packet into JavaScript objects. Even if a message only needs a few values, the entire structure gets turned into objects and arrays first. Most of that data is never used, but it still gets allocated and eventually collected.

So instead of decoding everything, the new client reads directly from the socket’s byte buffer and only extracts the fields we care about. That buffer comes from the browser’s native networking layer, so it is not JavaScript memory and does not contribute to garbage collection. Everything else in the packet gets skipped at the binary level and never becomes a JavaScript object. Each message defines the fields it expects ahead of time, and when a packet comes in the decoder scans through it, finds those fields regardless of order, and writes the results directly into preallocated storage. There is no intermediate representation, it goes straight from bytes to usable values.

This is the new actor movement packet handler using the new API.

Each message just declares the fields it expects and provides a callback that receives the decoded values directly, without building any intermediate objects or arrays. The same buffers and arrays are reused for every packet, so handling a message does not create new garbage and the performance stays consistent even under heavy load.

This has been a huge amount of work, but it removes one of the largest remaining sources of garbage in the game. Networking is something that touches everything, so cleaning this up has a noticeable effect across the entire experience, especially in busy areas where a lot of updates are coming in at once.

Our project tree structure is now too big to screenshot! As you can see we've added several new large complex files for the new Smartfox client and it feels so good to have this work mostly done. Now we can start porting over all our message handlers and start hooking up core stuff like spawning actors and moving them around!

Since we now have a connection to the server working, the next step is making this accessible to all Patrons. In the not too distant future I will be adding a way to try out the new engine on the main website. It will likely be something simple like a checkbox that switches the client to the new version instead of the current one, so everyone can see the progress as it happens!

See you next Sunday for another big devlog!

Have Fun & Keep Gaming!

P.S. - Our PxEngine package.json currently has zero dependencies and only 3 dev dependencies. That's awesome.