Or, how I learned to stop worrying and love interpreted languages
For all iOS developers, Apple is an important part of the process. They act as the gatekeepers of quality.. or at least, that's the theory. In practice, however, after an app makes it through the gate the first time, all it seems to accomplish is slowing down the release process. Having a 5-10 day pause in the player feedback loop is crippling to a rapidly developing game that has to react to the demands of our increasingly minute-to-minute society.
The solution is to develop a system that allows Apple to remain happy while removing developer barriers. My day job focuses on games that use javascript and the UIWebView runtime to allow for downloadable and embedded javascript. However, there's issues with that. Largely, the javascript runtime is a slow disgusting beast. With the release of Codea, I've found a solution. Codea uses a custom engine, is fairly decently performant, and allows development on-device using Lua and an integrated editor. Sounds like a fun toy, right? It is! However, Codea also allows for export to an Xcode project, and apps may be released with it, royalty free.
All of a sudden, I've got a system that allows me to rapidly develop good performant code on-device, with on-screen parametric editors and export for distribution to a testing group, and subsequently to Apple for release.
There's still that missing piece, though. The ability to update on-demand. Codea provides a mechanism to download files from the web. Lua provides a mechanism to interpret lua code. Put them together and magic happens.
There are, however, some flies in that ointment. Apple may get their knickers in a twist about this: They've gone on record about 'NO DOWNLOADABLE CODE'... but since then, they've officially backed off from embedded scripts, allowing using of such engines as Unity or Unreal Engine, and Codea itself. Downloadable code via the UIWebView runtime has also been permitted, because it's interpreted using an engine that Apple itself has written. The agreement itself is clear on this matter:
3.3.2 An Application may not download or install executable code. Interpreted code may only be used in an Application if all scripts, code and interpreters are packaged in the Application and not downloaded. The only exception to the foregoing is scripts and code downloaded and run by Apple's built- in WebKit framework, provided that such scripts and code do not change the primary purpose of the Application by providing features or functionality that are inconsistent with the intended and advertised purpose of the Application as submitted to the App Store.
To there you have it. Your app might get removed, or your dev account could get banned. However, it's absolutely fine if you want to use this functionality on a pre-release build, you just have to go back to an embedded model before submission to the App Store. Or if you somehow can get written approval from Apple.
So... brass tacks. How to do it? Proof-of-concept follows.
Prerequisites:
- iPad.
- Codea - it's an iPad-only app. It's super cheap, for what it can do.
- If you want to publish, Xcode & and a Developer Account. Exporting and Publishing left as an exercise for the reader.
Driver bootstrap - paste this into Codea. Change the URL.
modules = {}
function gotDynamicModule(data, status, headers)
loadstring(data)()
-- run all the modules
for name,m in pairs(modules) do
print("Setting up module: " .. name)
m.setup()
end
end
function setup()
print("Running setup()")
-- module registration preamble
http.request("http://example.org/test.lua", gotDynamicModule, (function(errString)
print("Got error: " .. errString)
end))
print("module completed")
end
function touched(touch)
print("touch")
for name,m in pairs(modules) do
m.touched(touch)
end
if (touch.state == ENDED) then
http.request("http://example.org/test.lua", gotDynamicModule)
end
end
-- This function gets called once every frame
function draw()
for name,m in pairs(modules) do
m.draw()
end
end
Downloadable module file - put the following onto a webserver at the URL you put in the above file.
print("LOADING TEST.LUA")
modules["UI"] = {
name = "UI",
version = 1,
setup = function()
print("UI.setup")
end,
touched = function(touch)
print("UI.touch")
if touch.state == ENDED then
print("touched")
end
end,
draw = function()
-- print("UI.draw")
-- This sets a dark background color
background(40, 40, 50)
-- This sets the line thickness
strokeWidth(5)
-- Do your drawing here
rectMode(CENTER)
rect(WIDTH/2, HEIGHT/2, 200, 200)
end
}