Counter service
Example apps / counter-service / v0.3.0
Node mod
A service with no window: other packages read and change its count through REDnode's mod services.
In the game this package has no window. Here the preview lists the functions it offers.
// Counter service: every function this file exports can be called by other packages,
// by this package's id ("counter-service"), through the REDnode host. It has no
// window and no main: the service file is the whole package.
//
// The JSDoc on each function is its description: the host reads it (REDnode 0.35.0 and
// later) to check each call's arguments before they reach this file, the Mod Manager
// shows it, and functions marked @tool become tools an AI assistant can call (0.36.0).
import { service } from 'rednode';
/**
* @event changed {{ count: number, by: number, from: string }} The count changed: its new value, by how much, and who changed it.
*/
let count = 0;
/**
* The count now.
* @tool
* @readonly
* @returns {number} The count.
*/
export function get() {
return count;
}
/**
* Adds to the count, and tells every connected package.
* @tool
* @param {number} [by=1] How much to add; negative takes away.
* @returns {number} The new count.
*/
export function add(by = 1) {
if (typeof by !== 'number' || !Number.isFinite(by)) throw new Error('add needs a number');
count += by;
const who = service.caller();
service.emit('changed', { count, by, from: who?.name ?? who?.assistant ?? 'unknown' });
return count;
}
/**
* Sets the count back to 0.
* @tool
* @returns {number} 0.
*/
export function reset() {
count = 0;
service.emit('changed', { count, by: 0, from: service.caller()?.name ?? 'unknown' });
return count;
}
/**
* Waits, then answers: for trying timeouts and Stop. Ends early when the caller gives up.
* @param {number} ms How long to wait, in milliseconds.
* @returns {string} How long it waited.
*/
export function wait(ms) {
const signal = service.signal();
return new Promise((resolve, reject) => {
const t = setTimeout(() => resolve(`waited ${ms} ms`), ms);
signal?.addEventListener('abort', () => {
clearTimeout(t);
console.log(`wait(${ms}) stopped: the caller gave up`);
reject(new Error('stopped'));
});
});
}
/**
* Always fails: for seeing how an error reaches the caller.
*/
export function fail() {
throw new Error('Counter service failed on purpose');
}
REDnode runs JavaScript. The rednode package also has TypeScript types, so an editor checks these calls; compile TypeScript to .mjs first.
{
"id": "counter-service",
"name": "Counter service",
"version": "0.3.0",
"author": "Edgemodders",
"description": "A service with no window: a count other packages read and change through REDnode's mod services. Its functions are in service.mjs. Counter panel uses it by its id; Roles test finds it by its role, \"counter\".",
"service": "service.mjs",
"provides": ["counter"],
"open": false
}
The manifest names the package and says how REDnode runs it. Its fields are in Overlay apps.
How it works
Its rednode-app.json names a service file, "service": "service.mjs", and no main: the service is the whole package. Every function service.mjs exports can be called by other packages, by this package's id, through the REDnode host.
service.emit("changed", ...) tells every package that connected. service.caller() says who is calling, and service.signal() aborts when the caller gives up, which wait uses.
The JSDoc on each function is its description: from SV2 the host reads it, and functions marked @tool become tools an AI assistant can call.
Its rednode-app.json also says "provides": ["counter"]: a package that does not know its id can find it with use("counter") (REDnode 0.44.0 or later), as Roles test does.
What it uses
service file service.emit service.caller service.signal JSDoc
Try this
- Open Counter panel's page: it runs this service beside it.
Use it
Copy the files into red4ext/plugins/REDnode/apps/counter-service/ in the game folder, or start your own package from them: change id and name in the manifest first. The whole folder is on GitHub.