mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 23:24:52 +09:00
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:
1 parent
11992d1839
commit
80bd75ba16
11 files changed
+120
-47
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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
|
||||
-----------------
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
File renamed without changes.
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "statistics_exporter",
|
||||
"load": ["export.lua"]
|
||||
}
|
||||
Whitespace-only changes.
@@ -0,0 +1 @@
|
||||
-- test
|
||||
@@ -0,0 +1 @@
|
||||
-- test
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 = {
|
||||
|
||||
Reference in new issue
Block a user