Skip to content

Commit 1ec88dd

Browse files
Update registry
1 parent bc3446a commit 1ec88dd

File tree

4 files changed

+161
-69
lines changed

4 files changed

+161
-69
lines changed

README.md

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
# Integrate
22

3-
A library for mod loading into serialisable registries.
3+
A library for mod loading into serialisable registries.
4+
Can be used in conjunction with [ISL](https://github.com/LightningLaser8/ISL) to perform complex operations.
45

56
## Terminology
67

78
- A _registry_ is a data structure for holding case-insensitive key-value pairs. Simply, it matches names to objects, without caring about capitalisation. They are instances of `Integrate.Registry`.
8-
- *Registry name*s are string locations of an item in a _registry_.
9-
- *Constructible object*s are basic objects with a `type` property, holding a _registry name_ of a class.
10-
- _Content_ refers to any constructible object with a registry name, defined by the mod. Any content is an instance of `Integrate.Content`.
9+
- *Registry name*s or *Registry location*s are strings which are keys in a _registry_. They can be used to refer to a _constructible object_.
10+
- *Constructible object*s are basic, serialisable objects with a `type` property, holding a _registry name_ of a class.
11+
- _Content_ refers to any _constructible object_ with a _registry name_, defined by the _mod_. Any content is an instance of `Integrate.Content`.
1112
- A _mod_ is a directory of files, each one adding _content_.
1213
- A _content file_ is a JSON file holding a _constructible object_.
1314

15+
### ISL
16+
17+
- _ISL_, or _Integrate Scripting Language_ is an external interpreted scripting language for use with this modloader, to create complex events.
18+
- A _script_ is a `.isl` file to be executed when an _event_ is fired.
19+
- An _event_ is a signal from the game that Integrate needs to run some scripts. Every script needs to define which events they fire on.
20+
1421
## Example (JS)
1522

1623
_Adding Integrate mods to your project_
@@ -91,11 +98,13 @@ This is the most important file in any Integrate mod, defining paths and registr
9198
It consists of a _single array_, each entry being an object with these three properties:
9299
`path` defining the _relative location_ of the _content file_ being described.
93100
`name` being the _registry name_ of this content.
94-
`registry` being optional, defining the registry this content will be added to. By default, this will be `"content"`. **This registry does not exist by default, and will throw errors if not defined.**
101+
`registry` being optional, defining the registry this content will be added to. By default, this will be `"content"`. **This registry does not exist by default, and will throw errors if not defined using `Integrate.addModdableRegistry()`.**
95102

96103
### Content Files
97104

98105
These describe the actual content itself, not metadata.
106+
They can be anywhere, even outside the mod directory, as long as `definitions.json` points to them, and the program can reach them.
107+
This is to leave organisation up to the mod developer, so you can organise the files hovever you like.
99108

100109
```json
101110
{
@@ -112,6 +121,7 @@ These describe the actual content itself, not metadata.
112121
## Interface
113122

114123
Integrate has several functions to customise modloading, which are documented here.
124+
This section assumes you imported Integrate in a single namespace, called `Integrate`.
115125

116126
### Integrate.add()
117127

@@ -142,7 +152,7 @@ Returns an `Integrate.Mod` object, holding all the info about the imported mod.
142152
Integrate.addModdableRegistry(reg: Integrate.Registry, name: string): void
143153
```
144154

145-
`reg` is the `Integrate.Registry` (or similar implementing the same methods) to allow modification of.
155+
`reg` is the `Integrate.Registry` (or similar object implementing the same methods) to allow modification of.
146156
`name` is the string that this registry will be referred to by.
147157

148158
### Integrate.setPrefix()
@@ -163,7 +173,7 @@ Integrate.setPrefix(value: boolean): void
163173
Integrate.setInfoOutput(func: (info: string) => void): void
164174
```
165175

166-
`func` callback for each status message. THe parameter `info` contains the message, as a string. By default, this function is `console.log`.
176+
`func` callback for each status message. The parameter `info` contains the message, as a string. By default, this function is `console.log`.
167177

168178
### Integrate.types
169179
`Integrate.types` is an `Integrate.Registry` holding all types mod content can be an instance of.
@@ -172,7 +182,7 @@ Integrate.types: Integrate.Registry
172182
```
173183

174184
### Integrate.construct()
175-
`Integrate.construct()` is a helpful function that combines `Integrate.Registry.create()` and `Integrate.Registry.construct` for mod content.
185+
`Integrate.construct()` is a helpful function that combines `Integrate.Registry.create()` and `Integrate.Registry.construct()` for mod content. It constructs an object either literally or from any moddable registry, using types from `Integrate.types`.
176186
```ts
177187
Integrate.construct(object: object | string, defaultType: class): object
178188
```
@@ -247,8 +257,8 @@ class Registry {
247257
`add()` Adds an item to registry.
248258
`has()` Checks for an item in registry.
249259
`get()` Gets an item from registry name.
250-
`create()` Constructs an item from registry. Note that this only works with objects. The parameter `registry` should be the registry holding all types, such as `Integrate.types`.
251-
`construct()` Constructs an item using a type from registry. Note that this only works with object entries.
260+
`create()` Constructs an item from this registry. Note that this only works with object entries. The parameter `registry` should be the registry holding all types, such as `Integrate.types`.
261+
`construct()` Constructs an item using a type from this registry. Note that this only works with object parameters.
252262
`rename()` Renames a registry item. Neither parameter is case-sensitive.
253263
`alias()` Adds another registry item with the same content as the specified one.
254264
`forEach()` Executes a function for each element in the registry.

integrate.png

9.22 KB
Loading

isl.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
## ISL
2+
_Integrate Scripting Language_
3+
4+
ISL is a scripting language designed for use with this loader, allowing mods to script complex events in addition to simple content.
5+
This can be disabled by game devs, so check the project's Integrate documentation for this information.
6+
7+
There are no standard keywords in Integrate, as they are all added by the developer. Because of this, it is recommended to fully read through their extension documentation before making an ISL mod for their project.
8+
9+
This documentation from this point assumes you know about ISL, and have familiarised yourself with its basic features. If not, go read the [ISL wiki](https://github.com/LightningLaser8/ISL/wiki).
10+
11+
### Creating Scripts
12+
Scripts are a bit different to normal mod content. They don't have a `type`, and are defined in `.isl` files, instead of `.json`.
13+
14+
A script file is essentially a normal ISL program, with some extra properties, defined in metatags.
15+
16+
An example script file (`script.isl`):
17+
```isl
18+
[onevent mod-load] // When the mod loads
19+
[with loadstate, contentnamelist] // Using some properties from the event
20+
21+
// Regular ISL from here
22+
function logstr name:string
23+
log :\name\
24+
end logstr
25+
26+
if \loadstate\ = true log "Mod loaded."
27+
else log "Loading failed."
28+
| stop
29+
30+
iterate \contentnamelist\ with logstr
31+
```
32+
33+
Definitions look like this:
34+
```json
35+
[
36+
{
37+
"type": "script",
38+
"path": "./script.isl"
39+
}
40+
]
41+
```

modules/registry.js

Lines changed: 100 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,8 @@ class Registry {
1313
* @param {*} item Item to add to registry.
1414
*/
1515
add(name, item) {
16-
if (!name) return; //catch empty name
17-
if (!item) return; //catch null items
18-
if (typeof name !== "string") name = name.toString(); //Stringify name
19-
name = name.toLowerCase(); //Remove case sensitivity.
16+
name = Registry.#processName(name);
17+
if (!item) throw new TypeError("Registries cannot contain null");
2018
//Throw an error if the item already exists.
2119
if (this.has(name))
2220
throw new SyntaxError(
@@ -33,8 +31,8 @@ class Registry {
3331
* @returns Whether or not the name exists.
3432
*/
3533
has(name) {
36-
if (typeof name !== "string") name = name.toString(); //Stringify name
37-
name = name.toLowerCase(); //Remove case sensitivity.
34+
if (!name) return false;
35+
name = Registry.#processName(name);
3836
//Return presence
3937
return this.#content.has(name);
4038
}
@@ -44,7 +42,8 @@ class Registry {
4442
* @returns The item, if present.
4543
*/
4644
get(name) {
47-
if (typeof name !== "string") name = name.toString(); //Stringify name
45+
if (!name) throw new ReferenceError("No registry contains null!");
46+
name = Registry.#processName(name);
4847
name = name.toLowerCase(); //Remove case sensitivity.
4948
//Throw an error if the item doesn't exist.
5049
if (!this.has(name))
@@ -54,53 +53,21 @@ class Registry {
5453
" does not exist in registry! Consider checking your spelling."
5554
);
5655
//Return item, if it exists.
57-
return this.#content.get(name);
58-
}
59-
/**
60-
* Constructs an item from registry. Note that this only works with objects.
61-
* @param {string} name Name of item to construct.
62-
* @param {Registry} registry Registry for the type of the item.
63-
* @param {Function} [defaultType=Object] Constructor function or class to use if there's no defined type.
64-
*/
65-
create(name, registry, defaultType = Object) {
66-
return this.#construct(this.get(name), registry, defaultType);
67-
}
68-
/**
69-
* Constructs an item using a type from registry. Note that this only works with objects.
70-
* @param {object} object Object to construct.
71-
* @param {Function} [defaultType=Object] Constructor function or class to use if there's no defined type.
72-
*/
73-
construct(object, defaultType = Object) {
74-
return this.#construct(object, this, defaultType);
75-
}
76-
#construct(object, registry, defaultType = Object) {
77-
if (!object) return; //Catch accidental calls using null, undefined or similar
78-
//Constructs an instance using type from registry, if it exists. If not, throw error.
79-
//If type is undefined, use the default.
80-
let instantiated = new (
81-
object.type ? registry.get(object.type) : defaultType
82-
)();
83-
let cloned = {};
84-
//Clone the object if possible, to copy stuff like bullet drawers, or weapon.shoot.pattern. If it fails, just use the original.
56+
let item = this.#content.get(name);
8557
try {
86-
cloned = structuredClone(object);
87-
} catch (error) {
88-
cloned = object;
89-
console.warn("Could not clone object:", error);
58+
item.registryName = name;
59+
} catch (e) {
60+
console.warn("Non-object entries do not have full feature support.");
9061
}
91-
checkForClashingFunctions(cloned, instantiated)
92-
instantiated = Object.assign(instantiated, cloned);
93-
instantiated.init ? instantiated.init() : {}; //Initialise if possible.
94-
return instantiated;
62+
return item;
9563
}
9664
/**
9765
* Renames a registry item. Neither parameter is case-sensitive.
9866
* @param {string} name Registry name to change.
9967
* @param {string} newName What to change the name to.
10068
*/
10169
rename(name, newName) {
102-
if (typeof name !== "string") name = name.toString(); //Stringify name
103-
name = name.toLowerCase(); //Remove case sensitivity.
70+
name = Registry.#processName(name);
10471
//Throw an error if the item doesn't exist.
10572
if (!this.has(name))
10673
throw new ReferenceError(
@@ -121,8 +88,7 @@ class Registry {
12188
* @param {string} as What to change the name to.
12289
*/
12390
alias(name, as) {
124-
if (typeof name !== "string") name = name.toString(); //Stringify name
125-
name = name.toLowerCase(); //Remove case sensitivity.
91+
name = Registry.#processName(name);
12692
//Throw an error if the item doesn't exist.
12793
if (!this.has(name))
12894
throw new ReferenceError(
@@ -136,13 +102,84 @@ class Registry {
136102
this.add(as, current);
137103
}
138104
/**
139-
* Executes a function for each element in the registry.
140-
* @param {(item, name: string) => void} func Callback for each element.
105+
* Performs a function on each item in registry.
106+
* @param {(name: string, item) => void} callback Function to perform on each item.
141107
*/
142-
forEach(func) {
143-
this.#content.forEach((element, name) => {
144-
void func(element, name);
145-
});
108+
forEach(callback) {
109+
this.#content.forEach((value, key) => void callback(key, value));
110+
return true;
111+
}
112+
/**
113+
* Performs a function on each item in registry asynchronously.
114+
* @param {(name: string, item) => void} callback Function to perform on each item.
115+
*/
116+
async forEachAsync(callback) {
117+
this.#content.forEach(
118+
async (value, key) => await void callback(key, value)
119+
);
120+
}
121+
/**
122+
*
123+
* @param {int} index Zero-based index of the item to get.
124+
* @returns The registry item at the index.
125+
*/
126+
at(index) {
127+
if (index >= this.#content.size)
128+
throw new RangeError(
129+
"Index " + index + " out of bounds for registry length " + this.size
130+
);
131+
return [...this.#content.keys()][index];
132+
}
133+
static #processName(name) {
134+
if (!name) throw new TypeError("Registry name must be defined");
135+
if (hasNonAscii(name))
136+
throw new TypeError("Registry names may only contain ASCII characters");
137+
return name.toString().toLowerCase();
138+
}
139+
static isValidName(name) {
140+
try {
141+
this.#processName(name);
142+
return true;
143+
} catch (error) {
144+
return false;
145+
}
146+
}
147+
/**
148+
* Constructs an item from this registry, using a type from another registry.
149+
* @param {string} name Name of item to construct.
150+
* @param {Registry} registry Registry for the type of the item.
151+
* @param {Function} [defaultType=Object] Constructor function or class to use if there's no defined type.
152+
*/
153+
create(name, registry, defaultType = Object) {
154+
return Registry.#construct(this.get(name), registry, defaultType);
155+
}
156+
/**
157+
* Constructs an item using a type from this registry. Note that this only works with objects.
158+
* @param {object} object Object to construct.
159+
* @param {Function} [defaultType=Object] Constructor function or class to use if there's no defined type.
160+
*/
161+
construct(object, defaultType = Object) {
162+
return Registry.#construct(object, this, defaultType);
163+
}
164+
static #construct(object, registry, defaultType = Object) {
165+
if (!object) return; //Catch accidental calls using null, undefined or similar
166+
//Constructs an instance using type from registry, if it exists. If not, throw error.
167+
//If type is undefined, use the default.
168+
let instantiated = new (
169+
object.type ? registry.get(object.type) : defaultType
170+
)();
171+
let cloned = {};
172+
//Clone the object if possible, to copy stuff like bullet drawers, or weapon.shoot.pattern. If it fails, just use the original.
173+
try {
174+
cloned = structuredClone(object);
175+
} catch (error) {
176+
cloned = object;
177+
console.warn("Could not clone object:", error);
178+
}
179+
checkForClashingFunctions(cloned, instantiated);
180+
instantiated = Object.assign(instantiated, cloned);
181+
instantiated.init ? instantiated.init() : {}; //Initialise if possible.
182+
return instantiated;
146183
}
147184
*[Symbol.iterator]() {
148185
for (let item of this.#content.values()) {
@@ -163,14 +200,18 @@ class Registry {
163200
}
164201
}
165202

166-
function checkForClashingFunctions(source, target){
167-
for(let prop in source){
168-
if(prop in target){
169-
if(typeof target[prop] === "function"){
170-
throw new SyntaxError("Property '"+prop+"' clashes with a class method!")
203+
let hasNonAscii = (str) => [...str].some((char) => char.charCodeAt(0) > 127);
204+
205+
function checkForClashingFunctions(source, target) {
206+
for (let prop in source) {
207+
if (prop in target) {
208+
if (typeof target[prop] === "function") {
209+
throw new SyntaxError(
210+
"Property '" + prop + "' clashes with a class method!"
211+
);
171212
}
172213
}
173214
}
174215
}
175216

176-
export { Registry };
217+
export { Registry };

0 commit comments

Comments
 (0)