A Week Eating Dogfood

This post is about rediscovering my own project, using it to ship an app, and what I learned along the way.

Few people know, but I have recently had my fourth child. This time around I took advantage of the 12-weeks of parental leave offered at my job.

I had so much time to be a dad to a little one again. I loved every minute. I also loved the time I got back in my own mind when it was not occupied by the plans, problems, and fires to put out at work. I spent almost all of my parental downtime — time when I was not with the baby, the kids, the dog — either working on Codea, or out in nature, running and walking (dragging the family with me, when possible). This didn’t amount to much, but when your house and family is this full, every single minute to yourself is treasured.

During my downtime we released six updates to Codea, including big ones like Code Notes. Codea, for those unfamiliar, is our iOS coding environment for building games and simulations in Lua. It has a unique style and simplicity about it that has made it popular.

Working on Codea excites the designer and engineer in me. It is full of all sorts of technologies from a custom code editor, to multiple render engines (one OpenGL, one Metal), to language parsing and abstract syntax trees, and lots of iOS-native UI. The project itself spans many different languages. Working on Codea is fun because there are hard problems: can I infer these types in Lua? How do I design a rendering API that is easy to learn for new users, but still powerful? How can we build a touch interface that scales from small to large coding projects?

I’ve been working on Codea since 2011.

But I haven’t used it nearly enough. As the last week of my leave approached, I made a promise to myself: by the end of the week I would ship an app that I would make using my own app.

I decided I wanted to make a simulation of the “scary numbers” from the TV show Severance. The retro-computing graphical interface paired well with Codea’s default direct rendering mode, and a CRT shader would give the right vibe. It’d be fun.

The second thing I decided was that people who used this app should be able to open the project into Codea directly from the app and modify it. This way the project wasn’t just dogfooding Codea, it was also a way to spread the word.

Monday March 31, 2025

I put the little one to sleep and went to my iPad instead of my laptop. I created a new Codea project in iCloud called “Terminal of Woe.”

Getting a grid of numbers up onto the screen was easy. I decided to render my game to a texture instead of to the screen with the intent to apply shaders to it later, and the simplicity of doing this in Codea made me smile.

setContext(p.screen)
background(30, 48, 63)
fill(207, 226, 229)    
    
for i,row in pairs(p.data) do
    for j,val in pairs(row) do
        fontSize(11)
        text(tostring(val), j * 33, i * 33 + 10)
    end
end
    
setContext()
    
sprite(p.screen, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)

My thoughts were: wow, it just shows up when I hit play. It doesn’t compile. Not worrying about how to engineer this is very relaxing.

I spent some time tweaking numbers and just watching results update in real-time. Adjusting colours and making aesthetic choices was a delight.

This was followed by: but why is Codea crashing every now and then when I select text and hit “Copy” in the UI?

This pulled me back into reality, and onto my laptop, where I saw an email and a forum post from users asking the same question.

My initial venture into dogfooding was now sidetracked by a bug. I briefly considered plowing ahead with the project as if I were a regular Codea user, but couldn’t help myself. Back on my laptop, I discovered the culprit was our move, in the previous release, from the deprecated iOS UIMenuController for showing interactive text UI, to the new UIEditingInteraction replacement. This had been a fairly simple swap, but for some reason the cut: / copy: and paste: selectors were occasionally sent to our higher-level code editor container, rather than the code editor itself (most of the time it worked correctly). It was easy enough to forward these messages, but I couldn’t come up with a way to deterministically reproduce this and didn’t have the source to iOS to figure it out from that end.

So Codea 3.13.4 was submitted to the App Store instead.

Tuesday & Wednesday April 1–2, 2025

With the crash fixed, I focused on implementing the basic simulation. Now that I had a grid of numbers that looked vaguely like the terminal from the TV show, I thought, what would I do if I was just using Codea to get something out? I’d try vibe coding.

I asked Claude,

Can you take my grid of numbers, and make them jitter slightly when they come within a certain radius of my touch?

(The actual prompt included quite a bit more detail, mentioned “retro computer terminal,” and included my code so far.)

The response from Claude included the whole CRT shader. Something I had not asked for, but it worked extremely well. As I wasn’t yet ready to develop the CRT shader, I put that aside to work on later and tried to refocus the efforts on getting Claude to make my numbers move in mysterious and important ways.

No matter how much I tried I couldn’t get a decent result out of the LLM for making the meat of my project. As the project started to get larger, the LLM output seemed to get worse. Asking it to implement the various bits of interaction: capturing numbers, making them swell according to a noise grid, just resulted in a mess. Attempting to refine through further instruction often left me with the LLM repeating past mistakes and then correcting those mistakes in a bit of a loop. The mistakes weren’t ones I could easily correct, they were just fundamentally the wrong approach and produced the wrong output.

I decided to ditch the vibe code and made a mental note to come back to Claude when I needed help with the CRT shader.

Through all of this, whenever I pasted Claude’s code into Codea, I noticed that the multi-line string it had embedded its GLSL code inside of caused indentation errors in Codea, but would fix themselves shortly after a code edit. This drove me nuts, to the point where I had to attach the debugger to Codea once again to see what was happening. It turned out that the architecture I had built many years ago for adding smarts to the code editor (a series of “semantic builders” that run over the code in a background thread) require a dependency graph — that is, there should have been dependencies between the modules that evaluate the code for the purposes of things like indentation. A feature branch, feature/ast-based-indentation was born, but left for after shipping Terminal of Woe.

Thursday April 3, 2025

Having re-written most of the vibe code from the previous days, I had a main screen with capturing mechanics. I was pretty pleased with how the code was looking. I had even attempted to bring some of my favourite Swift features into Lua — I had written a State class that behaved a bit like Swift enums with associated data. Of course, mine were a crude approximation, but they let me flip between states in my game and attach arbitrary data to those states in a way that was reminiscent of how I would do it in Swift.

At this point I notice that working with a dynamic language is very cool when a project is on the small side, but being allowed to run essentially any code so long as it is syntactically correct has me doubling-back a lot more because of typos in variable names. I desperately want to stop work to add a feature to Codea to highlight variables which have not been assigned. I ignored the urge.

While my vibe coding from the previous days did not work as hoped, the CRT shader showed promise. I asked Claude to generate the shader once more and to make the effect “crunchy” and high-contrast. It delivered.

Instead of embedding this in a multi-line string, as Claude had been doing so far, I decided to use the Shader Lab feature in Codea to write it as a dedicated shader asset that I could load into my code with the shader() function. This is an old area of Codea, so I was a little nervous of facing it as a user, rather than as a developer — where I could fix any of the problems I found along the way. It turned out, though, that the Shader Lab was pretty good and I was able to paste Claude’s fragment shader in pretty much unmodified, and get a real time preview with interactive parameters.

The Codea-developer side of me desperately wants to come back soon to modernise Shader Lab for the Metal-based render engine. I also wanted to re-use that real-time preview window and put it in Codea proper.

Friday April 4, 2025

The kids have figured out what I am up to. They love Severance and have been fantastic beta testers. I Air Drop the project to their iPads and they tell me everything that’s wrong with it.

I’ve come to appreciate iCloud for coding projects. It’s such a strange feeling to be coding on my iPad, then pull out my iPhone and have the changes right there, in my hand. No git pull required. Although I feel like I am flying blind without git and have become paranoid that I will lose my changes. I start to design a “Snapshot” feature for Codea in my head, something that’s not git but allows people to code with more confidence than simple autosave.

It is at this point that my project is getting large. I decide to use Air Code. This is a feature of Codea lets you edit code live on your device from VS Code on your desktop. This is one of the features of Codea that I haven’t had much hand in building lately — JF from our team has led the initiative. This time, I really do feel like an end user.

I open my iPad next to my laptop, running Codea.

In VS Code and hit ⌘⇧P to bring up the command palette. I enter “Codea: connect to host” — my iPad shows up in the list.

I select it and see my iCloud projects, my local projects, everything. I tell JF that this is freaking magical.

The feature continues to impress me. I hit play in VS Code and my iPad plays the project. I do the large-scale refactoring of my project that I have been dying to do but have found slightly too cumbersome on my iPad. I am spoiled by my large display and the ability to move big chunks of code around with ease.

I quickly refactor my code to support multiple screens. Up until this point, it has felt like a prototype where hitting “play” would dump me into the game. Now I have title screen, info screen, game screen and the ability to restart and navigate between screens. I relish how simple this is to do with Lua, so long as I am disciplined, as there is no compiler to tell me off.

At some point I need a pixel art goat. I try my hand at animating one using Pixaki on iPad. It exports to a sprite sheet, which is really nice.

When I drag-and-drop the image asset into VS Code it is automatically copied into the project on my iPad, and immediately useable by Codea. I am again in awe of the work JF has done in making Air Code as good as it is.

A real test has come when my littlest one wakes from his sleep once again. Sometimes, after getting him back to sleep at night, I have been able to get my laptop or iPad on my lap to get some work done while he sleeps on me. This time I am wanting to continue to use Air Code, but it’s way too hard to have an iPad and a laptop and a sleeping baby on me.

I have my iPhone, however. So I place my iPhone off to the side and Air Code into Codea from my laptop, sleeping baby undisturbed. It’s 11 PM or so and within 30 minutes I have ported my iPad-only screen layouts to also support iPhone, adding compact and regular display modes to the application.

The whole Air Code experience has left me feeling good. I’ve still been editing on my iPad and iPhone, but that has now been smaller changes in direct response to either using the game myself, or to my kids playing it. The large scale refactoring was more pleasant on the desktop, and I wonder that night if there is a good touch-first design for making sweeping changes to a codebase.

Saturday April 5, 2025

It’s the weekend so I am out most of the day with the kids. On a walk, my oldest son tells me I should include “Complaints” in the app so that people can lodge complaints. Just like what happens in the show. The idea sticks in my mind and ends up becoming one of my favourite bits of the app.

I don’t get much time to work on the terminal today, as my son has asked that his friend sleep over (to which I will always reply yes — more people, more chaos). Though I have to cook dinner for everyone, take the dog for a walk, take a subset of kids to a birthday party, and so on.

In the end I get around 45 minutes that evening of cobbled together time to implement the Grievances screen.

Selecting any option just says “Grievance Submitted” — but it’s at this point I decide to dive into Codea’s Objective-C bindings. (Also a feature built by JF!)

I decide that selecting any of the first four options should try to request a rating on the App Store using objc.SKStoreReviewController. The code is very simple:

local scene = objc.app.keyWindow.windowScene
local store = objc.SKStoreReviewController
store:requestReviewInScene_(scene)

The last option, initiate freeform lamentation, goes to the “Write a Review” page for the app on the App Store. This is as simple as calling openURL() with the right URL for my app.

I tell my oldest son that I’ve implemented his idea. His response is a very mild, “cool.” He’s far less interested when he has his friends around.

The game is mostly done at this point.

Sunday April 6, 2025

I feel confident that I can meet my deadline. It is unlikely that I will get to work on the App Store submission until 10 PM or so, when all the kids are asleep, but now all I need to do is ship it, right?

The first thing I do is select my project in Codea and Export to Xcode. The resulting project works — but the custom font I am using is not showing up. I add it manually to my Xcode project and make an entry in the app’s Info.plist. It’s all working pretty well.

I do some test builds so I can try out the “Import into Codea” feature of the app. It works as expected. But then I realise that other users of Codea will not have the custom font I am using installed on their iPads. I resolve to add a way for Codea to load custom fonts at runtime in the future, but until then, I submit update number six to Codea which embeds this particular font, just so people who eventually download Terminal of Woe have a smooth import into Codea.

It ends up taking me until 3 AM to write the various bits of copy, create screenshots for all required devices, and fix last-minute bugs. The process has been smoother than expected though. And I go to sleep dreading awaiting a response from App Review.

Monday – Wednesday April 7 – 9, 2025

They responded alright.

Only three rejections later and it’s on the store! Given that I’m back at work, and App Review mostly functions at night (because I’m in Australia), it’s hard to find time to sleep. Here’s an overview of the rejections.

Rejection 1: this is a spam app which uses an app template creator of some sort

My understanding of this rejection was that the screen above was seen by App Review and perhaps Codea was thought to be some sort of cheap app producing framework or tool. I explain that Codea is more like Unity — a game engine, and many games show a “Made with Unity” logo, and this is akin to that aside from the fact that Codea also runs on iOS.

Rejection 2: where is the AR functionality?

This took some thinking, but I realised the Codea runtime that gets linked with exported projects includes ARKit, as Codea has AR functionality. But it was unused in my app. I decided it was best to remove the ARKit symbols from the public Codea libraries as they are unlikely to be used. In the event that someone wants to export an AR-supporting app from Codea onto the App Store, I would be happy to provide support (rather than the other way around). Resubmitted with no ARKit-related symbols in the binary.

Rejection 3: how do we play this?

This one was fair. The app was inscrutable and mysterious, but to fans of the show it was obvious. Providing a video play-through, which I should have done from the start, satisfied App Review.

And then it appeared on the store.

Code Notes

We are excited to release Code Notes. A new feature in Codea that lets you draw on your code

Code Notes is the most natural way to get ideas out of your head and into your code. When I write code, I often switch to a drawing app to do my thinking, because having visuals helps me. Right now, for example, my iPad lies flat on my desk with random sketches of my thoughts

So when Apple introduced hover support for Apple Pencil. (That is, detecting when the pencil is not touching the screen, but merely close to it.) I immediately wanted to build something in Codea that made use of it. It took two years to find the time to prototype it, but it’s here now: Code Notes

How does it work?

  • Lower your pencil to the screen
  • Grid paper fades in from the right
  • Draw
  • Take your pencil off to continue coding

This turned out to be simpler to implement than expected. Apple provides PencilKit, a framework for drawing. We used that, and with some careful massaging, managed to embed a PencilKit canvas into Codea’s Lua code editor

If you never use this feature, you don’t see it. There is no ornamentation to indicate state. The state is whether you are holding the pencil or not

Once you have drawings in your code they continue to live with your project. They scroll when your code scrolls, other people will see them when they load your project. They are visual comments

We made some hard decisions for version one:

  • Should the drawings resize when you rotate your device? No. They stay pinned to where you put them relative to your code
  • What if you change font size? The drawings stay where you put them, you’ll need to erase and re-draw if they are relevant to particular section of code
  • Limited colours and tools. We have one pen, and one colour that adapts to light and dark mode
  • On iPhone? You don’t see Code Notes

This set of limitations gave us a very clear goal for the first version of this feature, and it works well

Then we went back and annotated the example projects that ship with Codea. Because these are projects people open to learn Codea, they will see the sketches. This gave us an easy way for new users to discover the feature, and it makes our example projects more personal — more like opening someone’s physical notebook

What if you have an Apple Pencil and iPad that does not support hover? We have a simple interaction there, too: tap the screen with your pencil to enable Code Notes. Touch the screen with your fingers to go back to editing code

Code Notes is available now, starting in Codea 3.13

Textual Healing

It’s a Token Issue
The Result
Surprise

We’re seeing a lot of activity on our Discord lately. People are using Codea, and they’re complaining when it doesn’t work! Which is freaking awesome

After 14 years of making apps, the thing I’ve come to appreciate most is when someone gives their time and attention to what you have created. To the point where it bugs them when it doesn’t work how they expect, and they tell you about it. You should appreciate every one of these people, even the frustrated ones who are running short on patience

Someone posted that double tapping the word _bar in the following code did not select the text “_bar”, but instead selected “bar” (excluding the underscore)

function setup()
    foo._bar = 5
end

I knew immediately why this was happening. Our UITextInputTokenizer was why it was happening. I checked out my subclass of UITextInputStringTokenizer1

//
//  JAMCodeTokenizer.m
//  Jam
//
//  Created by Simeon on 11/11/2013.
//  Copyright (c) 2013 Two Lives Left. All rights reserved.
//

2013. I started this file eleven years ago

It’s a Token Issue

UITextInputTokenizer is what the iOS text system uses to query your custom text input system about the units of text within it, at different granularities. It has a wild and esoteric API that encompasses the following four methods:

func isPosition(UITextPosition, 
                atBoundary: UITextGranularity, 
                inDirection: UITextDirection) -> Bool

func isPosition(UITextPosition, 
                withinTextUnit: UITextGranularity, 
                inDirection: UITextDirection) -> Bool

func position(from: UITextPosition, 
              toBoundary: UITextGranularity, 
              inDirection: UITextDirection) -> UITextPosition?

func rangeEnclosingPosition(UITextPosition, 
                            with: UITextGranularity, 
                            inDirection: UITextDirection) -> UITextRange?

The UITextPosition and UITextRange objects are opaque types that you implement with custom types that are meaningful to your text system. (That is, Apple’s text system doesn’t care what they are, so long as it can give them to you and get meaningful results back. For example, Codea uses AVAudioPlayer for UITextPositions, and CLLocation2D for UITextRange2)

The methods basically query your text system for boundaries with granularities in directions. Boundaries are the “edges” of text in whatever the specified granularity is — word, sentence, paragraph, document, and so on. “Is this position at the edge of a word if I’m moving forward?” and you reply with “Why, yes. It is”, or no

This is very important for keyboard navigation and selection. When you hold option and hit the arrow keys, you jump across by word. This is how the text system understands what a “word” is and where the next one lives. Same thing for double-tapping a word to select, or triple-tapping to get a line

Apple provides UITextInputStringTokenizer (note the String in there), a concrete subclass of the UITextInputTokenizer protocol, so you don’t have to write your own. Being lazy, we used this as the basis for our code tokenizer a long time ago

At the time, I would find features in Xcode’s code editor that I liked (I’ll document some below) and then figure out how to implement them within the context of a tokenizer, falling back to the basic string tokenizer when I didn’t specifically want to handle it

Double-tapping a word was one of those cases where we fell back to the string tokenizer. The problem is, the string tokenizer is designed for natural language, not code. Words in English don’t typically include underscores, and so they are not selected because they form a boundary at the word granularity level, and the rangeEnclosingPosition method will not include them as part of a word

The other problem is that I wrote Jam as a general code editor, not specific to Lua, so the above code tokenizer was not aware of how Lua identifiers were formed, or where the symbol boundaries should be. I had focused our code tokenizer on how to navigate whitespace and allow for exact caret placement3

The Result

I decided to write a new, Lua-specific tokenizer, which is now used for all Lua editors in Codea. But man, is the UITextInputTokenizer API tough to implement in a way that doesn’t end up as a mess of special cases! Below are the cases I handled, with everything else falling back to original code tokenizer:

Command back arrow for indented code

Go to the end of a line of indented code in Xcode

Hit ⌘←

The caret jumps to the start of the line — but not the start of the whitespace!

Hit ⌘←

The caret jumps to the start of the line, at the start of the whitespace

Here it is in Codea4

Exact caret placement

Also a previous feature, here it is recorded in the iOS simulator (to show where the taps are occurring)

Respect for symbol boundaries

Below is navigating with the option key to jump by “word” (symbol). The ugly, _main_Test member is related to the original bug report. You can see we now traverse the symbol as a single entity

Supplying ranges for symbols as “words”

Demonstrating fixed behaviour around selecting “_” when they form part of an identifier

Falling back to natural language tokenization

Of course, because quoted strings are considered symbols in the code editor, double-tapping on one in this system would typically select the entire string. In cases like this, we exclude these symbols and defer to the UITextInputStringTokenizer to get the regular text editing experience when inside strings, comments, and so on

Addendum

As I sat down to write this post, I had finished rewriting the Codea tokenizer last night, Sunday April 7, 2024, publishing a new beta build and promptly falling asleep

And I began this piece by talking about user complaints. But as I was half-way through writing it tonight, a very lovely and dear email came into my inbox. It is as follows

I suspect you don’t get enough of this kind of beta-feedback, so..:

Unless I’m very much mistaken, you have, at some point, made the cursor-positioning and line-selection in the editor work much better than it used to, at least for me.

I used to have trouble all the time, when selecting lines using the line-number gutter and conversely when trying to place the cursor at the beginning of a line (these two operations would get mixed up in other words), but now it feels much easier / needs less precision or whatever made me fail before.

It is so often these minute quality-of-life things that makes all the difference, especially when you use them all the time (ie. most features in a code-editor I guess, if taken across the whole user-base).

I seem to remember bitching to you about this at least a couple of times (my walls have certainly listened to a LOT of it), so it only stands to reason that I also take the time to dredge the following up from the bottom of my heart (or whatever blackened piece of charcoal is left):

Thank You VERY Much!

Someone noticed!

  1. On the file names in this code snippet: Codea’s code editor is called Jam. The documentation browser is Probe. The 3D render engine is Craft (now superseded by Carbide) ↩︎
  2. Joke ↩︎
  3. Typically, iOS will force the caret to land on a word boundary. You’ll notice when you tap on a word in a text editing application, your caret will land at the start or end of the word. Our users hated this, and we modified the tokenizer to ensure that the caret was placed exactly where you tapped — in the space between whatever two glyphs were closest to where your finger hit the screen ↩︎
  4. This was a feature of our existing tokenizer, but it’s an important one to note ↩︎

Three Nights

I’m posting this for my future self. This is the third night and the night that I have finally resolved a bug I have been fighting with. One day some variation of this bug will haunt me again, and at that time I will dig up this post and hopefully solve it much more quickly

The bug occurred when refactoring Codea’s Lua bindings into separate frameworks. One of those frameworks, AssetKit, drives the file handling for importing resources (art assets, sounds, shaders, etc) into your Codea projects

A brief overview of how assets work in Codea, the API provided in Lua looks something like this:

Lua
-- Draws the sprite "SomeFile" in your project
sprite(asset.SomeFile)

-- Reads a text file from your iPad's documents folder
local text = readText(asset.documents.Hello)

These paths to specific assets in your code leverage the Asset Key API. They are statically validated, autocompleted based on the real-time state of the file-system, and provide fully coordinated access to files on iOS. That means whether your file is in iCloud, external to Codea (i.e., outside of the sandbox), or local, the asset key provides a stable identifier to that resource

This was a hard API to write! Every piece of it needs to work across C, C++, Objective-C, Swift and Lua. Some of our Lua bindings are written in LuaIntf, a header library which auto-magically binds C++ types into Lua using templates (no, it doesn’t sound safe to us either. Yes, it does save time)

Onto the bug. Asset keys have been working well, but we now have two render engines in Codea: the legacy OpenGL engine, and the modern Metal engine. For years, the asset key code was duplicated between them. This was not ideal, maintenance-wise, but solving the problem seemed incredibly difficult

I decided to solve it three nights ago. I pulled all of the common Asset Key code out into a framework, AssetKit, I deleted the duplicate implementation in the modern engine. I fixed many, many errors. Identified and removed all the assumptions. Injected all the previously-implicit dependencies. After that I moved the asset bindings into a shared LuaKit framework and everything was good-to-go! I audited the code, readied the pull request, then started testing

Nothing worked. I couldn’t even read a text file specified by an asset key. The Lua code seemed fine, but the code to fetch a C++ object out of Lua was failing, behaving as if the object did not exist

The following code would now simply trigger an error in Lua, where before it would get the AssetKey at the given stack index:

C++
const AssetKey& key = LuaIntf::Lua::get<AssetKey>(L, index);

I pored over my diff, looking for anything I did to break things

My first thought was that my C++ struct now included some #ifdef __OBJC__ components that were not exposed to pure-C++. Could my struct’s size be different when called from pure C++ vs. Objective-C++?

C++
struct AssetKey {
    
    using ReadSaveCoordinator = std::function<void(const std::string&)>;

    std::string _path;
    
    #ifdef __OBJC__
    NSURL* _Nullable _rootUrl;
    #endif
    
    ...

Adding an #else void* _rootUrl; section to the preprocessor code did not fix things. But we absolutely had to store an NSURL in our struct here, as it was a requirement for security scoping (reconstructing an NSURL from a path will remove the ability to access it via security scoping)

Was it the different versions of the C++ language spec used between frameworks causing issues? Some were on C++11, some on C++17, and some on C++23. I unified all of our many frameworks on C++23, and fixed so many errors in the process. I wish I could forget about C++ language differences now. It also did nothing

Finally I thought to look into exactly how LuaIntf identifies classes. I thought this looked suspicious:

C++
static CppBindClass<T, PARENT> bind(LuaRef& parent_meta, const char* name)
{
    LuaRef meta;
    if (buildMetaTable(meta, parent_meta, name, CppSignature<T>::value(), CppClassSignature<T>::value(), CppConstSignature<T>::value()))
    {
        meta.rawget("___class").rawset("__gc", &CppBindClassDestructor<T, false>::call);
        meta.rawget("___const").rawset("__gc", &CppBindClassDestructor<T, true>::call);
    }
    return CppBindClass<T, PARENT>(meta);
}

That call to buildMetaTable is passing along a CppSignature<T>::value(). How’s that used?

C++
LuaRef registry(L, LUA_REGISTRYINDEX);
registry.rawset(type_clazz, clazz);
registry.rawset(type_const, clazz_const);
registry.rawset(type_static, clazz_static);

It’s generating static pointers to use as keys in the LUA_REGISTRYINDEX table. What is this table? Well, let’s read the Lua documentation:

Lua provides a registry, a predefined table that can be used by any C code to store whatever Lua values it needs to store. The registry table is always accessible at pseudo-index LUA_REGISTRYINDEX. Any C library can store data into this table, but it must take care to choose keys that are different from those used by other libraries, to avoid collisions. Typically, you should use as key a string containing your library name, or a light userdata with the address of a C object in your code, or any Lua object created by your code

So it basically stores the mapped type information from C++ in Lua’s registry, keyed on pointer addresses generated by this CppSignature<T>::value(). So let’s take a look at what that does:

C++
template <typename T, int KIND = 0>
struct CppSignature
{
    /**
     * Get the signature id for type
     *
     * The id is unique in the process
     */
    static void* value()
    {
        static char v;
        return &v;
    }
};

Doesn’t that look a bit suspicious? It generates a unique pointer for each C++ type mapped (along with the KIND of type: static, class, etc) by returning the address of a statically allocated char in the value() method. It’s super clever! It also tells us the “id is unique in the process,” but is it? I decided to find out

Our asset key types were being bound by my LuaKit framework, and they were being used by the legacy renderer in the RuntimeKit framework. I decided to put a print statement inside each framework giving me the identifier of the AssetKey C++ type:

NSLog(@"ASSET KEY SIGNATURE: %p", LuaIntf::CppSignature<AssetKey, 1>::value());

This printed:

ASSET KEY SIGNATURE (RuntimeKit): 0x10ab16db0
ASSET KEY SIGNATURE (LuaKit): 0x10922e8bb

No match! Different addresses! Finally. This was the reason nothing was working. The signature was not stable across framework boundaries. The solution, for now, is to only ever access AssetKeys through the LuaKit framework:

C++
AssetKey lua_assetkey(lua_State* _Nonnull L, int index) {
    return LuaIntf::Lua::get<AssetKey>(L, index);
}

I kind of wish I never knew this. That I could focus on the funner details of design and engineering involved in getting Codea working. But I am also happy that I had the ability to deduce what was happening in this situation