168 lines
4.9 KiB
Markdown
168 lines
4.9 KiB
Markdown
# Script System (JerryScript)
|
|
|
|
Source: `src/dusk/script/`, modules at `src/dusk/script/module/`
|
|
|
|
## Overview
|
|
|
|
The engine embeds **JerryScript** as its scripting runtime. Game scenes
|
|
and logic are authored in JavaScript (ES5 subset). The script system
|
|
initialises JerryScript, registers all built-in C modules as JS globals,
|
|
and runs the event loop each tick.
|
|
|
|
The full rules for writing JS asset scripts are in `CLAUDE.md` under
|
|
"JavaScript (asset scripts)". This doc covers the C-side module system.
|
|
|
|
## Script lifecycle
|
|
|
|
```c
|
|
errorret_t scriptInit(); // start JerryScript, register all modules
|
|
errorret_t scriptUpdate(); // run pending microjobs (call once per frame)
|
|
errorret_t scriptDispose(); // shut down JerryScript
|
|
|
|
errorret_t scriptExecString(const char_t *source);
|
|
// Evaluate a JS source string in global scope.
|
|
|
|
errorret_t scriptExecFile(const char_t *path);
|
|
// Load + eval a script from the asset archive. Result cached by asset
|
|
// system -- repeated calls with the same path do not re-execute.
|
|
```
|
|
|
|
## Module registration
|
|
|
|
All C modules are initialised in `src/dusk/script/module/modulelist.c`:
|
|
|
|
```c
|
|
void moduleListInit(void); // called by scriptInit
|
|
void moduleListDispose(void); // called by scriptDispose
|
|
```
|
|
|
|
Each module's `Init` is called once. The module registers its
|
|
properties and methods on `scriptproto_t` objects (see below), which
|
|
become JS globals.
|
|
|
|
## Writing a C module -- the `scriptproto_t` pattern
|
|
|
|
A `scriptproto_t` represents a JS class prototype backed by a C struct.
|
|
|
|
### 1. Declare in the header
|
|
|
|
```c
|
|
// moduleMything.h
|
|
extern scriptproto_t MODULE_MYTHING_PROTO;
|
|
|
|
// Init and dispose for the module itself:
|
|
void moduleMyThingInit(void);
|
|
void moduleMyThingDispose(void);
|
|
```
|
|
|
|
### 2. Implement
|
|
|
|
```c
|
|
// moduleMything.c
|
|
scriptproto_t MODULE_MYTHING_PROTO;
|
|
|
|
// JS-callable function using the convenience macro:
|
|
moduleBaseFunction(myThingDoSomething) {
|
|
moduleBaseRequireArgs(1);
|
|
moduleBaseRequireNumber(0);
|
|
float_t x = moduleBaseArgFloat(0);
|
|
// ... do work ...
|
|
return jerry_undefined();
|
|
}
|
|
|
|
void moduleMyThingInit(void) {
|
|
scriptProtoInit(
|
|
&MODULE_MYTHING_PROTO,
|
|
"MyThing", // JS global name; NULL to skip registration
|
|
sizeof(mything_t),
|
|
myThingCtor // constructor handler, or NULL
|
|
);
|
|
|
|
// Instance methods:
|
|
scriptProtoDefineFunc(
|
|
&MODULE_MYTHING_PROTO, "doSomething", myThingDoSomething
|
|
);
|
|
|
|
// Instance property (get/set):
|
|
scriptProtoDefineProp(
|
|
&MODULE_MYTHING_PROTO, "x", myThingGetX, myThingSetX
|
|
);
|
|
|
|
// Static method:
|
|
scriptProtoDefineStaticFunc(
|
|
&MODULE_MYTHING_PROTO, "create", myThingCreate
|
|
);
|
|
}
|
|
```
|
|
|
|
### 3. Register
|
|
|
|
In `modulelist.c`: `#include` the header and call `moduleMyThingInit()`
|
|
in `moduleListInit()` (and `Dispose` in `moduleListDispose()`).
|
|
|
|
## `moduleBaseFunction` macro
|
|
|
|
```c
|
|
moduleBaseFunction(myFn) {
|
|
// callInfo, args[], argc available
|
|
moduleBaseRequireArgs(2);
|
|
moduleBaseRequireNumber(0);
|
|
moduleBaseRequireString(1);
|
|
|
|
float_t x = moduleBaseArgFloat(0);
|
|
int32_t n = moduleBaseArgInt(0);
|
|
bool_t b = moduleBaseArgBool(0);
|
|
float_t opt = moduleBaseOptFloat(2, 0.0f); // optional with default
|
|
|
|
// Error propagation:
|
|
errorret_t ret = someCall();
|
|
if(errorIsNotOk(ret)) return moduleBaseThrowError(ret);
|
|
|
|
return jerry_undefined(); // or jerry_boolean(true) etc.
|
|
}
|
|
```
|
|
|
|
## Wrapping C values in JS objects
|
|
|
|
```c
|
|
// Create a JS object wrapping a copy of a C value:
|
|
jerry_value_t obj = scriptProtoCreateValue(&MY_PROTO, &myValue);
|
|
|
|
// Unwrap back to C pointer:
|
|
mything_t *ptr = scriptProtoGetValue(&MY_PROTO, jsObj);
|
|
// ptr is NULL if jsObj is not an instance of MY_PROTO.
|
|
```
|
|
|
|
## Utility helpers (`modulebase.h`)
|
|
|
|
| Helper | Purpose |
|
|
|--------|---------|
|
|
| `moduleBaseThrow(msg)` | Return a JS TypeError |
|
|
| `moduleBaseThrowError(ret)` | Convert `errorret_t` -> JS error |
|
|
| `moduleBaseToString(val, buf, len)` | Jerry value -> C string |
|
|
| `moduleBaseGetProp(obj, name)` | Get object property by name |
|
|
| `moduleBaseWrapPointer(ptr)` | Wrap a raw pointer in a JS object |
|
|
| `moduleBaseUnwrapPointer(val)` | Unwrap a raw pointer |
|
|
| `moduleBaseSetValue(name, val)` | Set a global JS variable |
|
|
| `moduleBaseSetNumber(name, n)` | Set a global JS number |
|
|
| `moduleBaseSetInt(name, n)` | Set a global JS integer |
|
|
| `moduleBaseDefineMethod(obj, name, fn)` | Add method to any JS object |
|
|
| `moduleBaseDefineGlobalMethod(name, fn)` | Add method to global scope |
|
|
|
|
## Async JS -- pending promises (`scriptpromisepend.h`)
|
|
|
|
When a C module needs to resolve a JS `Promise` from an asynchronous
|
|
C event, use `scriptpromisepend_t`. Each module declares a fixed-size
|
|
pending slot array; the helpers add/resolve/reject by an opaque key.
|
|
|
|
Full API and design notes: `.claude/script-promises.md`
|
|
|
|
## Type declarations (`.d.ts`)
|
|
|
|
Every module that is accessible from JS **must** have a corresponding
|
|
TypeScript declaration file in `types/`. The CLAUDE.md checklist
|
|
requires updating these whenever a `.c` module file changes.
|
|
|
|
- Add `types/<category>/mymod.d.ts`
|
|
- Add `/// <reference path="..." />` to `types/index.d.ts`
|