Add entity scripting

This commit is contained in:
2026-04-20 17:01:12 -05:00
parent d161182997
commit 7c3386cf3e
11 changed files with 332 additions and 2 deletions
+2 -1
View File
@@ -4,4 +4,5 @@
# https://opensource.org/licenses/MIT
add_subdirectory(display)
add_subdirectory(physics)
add_subdirectory(physics)
add_subdirectory(script)
@@ -0,0 +1,9 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
entityscript.c
)
@@ -0,0 +1,115 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entityscript.h"
#include "entity/entitymanager.h"
#include "assert/assert.h"
#include "util/memory.h"
void entityScriptInit(
const entityid_t entityId,
const componentid_t compId
) {
entityscript_t *data = componentGetData(
entityId, compId, COMPONENT_TYPE_ENTITYSCRIPT
);
memoryZero(data, sizeof(entityscript_t));
}
void entityScriptUnload(
const entityid_t entityId,
const componentid_t compId
) {
entityscript_t *data = componentGetData(
entityId, compId, COMPONENT_TYPE_ENTITYSCRIPT
);
if(!data->active) return;
lua_getglobal(data->context.luaState, "entityDispose");
if(lua_isfunction(data->context.luaState, -1)) {
if(lua_pcall(data->context.luaState, 0, 0, 0) != LUA_OK) {
lua_pop(data->context.luaState, 1);
}
} else {
lua_pop(data->context.luaState, 1);
}
scriptContextDispose(&data->context);
data->active = false;
}
errorret_t entityScriptSetScript(
const entityid_t entityId,
const componentid_t compId,
const char_t *filename
) {
assertNotNull(filename, "Entity script filename cannot be NULL");
entityScriptUnload(entityId, compId);
entityscript_t *data = componentGetData(
entityId, compId, COMPONENT_TYPE_ENTITYSCRIPT
);
errorChain(scriptContextInit(&data->context));
data->active = true;
// Expose entity identity to the script as globals
lua_pushnumber(data->context.luaState, (lua_Number)entityId);
lua_setglobal(data->context.luaState, "ENTITY_ID");
lua_pushnumber(data->context.luaState, (lua_Number)compId);
lua_setglobal(data->context.luaState, "ENTITY_COMPONENT_ID");
errorChain(scriptContextExecFile(&data->context, filename));
lua_getglobal(data->context.luaState, "entityInit");
if(lua_isfunction(data->context.luaState, -1)) {
if(lua_pcall(data->context.luaState, 0, 0, 0) != LUA_OK) {
errorThrow(
"Failed to call entityInit: %s",
lua_tostring(data->context.luaState, -1)
);
}
} else {
lua_pop(data->context.luaState, 1);
}
errorOk();
}
errorret_t entityScriptUpdate(
const entityid_t entityId,
const componentid_t compId
) {
entityscript_t *data = componentGetData(
entityId, compId, COMPONENT_TYPE_ENTITYSCRIPT
);
if(!data->active) errorOk();
lua_getglobal(data->context.luaState, "entityUpdate");
if(lua_isfunction(data->context.luaState, -1)) {
if(lua_pcall(data->context.luaState, 0, 0, 0) != LUA_OK) {
errorThrow(
"Failed to call entityUpdate: %s",
lua_tostring(data->context.luaState, -1)
);
}
} else {
lua_pop(data->context.luaState, 1);
}
errorOk();
}
void entityScriptDispose(
const entityid_t entityId,
const componentid_t compId
) {
entityScriptUnload(entityId, compId);
}
@@ -0,0 +1,77 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "entity/entitybase.h"
#include "script/scriptcontext.h"
typedef struct {
scriptcontext_t context;
bool_t active;
} entityscript_t;
/**
* Initializes the entity script component (empty context, not yet loaded).
*
* @param entityId The entity ID.
* @param compId The component ID.
*/
void entityScriptInit(
const entityid_t entityId,
const componentid_t compId
);
/**
* Unloads the currently active script: calls entityDispose if defined and
* disposes the context. No-op if no script is loaded.
*
* @param entityId The entity ID.
* @param compId The component ID.
*/
void entityScriptUnload(
const entityid_t entityId,
const componentid_t compId
);
/**
* Sets the script for this component. If a script is already loaded it is
* unloaded first. Then loads and executes the new file, sets entityId and
* entityScriptId as Lua globals, and calls entityInit if defined.
*
* @param entityId The entity ID.
* @param compId The component ID.
* @param filename The script file to load.
* @return The error return value.
*/
errorret_t entityScriptSetScript(
const entityid_t entityId,
const componentid_t compId,
const char_t *filename
);
/**
* Calls the entityUpdate Lua function in the entity script context, if defined.
*
* @param entityId The entity ID.
* @param compId The component ID.
* @return The error return value.
*/
errorret_t entityScriptUpdate(
const entityid_t entityId,
const componentid_t compId
);
/**
* Unloads the script if one is active, then disposes the component.
*
* @param entityId The entity ID.
* @param compId The component ID.
*/
void entityScriptDispose(
const entityid_t entityId,
const componentid_t compId
);
+3 -1
View File
@@ -10,9 +10,11 @@
#include "entity/component/display/entitymesh.h"
#include "entity/component/display/entitymaterial.h"
#include "entity/component/physics/entityphysics.h"
#include "entity/component/script/entityscript.h"
X(POSITION, entityposition_t, position, entityPositionInit, NULL)
X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL)
X(MESH, entitymesh_t, mesh, entityMeshInit, NULL)
X(MATERIAL, entitymaterial_t, material, entityMaterialInit, NULL)
X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, entityPhysicsDispose)
X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, entityPhysicsDispose)
X(ENTITYSCRIPT, entityscript_t, entityscript, entityScriptInit, entityScriptDispose)
@@ -12,3 +12,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
# Subdirectories
add_subdirectory(display)
add_subdirectory(physics)
add_subdirectory(script)
@@ -9,12 +9,25 @@
#include "assert/assert.h"
#include "entity/entitymanager.h"
#include "entity/entity.h"
#include "display/moduleentityposition.h"
#include "display/moduleentitycamera.h"
#include "display/moduleentitymesh.h"
#include "display/moduleentitymaterial.h"
#include "physics/moduleentityphysics.h"
#include "script/moduleentityscript.h"
void moduleEntity(scriptcontext_t *ctx) {
assertNotNull(ctx, "Script context cannot be NULL");
lua_register(ctx->luaState, "entityAdd", moduleEntityAdd);
lua_register(ctx->luaState, "entityRemove", moduleEntityRemove);
moduleEntityPosition(ctx);
moduleEntityCamera(ctx);
moduleEntityMesh(ctx);
moduleEntityMaterial(ctx);
moduleEntityPhysics(ctx);
moduleEntityScript(ctx);
}
int moduleEntityAdd(lua_State *L) {
@@ -0,0 +1,9 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
moduleentityscript.c
)
@@ -0,0 +1,85 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "moduleentityscript.h"
#include "assert/assert.h"
#include "entity/entity.h"
#include "entity/entitymanager.h"
#include "entity/component/script/entityscript.h"
void moduleEntityScript(scriptcontext_t *ctx) {
assertNotNull(ctx, "Script context cannot be NULL");
#define REG(name, func) lua_register(ctx->luaState, name, func)
REG("entityScriptAdd", moduleEntityScriptAdd);
REG("entityScriptSetScript", moduleEntityScriptSetScript);
REG("entityScriptUpdate", moduleEntityScriptUpdate);
REG("entityAddScripted", moduleEntityAddScripted);
#undef REG
}
int moduleEntityScriptAdd(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
entityid_t entityId = (entityid_t)luaL_checknumber(L, 1);
componentid_t compId = entityAddComponent(entityId, COMPONENT_TYPE_ENTITYSCRIPT);
lua_pushnumber(L, (lua_Number)compId);
return 1;
}
int moduleEntityScriptSetScript(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
entityid_t entityId = (entityid_t)luaL_checknumber(L, 1);
componentid_t compId = (componentid_t)luaL_checknumber(L, 2);
const char_t *filename = luaL_checkstring(L, 3);
errorret_t err = entityScriptSetScript(entityId, compId, filename);
if(err.code != ERROR_OK) {
luaL_error(L, "Failed to set entity script: %s", filename);
errorCatch(errorPrint(err));
return 0;
}
return 0;
}
int moduleEntityAddScripted(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
const char_t *filename = luaL_checkstring(L, 1);
entityid_t entityId = entityManagerAdd();
componentid_t compId = entityAddComponent(entityId, COMPONENT_TYPE_ENTITYSCRIPT);
errorret_t err = entityScriptSetScript(entityId, compId, filename);
if(err.code != ERROR_OK) {
luaL_error(L, "Failed to set entity script: %s", filename);
errorCatch(errorPrint(err));
return 0;
}
lua_pushnumber(L, (lua_Number)entityId);
lua_pushnumber(L, (lua_Number)compId);
return 2;
}
int moduleEntityScriptUpdate(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL");
entityid_t entityId = (entityid_t)luaL_checknumber(L, 1);
componentid_t compId = (componentid_t)luaL_checknumber(L, 2);
errorret_t err = entityScriptUpdate(entityId, compId);
if(err.code != ERROR_OK) {
luaL_error(L, "Failed to update entity script");
errorCatch(errorPrint(err));
return 0;
}
return 0;
}
@@ -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 "script/scriptcontext.h"
void moduleEntityScript(scriptcontext_t *ctx);
int moduleEntityScriptAdd(lua_State *L);
int moduleEntityScriptSetScript(lua_State *L);
int moduleEntityScriptUpdate(lua_State *L);
int moduleEntityAddScripted(lua_State *L);
+2
View File
@@ -28,6 +28,7 @@
#include "script/module/entity/display/moduleentitymesh.h"
#include "script/module/entity/display/moduleentitymaterial.h"
#include "script/module/entity/physics/moduleentityphysics.h"
#include "script/module/entity/script/moduleentityscript.h"
#include "script/scriptgame.h"
#include "util/string.h"
@@ -40,6 +41,7 @@ const scriptmodule_t SCRIPT_MODULE_LIST[] = {
REG("entitymesh", moduleEntityMesh)
REG("entitymaterial", moduleEntityMaterial)
REG("entityphysics", moduleEntityPhysics)
REG("entityscript", moduleEntityScript)
REG("entity", moduleEntity)
REG("input", moduleInput)
REG("platform", modulePlatform)