QuickEggBreaker (Corona -> Marmalade port)

Example project for recompiling Corona source directly to Marmalade Quick

I was recently asked “how easy would it be to port a game from Corona to Marmalade”. I wasn't really sure... So I did some porting!

Why port to Marmalade?

Both SDKs support iOS and Android deployments, but Marmalade adds support for:

Similar looking beasts with very different insides

Marmalade Quick is a set of tools and an open source engine that sits on top of the regular Marmalade C++ SDK. Marmalade Quick and Corona share a lot of similarities: both are 2D, use Lua for the language, have a scene graph with nodes/objects that are sprites, polygons or text and use Box2D for physics. They have similar, though far from identical, API signatures as well, plus a lot of similar libraries and types. However, they also have a lot of differences under the hood and each has features that the other does not support out of the box.

Although some core libraries have different names and the mechanics vary, some libraries are very similar and the majority of parameters are identical. I would expect around 30-60% of an app’s code to work without changes. Brute force porting from Corona to Quick would therefore be a case of replacing any non matching calls and types with the Quick equivalent, plus changing coordinate values (see below) and filling any API gaps.

Porting any app from Corona to Quick should ultimately be possible: a user could provide an implementation of any currently unsupported Corona feature in Quick since Quick can be extended to access all core Marmalade APIs, which can themselves be extended to support most platform capabilities (via the C++ Extensions Development Kit). Going from Quick to Corona ought to be similar for the core Quick API, but not beyond that since Corona’s libraries and build system are closed.

Some challenges

Scene and object libraries: Corona uses a library called Display which creates DisplayObjects - visual or container nodes used to create a 2D scene graph. Originally there was no higher level component to manage scenes and resources but a popular 3rd party open source library called “Director” was available. Corona then added Storyboards, which was deprecated and replaced with Composer for the same job. In contrast, Marmalade Quick uses an API that handles both creating nodes and managing scenes, also called Director. Quick’s is based on Cocos2d’s Director singleton, which was likely the inspiration for the 3rd party Corona original too.

Coordinate spaces: Corona’s coordinate system has the origin at the top left with Y axis going down the screen. Quick matches Cocos2d’s behaviour, with origin at bottom left and Y going up.

Functions vs properties: Some values are got and set via function calls in one API and by property read/assignment in the other.

Events and parameters: Some events are system ones in one API and related to a node/object in the other or vice versa. Corona accepts a “params” parameter to event registration functions so that user-data can be passed back to event handlers; Quick does not support this. Event names and signatures vary between SDKs.

Bloat in Corona APIs: Quick condenses some Corona APIs. For example it has just a single Sprite type for image based objects, whereas Corona has Images and Sprites; both of those can be replaced with Quick’s Sprite. Corona has GroupObjects, which are non-visible and the only object that can act as a parent for creating trees of display objects, plus Containers, which allow you to apply clipping to any child objects. In Quick any Node object can have children, Nodes themselves can be non-visible and any Node can also have clipping applied to it and it’s children.

Porting a real example

I decided to jump straight in and start porting:

  1. I selected an MIT-licensed example from the Corona SDK. I chose Egg Breakers - http://coronalabs.com/sample-apps/egg-breakers - since it is compact but has a good use of key features:
    • A single but sizable main.lua source file, plus a small open source utility library
    • The vanilla Display library to create objects
    • Objects of types: Images, physics, labels, button
    • Global and object events and a timer
  2. I pushed the source version to Github: github.com/nickchops/QuickEggBreaker #1
  3. I created an empty Marmalade Quick project in the git root folder and moved the original source into the default “resources” folder (NB: you can configure a project to look in any folder to leave the source where it is if needed; I kept it simple for this exercise!)
  4. I started porting the code from top down in the games source...

Stop! Brute force porting sucks - Lua can do better...

Given how similar the APIs are, can we do something smarter and more reusable than changing our game code? Yes we can! Since Lua is a flexible language with dynamic types and relatively high performance, we can re-map Corona APIs to quick equivalents by defining or redefining their implementations. A few lines into porting the example, I reverted the changes and switched to this approach so other users can take advantage of my hard work.

A working auto-ported example running in Marmalade

Let’s skip the implementation details for a minute and see the results. After working through the code and creating mappings for all the APIs used, I pushed the final version to the web:

http://github.com/nickchops/QuickEggBreaker

Go ahead and import this in Marmalade’s Hub, then run it in the simulator or on any supported device! Note the only very tiny changes made to the original project. The source game is almost identical but a new set of Lua files are responsible for redirecting calls as needed.

Implementing the porting layer

Defining APIs that are new to Quick

display = {}

function display.newGroup()
    return director:createNode()
end
function QNode:removeSelf() -- define Corona interface...
    self:removeFromParent() -- ...using Quick interface
end

function QNode:insert(self, child)
    self:addChild(child)
end

Overriding existing Quick APIs

If a named function exists in Quick but the behaviour needs to change, we can replace or intercept it. All variables in Lua are references that can be reassigned at any time. Example: Both APIs have audio.loadSound and audio.loadStream. However Corona’s return handles which are passed to a generic play() function while Quick stores handles internally but has separate playSound() and playStream() functions. Our overrides look like this:

--keep handle to Quick function
audio.loadSound_orig = audio.loadSound 

--redefine function (takes extra param as well)
audio.loadSound = function(fileName, baseDir)
    if baseDir then fileName = baseDir .. "/" .. fileName end
    audio:loadSound_orig(fileName) --call original
    -- original has no return value! so create handle
    local handle = {portType = "sound", fileName = fileName}
    return handle
end

audio.loadStream_orig = audio.loadStream
audio.loadStream = function(fileName, baseDir)
    if baseDir then fileName = baseDir .. "/" .. fileName end
    audio:loadStream_orig(fileName)
    local handle = {portType = "stream", fileName = fileName}
    return handle
end

function audio.play(handle, options)
    local loop = options.loops

    -- use our handle to pick function to call
    if handle.portType == "stream" then
        audio:playStream(handle.fileName, loop)
    else
        audio:playSound(handle.fileName, loop)
    end
end

Fixing our coordinates mismatch

To match Corona’s coord system, at the start we just need to flip and move the main y origin, aka the scene itself:

quickPorter.scene = director:getCurrentScene()
quickPorter.scene.yScale=-1
quickPorter.scene.y = director.displayHeight

Then for some types and features, we need to flip things back in Y. Examples:

local xGravity,yGravity = physics:getGravity()
physics:setGravity(xGravity,-yGravity)

-- plus override physics.getGravity and physics.setGravity!

Lua Metatables: where the real fun begins!

Metatables are Lua’s killer feature. Using them cleverly we can redirect any function or property call on a table to do whatever the hell we want! For example, myObject.valueA = 27 could be converted to a call to myOtherObject:setA(27+myObject.offset). Hopefully it’s instantly clear that this will be useful in our porter!

A quick intro to Metatables

In Lua, you can set any table to be the metatable of another table. Whenever the original table has certain actions performed on it, its metatable will be checked for functions with names that correspond to the action, and if the named function is found then it will be called. The actions supported include operators like trying to add or multiply tables, built in functions like tostring() and some special keys like __index.

A simple example for the add operator:

myMetatable = {}
myMetatable.__add = function(v1, v2)
    --do what you want here
end

myTable = {}
setmetatable(myTable, myMetatable)

myVar = myTable + 5
--will call the __add function with v1=myTable and v2=5

The two metatable functions of interest to us are __index and __newindex. The __index function is called if myTable.someIndex is called when someIndex is nil. Note that this includes function calls like myTable.someFunction(). __newindex is called if myTable.someIndex = someValue is called (an assignment) and someIndex is nil.

Using metatables in the porter

By having a table with index/newindex functions and setting it as the metatable for each node we create in our wrapper, we can then intercept any call and redirect it. Remember that this only works for indexes that are nil (not just for any index lookup) so we need the “node” itself to be a completely empty table! Updating our example from earlier, we make the objects returned to the game code into simple, almost empty tables, with the Quick nodes inside them (I’m using __node as a name that is unlikely to clash with game code!) and then we use display itself as our metatable for convenience:

function display.newGroup()
    local group = {}
    group.__node = director:createNode()
    setmetatable(group, display)
    return group
end

Passing simple tables to the game code, as opposed to Quick Nodes, is a good idea for other reasons: (1) if we tried to set a metatable for an actual Node then things get fiddly since nodes are really userdata types that already have metatables set as part of how classes are implemented in the engine, (2) we avoid having to know anything about Quick’s node object internals or accidentally redefining important indices; the less to touch the Quick nodes directly, the better.

Now we need to implement the index functions for our display metatable. Note that the “table” param here is the original table, for example “group” in the newGroup function above.

display.__index = function (table, key)
    --we can manually override named functions or properties here
    if key == "someName" then
        -- do something
    elseif key == "otherName" then
        -- do something else
    else
        return table.__node[key] --use the Quick node's value
    end
end

display.__newindex = function (table, key, value)
    if key == "someName" then
        -- do something

    else
        node[key] = value --set the nodes value
    end
end

Directing a property access to a function call

Let’s look at a real example of redirecting calls with the index functions. In Corona’s physics, you get/set angular velocity via the myObject.angularVelocity property. In Quick, we use the myNode.physics:getAngularVelocity() function (or its set() equivalent). Note the change from property to function and also the need to call the .physics sub-object of the node.

Using our .__index and .__newindex metatable methods, we can intercept the point where that property (table index) get’s accessed and then just call whatever we want:

display.__index = function (table, key)
    if key == "angularVelocity" then
        return table.__node.physics:getAngularVelocity()
        --etc

display.__newindex = function (table, key, value)
    if key == "angularVelocity" then
        return table.__node.physics:setAngularVelocity(value)
        --etc

And that’s all we need to know to implement/replace anything!

I worked top to bottom through my example code, adding wrappers, implementations and metatable overrides as needed. Generally I just did the minimum needed to port this one example app, but in some cases I proactively added handy functions for future projects to use.

Check out the finished project here: github.com/nickchops/QuickEggBreaker

What’s Next?

Our example is a great starting point for porting your own project. It could be extended to become a rich porting layer that removes 90%+ of the work for a given title. Currently it only supports a subset of APIs but is easy to extend. A key piece of work is to implement Corona’s director/storyboards/composer functionality if needed (remember not to allow one “director” to replace the other!) What about other Lua toolkits like Moai, Love or Guideros? The porting concepts should work for those too, and once again portability will depend on features needed.

Extending Quick to fill feature gaps

Both APIs have libraries and features not available in the other. Significant use of unsupported features might mean porting is not appropriate. When porting, be aware that Corona features that do not yet exist in Quick will require adding an implementation to the Lua or C++ source of Quick, or maybe an extension to Marmalade’s abstraction layer, or just removing from your game. Some key ones to bare in mind:

Useful Links