Rework module loading

Change module loading to use a module.json file describing the module
and patch in all files in a directory path passed to the patcher instead
of having to pass the files names and their content.  This allows the
specification of which files should be passed to the event_handler lib
and an easy implementation of loading stand alone modules.
This commit is contained in:
Hornwitser committed 2020-01-04 17:34:16 +01:00
1 parent 11992d1839
commit 80bd75ba16
11 files changed
+120 -47

No files matched your search

+43 -23
View File
@@ -155,37 +155,56 @@ class Instance extends link.Link{
// Patch save with lua modules from plugins
console.log("Clusterio | Patching save");
// For now it's assumed that all files in the lua folder of a plugin is
// to be patched in under the name of the plugin and loaded for all
// plugins that are not disabled. This will most likely change in the
// future when the plugin refactor is done.
let modules = [];
for (let pluginName of await fs.readdir("plugins")) {
let pluginDir = path.join("plugins", pluginName);
if (await fs.pathExists(path.join(pluginDir, "DISABLED"))) {
// Find plugin modules to patch in
let modules = new Map();
for (let pluginName of this.plugins.keys()) {
let modulePath = path.join('plugins', pluginName, 'module');
if (!await fs.pathExists(modulePath)) {
continue;
}
if (!await fs.pathExists(path.join(pluginDir, "lua"))) {
continue;
let moduleJsonPath = path.join(modulePath, 'module.json');
if (!await fs.pathExists(moduleJsonPath)) {
throw new Error(`Module for plugin ${pluginName} is missing module.json`);
}
let module = {
"name": pluginName,
"files": [],
};
for (let fileName of await fs.readdir(path.join(pluginDir, "lua"))) {
module["files"].push({
path: pluginName+"/"+fileName,
content: await fs.readFile(path.join(pluginDir, "lua", fileName)),
load: true,
});
let module = JSON.parse(await fs.readFile(moduleJsonPath));
if (module.name !== pluginName) {
throw new Error(`Expected name of module for plugin ${pluginName} to match the plugin name`);
}
modules.push(module);
module = Object.assign({
path: modulePath,
load: [],
}, module);
modules.set(module.name, module);
}
await factorio.patch(this.path("saves", saveName), modules);
for (let entry of await fs.readdir('modules', { withFileTypes: true })) {
if (entry.isDirectory()) {
if (modules.has(entry.name)) {
throw new Error(`Module with name ${entry.name} already exists in a plugin`);
}
let moduleJsonPath = path.join('modules', entry.name, 'module.json');
if (!await fs.pathExists(moduleJsonPath)) {
throw new Error(`Module ${entry.name} is missing module.json`);
}
let module = JSON.parse(await fs.readFile(moduleJsonPath));
if (module.name !== entry.name) {
throw new Error(`Expected name of module ${entry.name} to match the directory name`);
}
module = Object.assign({
path: path.join('modules', entry.name),
load: [],
}, module);
modules.set(module.name, module);
}
}
await factorio.patch(this.path("saves", saveName), modules.values());
this.server.on('rcon-ready', () => {
console.log("Clusterio | RCON connection established");
@@ -805,6 +824,7 @@ async function startClient() {
await fs.ensureDir(slaveConfig.instanceDirectory);
await fs.ensureDir("sharedMods");
await fs.ensureDir("modules");
// Set the process title, shows up as the title of the CMD window on windows
// and as the process name in ps/top on linux.
+31 -9
View File
@@ -180,13 +180,40 @@ for some pointers if you get stuck.
Clusterio Modules
-----------------
Modules are primarily used by plugins to inject code into Factorio
games with the save patcher. The save patcher puts modules into the
`modules` folder of the Factorio save and adds code to `control.lua` to
Modules are primarily used by plugins to inject code into Factorio games
with the save patcher, though it's also possible to make stand alone
modules that are loaded into the game if you don't need the capabilites
of the plugin system. The save patcher puts modules into the `modules`
folder of the Factorio save and adds code to `control.lua` to
load the modules by requiring it and passing the result of the require
call to the `add_lib` function of the event_handler lib. See the
section on the [event_handler interface](#event_handler-interface) for a
detailed description on how this works.
detailed description on how the event_handler lib works.
Stand alone modules are placed into the modules folder of Clusterio,
plugin modules are located in the module folder of the plugin. In
either case a `module.json` file is required and has the following
structure:
```json
{
"name": "my_module",
"load": ["foo.lua"]
}
```
The following entries are supported in the module.json file:
- `name`:
Name of the module, must match the folder the module is located in
for stand alone modules or the name of the plugin the module is
located in.
- `load`:
Lua files to load with the `event_handler` lib. They should be
specified as paths that's relative to the module and uses forward
slashes as directory sepparators. Thus foo.lua will require a file
by that name and feed the result of that require to the event
handler lib.
Because modules can be patched into an existing game you cannot rely on
the `on_init` callback to be called in Clusterio Modules. Nor can you
@@ -194,11 +221,6 @@ rely on the `on_configuration_changed` callback, as this is not called
when level code changes. Instead you will have to initialize whatever
global variable you need when you first use them.
Currently any files in a folder named `lua` in a Clusterio plugin is
assumed to be a module and will be patched into the save before starting
up Factorio. This will most likely change with the planned plugin
restructuring.
Clusterio Plugins
-----------------
+8 -1
View File
@@ -18,7 +18,8 @@ The basic file structure of a plugin is the following.
+- instance.js
+- test/
| +- plugin.js
+- lua/
+- module/
+- module.json
+- plugin.lua
The most important file is the `info.js` file. Without it the plugin
@@ -70,6 +71,12 @@ The following properties are recognized:
Object with link messages definitions for this plugin. See guide
for [defining link messages](#defining-link-messages) below.
The optional module folder contains a Clusterio module that will be
patched into the save when the plugin is enabled. See the section on
[Clusterio Modules](developing-for-clusterio.md) in the Developing for
Clusterio document. The only restriction imposed on modules embedded
into plugins is that they must be named the same as the plugin.
While there is no standard for how to organize a plugin it's recommended
to put the MasterPlugin class definition into master.js and the
InstancePlugin class definition into instance.js. You can put them into
+21 -8
View File
@@ -7,6 +7,7 @@
const events = require("events");
const fs = require("fs-extra");
const jszip = require("jszip");
const path = require("path");
const hash = require("lib/hash");
@@ -81,7 +82,7 @@ function generateLoader(patchInfo) {
for (let module of patchInfo["modules"]) {
for (let file of module["files"]) {
if (file["load"]) {
let requirePath = "modules/" + file["path"].slice(0, -4);
let requirePath = `modules/${module.name}/${file["path"].slice(0, -4)}`;
lines.push('event_handler.add_lib(require("'+requirePath+'"))');
}
}
@@ -135,19 +136,31 @@ async function patch(savePath, modules) {
}
// Add the modules to the save.
let modulesDir = root.folder("modules");
for (let module of modules) {
let moduleDir = root.folder(`modules/${module.name}`);
let moduleInfo = {
"name": module.name,
"files": [],
}
for (let file of module.files) {
modulesDir.file(file.path, file.content);
moduleInfo["files"].push({
"path": file.path,
"load": Boolean(file.load),
});
let dirs = [[module.path, '']];
while (dirs.length) {
let [dir, relativeDir] = dirs.pop();
for (let entry of await fs.readdir(dir, { withFileTypes: true })) {
let fsPath = path.join(dir, entry.name);
let relativePath = path.posix.join(relativeDir, entry.name);
if (entry.isFile()) {
moduleDir.file(relativePath, await fs.readFile(fsPath));
moduleInfo["files"].push({
"path": relativePath,
"load": module.load.includes(relativePath),
});
} else if (entry.isDirectory()) {
dirs.push([fsPath, relativePath]);
}
}
}
patchInfo["modules"].push(moduleInfo);
@@ -0,0 +1,4 @@
{
"name": "statistics_exporter",
"load": ["export.lua"]
}
Whitespace-only changes.
+1
View File
@@ -0,0 +1 @@
-- test
+1
View File
@@ -0,0 +1 @@
-- test
+10 -5
View File
@@ -23,18 +23,23 @@ describe("Integration of lib/factorio/patch", function() {
slowTest(this);
await factorio.patch(savePath, [{
name: "test",
files: [{ path: "test.lua", content: "-- test" }]
path: "test/file/modules/test",
load: [],
}, {
name: "subdir",
path: "test/file/modules/subdir",
load: [],
}]);
let zip = await jszip.loadAsync(await fs.readFile(savePath));
let content = await zip.file("test/modules/test.lua").async("string");
assert.equal(content, "-- test");
assert.equal(await zip.file("test/modules/test/test.lua").async("string"), "-- test\n");
assert.equal(await zip.file("test/modules/subdir/dir/test.lua").async("string"), "-- test\n");
});
it("should remove old modules in a save", async function() {
slowTest(this);
await factorio.patch(savePath, [{ name: "test", files: [] }]);
await factorio.patch(savePath, []);
let zip = await jszip.loadAsync(await fs.readFile(savePath));
assert.equal(zip.file("test/modules/test.lua"), null);
assert.equal(zip.file("test/modules/test/test.lua"), null);
});
});
});
+1 -1
View File
@@ -59,7 +59,7 @@ describe("lib/factorio", function() {
'event_handler.add_lib(require("foo"))',
'',
'-- Clusterio modules',
'event_handler.add_lib(require("modules/bar"))',
'event_handler.add_lib(require("modules/spam/bar"))',
'',
].join('\n');
let patchInfo = {