Part 5: Making Godot Viable on iPadOS: Development Loop
Before I embarked on pouring my heart and soul into making a native iPadOS user experience of Godot, I needed to make sure that I would end up with a viable version of Godot.
Building Godot to run on iPad is straightforward. The command line to do this is quite simple:
bash$ scons p=ios target=editor dev_build=yes arch=arm64
The challenging part was to blend Godot's design principles with the requirements of the iPadOS platform. Let me explain.
You can compile Godot in two modes: as a Game Development IDE, what we refer to as the "Editor," and as a runtime to play your games. Godot is designed only to run one thing at a time, and during development, Godot launches three programs depending on what you are doing to do this:
Each green arrow represents the launch of a fresh, new process with none of the baggage from a previous life.
When you first launch Godot, you start in the Project Manager. There, you can create a new project or open an existing one. This Project Manager is built as a Godot application, using the Godot UI toolkit.
Once you have created this project, Godot launches a fresh new process in editor mode and terminates the project manager. When you debug your application, it will launch a new process to show you your game in action. When you terminate your game, you go back to the editor.
When you decide to quit the editor and return to the welcoming screen, the editor terminates and launches the project manager.
This process works great on Unix and Windows but is not suitable to be used on the iPad.
On the iPad, applications are not allowed to terminate, nor is it possible to relaunch the application in a different mode or to rely on multiple app installations to work (say, one for the project manager, one for the editor, and one to play the game). This limitation is an AppStore policy rooted in user expectations for applications on iPadOS.
I decided not to bring the Godot Project Manager and instead adopt the idiom commonly used in iPadOS applications.
Godot's default project manager uses a different workflow, where the user must pick a location for the project and the style of the project:
I find the above dialog confusing and intimidating. It's confusing because of the way the path and directory selection work, plus the warnings and errors that go with it. I find it intimidating because users need to make what appear to be important decisions without knowing what to expect.
Instead, I followed existing iPadOS idioms. The screenshot below shows what my current Godot for iPad project manager looks like:
You only need to press the [+] sign to launch your project. Later, you can return to rename the project and change the settings.
One Godot gone, another one to go.
Virtualizing Godot
That last Godot was not easy to get rid of. Launching and stopping your game is at the heart of the Godot experience, and I needed to solve this satisfactorily, or this effort was dead in the water. And this happens hundreds of times.
These are some options that I dismissed:
- Attempt to get Godot on iPad with multi-processes, and then try to negotiate with the AppStore review team that this was acceptable and deploying some social media outrage. The chances of this succeeding are less than 1%. There is too much risk for such a significant investment of time.
- Compile the game to WebAssembly and run the resulting project in an embedded Safari window. This could work, but it was slow to start, and I had to keep any deeper integration I wanted at arm's length. Users would be surprised to find differences between what they see on the Godot Editor and what runs on the web.
I decided instead to reset the Godot state across invocations. When the user transitioned from the editor to the game, I would run all the destructors in Godot, clear up all the global states, and launch the game. Repeat this process when going back from the game to the editor.
It took me about ten days of intense work in October to get this to work, but in the end, it did. However, the experience was far from optimal because this meant relaunching the Godot editor every time the user stopped a game, and this was a very slow process. It was so slow that I feared it would render the entire project worthless.
But this proof of concept gave me the confidence to run everything in a single process.
My next step was to virtualize Godot, that is, to run multiple instances of Godot with the same codebase. This was not a new idea, I had shared a similar idea back in October but for slightly different reasons.
The idea is simple: every global variable in Godot has to be changed to an instance variable.
As it turns out, this was harder than I expected. The above turned out to be easier said than done. They said the devil is in the details, but this felt more like a mob of devils punching you in the face.
It took me about a month to refactor the code and get it to work. But by the end of it, I could launch games in Godot instantly and go back to the editor with no delay. This is what the patch looks like today:
702 files changed, 15064 insertions(+), 6647 deletions(-)
Sadly, this is a very intrusive patch.
Maintenance
I have taken notes for some general bug fixes and improvements that I will submit to Godot. But these improvements are mixed with the re-instance work, so I must redo the work on top of a clean branch.
One of the problems with this approach is the friction involved in tracking improvements to Godot over time. Let me share my findings.
I started this work on Godot 4.1 and upgraded to Godot 4.2.
It took me about a day to integrate the changes and another day to find the regressions.
I have repeated this effort with a pre-4.3 release, and the time it took to merge is similar: about a day to deal with merge conflicts and another day to fix new bugs.
So, it's not terrible, but it is far from ideal. But there is light at the end of the tunnel, and I will share that in an future blog post.