Add a thin declarative API for defining script methods/properties
scriptvalue.h/.c holds the scriptvalue_t tagged-union type and its JS<->C decode/encode helpers; scriptdef.h/.c holds the declarative scriptfuncdef_t/scriptpropdef_t definitions, the binding registry, and the JerryScript trampolines. Lets a module declare its JS-facing methods/properties/globals as typed data instead of hand-writing argument-checking boilerplate per method. Framework only for now -- no existing module has been migrated onto it yet.
This commit is contained in:
@@ -7,6 +7,8 @@ include(dusktest)
|
||||
|
||||
dusktest(test_modulerequire.c)
|
||||
|
||||
dusktest(test_scriptdef.c)
|
||||
|
||||
dusktest(test_overworldscene.c)
|
||||
target_compile_definitions(test_overworldscene PRIVATE
|
||||
DUSK_ASSETS_DIR="${DUSK_ASSETS_DIR}"
|
||||
|
||||
@@ -0,0 +1,447 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "dusktest.h"
|
||||
#include "script/scriptmanager.h"
|
||||
#include "script/scriptdef.h"
|
||||
#include "script/module/modulebase.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
|
||||
typedef struct {
|
||||
double numberValue;
|
||||
int32_t intValue;
|
||||
bool_t boolValue;
|
||||
char_t stringValue[SCRIPT_VALUE_STRING_MAX];
|
||||
vec3 vec3Value;
|
||||
void *pointerValue;
|
||||
jerry_value_t callbackValue;
|
||||
} testwidgethandle_t;
|
||||
|
||||
static scriptproto_t TEST_WIDGET_PROTO;
|
||||
static uint8_t TEST_SENTINEL;
|
||||
static double TEST_GLOBAL_VALUE;
|
||||
|
||||
moduleBaseFunction(testWidgetConstructor) {
|
||||
testwidgethandle_t *inst = (testwidgethandle_t *)memoryAllocate(
|
||||
sizeof(testwidgethandle_t)
|
||||
);
|
||||
memoryZero(inst, sizeof(testwidgethandle_t));
|
||||
inst->callbackValue = jerry_undefined();
|
||||
|
||||
jerry_object_set_native_ptr(
|
||||
callInfo->this_value, &TEST_WIDGET_PROTO.info, inst
|
||||
);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
static scriptvalue_t testWidgetSetNumber(
|
||||
void *handle, const scriptvalue_t *args, const uint32_t argc
|
||||
) {
|
||||
((testwidgethandle_t *)handle)->numberValue = args[0].as.number;
|
||||
return scriptValueVoid();
|
||||
}
|
||||
|
||||
static scriptvalue_t testWidgetGetNumberValue(void *handle) {
|
||||
return scriptValueNumber(((testwidgethandle_t *)handle)->numberValue);
|
||||
}
|
||||
|
||||
static scriptvalue_t testWidgetGetNumberMethod(
|
||||
void *handle, const scriptvalue_t *args, const uint32_t argc
|
||||
) {
|
||||
return testWidgetGetNumberValue(handle);
|
||||
}
|
||||
|
||||
static scriptvalue_t testWidgetSetInt(
|
||||
void *handle, const scriptvalue_t *args, const uint32_t argc
|
||||
) {
|
||||
((testwidgethandle_t *)handle)->intValue = args[0].as.intValue;
|
||||
return scriptValueVoid();
|
||||
}
|
||||
|
||||
static scriptvalue_t testWidgetSetBool(
|
||||
void *handle, const scriptvalue_t *args, const uint32_t argc
|
||||
) {
|
||||
((testwidgethandle_t *)handle)->boolValue = args[0].as.boolValue;
|
||||
return scriptValueVoid();
|
||||
}
|
||||
|
||||
static scriptvalue_t testWidgetGetBoolMethod(
|
||||
void *handle, const scriptvalue_t *args, const uint32_t argc
|
||||
) {
|
||||
return scriptValueBool(((testwidgethandle_t *)handle)->boolValue);
|
||||
}
|
||||
|
||||
static scriptvalue_t testWidgetSetString(
|
||||
void *handle, const scriptvalue_t *args, const uint32_t argc
|
||||
) {
|
||||
stringCopy(
|
||||
((testwidgethandle_t *)handle)->stringValue, args[0].as.string,
|
||||
SCRIPT_VALUE_STRING_MAX - 1
|
||||
);
|
||||
return scriptValueVoid();
|
||||
}
|
||||
|
||||
static scriptvalue_t testWidgetGetStringMethod(
|
||||
void *handle, const scriptvalue_t *args, const uint32_t argc
|
||||
) {
|
||||
return scriptValueString(((testwidgethandle_t *)handle)->stringValue);
|
||||
}
|
||||
|
||||
static scriptvalue_t testWidgetSetVec3(
|
||||
void *handle, const scriptvalue_t *args, const uint32_t argc
|
||||
) {
|
||||
testwidgethandle_t *inst = (testwidgethandle_t *)handle;
|
||||
inst->vec3Value[0] = args[0].as.vec3Value[0];
|
||||
inst->vec3Value[1] = args[0].as.vec3Value[1];
|
||||
inst->vec3Value[2] = args[0].as.vec3Value[2];
|
||||
return scriptValueVoid();
|
||||
}
|
||||
|
||||
static scriptvalue_t testWidgetGetVec3Value(void *handle) {
|
||||
return scriptValueVec3(((testwidgethandle_t *)handle)->vec3Value);
|
||||
}
|
||||
|
||||
static scriptvalue_t testWidgetGetVec3Method(
|
||||
void *handle, const scriptvalue_t *args, const uint32_t argc
|
||||
) {
|
||||
return testWidgetGetVec3Value(handle);
|
||||
}
|
||||
|
||||
static scriptvalue_t testWidgetSetPointer(
|
||||
void *handle, const scriptvalue_t *args, const uint32_t argc
|
||||
) {
|
||||
((testwidgethandle_t *)handle)->pointerValue = args[0].as.pointer;
|
||||
return scriptValueVoid();
|
||||
}
|
||||
|
||||
static scriptvalue_t testWidgetGetSentinelPointer(
|
||||
void *handle, const scriptvalue_t *args, const uint32_t argc
|
||||
) {
|
||||
return scriptValuePointer(&TEST_SENTINEL);
|
||||
}
|
||||
|
||||
static scriptvalue_t testWidgetPointerMatches(
|
||||
void *handle, const scriptvalue_t *args, const uint32_t argc
|
||||
) {
|
||||
return scriptValueBool(
|
||||
((testwidgethandle_t *)handle)->pointerValue == args[0].as.pointer
|
||||
);
|
||||
}
|
||||
|
||||
static scriptvalue_t testWidgetSetCallback(
|
||||
void *handle, const scriptvalue_t *args, const uint32_t argc
|
||||
) {
|
||||
testwidgethandle_t *inst = (testwidgethandle_t *)handle;
|
||||
if(jerry_value_is_function(inst->callbackValue)) {
|
||||
jerry_value_free(inst->callbackValue);
|
||||
}
|
||||
inst->callbackValue = args[0].as.value;
|
||||
return scriptValueVoid();
|
||||
}
|
||||
|
||||
static scriptvalue_t testWidgetInvokeCallback(
|
||||
void *handle, const scriptvalue_t *args, const uint32_t argc
|
||||
) {
|
||||
testwidgethandle_t *inst = (testwidgethandle_t *)handle;
|
||||
if(jerry_value_is_function(inst->callbackValue)) {
|
||||
errorret_t ret = scriptManagerCallValue(
|
||||
jerry_undefined(), inst->callbackValue, "TestWidget", "callback"
|
||||
);
|
||||
errorCatch(ret);
|
||||
}
|
||||
return scriptValueVoid();
|
||||
}
|
||||
|
||||
static scriptvalue_t testWidgetDispose(
|
||||
void *handle, const scriptvalue_t *args, const uint32_t argc
|
||||
) {
|
||||
testwidgethandle_t *inst = (testwidgethandle_t *)handle;
|
||||
if(jerry_value_is_function(inst->callbackValue)) {
|
||||
jerry_value_free(inst->callbackValue);
|
||||
}
|
||||
inst->callbackValue = jerry_undefined();
|
||||
return scriptValueVoid();
|
||||
}
|
||||
|
||||
static void testWidgetSetNumberProp(void *handle, const scriptvalue_t *value) {
|
||||
((testwidgethandle_t *)handle)->numberValue = value->as.number;
|
||||
}
|
||||
|
||||
static void testWidgetSetVec3Prop(void *handle, const scriptvalue_t *value) {
|
||||
testwidgethandle_t *inst = (testwidgethandle_t *)handle;
|
||||
inst->vec3Value[0] = value->as.vec3Value[0];
|
||||
inst->vec3Value[1] = value->as.vec3Value[1];
|
||||
inst->vec3Value[2] = value->as.vec3Value[2];
|
||||
}
|
||||
|
||||
static scriptfuncdef_t TEST_WIDGET_FUNCS[] = {
|
||||
{ "setNumber", SCRIPT_TYPE_VOID, { SCRIPT_TYPE_NUMBER }, 1,
|
||||
testWidgetSetNumber },
|
||||
{ "getNumber", SCRIPT_TYPE_NUMBER, { 0 }, 0, testWidgetGetNumberMethod },
|
||||
{ "setInt", SCRIPT_TYPE_VOID, { SCRIPT_TYPE_INT }, 1, testWidgetSetInt },
|
||||
{ "setBool", SCRIPT_TYPE_VOID, { SCRIPT_TYPE_BOOL }, 1, testWidgetSetBool },
|
||||
{ "getBool", SCRIPT_TYPE_BOOL, { 0 }, 0, testWidgetGetBoolMethod },
|
||||
{ "setString", SCRIPT_TYPE_VOID, { SCRIPT_TYPE_STRING }, 1,
|
||||
testWidgetSetString },
|
||||
{ "getString", SCRIPT_TYPE_STRING, { 0 }, 0, testWidgetGetStringMethod },
|
||||
{ "setVec3", SCRIPT_TYPE_VOID, { SCRIPT_TYPE_VEC3 }, 1, testWidgetSetVec3 },
|
||||
{ "getVec3", SCRIPT_TYPE_VEC3, { 0 }, 0, testWidgetGetVec3Method },
|
||||
{ "setPointer", SCRIPT_TYPE_VOID, { SCRIPT_TYPE_POINTER }, 1,
|
||||
testWidgetSetPointer },
|
||||
{ "getSentinelPointer", SCRIPT_TYPE_POINTER, { 0 }, 0,
|
||||
testWidgetGetSentinelPointer },
|
||||
{ "pointerMatches", SCRIPT_TYPE_BOOL, { SCRIPT_TYPE_POINTER }, 1,
|
||||
testWidgetPointerMatches },
|
||||
{ "setCallback", SCRIPT_TYPE_VOID, { SCRIPT_TYPE_CALLBACK }, 1,
|
||||
testWidgetSetCallback },
|
||||
{ "invokeCallback", SCRIPT_TYPE_VOID, { 0 }, 0, testWidgetInvokeCallback },
|
||||
{ "dispose", SCRIPT_TYPE_VOID, { 0 }, 0, testWidgetDispose },
|
||||
{ NULL, SCRIPT_TYPE_VOID, { 0 }, 0, NULL }
|
||||
};
|
||||
|
||||
static scriptpropdef_t TEST_WIDGET_PROPS[] = {
|
||||
{ "numberProp", SCRIPT_TYPE_NUMBER, testWidgetGetNumberValue,
|
||||
testWidgetSetNumberProp },
|
||||
{ "vec3Prop", SCRIPT_TYPE_VEC3, testWidgetGetVec3Value,
|
||||
testWidgetSetVec3Prop },
|
||||
{ NULL, SCRIPT_TYPE_VOID, NULL, NULL }
|
||||
};
|
||||
|
||||
static scriptvalue_t testGlobalGetValue(void *handle) {
|
||||
return scriptValueNumber(TEST_GLOBAL_VALUE);
|
||||
}
|
||||
|
||||
static void testGlobalSetValue(void *handle, const scriptvalue_t *value) {
|
||||
TEST_GLOBAL_VALUE = value->as.number;
|
||||
}
|
||||
|
||||
static scriptvalue_t testGlobalCompute(
|
||||
void *handle, const scriptvalue_t *args, const uint32_t argc
|
||||
) {
|
||||
return scriptValueNumber(args[0].as.number + args[1].as.number);
|
||||
}
|
||||
|
||||
static scriptpropdef_t TEST_GLOBAL_PROPS[] = {
|
||||
{ "value", SCRIPT_TYPE_NUMBER, testGlobalGetValue, testGlobalSetValue },
|
||||
{ NULL, SCRIPT_TYPE_VOID, NULL, NULL }
|
||||
};
|
||||
|
||||
static scriptfuncdef_t TEST_GLOBAL_FUNCS[] = {
|
||||
{ "compute", SCRIPT_TYPE_NUMBER,
|
||||
{ SCRIPT_TYPE_NUMBER, SCRIPT_TYPE_NUMBER }, 2, testGlobalCompute },
|
||||
{ NULL, SCRIPT_TYPE_VOID, { 0 }, 0, NULL }
|
||||
};
|
||||
|
||||
static int scriptdef_setup(void **state) {
|
||||
errorret_t ret = scriptManagerInit();
|
||||
if(errorIsNotOk(ret)) { errorCatch(ret); return -1; }
|
||||
|
||||
scriptProtoInit(
|
||||
&TEST_WIDGET_PROTO, "TestWidget", sizeof(testwidgethandle_t),
|
||||
testWidgetConstructor
|
||||
);
|
||||
scriptProtoDefineFuncDefs(&TEST_WIDGET_PROTO, TEST_WIDGET_FUNCS);
|
||||
scriptProtoDefinePropDefs(&TEST_WIDGET_PROTO, TEST_WIDGET_PROPS);
|
||||
|
||||
TEST_GLOBAL_VALUE = 0.0;
|
||||
jerry_value_t globalObj = jerry_object();
|
||||
scriptDefDefineGlobalProps(globalObj, TEST_GLOBAL_PROPS);
|
||||
scriptDefDefineGlobalFuncs(globalObj, TEST_GLOBAL_FUNCS);
|
||||
moduleBaseSetValue("TestGlobal", globalObj);
|
||||
jerry_value_free(globalObj);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int scriptdef_teardown(void **state) {
|
||||
errorret_t ret = scriptManagerDispose();
|
||||
if(errorIsNotOk(ret)) errorCatch(ret);
|
||||
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_scriptdef_number_and_int_and_bool_roundtrip(void **state) {
|
||||
jerry_value_t result;
|
||||
errorret_t ret = scriptManagerExec(
|
||||
"var w = new TestWidget();"
|
||||
"w.setNumber(3.5); w.setInt(7); w.setBool(true);"
|
||||
"w.getNumber() + ',' + w.getBool();",
|
||||
&result
|
||||
);
|
||||
assert_true(errorIsOk(ret));
|
||||
char_t buf[64];
|
||||
moduleBaseToString(result, buf, sizeof(buf));
|
||||
assert_string_equal(buf, "3.5,true");
|
||||
jerry_value_free(result);
|
||||
}
|
||||
|
||||
static void test_scriptdef_string_method_roundtrip(void **state) {
|
||||
jerry_value_t result;
|
||||
errorret_t ret = scriptManagerExec(
|
||||
"var w = new TestWidget();"
|
||||
"w.setString('hello'); w.getString();",
|
||||
&result
|
||||
);
|
||||
assert_true(errorIsOk(ret));
|
||||
char_t buf[32];
|
||||
moduleBaseToString(result, buf, sizeof(buf));
|
||||
assert_string_equal(buf, "hello");
|
||||
jerry_value_free(result);
|
||||
}
|
||||
|
||||
static void test_scriptdef_vec3_method_roundtrip(void **state) {
|
||||
jerry_value_t result;
|
||||
errorret_t ret = scriptManagerExec(
|
||||
"var w = new TestWidget();"
|
||||
"w.setVec3(4, 5, 6);"
|
||||
"var v = w.getVec3();"
|
||||
"v.x + ',' + v.y + ',' + v.z;",
|
||||
&result
|
||||
);
|
||||
assert_true(errorIsOk(ret));
|
||||
char_t buf[32];
|
||||
moduleBaseToString(result, buf, sizeof(buf));
|
||||
assert_string_equal(buf, "4,5,6");
|
||||
jerry_value_free(result);
|
||||
}
|
||||
|
||||
static void test_scriptdef_vec3_property_roundtrip(void **state) {
|
||||
jerry_value_t result;
|
||||
errorret_t ret = scriptManagerExec(
|
||||
"var w = new TestWidget();"
|
||||
"w.vec3Prop = { x: 1, y: 2, z: 3 };"
|
||||
"var v = w.vec3Prop;"
|
||||
"v.x + ',' + v.y + ',' + v.z;",
|
||||
&result
|
||||
);
|
||||
assert_true(errorIsOk(ret));
|
||||
char_t buf[32];
|
||||
moduleBaseToString(result, buf, sizeof(buf));
|
||||
assert_string_equal(buf, "1,2,3");
|
||||
jerry_value_free(result);
|
||||
}
|
||||
|
||||
static void test_scriptdef_number_property_roundtrip(void **state) {
|
||||
jerry_value_t result;
|
||||
errorret_t ret = scriptManagerExec(
|
||||
"var w = new TestWidget(); w.numberProp = 42; w.numberProp;", &result
|
||||
);
|
||||
assert_true(errorIsOk(ret));
|
||||
assert_true(jerry_value_is_number(result));
|
||||
assert_int_equal((int)jerry_value_as_number(result), 42);
|
||||
jerry_value_free(result);
|
||||
}
|
||||
|
||||
static void test_scriptdef_pointer_roundtrip(void **state) {
|
||||
jerry_value_t result;
|
||||
errorret_t ret = scriptManagerExec(
|
||||
"var w = new TestWidget();"
|
||||
"var p = w.getSentinelPointer();"
|
||||
"w.setPointer(p);"
|
||||
"w.pointerMatches(p);",
|
||||
&result
|
||||
);
|
||||
assert_true(errorIsOk(ret));
|
||||
assert_true(jerry_value_is_true(result));
|
||||
jerry_value_free(result);
|
||||
}
|
||||
|
||||
static void test_scriptdef_callback_can_be_invoked_later(void **state) {
|
||||
jerry_value_t result;
|
||||
errorret_t ret = scriptManagerExec(
|
||||
"var calls = 0;"
|
||||
"var w = new TestWidget();"
|
||||
"w.setCallback(function() { calls++; });"
|
||||
"w.invokeCallback(); w.invokeCallback();"
|
||||
"w.dispose();"
|
||||
"calls;",
|
||||
&result
|
||||
);
|
||||
assert_true(errorIsOk(ret));
|
||||
assert_true(jerry_value_is_number(result));
|
||||
assert_int_equal((int)jerry_value_as_number(result), 2);
|
||||
jerry_value_free(result);
|
||||
}
|
||||
|
||||
static void test_scriptdef_wrong_type_args_throw(void **state) {
|
||||
errorret_t ret = scriptManagerExec(
|
||||
"var w = new TestWidget(); w.setNumber('nope');", NULL
|
||||
);
|
||||
assert_true(errorIsNotOk(ret));
|
||||
errorCatch(ret);
|
||||
|
||||
ret = scriptManagerExec(
|
||||
"var w = new TestWidget(); w.setString(123);", NULL
|
||||
);
|
||||
assert_true(errorIsNotOk(ret));
|
||||
errorCatch(ret);
|
||||
|
||||
ret = scriptManagerExec(
|
||||
"var w = new TestWidget(); w.setPointer(42);", NULL
|
||||
);
|
||||
assert_true(errorIsNotOk(ret));
|
||||
errorCatch(ret);
|
||||
}
|
||||
|
||||
static void test_scriptdef_global_object_props_and_funcs(void **state) {
|
||||
jerry_value_t result;
|
||||
errorret_t ret = scriptManagerExec(
|
||||
"TestGlobal.value = 10;"
|
||||
"TestGlobal.value + ',' + TestGlobal.compute(2, 3);",
|
||||
&result
|
||||
);
|
||||
assert_true(errorIsOk(ret));
|
||||
char_t buf[32];
|
||||
moduleBaseToString(result, buf, sizeof(buf));
|
||||
assert_string_equal(buf, "10,5");
|
||||
jerry_value_free(result);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
assertInit();
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test_setup_teardown(
|
||||
test_scriptdef_number_and_int_and_bool_roundtrip,
|
||||
scriptdef_setup, scriptdef_teardown
|
||||
),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
test_scriptdef_string_method_roundtrip,
|
||||
scriptdef_setup, scriptdef_teardown
|
||||
),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
test_scriptdef_vec3_method_roundtrip,
|
||||
scriptdef_setup, scriptdef_teardown
|
||||
),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
test_scriptdef_vec3_property_roundtrip,
|
||||
scriptdef_setup, scriptdef_teardown
|
||||
),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
test_scriptdef_number_property_roundtrip,
|
||||
scriptdef_setup, scriptdef_teardown
|
||||
),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
test_scriptdef_pointer_roundtrip,
|
||||
scriptdef_setup, scriptdef_teardown
|
||||
),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
test_scriptdef_callback_can_be_invoked_later,
|
||||
scriptdef_setup, scriptdef_teardown
|
||||
),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
test_scriptdef_wrong_type_args_throw,
|
||||
scriptdef_setup, scriptdef_teardown
|
||||
),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
test_scriptdef_global_object_props_and_funcs,
|
||||
scriptdef_setup, scriptdef_teardown
|
||||
),
|
||||
};
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
}
|
||||
Reference in New Issue
Block a user