The Corona Labs Blog
Posted on . Written by

Modules in CoronaThere’s often confusion as to what exactly happens when external modules are “required” into your code, which leads to further confusion and unexpected behavior when it comes to things such as Storyboard Scenes or even custom modules of your own.

Today I’m going to guide you through a series of exercises (with explanations) that should illustrate exactly how modules work in Lua, so you get a full understanding of when the code in your modules is executed, including what code is not run when you call the built-in require() function.

Including External Modules

In it’s simplest form, an external module is simply a Lua file that returns something, most-likely a table. It can almost be thought of as a function definition, but has an entire file all to itself.

Here’s a really simple module, example1.lua, which simply prints a statement to the Terminal and returns an empty table:

example1.lua


local t = {}

print( "example1.lua has been loaded." )

return t

Now, in a different module—let’s just use main.lua—require the example1.lua module and see what happens. NOTE: All code samples in this tutorial assume all custom external modules are placed in the top-level of your project folder (same location as main.lua).

main.lua


local ex1 = require "example1"

ex1.testvar = "Hello world"

And as expected, the variable ex1 is equal to the blank table we returned in example1.lua, and the words “example1.lua has been loaded.” appear in the Terminal. We then assign a trivial property to the ex1 table.

Pay close attention now, because here’s where things start getting tricky. Let’s require example1.lua in a separate module, scene1.lua, after we have already required the module in main.lua and see what happens:

scene1.lua (previous main.lua still applies)


-- ...

local examp1 = require "example1"
print( examp1.testvar )

-- ...

This time—in scene1.lua—when you require the example1.lua module, the words “example1.lua has been loaded” did not appear in the terminal. However, when you print the value of ex1.testvar, the value of “Hello World” is printed to the terminal (which means the ‘testvar’ property exists).

What can we learn from this?

First, the reason why “example1.lua has been loaded” did not appear in the Terminal is because the module has already been loaded by main.lua. Once a module is loaded, the code is executed from top to bottom (as usual), and the return value of the module is stored in a global table called package.loaded.

When you call require, the first thing that happens is the package.loaded table is checked to see if the module has been loaded previously. If it is found, then instead of re-requiring the external module, the stored return value in package.loaded returned. This returned value is a reference, not a copy. So if your module returns a table, that’s the same table you’ll be getting (properties and all) when you call require on the same module in the future (unless you unloaded the module—more on that in a moment).

If the module is not found in package.loaded, however, the module will then be loaded (and the code will run from top to bottom), and the return value of the module will be stored in the package.loaded table (for any future requires of the module). This also explains why the “example1.lua has been loaded.” print statement was not shown again. When require() is called, the module is not re-loaded if it already exists in the global package.loaded table.

There are two ways to get code in a module to run twice:

  1. Put the code in a function (within the module), and call it.
  2. Remove module from package.loaded table, and re-require it.

Here’s an example of the first scenario:

example2.lua

local t = {}

print( "example2.lua has been loaded." )

t.hello = function()
print( 'Hello world.' )
end

return t

main.lua

local ex2 = require "example2"
-- Terminal: example 2 has been loaded.

ex2.hello()
-- Terminal: Hello world.

scene1.lua

local examp2 = require "example2"

examp2.hello()
-- Terminal: Hello world.

As you can see from the example, we required example2.lua in two different modules (main.lua and scene1.lua). The first print statement (outside of any functions) was shown only once, which is the first time the module was required.

The second print statement, the one within the hello() function, was shown each time the hello() function was called. Lesson learned: Attach functions to the table you return at the end of the module, if you want that code to be run more than once (or if you don’t want it to be run immediately upon requiring the module).

Removing from package.loaded

When you require a module, example2.lua for instance, you do so using the following line:


require "example2"

The return value of example2.lua is then stored in the package.loaded table under within: package.loaded["example2"]

If you need the code in example2.lua to be re-executed (that is, any code outside of functions), you need to remove the entry from the package.loaded table and then call require again.

Here’s an example that should illustrate my point:

main.lua

require "example2"
-- Terminal: example2.lua has been loaded.

package.loaded["example2"] = nil

require "example2"
-- Terminal: example2.lua has been loaded.

require "example2"
-- Terminal:

This is Universal

When dealing with any module, whether it’s your own custom-made modules, modules you downloaded from the community, or even built-in modules (that you don’t drop into your project), all of the rules I described in the blog post apply to all of them.

So when working with storyboard scenes, the things I described here should explain why some code is executed when a scene is loaded, and why some is not (which is why most of your code, with an exception of forward declaration variables) should be within Storyboard listener functions—but that’s a topic for another day.

9 Responses to “How External Modules Work in Corona”

  1. Dave Baxter

    Very interesting stuff and as you say makes understanding Storyboard a whole lot easier.

    I have some standard code in all of my scenes, things like detecting swipes, button presses etc… Can all this be put in a external module and then I just require the module in each scene and call the functions ? Would I actually be able to assign the functions to touch events or is that a little dangerous ?

    Dave

    Reply
    • Jonathan Beebe

      Hi Dave, yes, you can definitely do that. In fact, if your project gets big, it’s actually recommended as it makes organizing things easier (and keeps your Lua scripts lean).

      Reply
  2. Tahir

    What happens if I require a module that does not return any thing? What would go in package.loaded table?

    Reply
  3. srdjan

    Great!
    Does this mean that we can define require file which can be holder for variables used thru the whole game? Variable holder with functions…that will be uber cool!
    No need for globals at all.

    Can’t wait to see Modules and the Storyboard post :-)

    Reply
  4. Will K

    On this topic, can you explain this:

    module(…, package.seeall)

    And related, can you explain the difference between using new() to instantiate an object from a module, and just including it? In other languages, that pattern is clearer, you create a class, put that in an external file, include the file and instantiate the object of the class. But in lua, I’m vague, there seem to be two styles of doing this. Older styles seem to use new() and more recent don’t bother. Can you provide a best practice example for the community to follow?

    Thank you very much!

    Reply
    • Walter

      @Will, module() was the old way of creating modules. It’s fallen out of favor for a variety of reasons, so much so that I believe it’s been removed from Lua 5.2.

      Reply
  5. Naomi

    Thank you for the detailed and clear explanation of how requiring a module works. Very helpful.

    Although this tutorial is not about Storyboard API, now I’m wondering how it may relate to it. For example, when dose a scene get fully unloaded from memory? I mean, let’s say, I transition to scene1.lua from main.lua, and then I may move on to scene2.lua — at what point will package.loaded["scene1"] = nil be called (assuming it is handled by the API.) Does it get called upon leaving scene1.lua if I have storyboard.purgeOnSceneChange set to true?

    Reply
  6. Robert

    Thanks, its great to have module loading explained succinctly. This tutorial, combined with the other tutorials you’ve provided on use of external modules has been very helpful.

    Reply

Leave a Reply

  • (Will Not Be Published)