Subtitle: A developer diary on breaking the 66k ceiling and the power of Object Pooling in .NET.
In my last deep dive, "The Quick Brown Fox 2: The Silent Hordes," we pushed the FSM_API to its absolute limits. We built a dynamic, expanding world where Foxes tried to survive a landscape littered with Sleeping Dogs.
We hit a hard ceiling: 66,000 Agents at 30 FPS.
At the time, I assumed the bottleneck was purely architectural—specifically, the overhead of our string-based state lookups. I was ready to accept that 66k was the limit until we refactored the entire core to use Integer Hashing.
I was wrong. The bottleneck wasn't just the architecture; it was a "Design Miss" in my own simulation logic.
The "Oops" Moment
While gathering data for my article on the "$320 Billion Garbage Tax," I ran a profiler on the "Silent Hordes" demo to measure memory allocation. The results were horrifying. The simulation was generating 20 MB of garbage every single frame.
For a C# developer, this is the equivalent of lighting your CPU on fire. The Garbage Collector (GC) was stopping the world constantly to clean up a mess I was creating 30 times a second.
I tracked the allocation to the Spatial Hashing loop—the system the agents use to find each other.
The Culprit:
// Inside the Update Loop...
foreach (var agent in _allAgents)
{
// ... calculate cellKey ...
if (!Grid.TryGetValue(cellKey, out var cell))
{
cell = new GridCell(); // <--- THE KILLER
Grid[cellKey] = cell;
}
// ...
}
In my rush to build the stress test, I was allocating a new GridCell() for every populated cell in the world, every single frame, only to discard them at the end of the tick. At 66,000 agents, that is thousands of allocations per millisecond.
The Fix: Don't Create, Reuse.
The solution was a classic game development pattern: Object Pooling.
Instead of letting the GC eat those cells, I created a Queue<GridCell>. When the frame ends, instead of clearing the dictionary and letting the cells rot in memory, I wipe their data and shove them into the queue. When the next frame starts, I pull them out.
The Impact:
- Allocations per Frame: 0 MB.
- GC Pressure: Non-existent.
The New Ceiling
The results of this single optimization were instant and violent. We didn't just inch past the previous record; we smashed it.
- Previous Limit: 66,000 Agents @ 30 FPS.
- New Limit: 99,000 Agents @ 30 FPS.
We gained 33,000 additional agents—a 50% increase in total capacity—just by stopping the memory churn.
If we look at the previous cap of 64,000 agents, the performance jumped from 30 FPS to 53 FPS (a 76% increase in speed).
What This Means
This discovery validates the thesis of our "Garbage Tax" article: we are often paying for performance we already own but are wasting on bad memory management.
The FSM_API was ready to handle 100,000 agents all along. The logic was fast enough. The architecture was sound. It was simply drowning in digital trash.
With the memory leak plugged, we are now standing at the door of 100,000 active, logic-driven entities. And remember—we still haven't switched to Integer Hashing yet.
When we combine this memory efficiency with the upcoming O(1) lookup refactor, 100k might just be the starting line.
Read the Full Story:
Get the Code & Run the Test:
Clone the repo and run the StressProfiler to see these numbers on your own machine.
https://github.com/TrentBest/SimpleDemos
Resources & Code:
The FSM Package (Unity Asset Store):
https://assetstore.unity.com/packages/slug/332450
NuGet Package (Non-Unity Core):
https://www.nuget.org/packages/TheSingularityWorkshop.FSM_API
GitHub Repository:
https://github.com/TrentBest/FSM_API
Simple Demos Repository (Run the Stress Test):
https://github.com/TrentBest/SimpleDemos (Clone this repo and run the StressProfiler project to reproduce these numbers.)
Support Our Work:
Patreon Page:
https://www.patreon.com/c/TheSingularityWorkshop
Support Us (PayPal Donation):
https://www.paypal.com/donate/?hosted_button_id=3Z7263LCQMV9J
We'd love to hear your thoughts! Please Like this post, Love the code, and Share your feedback or what you've built with the API in the comments.
