Mastering Live-Coding with CodeFlow (1)

To use the live-coding feature of CodeFlow to its full potential, it is useful to understand the process that transforms a change in the source code into a live update of your application, and how you can control this process. Such is precisely the goal of this series of two articles: to give you the keys to become a live-coding master.

What is live-coding?

Live-coding can have several meanings. At Celedev, we define live-coding as the possibility to interactively develop an application while it is already running.

Interactively means that the developer can test immediately in the running application, the effect of a change in the application source code; while it is already running means that the application doesn't need to be restarted to run the updated code, and, by extension, that the current state (or screen) of the running application is not lost after a code update.

For a developer, having an immediate and visual feedback of his work in the actually running application has huge benefits: it saves time, encourages experimentation and creativity, and tends to produce better polished apps at the end of the day.

This is clearly different of the classic way of developing apps for iOS where an application needs to be re-built, re-loaded on the target device and re-started after every change in the source code.

In this context, enabling true live-coding of iOS apps is certainly the major feature of Celedev CodeFlow.

But live-coding in CodeFlow is of course not magic, and you need to get a simple understanding of the way it works to use it effectively. This is the goal of this mastering live-coding with CodeFlow series of two articles.

CodeFlow live-coding overview

Live-coding in CodeFlow works with both code and resources. Although there are obvious differences between updating a code module and an image resource, the overall process is the same in both case, and involves two steps:

CodeFlow live-coding overview
  • Step 1: the updated Lua code module or resource is loaded, and replaces an eventual previously loaded version of the same Lua module or resource.

  • At the end of step 1, when the code / resource loading in complete, a notification is sent to interested objects in the system.

  • Step 2: Upon reception of a code / resource loaded notification, the application is refreshed to reflect the change, in a way that depends both on the application internal architecture and on which module or resource was updated.

The Lua modules and resources (re-)loading, i.e. Step 1, is handled by CodeFlow. We will see in more details in the following sections what means loading and replacing a Lua code module or resource, and what notifications are sent at the end of step 1.

The application refresh logic, i.e. step 2, is the app developper's responsibility. The reason for this is that there is no one size fits all way of refreshing an application: the right way to do it is tightly related to the internal design and role in your application of the module or resource being reloaded. However there are a number of usual design patterns for refresh and we will review the most common ones in this document.

Lua module loading

What is a Lua module? A Lua module is a Lua source code file that can be loaded as a module, via the Lua require function. (For detailed information on Lua modules in CodeFlow, see Using Lua Modules)
In a CodeFlow project, all Lua source files are Lua modules.

What happens when a Lua module is loaded?

The code update feature in CodeFlow works at the Lua module level. In fact, a Lua module is the smallest possible entity that can be (re-)loaded by CodeFlow. Therefore it is important to understand what happens when a module is loaded.

When a module is loaded, Lua compiles it and executes the top-level code in sequence.

To illustrate this let's consider a simple example.

We define a Lua module that we name SimpleFunction:

local a = 3 
local b = 1 

local function f(x)
    return x * b
end

b = b + 2 * a

return f -- f(1) will return 7

Now we can define in the same CodeFlow project a main module:

local g = require "SimpleFunction"

print (g(1)) -- -> 7
print (g(2)) -- -> 14

If we run this main module in CodeFlow with a Target set to Local Lua Context, i.e. Lua playground-like mode, we can step into the require "SimpleFunction" instruction (on line 1 of module main) and see how the SimpleFunction module is executed.

CodeFlow module execution

On this screenshot, we are stopped on line 8, so we have just defined the f function (highlighted in yellow in the variables inspector), and we are about to change the value of b from 1 to 7. Therefore when the SimpleFunction module returns, b has value 7, so f(x) will return x * 7. If we hit continue the execution returns to the main module and run until the end of the main module.

And the Lua Console window displays the result:

7
14

Now, what happens if we run the main module again? If we hit step into on the require "SimpleFunction" instruction on line 1, we don't go into the SimpleFunction module, but we step directly to line 3 of module main:

CodeFlow module skip re-execution

Actually the SimpleFunction module didn't need to be re-loaded because its code hasn't changed since the first execution. And you can see that the value of g returned by require is the same as the function f created previously in SimpleFunction. So everything looks correct.

More generally:

  • a loaded Lua module is executed only once, unless its code changes,
  • if called multiple times for a same module, require just returns the result(s) saved during the last execution, without re-executing the module.

Updating a Lua module

When you change the code in a Lua module, the behavior of CodeFlow depends on the state of the auto-reload flag for this file: if the auto-reload flag is set, CodeFlow will automatically reload the module in the target Lua context; on the other hand, if the auto-reload flag is not set, CodeFlow won't reload the module until you ask for an explicit reload of this file or set its auto-reload flag. The auto-reload flag is toggled via the command Reload Source File on Update (cmd-U) in the Execute menu.

If we continue with our simple function example, we can see on the screenshots that SimpleFunction has the auto-reload flag set, via the rotating-arrows badge badge displayed next to it in the Source Files list. We set a breakpoint on line 8 of SimpleFunction to stop if the module's top-level code is executed, and we change the code.

CodeFlow module re-execution

We can see here that the module has been reloaded (the Lua file SimpleFunction-17:56:43 in the Loaded Items list) and that the execution is stopped on line 8, as expected. We can also see in the variable inspector that the SimpleFunction code is called directly when reloaded and not by a require in the main module as in the previous case.

We can see also in the variable inspector that the value of f has changed. This is completely normal, since we have just redefined f by executing the code on line 4. So this new execution of the module will return a different result from the one returned by the previous execution...

In this ultra-simple example, this is not an issue, but if some persistent object in the system had stored the function returned by require "SimpleFunction" prior to the update, this object would still had a reference to the old version of f that returns x * 7, while other objects calling require "SimpleFunction" after the update would have a reference to the new version of f that returns x * 13. This could lead to strange and undesired behavior.

For this reason, a recommended rule when writing Lua modules that can be updated without trouble is:
For consistency and predictability, a Lua module should return the same Lua value(s) (i.e. object, Lua table..) when reloaded as the value(s) returned during its initial execution.
(With a few exceptions, of course, but sticking to this rule is generally a good thing, unless you know what you are doing!)

Lua modules and the Celedev Object Framework

The Celedev Object Framework has been specifically designed to support on-the-fly redefinition of methods and properties in a class during a module update, so classes and instances defined with it fit perfectly in a live-coding-oriented development environment.

Consequently, the easiest and safest way to write reload-compatible Lua modules is certainly to base them on the Celedev Object Framework.

In terms of structure, a class definition module follows a simple pattern:

  1. it first requires other needed modules
  2. then it creates the class by calling class.createClass, or create a class extension by calling class.extendClass
  3. then it implements the class methods, getters, setters, properties …
  4. finally it returns the class as the module result

Writing a Lua class module is really easy.

-- require needed modules
local SomeModuleData = require "SomeModuleData"

-- Declare a class extension of the Objective-C class "MyViewController"
local MyViewController = class.extendClass (objc.MyViewController)

-- Define the controller methods and properties
-- and update them as frequently as you want!
-- ...

-- Finally return the controller class
return MyViewController

Module loaded notifications

Two main types of notifications can be used to trigger a refresh of the application after a Lua module has been updated:

  1. You can use the standard "system.did_load_module" message which is posted by the loading system each time a Lua module is loaded or updated.
    The advantage of "system.did_load_module" is that you don't need to do anything special to activate it; its disadvantage is that an object subscribing to this message will receive the updates for every Lua module in the Lua context, and will generally need to filter out most of these messages based on the loaded module name.

  2. You can send a custom update notification for a module by posting a message from this module's top-level code, just before returning.
    The advantage of this approach is that the notification is really focussed on a given Lua module. However client objects need to know the exact name of the message to subscribe. To avoid hardcoding this name in the client code, the custom message name can be returned as the module's second result, as in the following example.

-- define the specific message
local MyViewControllerUpdateMessage = "MyViewController was updated"

-- Declare a class extension of the Objective-C class "MyViewController"
local MyViewController = class.extendClass (objc.MyViewController)

-- Define the controller methods and properties
-- and update them as frequently as you want!
-- ...

-- post the message
message.post (MyViewControllerUpdateMessage)

-- Finally return the controller class, and the message name
return MyViewController, MyViewControllerUpdateMessage

For more details on this topic, you can read Messages and update notifications.

Resource loading

In CodeFlow, any non-Lua file in your project is called a resource. Using a resource in your Lua code is really straightforward: you call the getResource function to associate a resource in your CodeFlow project with a given field of an object in the current Lua Context. The term associate here means that the object field is first set with the current value of the resource, and that it will be updated with any changed value of the resource that may come in the future.

Here is a typical example that associates the object field myObject.image1 with the image-typed resource named "ImageResourceName".

getResource ("ImageResourceName", "public.image", myObject, "image1")

Note that it is perfectly valid for the object field associated to a resource to be an Objective-C property. As an example, if self.imageView is a UIImageView, you can write:

-- associate self.imageView.image with a dynamically updatable resource
getResource ("ImageResourceName", "public.image", self.imageView, "image")

For more detailed information about resources in CodeFlow, see Using resources in your program.

Updating a resource with CodeFlow

There are two main ways of updating a resource with CodeFlow.

The first one is modify the resource in an external editor. Double-clicking on a resource in the Files list opens it with the default editor for the corresponding resource type; alternatively you can select an editor by opening the contextual menu on the item and selecting Open with..., or you can drag the resource file item from CodeFlow to a compatible application opened in the dock. Each time you save the resource file in the external editor, the resource gets updated in CodeFlow and in your application as well.

The second way of changing a resource is to replace its content by dragging another file on it, as shown by this screen capture:

CodeFlow resource drop update

Detecting a resource update

Having an associated object field updated when a resource is reloaded is good, but in most cases not sufficient. Generally, when a resource is updated, you need to perform some action in your app, in order to refresh its internal state or display.

The recommended way to detect and process a resource update is to define a setter for the object field associated to the resource. When the resource is updated, this setter is called and you can put in it the code that will handle the update or, alternatively, post a message for notifying other objects in the Lua Context that this resource has changed.

We can illustrate this with a code example, supposed part of a Lua module defining a class MyClass :

-- we are defining a class "MyClass" here ...

function MyClass:configure(imageResourceName, textResourceName)
    getResource (imageResourceName, "public.image", self, "texture")
    getResource (textResourceName, 'txt', self, "summary")
end

MyClass:declareSetters { texture = "setTexture",
                         summary = function (self, text)
                                       self._summary = text
                                       message.post ("MyClass summary updated", self)
                                   end
                       }

MyClass:declareGetters { summary = function (self) return self._summary end }

function MyClass:setTexture (texture)
    self._texture = texture
    -- Apply the texture to the child nodes
    -- ...
end
  • Line 3: define a configure method that associates an image resource texture and a text resource summary to the current object self.
  • Line 8: declare the setters corresponding to the resources associated fields. The texture setter is a reference to a method of MyClass, whereas the summary setter is defined by an anonymous function (note the explicit self parameter of this function).
  • Line 11: the summary setter posts a message to notify interested objects that the summary field has been changed; this message contains self in its parameters because the summary field is different for each instance of MyClass.
  • Line 15: declare a getter for the summaryfield, so that receivers of the "MyClass summary updated" message can get the summary for the notified object.
  • Line 17: declare the setTexture method. This method includes code for refreshing child nodes (whatever that means) of the current object with the updated texture image.

For more information on setters, see Celedev Object Framework; for more on notification messages for resources, see Messages and update notifications.

Application refresh strategies

Now you should have a clearer view of how Lua modules and resources are loaded and of the ways to be notified of reload events. In the second part of this mastering live-coding series, we will see how to use these reload events to refresh your application's internal state and display for a seamless live-coding experience.

Post a Comment