Preparing a basic file structure for the game engine
When programming a larger project, it's always important to keep it maintainable. One of the common practices is to keep a modular structure. Modular structure can be achieved by keeping files separated in certain directories.
Lua language uses a require
function to include modules in your script files. This function uses a default list of paths where it tries to find the module file. The Lua modules can be written as plain Lua scripts or use a form of binary library, which is OS and CPU architecture dependent. This is especially troublesome if want to include binary libraries for all supported operating systems and CPU architectures in one project.
A default set of paths might not always be appropriate for your project, mainly if you bundle many third-party modules with it.
This recipe shows how to set up the Lua interpreter so that it can find correct files in a systematic and user-definable way. This recipe should be used at the beginning of your main Lua script file so that further calls to the require
function in Lua will use your file path structure.
Getting ready
You can use a directory structure as shown in the following diagram. If you intend to implement your application for multiple platforms, always divide platform-specific files into separate directories.
The Lib directory contains all the Lua module files and binary libraries.
However, each operating system uses its own file naming convention for binary libraries. The Lua language doesn't have an easy way to obtain the OS name. For this purpose, you can download and use the Lua script module os_name.lua
from https://gist.github.com/soulik/82e9d02a818ce12498d1.
You should copy this file into your project directory so that the Lua interpreter can find it.
How to do it…
The require
function in the Lua language uses a set of default paths defined in package.path
and package.cpath
string variables. With your new directory structure, you'd have to change those two variables manually for each operating system, which could be cumbersome.
Instead, you can define a Lua script to build up these two string variables from a generic list of paths for both Lua script files and binary libraries.
In the first step, you need to create a list of directories:
-- A list of paths to lua script modules local paths = { '{root}/{module}', '{root}/lib/{module}', '{root}/lib/external/{module}', '{root}/lib/external/platform-specific/{platform}/{module}', } -- A list of paths to binary Lua modules local module_paths = { '?.{extension}', '?/init.{extension}', '?/core.{extension}', }
Strings enclosed with curly brackets will be substituted with the following values:
Name |
Value |
---|---|
root |
This is the application's root directory |
platform |
This is the current platform |
module |
This is the module's file path |
extension |
This is the module's filename extension, which is platform dependent |
Binary module filename extensions that are platform dependent are also set in a table:
-- List of supported OS paired with binary file extension name local extensions = { Windows = 'dll', Linux = 'so', Mac = 'dylib', }
Now, you need to set root_dir
which is the current working directory of the application and the current platform name as follows:
-- os_name is a supplemental module for -- OS and CPU architecture detection local os_name = require 'os_name' -- A dot character represent current working directory local root_dir = '.' local current_platform, current_architecture = os_name.getOS() local cpaths, lpaths = {}, {} local current_clib_extension = extensions[current_platform]
Before you start building the path list, you need to check whether the current platform has defined binary module extensions as follows:
if current_clib_extension then -- now you can process each defined path for module. for _, path in ipairs(paths) do local path = path:gsub("{(%w+)}", { root = root_dir, platform = current_platform, }) -- skip empty path entries if #path>0 then -- make a substitution for each module file path. for _, raw_module_path in ipairs(module_paths) do local module_path = path:gsub("{(%w+)}", { module = raw_module_path }) -- add path for binary module cpaths[#cpaths+1] = module_path:gsub("{(%w+)}", { extension = current_clib_extension }) -- add paths for platform independent lua and luac modules lpaths[#lpaths+1] = module_path:gsub("{(%w+)}", { extension = 'lua' }) lpaths[#lpaths+1] = module_path:gsub("{(%w+)}", { extension = 'luac' }) end end end -- build module path list delimited with semicolon. package.path = table.concat(lpaths, ";") package.cpath = table.concat(cpaths, ";") end
With this design, you can easily manage your module paths just by editing paths
and module_paths
tables.
Keep in mind that you need to execute this code before any require
command.
How it works…
This recipe builds content for two variables that are used in the require
function—package.path
and package.cpath
.
Both variables use a semicolon as a delimiter for individual paths. There's also a special character—the question mark which is substituted with the module name. Note that the path order might not be as important in this case as with our default list of paths. The path order might cause problems if you expect to use a module out of the project directory structure. Therefore, a customized set of paths from this recipe should always be used before the default set of paths.
The Lua language allows the use of hierarchical structure of modules. You can specify a submodule with package names delimited by a dot.
require 'main_module.submodule'
A dot is always replaced with the correct directory separator.