Alright, let’s talk about what went down during phase 5 with sol3. This phase was really all about tackling the performance dips we started seeing, especially when our game logic in Lua got heavy.

The Starting Point
So, coming out of phase 4, things were working, mostly. But we had this nagging slowdown. Whenever a bunch of scripts ran together, the C++ side interfacing with Lua felt sluggish. The profiler pointed fingers towards how we were handling object access and function calls between C++ and Lua. It wasn’t pretty, lots of temporary objects and maybe some inefficient ways we’d set things up initially using sol3.
Digging In
First thing I did was just stare at the code. Reviewed the main C++ classes we were exposing to Lua via sol3. Looked at the Lua scripts that were causing the most trouble. My gut told me we were doing too much work on the boundary.
I decided to target a few key areas:
- Object Lifecycles: How were C++ objects, exposed as userdata in Lua, being managed? Were we creating too many copies? Were they sticking around too long?
- Function Calls: Were the C++ functions being called efficiently from Lua? Could we batch calls or change how data was passed?
- Data Structures: Passing big tables back and forth between Lua and C++ seemed suspicious.
I started experimenting. First, I tried changing how we registered some core C++ classes. Instead of passing things by value implicitly sometimes, I made sure we were using references or pointers where it made sense, using sol3’s features for that. That involved changing the `usertype` definitions quite a bit.
Hitting Snags
Naturally, that broke stuff. Lots of compiler errors initially. Then, runtime crashes. Debugging C++/Lua interactions can be a real pain. Lots of stepping through C++ code, then trying to guess what the Lua state looked like. I ended up adding a bunch of temporary logging inside the C++ functions exposed to Lua, just printing values to see where things went wrong. Old school, but it works.
One specific issue took almost a whole day. It turned out a Lua script was holding onto a reference to a C++ object that got deleted. Sol3 usually handles this okay, but the way we had structured one specific callback made it tricky. Had to refactor that callback logic significantly, making sure the C++ side explicitly managed the lifetime and nulled out the Lua reference when needed.

Refactoring and Optimizing
After fixing the immediate crashes, I focused on the data passing. We had Lua scripts returning huge tables of results. I changed this pattern. Instead of Lua returning a big table, I modified the C++ caller function to pass a C++ container (like a `std::vector`) wrapped by sol3 into the Lua function. The Lua script would then populate this structure directly. This avoided creating a massive Lua table only to immediately convert it back into C++ data.
I also looked at frequently called C++ functions. Some were doing simple lookups or calculations. I experimented with moving some of that logic into Lua itself, or providing C++ helper functions that could do more work with fewer calls across the boundary.
The Outcome of Phase 5
So, where did we end up? It’s definitely better. The major slowdowns in those heavy scripting scenarios are mostly gone. Things feel smoother.
What worked well:
- Being more explicit about object ownership and lifetimes using sol3’s reference/pointer mechanics.
- Changing the pattern for returning large data sets from Lua, populating C++ structures directly instead.
- Carefully reviewing and minimizing the number of calls across the C++/Lua boundary for trivial operations.
It wasn’t a magic bullet, and the code involved a fair bit of rewriting on both the C++ and Lua sides. We had to coordinate these changes carefully. But phase 5 achieved its main goal: making the sol3 integration performant enough for our needs again. Still some tidying up to do, maybe in a future phase, but the core performance issue is tackled for now.