Chap. 07A --- The Module System

The Module System we Discarded

Before we begin, we should mention that the Lua manual refers to a module system that is built-in to Lua. This module system has been stripped out, and has been replaced with one that retrieves source files from the Wild Pockets file server.

The Main Module

Every game has a main module - a Lua script file which is loaded when the game is launched. The saved game file contains the filename of the main module. In the development environment, open the global scene properties dialog, and look in the script properties. There, you will find the filename of the main module. You can point this at any lua script you have written.

This main module will automatically be "imported" (loaded) when the game starts.

Creating Script Files

To create script files in the first place, open the development environment. Select the menu item "Open Scripts Directory" from the file menu. This will open an explorer folder. Any file with a lua extension that you put in this directory will be automatically uploaded by the development environment to your account on the Wild Pockets file server. Any changes you make will be automatically uploaded as well.

If you make changes to files in this directory when the development environment is not running, the changes will not be uploaded until the next time you launch the development environment.

Script File Naming Conventions

When you put a script file into the scripts directory on your machine, it must have the extension "lua," or it will not be uploaded. However, the lua extension is stripped from the filename when it is uploaded. Traditionally, filenames on the Wild Pockets file server don't have extensions.

All filenames on the Wild Pockets file server have usernames. The username becomes part of the filename when a file is uploaded.

For example, if Josh puts a file called "myGame.lua" into his scripts directory, then when the file is uploaded, its name becomes "josh/myGame".

Module Initialization

It is intended that your modules should contain only function definitions and class definitions. It is possible to put other commands that are not inside a function into a module, but this may not always be supported.

The first thing that happens when a module is loaded is that the function and class definitions inside the module are processed, top-to-bottom. This has the effect of building up a scope full of functions and classes.

The second thing that happens is that the system looks inside the module for a definition of function _init. If present, this function is called: this is the module constructor.

Finally, if the module is the main module, then the function onSceneStart is called within the module.

Manually Imported Modules

In addition to the one lua script file that is automatically imported at game initialization time, you can manually import additional modules using the "import" statement:

import("josh/robotCode")

This will download the Lua script file "josh/robotCode" from the Wild Pockets server, and load it.

When loading multiple modules, each module exists within its own scope. By default, a module cannot see the functions and classes defined in any other module. In fact, a scope is a lua table. When you import a module, this scope table is automatically created. All the defined functions and classes are stored inside this scope table.

The import function actually returns the scope table for the module it imported. Using the scope table, you can retrieve the functions, classes, and global variables from that scope. Here's an example:

robotModule = import("josh/robotCode")
robotModule.killRobots() -- call the function 'killRobots' defined in the module

Don't Put Import Statements at the Top

C programmers might think it's normal to put the import statement at the top of your file, where the C preprocessor include-directives normally go. But that's not what you're supposed to do. Import-statements are executable statements, they go inside functions. Often, it makes sense to put them inside the _init function:

myModule = import("rharke/mymodule") -- wrong

function _init()
    myModule = import("rharke/mymodule")  -- right
end

It is also possible to put them right inside another function, such as this one:

function calculateCosts(data)
    local mathLibrary = import("josh/mathLibrary")
    local results = mathLibrary.analyze(data)
    ...
end

This approach has the advantage that the file will only be imported if the calculateCosts function gets run.

Duplicate Imports

If you import a file twice, it will not be loaded twice. The first time, it will be loaded. The second time, the import statement will just return the same scope table that it returned the first time.

You can use the import-statement to import the main module. This will return you the scope table of the main module. Of course, since the main module was already imported, this is a duplicate import, and will not trigger the main module to be loaded again.

Two Modules that Import Each Other

It is possible for two modules to import each other, so that each can call functions that are defined in the other. However, there's a small caveat.

Normally, the import statement doesn't return until the module is loaded and the initialization function is finished. But what if module A imports module B, and then module B turns around and imports module A? In that case, B cannot wait for A to finish initializing, because A is already waiting for B to finish initializing. The deadlock is broken by having the second import statement return before the initialization function is finished. Usually, this is not an issue.

The Three Scope Levels

There are three scope levels in Wild Pockets:

  • Local variables
  • Global variables
  • Super-global variables

Local variables are created when you use the local keyword inside a function.
These are only visible within the one function:

function xyz()
   local a = 3 -- this is visible only inside function xyz
   print(a)
end

Global variables are created when you assign a variable without using the local keyword. Here, the word 'global' is somewhat misleading - it is true that these variables are visible to all the functions in the module, but they're only visible to functions in the same module:

function wxy()
    b = 3 -- this is visible to all functions in this module
    print(b)
end

The last level, super-global, is for variables that are visible throughout the system. To assign a super-global, you have to use the table 'GLOBAL'. But to read one, you can just read it like any other variable:

function vwy()
    GLOBAL["c"] = 3 --- this is visible anywhere.
    print(c)
end

There is a precedence ordering: local variables override global variables, and global variables override super-global variables.