[Tutorial] Using Additional .lua Files⚓︎
If you want to load an additional .lua file besides your main.lua file, you can use either the require
or include
functions. Both have different purposes.
require
⚓︎
require
is a built-in Lua function. Using require
is the conventional way in Lua programs to split code up into multiple files. For example:
main.lua
⚓︎
1 2 3 |
|
foo.lua
⚓︎
1 2 3 4 5 6 7 |
|
Here, "foo" is a Lua module that provides variables and methods. It's also possible to return functions or primitive values, but conventionally Lua modules always return a table.
One important aspect of require
is that when it is used, it caches the result. Thus, when a file is required in two different places in the code, it will execute all the code normally on the first require, and then return a reference to the module on the second require. (This default behavior makes sense, because there is no need to execute the same code over and over.)
require
With Directories⚓︎
Unlike other programming languages, it is conventional in Lua to use a period as a path separator. For example, if you wanted to import a file called bar.lua
in a subdirectory called foo
, you would use the following require
statement:
1 |
|
The Namespacing Problem With require
⚓︎
Unlike import statements in other programming languages, the require
function does not use relative paths. Instead, it is based on the exact string passed into to the function. (And every mod directory is added to list of directories to look through.)
This causes a problem for mods that have an overlap in the require
string. For example, imagine that there are two mods, mod 1 and mod 2. Both mods have a file called "foo.lua" and both mods use a require statement of local foo = require("foo")
. Mod 1 will work normally, but when mod 2 loads, its require statement will actually return the "foo.lua" file from mod 1.
In order to work around this problem, mods have conventionally put all of their Lua files in a directory that matches the name of their mod. For example, mod 1 would make a directory called mod1
and have an import statement like: local foo = require("mod1.foo")
This way, there would never be a conflict as long as there are no other mods called "mod1".
The luamod
Problem With require
⚓︎
luamod
is a console command that will reload a mod. This is helpful when you are developing a mod and you want to immediately test your changes without having to go back to the menu.
Unfortunately, require caching causes the luamod
console command to not work correctly. If code inside of a module is updated, it will not be reflected in game after using the luamod
command because the reference to the module is already cached.
include
⚓︎
In order to get around the namespacing problem and the luamod
problem, Kilburn added an Isaac-specific API function called include
in Repentance patch v1.06.J818. include
works in a mostly identical way to require
, except it will never cache the result, causing the code to execute every time. (It will also never get files from other people's mods, even if the paths are identical.)
Sharing Variables⚓︎
include
is only designed for pure modules that have no side effects. In other words, if you use include
on a module with module-level state variables, they will be instantiated N times, once for each include
. Obviously, this is really bad, because internal state between files will become desynchronized.
Thus, if you have module-level state or need to share variables between files, you cannot use include
and must use require
.
Workaround for Require Problems⚓︎
If you need to use require
instead of include
, it's recommended to put all of your Lua code inside of a namespaced directory, as mentioned earlier. If you also want to have luamod
functionality, you can enable the --luadebug
launch flag and then hack the require
function with something along the lines of:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
|
Alternate Workaround for Require Problems⚓︎
It's also worth noting that the require
problems discussed above are non-problems if you are writing your mod with TypeScript using the IsaacScript framework. This is because the transpiler automatically combines all of your code into a single "main.lua" file. This means that you don't have to bother juggling between using include
and require
, worrying about state, or monkey patching the require
function - you can simply write code that works.