Files
dusk/.claude/script.md
T
2026-06-16 10:15:59 -05:00

4.9 KiB

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

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:

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

// moduleMything.h
extern scriptproto_t MODULE_MYTHING_PROTO;

// Init and dispose for the module itself:
void moduleMyThingInit(void);
void moduleMyThingDispose(void);

2. Implement

// 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

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

// 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