Porting Mistreevous to C#: A High-Performance Behavior Tree Library for Modern .NET
Source: Dev.to
When I started building dedicated servers for multiplayer games, I ran into a very real problem: server‑side entities needed rich, structured, and consistent AI behavior. In single‑player games the client usually handles this, but in modern multiplayer titles with authoritative servers the AI must run server‑side — and that completely changes the performance and architectural demands.
Behavior Trees were the obvious choice. I fell in love with Mistreevous, a beautifully designed TypeScript library by nikkorn, thanks to its clean, modular, and highly expressive DSL. The challenge? Bring that same elegance to the .NET world — but optimized for the harsh reality of server tick loops.
Why Behavior Trees?
Behavior Trees shine in games, robotics, simulations, and any agent‑based system because they offer:
- Hierarchical composition
- Excellent readability and intent clarity
- Strong modularity
- Easy debugging
- Predictable execution order
For multiplayer servers specifically, they deliver:
- Extremely low cost per tick
- Deterministic outcomes
- Clean separation of logic and state
- Horizontal scaling across hundreds of entities
Mistreevous already nailed the design in TypeScript. The C# version had to be just as expressive — but truly server‑ready.
Why Mistreevous Specifically?
The original Mistreevous stands out with:
- A fluid, readable DSL
- Well‑defined node types
- Simple parsing
- Full JSON compatibility
- An intuitive API
It was built for a JavaScript runtime. In .NET (especially .NET 9 and .NET Standard 2.1) we have far more control over memory and performance. The goal: keep the spirit and compatibility, but eliminate allocations and make it scream on the server.
Not Just a Port — A Performance‑Focused Rewrite
While staying 100 % semantically compatible with the original, almost every internal detail was re‑engineered.
Zero‑Allocation: The Core Principle
The primary objective was simple: calling Step() (one AI tick) must generate zero garbage. On a server with dozens or hundreds of entities ticking 30–60 times per second, even tiny repeated allocations become a GC nightmare.
Key techniques applied:
- Eliminated LINQ in hot paths – no enumerators, closures, or temporary collections.
- Reusable buffers and pools – pre‑allocated, thread‑safe lists reused across ticks.
- Manual Span‑based parsing – character‑by‑character processing instead of
Split()or regex. - No captured closures – avoided delegate allocations wherever possible.
- Classic
forloops – bypassing enumerator overhead. - Compact node design – fixed arrays, lightweight structs, minimal fields.
Full DSL and JSON Compatibility
A non‑negotiable requirement: any tree from the original Mistreevous must work unchanged.
root {
sequence {
action [CheckHealth]
selector {
action [Flee]
action [Fight]
}
}
}
MistreevousSharp supports:
- Identical
SUCCESS/FAILURE/RUNNINGsemantics - Same guard evaluation
- Subtree references
- Decorator behavior
- Execution order
You can literally copy‑paste trees between TypeScript and C# projects.
Project Architecture Overview
The repository mirrors the conceptual structure while making optimizations explicit:
MistreevousSharp/
├── assets/ # Images and thumbnails
├── example/ # Full working demo (MyAgent + Program.cs)
├── src/Mistreevous/
│ ├── Agent.cs
│ ├── BehaviourTree.cs # Core execution engine
│ ├── BehaviourTreeBuilder.cs
│ ├── MDSLDefinitionParser.cs # Zero‑alloc DSL parser
│ ├── Nodes/
│ │ ├── Composite/ (Sequence, Selector, Parallel, …)
│ │ ├── Decorator/ (Repeat, Retry, Flip, …)
│ │ └── Leaf/ (Action, Condition, Wait)
│ ├── Attributes/ # Guards and callbacks
│ └── Optimizations/ # Zero‑allocation helpers
├── .github/workflows/ # Automated NuGet publish
└── README.md
The Optimizations/ folder is unique to the C# version — it centralizes all performance‑critical tricks that don’t exist in the TS original.
Rough Performance Comparison
| Operation | Original Mistreevous (TS) | MistreevousSharp (C#) | Improvement |
|---|---|---|---|
| DSL Parsing | ~0.8 ms | ~0.35 ms | ~2.3× faster |
| Per‑tick execution | Variable (GC pauses) | Stable ~0.00 ms | Near‑zero overhead |
| Allocations per tick | Multiple objects | Zero | Completely eliminated |
The real gains scale dramatically with entity count.
Usage Example in C#
var definition = @"
root {
sequence {
action [CheckHealth]
selector {
action [Flee]
action [Fight]
}
}
}";
var agent = new MyAgent(); // Implements your action callbacks
var tree = new BehaviourTree(definition, agent);
while (gameIsRunning)
{
tree.Step(); // One AI tick — zero allocations
}
See the full example in the repo’s example/ folder.
Where It Shines
Beyond multiplayer servers, MistreevousSharp is perfect for:
- Unity games (no GC spikes)
- Godot C# projects
- Simulations and autonomous agents
- Robotics and drone control
- Any system needing modular, readable AI
Final Thoughts
Porting Mistreevous to C# was far more than translation — it required rethinking every allocation, every loop, and every object for the .NET reality. The result is a library that keeps the original’s elegance and compatibility while being truly ready for high‑performance, server‑scale scenarios.
If you’re building anything with complex agent behavior in .NET, give it a try! Feedback, stars, and contributions are very welcome. Let me know if you’ve used behavior trees on the server side — what pains did you hit?