-
Notifications
You must be signed in to change notification settings - Fork 33
Intro to Lodestone Atom
- The API is not stable
- Breaking changes can be made at any time without warning
- The documentation is not complete
- Code may have unintended side effects
Last updated for: v0.5.0-beta.3
- Intermediate TypeScript knowledge (async/await, OOP, etc)
- Familiarity with Deno
- Familiarity with Lodestone on a user level
Lodestone Atom allows you to implement your custom instance logic in typescript.
For those familiar with Pterodactyl eggs, Lodestone Atom serves a similar purpose, but with typescript instead of a declarative json.
You can host the code of your Atom on Github, and import it via a url.
- Macros are executed in a modified Deno runtime, so it is recommended that you follow their environment setup.
Note that this is not strictly necessary, but will provide you with a better developer experience.
Throughout this guide, we will be using VSCode, but you can use any editor you like as long as it supports the Deno language server.
- Download Lodestone CLI.
Install the latest Lodestone Core beta with
lodestone_cli -v v0.5.0-beta.3
and run it
- Head over to https://dev.lodestone.cc/ and connect to your core
Since the architecture of Lodestone Atom is quite complicated, let's start with a example.
In the instance creation screen, paste in the following url for Atom: https://raw.githubusercontent.com/Lodestone-Team/lodestone-atom-lib/main/examples/basic.ts
Then click "Load Instance", and click through the rest of the creation process, the setting fields don't matter.
You should see a progress bar to indicate the instance is being created.
Once it's done, click on the instance power button and click "Start", it will then put "Output" in the instance console every second.
You can also type in commands in the console, and it will respond with "Got command, {your command}".
An Atom is a typescript class that extends the Atom
class from the lodestone-atom-lib
package.
Let's break down the example Atom method by method.
public async setupManifest(): Promise<Atom.SetupManifest> {
return {
setting_sections: {
"section_id1": {
section_id: "section_id1",
name: "section_name1",
description: "section_description1",
settings: {
"setting_id1": {
setting_id: "setting_id1",
name: "Port",
description: "Port to run the server on",
value: null,
value_type: { type: "UnsignedInteger", min: 0, max: 65535 },
default_value: { type: "UnsignedInteger", value: 6969 },
is_secret: false,
is_required: true,
is_mutable: true,
}
},
}
}
};
}
The setupManifest method is used to define the settings that the user can configure when creating an instance.
You should recall that we were able to set the port of the instance in the creation screen.
The setting_sections
field is a map of SettingSection
s, which are used to group settings together.
Note that all section_id
s should be unique, and all setting_id
s should be unique across all sections.
public async setup(setupValue: Atom.SetupValue, dotLodestoneConfig: Atom.DotLodestoneConfig, progression_handler: ProgressionHandler, path: string): Promise<void> {
this.uuid = dotLodestoneConfig.uuid;
let port: number;
if (setupValue.setting_sections["section_id1"].settings["setting_id1"].value?.type == "UnsignedInteger") {
port = setupValue.setting_sections["section_id1"].settings["setting_id1"].value.value;
} else {
throw new Error("Invalid value type");
}
this.config = {
name: setupValue.name,
description: setupValue.description ?? "",
port: port,
};
// write config to file
await Deno.writeTextFile(path + "/" + TestInstance.restoreConfigName, JSON.stringify(this.config));
this.event_stream = new EventStream(this.uuid, this.config.name);
for (let i = 0; i < 100; i++) {
progression_handler.setProgress(i, `Progress ${i}`);
await new Promise(r => setTimeout(r, 100));
}
// no need to call `progression_handler.complete()` since it's handled.
return;
}
The setup
method is the "constructor" of the Atom.
The setupValue
parameter contains the values that the user has configured in the creation screen, and the dotLodestoneConfig
parameter contains values generated by Lodestone, such as the instance uuid, time of creation, etc.
The path
parameter is the path to the instance directory, all of your files should be stored in this directory.
The progression_handler
parameter is used to report progress to Lodestone, which will be displayed in the notification area and instance list.
Usually you will have to call complete()
on the progression handler, but the setup method is a special case, where Lodestone will automatically call complete()
when the method returns. You can still call complete()
early if you want to.
We also write the config to a file, so that we can restore it later.
public async restore(dotLodestoneConfig: Atom.DotLodestoneConfig, path: string): Promise<void> {
this.uuid = dotLodestoneConfig.uuid;
this.event_stream = new EventStream(this.uuid, this.config.name);
this.config = JSON.parse(await Deno.readTextFile(path + "/" + TestInstance.restoreConfigName)) as RestoreConfig;
return;
}
The restore
method is called when the Atom is restored from file system by Lodestone.
Note the lack of state passed into the parameters, the implementation is responsible for restoring any state the Atom wishes to persist, this is why we wrote the config to file in the setup
method.
public async start(caused_by: Atom.CausedBy, block: boolean): Promise<void> {
console.log("start");
this.event_stream.emitStateChange("Running");
this._state = "Running";
(async () => {
while (this._state == "Running") {
await new Promise(r => setTimeout(r, 1000));
this.event_stream.emitConsoleOut("Output");
}
})();
return;
}
The start
method is called when the instance is requested to start.
The caused_by
parameter is planned to be deprecated, so don't worry about it.
The block
parameter is used to indicate whether the method should block until the instance is started.
If block
is set to false, then this method should perform the bare minimum to check for valid states and start the instance, then return as soon as possible.
If block
is set to true, then the method should block until the instance is fully started, and return only when the instance is ready to be used. This is useful to implement restart.
public async stop(caused_by: Atom.CausedBy, block: boolean): Promise<void> {
console.log("stop");
this._state = "Stopped";
this.event_stream.emitStateChange("Stopped");
return;
}
The stop
method is called when the instance is requested to stop.
Just like the start
method, the block
parameter is used to indicate whether the method should block until the instance is fully stopped.