Add asset reaping

This commit is contained in:
2026-06-01 22:20:57 -05:00
parent 8f78bba9e9
commit 2b78370cb8
20 changed files with 813 additions and 24 deletions
+55
View File
@@ -96,6 +96,34 @@ errorret_t assetRequireLoaded(assetentry_t *entry) {
errorOk();
}
assetentry_t * assetLock(
const char_t *name,
const assetloadertype_t type,
assetloaderinput_t *input
) {
assetentry_t *entry = assetGetEntry(name, type, input);
assetEntryLock(entry);
return entry;
}
void assetUnlock(const char_t *name) {
assertNotNull(name, "Name cannot be NULL.");
assetentry_t *entry = ASSET.entries;
do {
if(entry->type != ASSET_LOADER_TYPE_NULL && stringEquals(entry->name, name)) {
assetEntryUnlock(entry);
return;
}
entry++;
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
assertUnreachable("Asset entry not found for unlock.");
}
void assetUnlockEntry(assetentry_t *entry) {
assertNotNull(entry, "Entry cannot be NULL.");
assetEntryUnlock(entry);
}
errorret_t assetUpdate(void) {
// Determine how many available loading slots we have.
assetloading_t *availableLoading[ASSET_LOADING_COUNT_MAX];
@@ -216,6 +244,33 @@ errorret_t assetUpdate(void) {
continue;
}
} while(loading < ASSET.loading + ASSET_LOADING_COUNT_MAX);
// Reap entries that have no external locks (refs.count == 1 means only the
// system hold remains). Only safe to reap LOADED and NOT_STARTED states —
// mid-load entries are left for the next cycle.
entry = ASSET.entries;
do {
if(entry->state != ASSET_ENTRY_STATE_LOADED) {
entry++;
continue;
}
if(entry->type == ASSET_LOADER_TYPE_NULL) {
entry++;
continue;
}
if(entry->refs.count > 0) {
entry++;
continue;
}
consolePrint("Reaping asset %s", entry->name);
errorChain(assetEntryDispose(entry));
entry++;
} while(entry < ASSET.entries + ASSET_ENTRY_COUNT_MAX);
errorOk();
}
+35 -3
View File
@@ -56,11 +56,11 @@ errorret_t assetInit(void);
bool_t assetFileExists(const char_t *filename);
/**
* Gets, or creates, a new asset entry. You will need to lock the asset soon
* after creating or else it will be freed up on the next update cycle.
*
* Gets, or creates, a new asset entry. Internal — prefer assetLock.
*
* @param name Filename of the asset.
* @param type Type of the asset.
* @param input Loader-specific parameters.
*/
assetentry_t * assetGetEntry(
const char_t *name,
@@ -68,6 +68,38 @@ assetentry_t * assetGetEntry(
assetloaderinput_t *input
);
/**
* Gets, creates, and locks an asset entry. The asset will begin loading on
* the next assetUpdate. Call assetUnlock when done to allow the entry to be
* reclaimed.
*
* @param name Filename of the asset.
* @param type Type of the asset.
* @param input Loader-specific parameters.
* @return The locked asset entry.
*/
assetentry_t * assetLock(
const char_t *name,
const assetloadertype_t type,
assetloaderinput_t *input
);
/**
* Releases a lock on an asset entry by name. When all locks are released the
* entry will be reclaimed at the start of the next assetUpdate.
*
* @param name Filename of the asset to unlock.
*/
void assetUnlock(const char_t *name);
/**
* Releases a lock on an asset entry by pointer. When all locks are released
* the entry will be reclaimed at the start of the next assetUpdate.
*
* @param entry The asset entry to unlock.
*/
void assetUnlockEntry(assetentry_t *entry);
/**
* Requires an asset entry to be loaded. This will block until the asset entry
* is fully loaded.
+5 -15
View File
@@ -20,35 +20,25 @@ errorret_t textInit(void) {
assetloaderinput_t input = {
.texture = TEXTURE_FORMAT_RGBA
};
assetentry_t *entryTexture = assetGetEntry(
assetentry_t *entryTexture = assetLock(
"ui/minogram.png", ASSET_LOADER_TYPE_TEXTURE, &input
);
assetentry_t *entryTileset = assetGetEntry(
assetentry_t *entryTileset = assetLock(
"ui/minogram.dtf", ASSET_LOADER_TYPE_TILESET, NULL
// "ui/minogram.dtx", ASSET_LOADER_TYPE_TILESET, NULL
);
errorChain(assetRequireLoaded(entryTexture));
errorChain(assetRequireLoaded(entryTileset));
FONT_DEFAULT.texture = &entryTexture->data.texture;
FONT_DEFAULT.tileset = &entryTileset->data.tileset;
// assetentry_t *entryTileset = assetGetEntry(
// "ui/minogram.dtf", ASSET_LOADER_TYPE_TILESET
// );
// assetbatch_t batch;
// assetBatchInit(&batch, NULL, NULL, NULL);
// assetBatchTexture(&batch, "ui/minogram.png", TEXTURE_FORMAT_RGBA, &FONT_DEFAULT.texture);
// assetBatchTileset(&batch, "ui/minogram.dtf", &FONT_DEFAULT.tileset);
// errorChain(assetBatchLoad(&batch));
errorOk();
}
errorret_t textDispose(void) {
// assetCacheRelease(&ASSET.cache, "ui/minogram.png");
// assetCacheRelease(&ASSET.cache, "ui/minogram.dtf");
FONT_DEFAULT.texture = NULL;
FONT_DEFAULT.tileset = NULL;
assetUnlock("ui/minogram.png");
assetUnlock("ui/minogram.dtf");
errorOk();
}
+11 -1
View File
@@ -20,14 +20,24 @@ errorret_t localeManagerInit() {
errorret_t localeManagerSetLocale(const localeinfo_t *locale) {
assertNotNull(locale, "Locale cannot be NULL");
if(LOCALE.entry != NULL) {
assetEntryUnlock(LOCALE.entry);
LOCALE.entry = NULL;
}
LOCALE.locale = locale;
LOCALE.entry = assetGetEntry(locale->file, ASSET_LOADER_TYPE_LOCALE, NULL);
assetEntryLock(LOCALE.entry);
errorChain(assetRequireLoaded(LOCALE.entry));
errorOk();
}
void localeManagerDispose() {
LOCALE.entry = NULL;
if(LOCALE.entry != NULL) {
assetEntryUnlock(LOCALE.entry);
LOCALE.entry = NULL;
}
LOCALE.locale = NULL;
}
@@ -0,0 +1,46 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "display/screen/screen.h"
static scriptproto_t MODULE_SCREEN_PROTO;
moduleBaseFunction(moduleScreenGetWidth) {
return jerry_number((double)SCREEN.width);
}
moduleBaseFunction(moduleScreenGetHeight) {
return jerry_number((double)SCREEN.height);
}
moduleBaseFunction(moduleScreenGetAspect) {
return jerry_number((double)SCREEN.aspect);
}
static void moduleScreenInit(void) {
scriptProtoInit(&MODULE_SCREEN_PROTO, "Screen", sizeof(uint8_t), NULL);
scriptProtoDefineStaticProp(
&MODULE_SCREEN_PROTO, "width",
moduleScreenGetWidth, NULL
);
scriptProtoDefineStaticProp(
&MODULE_SCREEN_PROTO, "height",
moduleScreenGetHeight, NULL
);
scriptProtoDefineStaticProp(
&MODULE_SCREEN_PROTO, "aspect",
moduleScreenGetAspect, NULL
);
}
static void moduleScreenDispose(void) {
scriptProtoDispose(&MODULE_SCREEN_PROTO);
}
@@ -0,0 +1,16 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
/* Include component modules here as they are added. */
static void moduleComponentListInit(void) {
}
static void moduleComponentListDispose(void) {
}
@@ -0,0 +1,93 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "entity/component.h"
/** C struct wrapped by every Component JS instance. */
typedef struct {
entityid_t entityId;
componentid_t componentId;
} jscomponent_t;
static scriptproto_t MODULE_COMPONENT_PROTO;
moduleBaseFunction(moduleComponentCtor) {
(void)callInfo; (void)args; (void)argc;
return moduleBaseThrow("Component cannot be instantiated with new");
}
moduleBaseFunction(moduleComponentGetEntity) {
jscomponent_t *comp = scriptProtoGetValue(
&MODULE_COMPONENT_PROTO, callInfo->this_value
);
if(!comp) return jerry_undefined();
return jerry_number((double)comp->entityId);
}
moduleBaseFunction(moduleComponentGetId) {
jscomponent_t *comp = scriptProtoGetValue(
&MODULE_COMPONENT_PROTO, callInfo->this_value
);
if(!comp) return jerry_undefined();
return jerry_number((double)comp->componentId);
}
moduleBaseFunction(moduleComponentToString) {
jscomponent_t *comp = scriptProtoGetValue(
&MODULE_COMPONENT_PROTO, callInfo->this_value
);
if(!comp) return jerry_string_sz("Component:invalid");
jerry_value_t num = jerry_number((double)comp->componentId);
jerry_value_t str = jerry_value_to_string(num);
jerry_value_free(num);
return str;
}
static void moduleComponentInit(void) {
scriptProtoInit(
&MODULE_COMPONENT_PROTO, "Component",
sizeof(jscomponent_t), moduleComponentCtor
);
/* Instance properties */
scriptProtoDefineProp(
&MODULE_COMPONENT_PROTO, "entity",
moduleComponentGetEntity, NULL
);
scriptProtoDefineProp(
&MODULE_COMPONENT_PROTO, "id",
moduleComponentGetId, NULL
);
scriptProtoDefineToString(&MODULE_COMPONENT_PROTO, moduleComponentToString);
/* Component.POSITION, Component.CAMERA, etc. from componentlist.h */
jerry_value_t ctor = MODULE_COMPONENT_PROTO.constructor;
#define X(enumName, type, field, init, dispose, render) \
do { \
jerry_value_t _key = jerry_string_sz(#enumName); \
jerry_value_t _val = jerry_number((double)COMPONENT_TYPE_##enumName); \
jerry_object_set(ctor, _key, _val); \
jerry_value_free(_val); \
jerry_value_free(_key); \
} while(0);
#include "entity/componentlist.h"
#undef X
/* Component.INVALID */
jerry_value_t _key = jerry_string_sz("INVALID");
jerry_value_t _val = jerry_number((double)COMPONENT_ID_INVALID);
jerry_object_set(ctor, _key, _val);
jerry_value_free(_val);
jerry_value_free(_key);
}
static void moduleComponentDispose(void) {
scriptProtoDispose(&MODULE_COMPONENT_PROTO);
}
@@ -0,0 +1,105 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "script/module/entity/modulecomponent.h"
#include "entity/entitymanager.h"
/** C struct wrapped by every Entity JS instance. */
typedef struct {
entityid_t id;
} jsentity_t;
static scriptproto_t MODULE_ENTITY_PROTO;
moduleBaseFunction(moduleEntityCtor) {
(void)callInfo; (void)args; (void)argc;
return moduleBaseThrow("Entity cannot be instantiated with new");
}
moduleBaseFunction(moduleEntityCreate) {
entityid_t id = entityManagerAdd();
if(id == ENTITY_ID_INVALID) {
return moduleBaseThrow("Entity.create: no entity slots available");
}
jsentity_t ent = { .id = id };
return scriptProtoCreateValue(&MODULE_ENTITY_PROTO, &ent);
}
moduleBaseFunction(moduleEntityDisposeEntity) {
moduleBaseRequireArgs(1);
jsentity_t *ent = scriptProtoGetValue(&MODULE_ENTITY_PROTO, args[0]);
if(!ent) return moduleBaseThrow("Entity.dispose: expected Entity object");
entityDispose(ent->id);
return jerry_undefined();
}
moduleBaseFunction(moduleEntityAdd) {
jsentity_t *ent = scriptProtoGetValue(
&MODULE_ENTITY_PROTO, callInfo->this_value
);
if(!ent) return moduleBaseThrow("Entity.add: invalid this");
moduleBaseRequireArgs(1);
moduleBaseRequireNumber(0);
const componenttype_t type = (componenttype_t)moduleBaseArgInt(0);
if(type <= COMPONENT_TYPE_NULL || type >= COMPONENT_TYPE_COUNT) {
return moduleBaseThrow("Entity.add: invalid component type");
}
componentid_t cid = entityAddComponent(ent->id, type);
if(cid == COMPONENT_ID_INVALID) {
return moduleBaseThrow("Entity.add: failed to add component");
}
jscomponent_t comp = { .entityId = ent->id, .componentId = cid };
return scriptProtoCreateValue(&MODULE_COMPONENT_PROTO, &comp);
}
moduleBaseFunction(moduleEntityToString) {
jsentity_t *ent = scriptProtoGetValue(
&MODULE_ENTITY_PROTO, callInfo->this_value
);
if(!ent) return jerry_string_sz("Entity:invalid");
jerry_value_t num = jerry_number((double)ent->id);
jerry_value_t str = jerry_value_to_string(num);
jerry_value_free(num);
return str;
}
static void moduleEntityInit(void) {
scriptProtoInit(
&MODULE_ENTITY_PROTO, "Entity",
sizeof(jsentity_t), moduleEntityCtor
);
/* Static methods */
scriptProtoDefineStaticFunc(
&MODULE_ENTITY_PROTO, "create", moduleEntityCreate
);
scriptProtoDefineStaticFunc(
&MODULE_ENTITY_PROTO, "dispose", moduleEntityDisposeEntity
);
/* Entity.INVALID */
jerry_value_t ctor = MODULE_ENTITY_PROTO.constructor;
jerry_value_t _key = jerry_string_sz("INVALID");
jerry_value_t _val = jerry_number((double)ENTITY_ID_INVALID);
jerry_object_set(ctor, _key, _val);
jerry_value_free(_val);
jerry_value_free(_key);
/* Instance methods */
scriptProtoDefineFunc(&MODULE_ENTITY_PROTO, "add", moduleEntityAdd);
scriptProtoDefineToString(&MODULE_ENTITY_PROTO, moduleEntityToString);
}
static void moduleEntityDispose(void) {
scriptProtoDispose(&MODULE_ENTITY_PROTO);
}
+6 -1
View File
@@ -13,7 +13,12 @@
static scriptproto_t MODULE_INPUT_PROTO;
/** Validates an inputaction_t argument and returns a type error if bad. */
/**
* Validates an inputaction_t argument and returns a type error if bad.
*
* @param i Argument index.
* @param ctx Context string for error messages.
*/
#define moduleInputRequireAction(i, ctx) do { \
if(!jerry_value_is_number(args[(i)])) { \
return moduleBaseThrow(ctx ": action must be a number"); \
+127
View File
@@ -0,0 +1,127 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "util/memory.h"
#include "cglm/cglm.h"
static scriptproto_t MODULE_VEC3_PROTO;
/**
* Returns the native float[3] pointer from the Vec3 instance that owns
* the current call (this_value). Returns NULL if not a Vec3.
*/
static inline float_t *moduleVec3Get(const jerry_call_info_t *callInfo) {
return (float_t *)scriptProtoGetValue(
&MODULE_VEC3_PROTO, callInfo->this_value
);
}
/**
* Returns the native float[3] pointer from any jerry value.
* Returns NULL if the value is not a Vec3 instance.
*/
static inline float_t *moduleVec3From(const jerry_value_t val) {
return (float_t *)scriptProtoGetValue(&MODULE_VEC3_PROTO, val);
}
/**
* Creates a Vec3 JS object from a C vec3 array.
*
* @param v Source vec3 to copy.
* @return A new Vec3 JS instance owning a copy of the data.
*/
static inline jerry_value_t moduleVec3Push(const vec3 v) {
return scriptProtoCreateValue(&MODULE_VEC3_PROTO, v);
}
moduleBaseFunction(moduleVec3Constructor) {
float_t *ptr = (float_t *)memoryAllocate(sizeof(vec3));
ptr[0] = moduleBaseOptFloat(0, 0.0f);
ptr[1] = moduleBaseOptFloat(1, 0.0f);
ptr[2] = moduleBaseOptFloat(2, 0.0f);
jerry_object_set_native_ptr(
callInfo->this_value, &MODULE_VEC3_PROTO.info, ptr
);
return jerry_undefined();
}
moduleBaseFunction(moduleVec3GetX) {
float_t *v = moduleVec3Get(callInfo);
if(!v) return jerry_undefined();
return jerry_number((double)v[0]);
}
moduleBaseFunction(moduleVec3SetX) {
moduleBaseRequireArgs(1);
float_t *v = moduleVec3Get(callInfo);
if(!v) return jerry_undefined();
v[0] = moduleBaseArgFloat(0);
return jerry_undefined();
}
moduleBaseFunction(moduleVec3GetY) {
float_t *v = moduleVec3Get(callInfo);
if(!v) return jerry_undefined();
return jerry_number((double)v[1]);
}
moduleBaseFunction(moduleVec3SetY) {
moduleBaseRequireArgs(1);
float_t *v = moduleVec3Get(callInfo);
if(!v) return jerry_undefined();
v[1] = moduleBaseArgFloat(0);
return jerry_undefined();
}
moduleBaseFunction(moduleVec3GetZ) {
float_t *v = moduleVec3Get(callInfo);
if(!v) return jerry_undefined();
return jerry_number((double)v[2]);
}
moduleBaseFunction(moduleVec3SetZ) {
moduleBaseRequireArgs(1);
float_t *v = moduleVec3Get(callInfo);
if(!v) return jerry_undefined();
v[2] = moduleBaseArgFloat(0);
return jerry_undefined();
}
moduleBaseFunction(moduleVec3ToString) {
float_t *v = moduleVec3Get(callInfo);
if(!v) return jerry_string_sz("Vec3:invalid");
char_t buf[64];
snprintf(buf, sizeof(buf), "Vec3(%g, %g, %g)",
(double)v[0], (double)v[1], (double)v[2]
);
return jerry_string_sz(buf);
}
static void moduleVec3Init(void) {
scriptProtoInit(
&MODULE_VEC3_PROTO, "Vec3",
sizeof(vec3), moduleVec3Constructor
);
scriptProtoDefineProp(
&MODULE_VEC3_PROTO, "x", moduleVec3GetX, moduleVec3SetX
);
scriptProtoDefineProp(
&MODULE_VEC3_PROTO, "y", moduleVec3GetY, moduleVec3SetY
);
scriptProtoDefineProp(
&MODULE_VEC3_PROTO, "z", moduleVec3GetZ, moduleVec3SetZ
);
scriptProtoDefineToString(&MODULE_VEC3_PROTO, moduleVec3ToString);
}
static void moduleVec3Dispose(void) {
scriptProtoDispose(&MODULE_VEC3_PROTO);
}
+18
View File
@@ -7,20 +7,38 @@
#pragma once
#include "script/module/console/moduleconsole.h"
#include "script/module/display/modulescreen.h"
#include "script/module/engine/moduleengine.h"
#include "script/module/entity/component/modulecomponentlist.h"
#include "script/module/entity/modulecomponent.h"
#include "script/module/entity/moduleentity.h"
#include "script/module/input/moduleinput.h"
#include "script/module/math/modulevec3.h"
#include "script/module/scene/modulescene.h"
#include "script/module/system/modulesystem.h"
static void moduleListInit(void) {
moduleConsoleInit();
moduleScreenInit();
moduleEngineInit();
moduleVec3Init();
moduleComponentInit();
moduleEntityInit();
moduleComponentListInit();
moduleInputInit();
moduleSceneInit();
moduleSystemInit();
}
static void moduleListDispose(void) {
moduleSystemDispose();
moduleSceneDispose();
moduleInputDispose();
moduleComponentListDispose();
moduleEntityDispose();
moduleComponentDispose();
moduleVec3Dispose();
moduleEngineDispose();
moduleScreenDispose();
moduleConsoleDispose();
}
@@ -0,0 +1,21 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/modulebase.h"
#include "script/scriptproto.h"
#include "scene/scene.h"
static scriptproto_t MODULE_SCENE_PROTO;
static void moduleSceneInit(void) {
scriptProtoInit(&MODULE_SCENE_PROTO, "Scene", sizeof(uint8_t), NULL);
}
static void moduleSceneDispose(void) {
scriptProtoDispose(&MODULE_SCENE_PROTO);
}
+3 -1
View File
@@ -59,9 +59,11 @@ errorret_t scriptExecFile(const char_t *path) {
assertNotNull(path, "Path cannot be NULL");
assertTrue(SCRIPT.initialized, "Script system not initialized");
assetentry_t *entry = assetGetEntry(path, ASSET_LOADER_TYPE_SCRIPT, NULL);
assetentry_t *entry = assetLock(path, ASSET_LOADER_TYPE_SCRIPT, NULL);
assertNotNull(entry, "Failed to get asset entry for script");
errorChain(assetRequireLoaded(entry));
assetUnlockEntry(entry);
errorOk();
}
+3 -3
View File
@@ -18,7 +18,7 @@ void refInit(
) {
assertNotNull(ref, "Ref cannot be NULL.");
memoryZero(ref, sizeof(ref_t));
ref->count = 1;
ref->count = 0;
ref->data = data;
ref->onLock = onLock;
ref->onUnlock = onUnlock;
@@ -27,14 +27,14 @@ void refInit(
void refLock(ref_t *ref) {
assertNotNull(ref, "Ref cannot be NULL.");
assertTrue(ref->count > 0, "Cannot lock a ref with zero count.");
assertTrue(ref->count >= 0, "Cannot lock a ref with negative count.");
ref->count++;
if(ref->onLock != NULL) ref->onLock(ref);
}
bool_t refUnlock(ref_t *ref) {
assertNotNull(ref, "Ref cannot be NULL.");
assertTrue(ref->count > 0, "Cannot unlock a ref with zero count.");
assertTrue(ref->count >= 0, "Cannot unlock a ref with negative count.");
ref->count--;
+34
View File
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
/**
* Interface for the in-game developer console.
*/
interface ConsoleNamespace {
/**
* Prints one or more values to the in-game console, separated by tabs.
* Each argument is coerced to a string before printing.
*
* @param args - Values to print.
*
* @example
* Console.print("x =", player.x, "y =", player.y);
*/
print(...args: unknown[]): void;
/**
* Whether the in-game console overlay is currently visible.
* Set to `true` to show the console, `false` to hide it.
*
* @example
* Console.visible = true;
*/
visible: boolean;
}
/** In-game developer console. */
declare var Console: ConsoleNamespace;
+33
View File
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
/**
* Controls over the engine main loop.
*/
interface EngineNamespace {
/**
* Whether the engine main loop is still running (read-only).
* Becomes `false` after `Engine.exit()` is called.
*
* @example
* while (Engine.running) { ... }
*/
readonly running: boolean;
/**
* Requests an orderly shutdown of the engine.
* Sets the internal running flag to `false`; the main loop exits at the end
* of the current tick.
*
* @example
* Engine.exit();
*/
exit(): void;
}
/** Engine lifecycle controls. */
declare var Engine: EngineNamespace;
+21
View File
@@ -0,0 +1,21 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*
* Root type declarations for the Dusk engine built-in JavaScript modules.
* These globals are injected by the native JerryScript runtime — they are not
* ES modules and cannot be imported.
*
* Reference this file from a jsconfig.json or tsconfig.json to get
* IntelliSense across all game scripts:
*
* { "compilerOptions": { "typeRoots": ["./types"] } }
*/
/// <reference path="./console.d.ts" />
/// <reference path="./screen.d.ts" />
/// <reference path="./engine.d.ts" />
/// <reference path="./input.d.ts" />
/// <reference path="./system.d.ts" />
+115
View File
@@ -0,0 +1,115 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
/**
* Opaque type alias for input action identifiers.
* Use the `INPUT_ACTION_*` constants rather than raw numbers.
*/
type InputAction = number;
/**
* Polling-based input queries and button rebinding.
*/
interface InputNamespace {
/**
* Returns `true` while the given action is held down this frame.
*
* @param action - An `INPUT_ACTION_*` constant.
*
* @example
* if (Input.isDown(INPUT_ACTION_UP)) { player.moveUp(); }
*/
isDown(action: InputAction): boolean;
/**
* Returns `true` if the given action was held down in the previous frame.
*
* @param action - An `INPUT_ACTION_*` constant.
*/
wasDown(action: InputAction): boolean;
/**
* Returns `true` on the first frame the action transitions from up → down.
*
* @param action - An `INPUT_ACTION_*` constant.
*
* @example
* if (Input.pressed(INPUT_ACTION_ACCEPT)) { confirmSelection(); }
*/
pressed(action: InputAction): boolean;
/**
* Returns `true` on the first frame the action transitions from down → up.
*
* @param action - An `INPUT_ACTION_*` constant.
*/
released(action: InputAction): boolean;
/**
* Returns the continuous (analog) value of an action in the range `0.01.0`.
* Digital buttons return either `0` or `1`.
*
* @param action - An `INPUT_ACTION_*` constant.
*
* @example
* const speed = Input.getValue(INPUT_ACTION_UP) * MAX_SPEED;
*/
getValue(action: InputAction): number;
/**
* Returns a signed axis value in the range `-1.01.0`, derived from two
* opposing actions: `getValue(pos) - getValue(neg)`.
*
* @param neg - Action mapped to the negative direction.
* @param pos - Action mapped to the positive direction.
*
* @example
* const moveX = Input.axis(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT);
*/
axis(neg: InputAction, pos: InputAction): number;
/**
* Rebinds a physical button (by name) to a logical input action at runtime.
* Throws if `buttonName` is unknown or `action` is out of range.
*
* @param buttonName - Platform-specific button identifier string (e.g. `"A"`, `"START"`).
* @param action - Target `INPUT_ACTION_*` constant.
*
* @example
* Input.bind("A", INPUT_ACTION_ACCEPT);
*/
bind(buttonName: string, action: InputAction): void;
}
/** Polling-based input system. */
declare var Input: InputNamespace;
// ---------------------------------------------------------------------------
// Input action constants.
// Injected as plain global variables by the engine at startup.
// ---------------------------------------------------------------------------
/** Move / navigate upward. */
declare var INPUT_ACTION_UP: InputAction;
/** Move / navigate downward. */
declare var INPUT_ACTION_DOWN: InputAction;
/** Move / navigate left. */
declare var INPUT_ACTION_LEFT: InputAction;
/** Move / navigate right. */
declare var INPUT_ACTION_RIGHT: InputAction;
/** Confirm / accept the current selection. */
declare var INPUT_ACTION_ACCEPT: InputAction;
/** Cancel / go back. */
declare var INPUT_ACTION_CANCEL: InputAction;
/** Emergency quit (e.g. hold-to-exit on embedded platforms). */
declare var INPUT_ACTION_RAGEQUIT: InputAction;
/** Toggle the developer console overlay. */
declare var INPUT_ACTION_CONSOLE: InputAction;
/** Pointer / cursor horizontal position (analog). */
declare var INPUT_ACTION_POINTERX: InputAction;
/** Pointer / cursor vertical position (analog). */
declare var INPUT_ACTION_POINTERY: InputAction;
+28
View File
@@ -0,0 +1,28 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
/**
* Read-only information about the current display surface.
*/
interface ScreenNamespace {
/** Current render-target width in pixels (read-only). */
readonly width: number;
/** Current render-target height in pixels (read-only). */
readonly height: number;
/**
* Aspect ratio of the current render target: `width / height` (read-only).
*
* @example
* if (Screen.aspect > 1) { /* landscape *\/ }
*/
readonly aspect: number;
}
/** Current display / render-target dimensions. */
declare var Screen: ScreenNamespace;
+38
View File
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
/**
* Runtime platform detection and system-level information.
*/
interface SystemNamespace {
/**
* Numeric identifier for the platform the engine is running on (read-only).
* Compare against the `System.PLATFORM_*` constants.
*
* @example
* if (System.platform === System.PLATFORM_PSP) { useLowResAssets(); }
*/
readonly platform: number;
/** Linux desktop. */
readonly PLATFORM_LINUX: number;
/** Knulli handheld (Linux-based). */
readonly PLATFORM_KNULLI: number;
/** Sony PlayStation Portable. */
readonly PLATFORM_PSP: number;
/** Nintendo GameCube. */
readonly PLATFORM_GAMECUBE: number;
/** Nintendo Wii. */
readonly PLATFORM_WII: number;
}
/** Platform detection and system-level information. */
declare var System: SystemNamespace;