Add subscriptions and luacheck

This commit is contained in:
Cooldude2606 committed 2024-02-17 03:11:54 +00:00
1 parent 58231cc02c
commit f392b258f7
15 files changed
+3644 -39

No files matched your search

+6 -1
View File
@@ -522,15 +522,18 @@ async function copyPluginTemplateFile(src, dst, props) {
const templateData = await fs.readFile(src, "utf8");
const outputData = templateData
.replace(/\/\/ \[([a-z_]*)\] \/\/(.*?)\/\/ \[\] \/\//gs, (_, prop, content) => {
// matches: // [prop] // anything // [] // or /*/ [prop] /*/ anything /*/ [] /*/
.replace(/\/\*?\/ \[([a-z_]*)\] \/\*?\/(.*?)\/\*?\/ \[\] \/\*?\//gs, (_, prop, content) => {
// Find and replace // [prop] //content// [] //
if (!props.hasOwnProperty(prop)) {
logger.warn(`Unknown property clause (${prop}) found in template file ${src}`);
return content;
}
// Would be nice to support + (and) | (or) for prop clauses
return Boolean(props[prop]) ? content : "";
})
// matches: // prop //
.replace(/\/\/ ([a-z_]*) \/\//g, (_, prop) => {
// Find and replace // prop //
if (!props.hasOwnProperty(prop)) {
@@ -607,6 +610,7 @@ async function copyPluginTemplate(pluginName, templates) {
}
if (templates.includes("module")) {
files.set(".luacheckrc", path.join(templatePath, ".luacheckrc"));
files.set("module/module.json", path.join(templatePath, "module/module.json"));
files.set("module/control.lua", path.join(templatePath, "module/control.lua"));
files.set("module/module_exports.lua", path.join(templatePath, "module/module_exports.lua"));
@@ -620,6 +624,7 @@ async function copyPluginTemplate(pluginName, templates) {
// Properties that will control the replacements in the templates
const props = {
controller: templates.includes("controller"),
subscribable: templates.includes("controller") && templates.includes("web"),
host: templates.includes("host"),
instance: templates.includes("instance"),
module: templates.includes("module"),
File diff suppressed because it is too large. Load diff
@@ -1,12 +1,17 @@
"use strict";
const lib = require("@clusterio/lib");
const { BaseControllerPlugin } = require("@clusterio/controller");
const { PluginExampleEvent, PluginExampleRequest } = require("./messages");
const { PluginExampleEvent, PluginExampleRequest,/*/ [subscribable] /*/ ExampleSubscribableUpdate, ExampleSubscribableValue/*/ [] /*/ } = require("./messages");
class ControllerPlugin extends BaseControllerPlugin {
class ControllerPlugin extends BaseControllerPlugin {// [subscribable] //
exampleDatabase;
storageDirty = false;
// [] //
async init() {
this.controller.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
this.controller.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));
this.controller.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));// [subscribable] //
this.controller.subscriptions.handle(ExampleSubscribableUpdate, this.handleExampleSubscription.bind(this));
this.exampleDatabase = new Map(); // If needed, replace with loading from database file // [] //
}
async onControllerConfigFieldChanged(field, curr, prev) {
@@ -39,7 +44,14 @@ class ControllerPlugin extends BaseControllerPlugin {
myResponseString: request.myString,
myResponseNumbers: request.myNumberArray,
};
}
}// [subscribable] //
async handleExampleSubscription(request) {
const values = [...this.exampleDatabase.values()].filter(
value => value.updatedAtMs > request.lastRequestTimeMs,
);
return values.length ? new ExampleSubscribableUpdate(values) : null;
}// [] //
}
module.exports = {
+12 -6
View File
@@ -1,7 +1,6 @@
"use strict";
const lib = require("@clusterio/lib");
const { PluginExampleEvent, PluginExampleRequest } = require("./messages");
const messages = require("./messages");
lib.definePermission({
name: "// plugin_name //.example.permission.event",
@@ -13,7 +12,13 @@ lib.definePermission({
name: "// plugin_name //.example.permission.request",
title: "Example permission request",
description: "My plugin's example permission that I forgot to remove",
});// [web] //
});// [subscribable] //
lib.definePermission({
name: "// plugin_name //.example.permission.subscribe",
title: "Example permission subscribe",
description: "My plugin's example permission that I forgot to remove",
});// [] //// [web] //
lib.definePermission({
name: "// plugin_name //.page.view",
@@ -26,7 +31,7 @@ const plugin = {
title: "// plugin_name //",
description: "I didn't update my description",// [entry_points] //
// entry_points //
// [] //// [controller] //
// [] //// [controller] //
controllerConfigFields: {
"// plugin_name //.myControllerField": {
title: "My Controller Field",
@@ -61,8 +66,9 @@ const plugin = {
},// [] //
messages: [
PluginExampleEvent,
PluginExampleRequest,
messages.PluginExampleEvent,
messages.PluginExampleRequest,// [subscribable] //
messages.ExampleSubscribableUpdate,// [] //
],
};
@@ -1,6 +1,6 @@
"use strict";
const { plainJson, jsonArray, JsonBoolean, JsonNumber, JsonString, StringEnum } = require("@clusterio/lib");
const { Type, Static } = require("@sinclair/typebox");
const { Type } = require("@sinclair/typebox");
class PluginExampleEvent {
static type = "event";
@@ -49,9 +49,49 @@ class PluginExampleRequest {
"myResponseString": Type.String(),
"myResponseNumbers": Type.Array(Type.Number()),
}));
}// [subscribable] //
class ExampleSubscribableValue {
constructor(id, updatedAtMs, isDeleted) {
this.id = id;
this.updatedAtMs = updatedAtMs;
this.isDeleted = isDeleted;
}
static jsonSchema = Type.Object({
id: Type.String(),
updatedAtMs: Type.Number(),
isDeleted: Type.Boolean(),
});
static fromJSON(json) {
return new this(json.id, json.updatedAtMs, json.isDeleted);
}
}
class ExampleSubscribableUpdate {
static type = "event";
static src = "controller";
static dst = "control";
static plugin = "// plugin_name //";
static permission = "// plugin_name //.example.permission.subscribe";
constructor(updates) {
this.updates = updates;
}
static jsonSchema = Type.Object({
"updates": Type.Array(ExampleSubscribableValue.jsonSchema),
});
static fromJSON(json) {
return new this(json.updates.map(update => ExampleSubscribableValue.fromJSON(update)));
}
}// [] //
module.exports = {
PluginExampleEvent,
PluginExampleRequest,
PluginExampleRequest,// [subscribable] //
ExampleSubscribableValue,
ExampleSubscribableUpdate,// [] //
};
@@ -1,18 +1,40 @@
local clusterio_api = require("modules/clusterio/api")
--- Top level module table, contains event handlers and public methods
local MyModule = {
events = {},
on_nth_tick = {},
}
--- global is 'synced' between players, you should use your plugin name to avoid conflicts
-- setupGlobalData should either be removed or called during clusterio_api.events.on_server_startup
local globalData = {}
local function setupGlobalData()
if global["// plugin_name //"] == nil then
global["// plugin_name //"] = {
-- starting values go here
}
end
globalData = global["// plugin_name //"]
end
--- Public methods should be available though your top level module table
function MyModule.foo()
game.print("foo")
end
--- Private methods should be local to the file, this will prevent others from calling it
local function bar()
game.print("bar")
end
--- Clusterio provides a few custom events, on_server_startup is the most useful and should be used in place of on_load
MyModule.events[clusterio_api.events.on_server_startup] = function(event)
setupGlobalData()
game.print(game.table_to_json(event))
end
--- Factorio events are accessible through defines.events, you can have one handler per event per module
MyModule.events[defines.events.on_player_crafted_item] = function(event)
game.print(game.table_to_json(event))// [instance] //
clusterio_api.send_json("// plugin_name //-plugin_example_ipc", {
@@ -20,8 +42,11 @@ MyModule.events[defines.events.on_player_crafted_item] = function(event)
})// [] //
end
--- Nth tick is a special case that requires its own table, the index represents the time period between calls in ticks
MyModule.on_nth_tick[300] = function()
game.print(game.tick)
bar()
end
--- Always return the top level module table from control, this is how clusterio will access your event handlers
return MyModule
@@ -1,7 +1,8 @@
local MyModule = require("./control")// [instance] //
-- Can contain anything you plan to use with send_rcon from functions, modules, or data
--- Can contain anything you plan to use with send_rcon including functions, modules, or data
-- luacheck: globals ipc_// plugin_name //
ipc_// plugin_name // = MyModule// [] //
-- Can contain anything you want to allow other plugins to have access to, this example exposes the whole module
--- Can contain anything you want to allow other plugins to have access to, this example exposes the whole module
return MyModule
@@ -1,4 +1,4 @@
const { useContext, useEffect, useState } = require("react");
const { useContext, useEffect, useState,/*/ [subscribable] /*/ useCallback, useSyncExternalStore,/*/ [] /*/ } = require("react");
// const { } = require("antd");
const {
@@ -6,17 +6,22 @@ const {
} = require("@clusterio/web_ui");
const lib = require("@clusterio/lib");
const { PluginExampleEvent, PluginExampleRequest } = require("../messages");
const { PluginExampleEvent, PluginExampleRequest,/*/ [subscribable] /*/ ExampleSubscribableUpdate,/*/ [] /*/ } = require("../messages");
function MyTemplatePage() {
let control = useContext(ControlContext);
let control = useContext(ControlContext);// [subscribable] //
const plugin = control.plugins.get("// plugin_name //");
const [subscribableData, synced] = plugin.useSubscribableData();// [] //
return <PageLayout nav={[{ name: "// plugin_name //" }]}>
<h2>// plugin_name //</h2>
<h2>// plugin_name //</h2>// [subscribable] //
Synced: {String(synced)} Data: {JSON.stringify([...subscribableData.values()])}// [] //
</PageLayout>;
}
class WebPlugin extends BaseWebPlugin {
class WebPlugin extends BaseWebPlugin {// [subscribable] //
subscribableData = new lib.EventSubscriber(ExampleSubscribableUpdate, this.control);
// [] //
async init() {
this.pages = [
{
@@ -29,7 +34,13 @@ class WebPlugin extends BaseWebPlugin {
this.control.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
this.control.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));
}
}// [subscribable] //
useSubscribableData() {
const control = useContext(ControlContext);
const subscribe = useCallback((callback) => this.subscribableData.subscribe(callback), [control]);
return useSyncExternalStore(subscribe, () => this.subscribableData.getSnapshot());
}// [] //
async handlePluginExampleEvent(event) {
this.logger.info(JSON.stringify(event));
File diff suppressed because it is too large. Load diff
@@ -1,11 +1,16 @@
import * as lib from "@clusterio/lib";
import { BaseControllerPlugin, InstanceInfo } from "@clusterio/controller";
import { PluginExampleEvent, PluginExampleRequest } from "./messages";
import { PluginExampleEvent, PluginExampleRequest,/*/ [subscribable] /*/ ExampleSubscribableUpdate, ExampleSubscribableValue/*/ [] /*/ } from "./messages";
export class ControllerPlugin extends BaseControllerPlugin {
export class ControllerPlugin extends BaseControllerPlugin {// [subscribable] //
exampleDatabase!: Map<string, ExampleSubscribableValue>;
storageDirty = false;
// [] //
async init() {
this.controller.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
this.controller.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));
this.controller.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));// [subscribable] //
this.controller.subscriptions.handle(ExampleSubscribableUpdate, this.handleExampleSubscription.bind(this));
this.exampleDatabase = new Map();// [] //
}
async onControllerConfigFieldChanged(field: string, curr: unknown, prev: unknown) {
@@ -38,5 +43,12 @@ export class ControllerPlugin extends BaseControllerPlugin {
myResponseString: request.myString,
myResponseNumbers: request.myNumberArray,
};
}
}// [subscribable] //
async handleExampleSubscription(request: lib.SubscriptionRequest) {
const values = [...this.exampleDatabase.values()].filter(
value => value.updatedAtMs > request.lastRequestTimeMs,
);
return values.length ? new ExampleSubscribableUpdate(values) : null;
}// [] //
}
+12 -6
View File
@@ -1,6 +1,5 @@
import * as lib from "@clusterio/lib";
import { PluginExampleEvent, PluginExampleRequest } from "./messages";
import * as Messages from "./messages";
lib.definePermission({
name: "// plugin_name //.example.permission.event",
@@ -12,7 +11,13 @@ lib.definePermission({
name: "// plugin_name //.example.permission.request",
title: "Example permission request",
description: "My plugin's example permission that I forgot to remove",
});// [web] //
});// [subscribable] //
lib.definePermission({
name: "// plugin_name //.example.permission.subscribe",
title: "Example permission subscribe",
description: "My plugin's example permission that I forgot to remove",
});// [] //// [web] //
lib.definePermission({
name: "// plugin_name //.page.view",
@@ -40,7 +45,7 @@ export const plugin: lib.PluginDeclaration = {
title: "// plugin_name //",
description: "I didn't update my description",// [entry_points] //
// entry_points //
// [] //// [controller] //
// [] //// [controller] //
controllerConfigFields: {
"// plugin_name //.myControllerField": {
title: "My Controller Field",
@@ -75,7 +80,8 @@ export const plugin: lib.PluginDeclaration = {
},// [] //
messages: [
PluginExampleEvent,
PluginExampleRequest,
Messages.PluginExampleEvent,
Messages.PluginExampleRequest,// [subscribable] //
Messages.ExampleSubscribableUpdate,// [] //
],
};
@@ -52,4 +52,44 @@ export class PluginExampleRequest {
"myResponseString": Type.String(),
"myResponseNumbers": Type.Array(Type.Number()),
}));
}// [subscribable] //
export class ExampleSubscribableValue {
constructor(
public id: string,
public updatedAtMs: number,
public isDeleted: boolean,
) {
}
static jsonSchema = Type.Object({
id: Type.String(),
updatedAtMs: Type.Number(),
isDeleted: Type.Boolean(),
});
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(json.id, json.updatedAtMs, json.isDeleted);
}
}
export class ExampleSubscribableUpdate {
declare ["constructor"]: typeof ExampleSubscribableUpdate;
static type = "event" as const;
static src = "controller" as const;
static dst = "control" as const;
static plugin = "// plugin_name //" as const;
static permission = "// plugin_name //.example.permission.subscribe";
constructor(
public updates: ExampleSubscribableValue[],
) { }
static jsonSchema = Type.Object({
"updates": Type.Array(ExampleSubscribableValue.jsonSchema),
});
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(json.updates.map(update => ExampleSubscribableValue.fromJSON(update)));
}
}// [] //// [] //
@@ -1,18 +1,40 @@
local clusterio_api = require("modules/clusterio/api")
--- Top level module table, contains event handlers and public methods
local MyModule = {
events = {},
on_nth_tick = {},
}
--- global is 'synced' between players, you should use your plugin name to avoid conflicts
-- setupGlobalData should either be removed or called during clusterio_api.events.on_server_startup
local globalData = {}
local function setupGlobalData()
if global["// plugin_name //"] == nil then
global["// plugin_name //"] = {
-- starting values go here
}
end
globalData = global["// plugin_name //"]
end
--- Public methods should be available though your top level module table
function MyModule.foo()
game.print("foo")
end
--- Private methods should be local to the file, this will prevent others from calling it
local function bar()
game.print("bar")
end
--- Clusterio provides a few custom events, on_server_startup is the most useful and should be used in place of on_load
MyModule.events[clusterio_api.events.on_server_startup] = function(event)
setupGlobalData()
game.print(game.table_to_json(event))
end
--- Factorio events are accessible through defines.events, you can have one handler per event per module
MyModule.events[defines.events.on_player_crafted_item] = function(event)
game.print(game.table_to_json(event))// [instance] //
clusterio_api.send_json("// plugin_name //-plugin_example_ipc", {
@@ -20,8 +42,11 @@ MyModule.events[defines.events.on_player_crafted_item] = function(event)
})// [] //
end
--- Nth tick is a special case that requires its own table, the index represents the time period between calls in ticks
MyModule.on_nth_tick[300] = function()
game.print(game.tick)
bar()
end
--- Always return the top level module table from control, this is how clusterio will access your event handlers
return MyModule
@@ -1,7 +1,8 @@
local MyModule = require("./control")// [instance] //
-- Can contain anything you plan to use with send_rcon from functions, modules, or data
--- Can contain anything you plan to use with send_rcon including functions, modules, or data
-- luacheck: globals ipc_// plugin_name //
ipc_// plugin_name // = MyModule// [] //
-- Can contain anything you want to allow other plugins to have access to, this example exposes the whole module
--- Can contain anything you want to allow other plugins to have access to, this example exposes the whole module
return MyModule
@@ -1,4 +1,4 @@
import React, { useContext, useEffect, useState } from "react";
import React, { useContext, useEffect, useState,/*/ [subscribable] /*/ useCallback, useSyncExternalStore,/*/ [] /*/ } from "react";
// import { } from "antd";
import {
@@ -6,17 +6,22 @@ import {
} from "@clusterio/web_ui";
import * as lib from "@clusterio/lib";
import { PluginExampleEvent, PluginExampleRequest } from "../messages";
import { PluginExampleEvent, PluginExampleRequest,/*/ [subscribable] /*/ ExampleSubscribableUpdate,/*/ [] /*/ } from "../messages";
function MyTemplatePage() {
let control = useContext(ControlContext);
const control = useContext(ControlContext);// [subscribable] //
const plugin = control.plugins.get("// plugin_name //") as WebPlugin;
const [subscribableData, synced] = plugin.useSubscribableData();// [] //
return <PageLayout nav={[{ name: "// plugin_name //" }]}>
<h2>// plugin_name //</h2>
<h2>// plugin_name //</h2>// [subscribable] //
Synced: {String(synced)} Data: {JSON.stringify([...subscribableData.values()])}// [] //
</PageLayout>;
}
export class WebPlugin extends BaseWebPlugin {
export class WebPlugin extends BaseWebPlugin {// [subscribable] //
subscribableData = new lib.EventSubscriber(ExampleSubscribableUpdate, this.control);
// [] //
async init() {
this.pages = [
{
@@ -29,7 +34,13 @@ export class WebPlugin extends BaseWebPlugin {
this.control.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
this.control.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));
}
}// [subscribable] //
useSubscribableData() {
const control = useContext(ControlContext);
const subscribe = useCallback((callback: () => void) => this.subscribableData.subscribe(callback), [control]);
return useSyncExternalStore(subscribe, () => this.subscribableData.getSnapshot());
}// [] //
async handlePluginExampleEvent(event: PluginExampleEvent) {
this.logger.info(JSON.stringify(event));