Lua API implemented

This commit is contained in:
2026-04-29 14:13:50 +03:00
parent abe6eef6b3
commit 02fa78764a
11 changed files with 3084 additions and 3 deletions

View File

@@ -65,8 +65,6 @@ set(EDITSCENE_SOURCES
systems/PrefabSystem.cpp
ui/PrefabInstanceEditor.cpp
LuaScripting.cpp
systems/ItemSystem.cpp
components/ItemModule.cpp
components/InventoryModule.cpp
@@ -149,6 +147,10 @@ set(EDITSCENE_SOURCES
gizmo/Gizmo.cpp
gizmo/Cursor3D.cpp
physics/physics.cpp
lua/LuaState.cpp
lua/LuaEntityApi.cpp
lua/LuaComponentApi.cpp
lua/LuaEventApi.cpp
)
set(EDITSCENE_HEADERS
@@ -293,7 +295,10 @@ set(EDITSCENE_HEADERS
gizmo/Gizmo.hpp
gizmo/Cursor3D.hpp
physics/physics.h
LuaScripting.hpp
lua/LuaState.hpp
lua/LuaEntityApi.hpp
lua/LuaComponentApi.hpp
lua/LuaEventApi.hpp
)
add_executable(editSceneEditor ${EDITSCENE_SOURCES} ${EDITSCENE_HEADERS})

View File

@@ -84,6 +84,9 @@
#include <OgreRTShaderSystem.h>
#include <imgui.h>
#include "lua/LuaEntityApi.hpp"
#include "lua/LuaComponentApi.hpp"
#include "lua/LuaEventApi.hpp"
//=============================================================================
// ImGuiRenderListener Implementation
@@ -484,6 +487,28 @@ void EditorApp::setup()
addInputListener(this);
addInputListener(getImGuiInputListener());
// Initialize Lua scripting
{
lua_State *L = m_lua.getState();
// Store the Flecs world pointer in the Lua registry
// so Lua API functions can access it.
flecs::world *worldPtr = &m_world;
lua_pushlightuserdata(L, worldPtr);
lua_setfield(L, LUA_REGISTRYINDEX,
"EditSceneFlecsWorld");
// Register all Lua API modules.
// Order matters: Entity API creates the "ecs" table,
// Component and Event APIs add to it.
editScene::registerLuaEntityApi(L);
editScene::registerLuaComponentApi(L);
editScene::registerLuaEventApi(L);
// Run late setup: load data.lua and initial scripts.
m_lua.lateSetup();
}
// Game mode can be set externally before setup() is called
m_setupComplete = true;

View File

@@ -9,6 +9,7 @@
#include <OgreRenderTargetListener.h>
#include <flecs.h>
#include <memory>
#include "lua/LuaState.hpp"
// Forward declarations
class EditorUISystem;
@@ -261,6 +262,9 @@ private:
bool m_setupComplete = false;
bool m_debugBuoyancy = false;
// Lua scripting
editScene::LuaState m_lua;
// Editor visualization nodes
Ogre::SceneNode *m_gridNode = nullptr;
Ogre::SceneNode *m_axisNode = nullptr;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,55 @@
#ifndef EDITSCENE_LUA_COMPONENT_API_HPP
#define EDITSCENE_LUA_COMPONENT_API_HPP
#pragma once
#include <lua.hpp>
/**
* @file LuaComponentApi.hpp
* @brief Lua API for adding, removing, and modifying ECS components.
*
* Provides a generic component API where Lua scripts can add, get, set,
* and remove components on entities. Each component type is registered
* with a name string and a set of field accessors.
*
* Exposed Lua globals (in the "ecs" table):
* ecs.add_component(id, "ComponentName") -> nil
* ecs.remove_component(id, "ComponentName") -> nil
* ecs.has_component(id, "ComponentName") -> bool
* ecs.get_component(id, "ComponentName") -> table (field -> value)
* ecs.set_component(id, "ComponentName", table) -> nil
* ecs.get_field(id, "ComponentName", "fieldName") -> value
* ecs.set_field(id, "ComponentName", "fieldName", value) -> nil
*
* Supported component names (case-sensitive):
* "EntityName", "Transform", "Renderable", "Light", "Camera",
* "RigidBody", "PhysicsCollider", "Character", "CharacterSlots",
* "AnimationTree", "AnimationTreeTemplate", "BehaviorTree",
* "GoapBlackboard", "GoapAction", "GoapGoal", "ActionDatabase",
* "ActionDebug", "SmartObject", "Actuator", "EventHandler",
* "GoapPlanner", "GoapRunner", "PathFollowing", "NavMesh",
* "NavMeshGeometrySource", "NavMeshAgent", "Item", "Inventory",
* "Lod", "LodSettings", "StaticGeometry", "StaticGeometryMember",
* "ProceduralTexture", "ProceduralMaterial", "Primitive",
* "TriangleBuffer", "Sun", "Skybox", "WaterPlane", "WaterPhysics",
* "BuoyancyInfo", "StartupMenu", "Dialogue", "PlayerController",
* "CellGrid", "Room", "ClearArea", "Roof", "Lot", "District",
* "Town", "FurnitureTemplate", "PrefabInstance", "EditorMarker"
*/
namespace editScene
{
/**
* @brief Register all component Lua API functions into the "ecs" global table.
*
* Adds functions for component manipulation (add, remove, has, get, set,
* get_field, set_field) to the existing "ecs" table.
*
* @param L The Lua state.
*/
void registerLuaComponentApi(lua_State *L);
} // namespace editScene
#endif // EDITSCENE_LUA_COMPONENT_API_HPP

View File

@@ -0,0 +1,236 @@
#include "LuaEntityApi.hpp"
#include "components/EditorMarker.hpp"
#include <OgreLogManager.h>
#include <iostream>
namespace editScene
{
// Global entity ID map
LuaEntityIdMap g_luaEntityIdMap;
int luaEntityToId(flecs::entity e)
{
if (!e.is_valid())
return -1;
return g_luaEntityIdMap.addEntity(e);
}
flecs::entity luaIdToEntity(int id)
{
return g_luaEntityIdMap.getEntity(id);
}
// ---------------------------------------------------------------------------
// Helper: get the Flecs world from the Lua registry
// ---------------------------------------------------------------------------
static flecs::world getWorld(lua_State *L)
{
lua_getfield(L, LUA_REGISTRYINDEX, "EditSceneFlecsWorld");
OgreAssert(lua_islightuserdata(L, -1), "Flecs world not registered");
flecs::world *world =
static_cast<flecs::world *>(lua_touserdata(L, -1));
lua_pop(L, 1);
return *world;
}
// ---------------------------------------------------------------------------
// Lua: ecs.create_entity() -> int (entity ID)
// Creates a new entity with EditorMarkerComponent and returns its Lua ID.
// ---------------------------------------------------------------------------
static int luaCreateEntity(lua_State *L)
{
flecs::world world = getWorld(L);
flecs::entity e = world.entity();
// Add EditorMarkerComponent so it appears in the editor hierarchy
// (the component is forward-declared; we use a tag approach)
e.add<EditorMarkerComponent>();
int id = g_luaEntityIdMap.addEntity(e);
lua_pushinteger(L, id);
return 1;
}
// ---------------------------------------------------------------------------
// Lua: ecs.destroy_entity(id) -> nil
// Destroys an entity and removes it from the ID map.
// ---------------------------------------------------------------------------
static int luaDestroyEntity(lua_State *L)
{
luaL_checktype(L, 1, LUA_TNUMBER);
int id = lua_tointeger(L, 1);
flecs::entity e = g_luaEntityIdMap.getEntity(id);
if (e.is_alive()) {
g_luaEntityIdMap.removeEntity(e);
e.destruct();
}
return 0;
}
// ---------------------------------------------------------------------------
// Lua: ecs.entity_exists(id) -> bool
// ---------------------------------------------------------------------------
static int luaEntityExists(lua_State *L)
{
luaL_checktype(L, 1, LUA_TNUMBER);
int id = lua_tointeger(L, 1);
lua_pushboolean(L, g_luaEntityIdMap.hasId(id) ? 1 : 0);
return 1;
}
// ---------------------------------------------------------------------------
// Lua: ecs.get_player_entity() -> int (entity ID) or nil
// Looks up the entity named "player" in the Flecs world.
// ---------------------------------------------------------------------------
static int luaGetPlayerEntity(lua_State *L)
{
flecs::world world = getWorld(L);
flecs::entity e = world.lookup("player");
if (e.is_valid()) {
int id = g_luaEntityIdMap.addEntity(e);
lua_pushinteger(L, id);
} else {
lua_pushnil(L);
}
return 1;
}
// ---------------------------------------------------------------------------
// Lua: ecs.get_entity_by_name(name) -> int (entity ID) or nil
// ---------------------------------------------------------------------------
static int luaGetEntityByName(lua_State *L)
{
luaL_checktype(L, 1, LUA_TSTRING);
const char *name = lua_tostring(L, 1);
flecs::world world = getWorld(L);
flecs::entity e = world.lookup(name);
if (e.is_valid()) {
int id = g_luaEntityIdMap.addEntity(e);
lua_pushinteger(L, id);
} else {
lua_pushnil(L);
}
return 1;
}
// ---------------------------------------------------------------------------
// Lua: ecs.set_entity_name(id, name) -> nil
// ---------------------------------------------------------------------------
static int luaSetEntityName(lua_State *L)
{
luaL_checktype(L, 1, LUA_TNUMBER);
luaL_checktype(L, 2, LUA_TSTRING);
int id = lua_tointeger(L, 1);
const char *name = lua_tostring(L, 2);
flecs::entity e = g_luaEntityIdMap.getEntity(id);
e.set_name(name);
return 0;
}
// ---------------------------------------------------------------------------
// Lua: ecs.get_entity_name(id) -> string or nil
// ---------------------------------------------------------------------------
static int luaGetEntityName(lua_State *L)
{
luaL_checktype(L, 1, LUA_TNUMBER);
int id = lua_tointeger(L, 1);
flecs::entity e = g_luaEntityIdMap.getEntity(id);
const char *name = e.name();
if (name && name[0]) {
lua_pushstring(L, name);
} else {
lua_pushnil(L);
}
return 1;
}
// ---------------------------------------------------------------------------
// Lua: ecs.parent(id) -> int (parent ID) or nil
// ---------------------------------------------------------------------------
static int luaGetParent(lua_State *L)
{
luaL_checktype(L, 1, LUA_TNUMBER);
int id = lua_tointeger(L, 1);
flecs::entity e = g_luaEntityIdMap.getEntity(id);
flecs::entity parent = e.parent();
if (parent.is_valid()) {
int parentId = g_luaEntityIdMap.addEntity(parent);
lua_pushinteger(L, parentId);
} else {
lua_pushnil(L);
}
return 1;
}
// ---------------------------------------------------------------------------
// Lua: ecs.children(id) -> table of child IDs
// ---------------------------------------------------------------------------
static int luaGetChildren(lua_State *L)
{
luaL_checktype(L, 1, LUA_TNUMBER);
int id = lua_tointeger(L, 1);
flecs::entity e = g_luaEntityIdMap.getEntity(id);
lua_newtable(L); // result table
int index = 1;
e.children([&](flecs::entity child) {
int childId = g_luaEntityIdMap.addEntity(child);
lua_pushinteger(L, childId);
lua_rawseti(L, -2, index);
index++;
});
return 1;
}
// ---------------------------------------------------------------------------
// Register all entity API functions
// ---------------------------------------------------------------------------
void registerLuaEntityApi(lua_State *L)
{
// Create the "ecs" global table
lua_newtable(L);
// Entity management
lua_pushcfunction(L, luaCreateEntity);
lua_setfield(L, -2, "create_entity");
lua_pushcfunction(L, luaDestroyEntity);
lua_setfield(L, -2, "destroy_entity");
lua_pushcfunction(L, luaEntityExists);
lua_setfield(L, -2, "entity_exists");
lua_pushcfunction(L, luaGetPlayerEntity);
lua_setfield(L, -2, "get_player_entity");
lua_pushcfunction(L, luaGetEntityByName);
lua_setfield(L, -2, "get_entity_by_name");
lua_pushcfunction(L, luaSetEntityName);
lua_setfield(L, -2, "set_entity_name");
lua_pushcfunction(L, luaGetEntityName);
lua_setfield(L, -2, "get_entity_name");
// Hierarchy
lua_pushcfunction(L, luaGetParent);
lua_setfield(L, -2, "parent");
lua_pushcfunction(L, luaGetChildren);
lua_setfield(L, -2, "children");
// Set the global
lua_setglobal(L, "ecs");
}
} // namespace editScene

View File

@@ -0,0 +1,126 @@
#ifndef EDITSCENE_LUA_ENTITY_API_HPP
#define EDITSCENE_LUA_ENTITY_API_HPP
#pragma once
#include <flecs.h>
#include <lua.hpp>
#include <Ogre.h>
#include <unordered_map>
/**
* @file LuaEntityApi.hpp
* @brief Lua API for entity creation, destruction, and ID mapping.
*
* Provides a bidirectional mapping between Lua integer IDs and
* Flecs entity handles. Lua scripts reference entities by integer
* IDs rather than raw Flecs handles for safety and simplicity.
*
* Exposed Lua globals:
* ecs.create_entity() -> int (entity ID)
* ecs.destroy_entity(id) -> nil
* ecs.entity_exists(id) -> bool
* ecs.get_player_entity() -> int (entity ID)
* ecs.get_entity_by_name(name) -> int (entity ID) or nil
* ecs.set_entity_name(id, name)-> nil
* ecs.get_entity_name(id) -> string or nil
* ecs.parent(id) -> int (parent ID) or nil
* ecs.children(id) -> table of child IDs
*/
namespace editScene
{
/**
* @brief Global bidirectional mapping between Lua integer IDs and Flecs entities.
*
* This is a singleton-like global so that all Lua API functions can
* access it without needing to pass it through every closure.
*/
struct LuaEntityIdMap {
std::unordered_map<int, flecs::entity> id2entity;
std::unordered_map<flecs::entity_t, int> entity2id;
int nextId = 0;
/** @brief Get the next available integer ID. */
int getNextId()
{
nextId++;
return nextId;
}
/**
* @brief Add an entity to the map, returning its integer ID.
* If the entity is already mapped, returns the existing ID.
*/
int addEntity(flecs::entity e)
{
if (entity2id.find(e.id()) != entity2id.end())
return entity2id[e.id()];
int id = getNextId();
id2entity[id] = e;
entity2id[e.id()] = id;
return id;
}
/**
* @brief Get the Flecs entity for an integer ID.
* Asserts if the ID is not found or the entity is invalid.
*/
flecs::entity getEntity(int id)
{
auto it = id2entity.find(id);
OgreAssert(it != id2entity.end(), "Invalid entity ID");
OgreAssert(it->second.is_valid(), "Entity is no longer valid");
return it->second;
}
/**
* @brief Remove an entity from the map.
*/
void removeEntity(flecs::entity e)
{
auto it = entity2id.find(e.id());
if (it != entity2id.end()) {
id2entity.erase(it->second);
entity2id.erase(it);
}
}
/**
* @brief Check if an integer ID is valid.
*/
bool hasId(int id) const
{
auto it = id2entity.find(id);
return it != id2entity.end() && it->second.is_valid();
}
};
/** @brief Global entity ID map instance. */
extern LuaEntityIdMap g_luaEntityIdMap;
/**
* @brief Convert a Flecs entity to a Lua integer ID.
* @return The integer ID, or -1 if the entity is invalid.
*/
int luaEntityToId(flecs::entity e);
/**
* @brief Convert a Lua integer ID to a Flecs entity.
* Asserts if the ID is invalid.
*/
flecs::entity luaIdToEntity(int id);
/**
* @brief Register all entity-related Lua API functions into the global table.
*
* Creates the "ecs" global table (or adds to it) with entity management
* functions. Must be called after LuaState is constructed.
*
* @param L The Lua state.
*/
void registerLuaEntityApi(lua_State *L);
} // namespace editScene
#endif // EDITSCENE_LUA_ENTITY_API_HPP

View File

@@ -0,0 +1,320 @@
#include "LuaEventApi.hpp"
#include "LuaEntityApi.hpp"
#include "../systems/EventBus.hpp"
#include "../components/GoapBlackboard.hpp"
#include <OgreLogManager.h>
#include <OgreVector3.h>
#include <unordered_map>
namespace editScene
{
// ---------------------------------------------------------------------------
// Internal: subscription ID tracking for Lua-managed subscriptions
// ---------------------------------------------------------------------------
/**
* @brief Map from Lua subscription IDs to EventBus ListenerIds.
*
* When Lua subscribes to an event, we store the mapping so that
* unsubscribe_event() can remove the correct listener.
*/
static std::unordered_map<int, EventBus::ListenerId> s_luaSubscriptions;
static int s_nextLuaSubId = 1;
// ---------------------------------------------------------------------------
// Helper: push a GoapBlackboard as a Lua table
// ---------------------------------------------------------------------------
/**
* @brief Push a GoapBlackboard as a Lua table with named fields.
*
* The resulting table has:
* .bits -> integer
* .mask -> integer
* .values -> {string -> int}
* .floatValues -> {string -> float}
* .vec3Values -> {string -> {x, y, z}}
* .stringValues -> {string -> string}
*
* @param L Lua state.
* @param bb The GoapBlackboard to convert.
*/
static void pushGoapBlackboard(lua_State *L, const GoapBlackboard &bb)
{
lua_newtable(L);
// bits and mask
lua_pushinteger(L, (lua_Integer)bb.bits);
lua_setfield(L, -2, "bits");
lua_pushinteger(L, (lua_Integer)bb.mask);
lua_setfield(L, -2, "mask");
// values (int map)
lua_newtable(L);
for (auto &kv : bb.values) {
lua_pushinteger(L, kv.second);
lua_setfield(L, -2, kv.first.c_str());
}
lua_setfield(L, -2, "values");
// floatValues
lua_newtable(L);
for (auto &kv : bb.floatValues) {
lua_pushnumber(L, kv.second);
lua_setfield(L, -2, kv.first.c_str());
}
lua_setfield(L, -2, "floatValues");
// vec3Values
lua_newtable(L);
for (auto &kv : bb.vec3Values) {
lua_newtable(L);
lua_pushnumber(L, kv.second.x);
lua_rawseti(L, -2, 1);
lua_pushnumber(L, kv.second.y);
lua_rawseti(L, -2, 2);
lua_pushnumber(L, kv.second.z);
lua_rawseti(L, -2, 3);
lua_setfield(L, -2, kv.first.c_str());
}
lua_setfield(L, -2, "vec3Values");
// stringValues
lua_newtable(L);
for (auto &kv : bb.stringValues) {
lua_pushstring(L, kv.second.c_str());
lua_setfield(L, -2, kv.first.c_str());
}
lua_setfield(L, -2, "stringValues");
}
/**
* @brief Read a Lua table at the given index as a GoapBlackboard.
*
* Expects the same format as pushGoapBlackboard produces.
*
* @param L Lua state.
* @param idx Stack index of the table.
* @return GoapBlackboard populated from the table.
*/
static GoapBlackboard readGoapBlackboard(lua_State *L, int idx)
{
GoapBlackboard bb;
// bits
lua_getfield(L, idx, "bits");
if (lua_isnumber(L, -1))
bb.bits = (uint64_t)lua_tointeger(L, -1);
lua_pop(L, 1);
// mask
lua_getfield(L, idx, "mask");
if (lua_isnumber(L, -1))
bb.mask = (uint64_t)lua_tointeger(L, -1);
lua_pop(L, 1);
// values (int map)
lua_getfield(L, idx, "values");
if (lua_istable(L, -1)) {
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
if (lua_isstring(L, -2) && lua_isnumber(L, -1))
bb.values[lua_tostring(L, -2)] =
(int)lua_tointeger(L, -1);
lua_pop(L, 1);
}
}
lua_pop(L, 1);
// floatValues
lua_getfield(L, idx, "floatValues");
if (lua_istable(L, -1)) {
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
if (lua_isstring(L, -2) && lua_isnumber(L, -1))
bb.floatValues[lua_tostring(L, -2)] =
(float)lua_tonumber(L, -1);
lua_pop(L, 1);
}
}
lua_pop(L, 1);
// vec3Values
lua_getfield(L, idx, "vec3Values");
if (lua_istable(L, -1)) {
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
if (lua_isstring(L, -2) && lua_istable(L, -1)) {
Ogre::Vector3 v;
lua_rawgeti(L, -1, 1);
v.x = (float)lua_tonumber(L, -1);
lua_pop(L, 1);
lua_rawgeti(L, -1, 2);
v.y = (float)lua_tonumber(L, -1);
lua_pop(L, 1);
lua_rawgeti(L, -1, 3);
v.z = (float)lua_tonumber(L, -1);
lua_pop(L, 1);
bb.vec3Values[lua_tostring(L, -2)] = v;
}
lua_pop(L, 1);
}
}
lua_pop(L, 1);
// stringValues
lua_getfield(L, idx, "stringValues");
if (lua_istable(L, -1)) {
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
if (lua_isstring(L, -2) && lua_isstring(L, -1))
bb.stringValues[lua_tostring(L, -2)] =
lua_tostring(L, -1);
lua_pop(L, 1);
}
}
lua_pop(L, 1);
return bb;
}
// ---------------------------------------------------------------------------
// Lua: ecs.send_event("eventName", [params_table])
// ---------------------------------------------------------------------------
/**
* @brief Lua: ecs.send_event("eventName", [params_table]) -> nil
*
* Sends an event through the global EventBus. The optional params table
* is converted to a GoapBlackboard payload.
*
* Usage:
* ecs.send_event("collision")
* ecs.send_event("collision", { entity_id = 42, damage = 10 })
*/
static int luaSendEvent(lua_State *L)
{
const char *eventName = luaL_checkstring(L, 1);
GoapBlackboard params;
if (lua_gettop(L) >= 2 && lua_istable(L, 2)) {
params = readGoapBlackboard(L, 2);
}
EventBus::getInstance().send(eventName, params);
return 0;
}
// ---------------------------------------------------------------------------
// Lua: ecs.subscribe_event("eventName", callback_fn) -> int (subscription id)
// ---------------------------------------------------------------------------
/**
* @brief Lua: ecs.subscribe_event("eventName", callback_fn) -> int
*
* Subscribes a Lua function to an event. The callback receives:
* function(event_name, params_table)
*
* Returns a subscription ID that can be used with unsubscribe_event().
*
* Usage:
* local sub_id = ecs.subscribe_event("collision", function(event, params)
* print("Collision event received!")
* print("entity_id: " .. params.values.entity_id)
* end)
*/
static int luaSubscribeEvent(lua_State *L)
{
const char *eventName = luaL_checkstring(L, 1);
luaL_checktype(L, 2, LUA_TFUNCTION);
// Create a reference to the Lua callback function
lua_pushvalue(L, 2);
int callbackRef = luaL_ref(L, LUA_REGISTRYINDEX);
// Subscribe to the EventBus with a C++ lambda that calls the Lua function
EventBus::ListenerId listenerId = EventBus::getInstance().subscribe(
eventName, [L, callbackRef](const Ogre::String &eventName,
const GoapBlackboard &params) {
// Push the Lua callback function
lua_rawgeti(L, LUA_REGISTRYINDEX, callbackRef);
// Push event name
lua_pushstring(L, eventName.c_str());
// Push params as a Lua table
pushGoapBlackboard(L, params);
// Call the Lua function (2 args, 0 results)
if (lua_pcall(L, 2, 0, 0) != LUA_OK) {
Ogre::LogManager::getSingleton().stream()
<< "Lua event callback error: "
<< lua_tostring(L, -1);
lua_pop(L, 1);
}
});
// Store the mapping from Lua subscription ID to EventBus listener ID
int luaSubId = s_nextLuaSubId++;
s_luaSubscriptions[luaSubId] = listenerId;
lua_pushinteger(L, luaSubId);
return 1;
}
// ---------------------------------------------------------------------------
// Lua: ecs.unsubscribe_event(subscription_id) -> nil
// ---------------------------------------------------------------------------
/**
* @brief Lua: ecs.unsubscribe_event(subscription_id) -> nil
*
* Unsubscribes a previously registered event subscription.
*
* Usage:
* ecs.unsubscribe_event(sub_id)
*/
static int luaUnsubscribeEvent(lua_State *L)
{
int luaSubId = (int)luaL_checkinteger(L, 1);
auto it = s_luaSubscriptions.find(luaSubId);
if (it != s_luaSubscriptions.end()) {
EventBus::getInstance().unsubscribe(it->second);
s_luaSubscriptions.erase(it);
}
return 0;
}
// ---------------------------------------------------------------------------
// Public registration function
// ---------------------------------------------------------------------------
void registerLuaEventApi(lua_State *L)
{
// Get or create the "ecs" global table
lua_getglobal(L, "ecs");
if (lua_isnil(L, -1)) {
lua_newtable(L);
lua_setglobal(L, "ecs");
lua_getglobal(L, "ecs");
}
// Register event functions
lua_pushcfunction(L, luaSendEvent);
lua_setfield(L, -2, "send_event");
lua_pushcfunction(L, luaSubscribeEvent);
lua_setfield(L, -2, "subscribe_event");
lua_pushcfunction(L, luaUnsubscribeEvent);
lua_setfield(L, -2, "unsubscribe_event");
// Pop the ecs table
lua_pop(L, 1);
}
} // namespace editScene

View File

@@ -0,0 +1,55 @@
#ifndef EDITSCENE_LUA_EVENT_API_HPP
#define EDITSCENE_LUA_EVENT_API_HPP
#pragma once
#include <lua.hpp>
/**
* @file LuaEventApi.hpp
* @brief Lua API for the EventBus system.
*
* Provides Lua bindings for the global EventBus singleton, allowing
* Lua scripts to subscribe to events, send events, and manage
* event subscriptions.
*
* The EventBus is a synchronous publish/subscribe system. Events are
* identified by name strings. Payloads use GoapBlackboard, which
* supports int, float, Vector3, and string values.
*
* Exposed Lua globals (in the "ecs" table):
* ecs.send_event("eventName") -> nil
* ecs.send_event("eventName", {key=value, ...}) -> nil
* ecs.subscribe_event("eventName", callback_fn) -> int (subscription id)
* ecs.unsubscribe_event(subscription_id) -> nil
*
* The callback function receives (event_name, params_table):
* ecs.subscribe_event("collision", function(event, params)
* print(event .. " occurred")
* print("entity_id: " .. params.entity_id)
* end)
*
* The params table contains the GoapBlackboard fields:
* params.bits -> integer (bitfield)
* params.mask -> integer (bitmask)
* params.values -> table {string -> int}
* params.floatValues -> table {string -> float}
* params.vec3Values -> table {string -> {x, y, z}}
* params.stringValues -> table {string -> string}
*/
namespace editScene
{
/**
* @brief Register all event-related Lua API functions into the "ecs" table.
*
* Adds functions for event subscription, unsubscription, and sending.
* Must be called after LuaState is constructed and the "ecs" table exists.
*
* @param L The Lua state.
*/
void registerLuaEventApi(lua_State *L);
} // namespace editScene
#endif // EDITSCENE_LUA_EVENT_API_HPP

View File

@@ -0,0 +1,187 @@
#include "LuaState.hpp"
#include <OgreResourceGroupManager.h>
#include <OgreLogManager.h>
#include <OgreDataStream.h>
#include <iostream>
extern "C" {
int luaopen_lpeg(lua_State *L);
}
namespace editScene
{
// ---------------------------------------------------------------------------
// Custom library loader: loads .lua files from OGRE resource groups
// ---------------------------------------------------------------------------
int LuaState::luaLibraryLoader(lua_State *L)
{
if (!lua_isstring(L, 1)) {
luaL_error(
L,
"luaLibraryLoader: Expected string for first parameter");
}
std::string libraryFile = lua_tostring(L, 1);
// Translate '.' to '/' for OGRE resource path compatibility
while (libraryFile.find('.') != std::string::npos)
libraryFile.replace(libraryFile.find('.'), 1, "/");
libraryFile += ".lua";
Ogre::DataStreamPtr stream =
Ogre::ResourceGroupManager::getSingleton().openResource(
libraryFile, "LuaScripts");
Ogre::String script = stream->getAsString();
if (luaL_loadbuffer(L, script.c_str(), script.length(),
libraryFile.c_str())) {
luaL_error(
L,
"Error loading library '%s' from resource archive.\n%s",
libraryFile.c_str(), lua_tostring(L, -1));
}
return 1;
}
// ---------------------------------------------------------------------------
// Install the custom loader into package.searchers
// ---------------------------------------------------------------------------
void LuaState::installLibraryLoader()
{
lua_getglobal(L, "table");
lua_getfield(L, -1, "insert");
lua_remove(L, -2); // table
lua_getglobal(L, "package");
lua_getfield(L, -1, "searchers");
lua_remove(L, -2); // package
lua_pushnumber(L, 1); // insert at position 1 (highest priority)
lua_pushcfunction(L, luaLibraryLoader);
if (lua_pcall(L, 3, 0, 0))
Ogre::LogManager::getSingleton().stream() << lua_tostring(L, 1);
}
// ---------------------------------------------------------------------------
// Constructor: create Lua state and open standard libraries
// ---------------------------------------------------------------------------
LuaState::LuaState()
: L(luaL_newstate())
{
luaopen_base(L);
luaopen_table(L);
luaopen_package(L);
luaL_requiref(L, "table", luaopen_table, 1);
lua_pop(L, 1);
luaL_requiref(L, "math", luaopen_math, 1);
lua_pop(L, 1);
luaL_requiref(L, "package", luaopen_package, 1);
lua_pop(L, 1);
luaL_requiref(L, "string", luaopen_string, 1);
lua_pop(L, 1);
luaL_requiref(L, "io", luaopen_io, 1);
lua_pop(L, 1);
luaL_requiref(L, "lpeg", luaopen_lpeg, 1);
lua_pop(L, 1);
installLibraryLoader();
lua_pop(L, 1);
}
// ---------------------------------------------------------------------------
// Destructor
// ---------------------------------------------------------------------------
LuaState::~LuaState()
{
lua_close(L);
}
// ---------------------------------------------------------------------------
// Handler registration
// ---------------------------------------------------------------------------
int LuaState::setupHandler()
{
luaL_checktype(L, 1, LUA_TFUNCTION);
lua_pushvalue(L, 1);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
setupHandlers.push_back(ref);
return 0;
}
// ---------------------------------------------------------------------------
// Call all handlers with an event name (no entities)
// ---------------------------------------------------------------------------
int LuaState::callHandler(const Ogre::String &event)
{
for (int ref : setupHandlers) {
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
lua_pushstring(L, event.c_str());
lua_pushinteger(L, -1);
lua_pushinteger(L, -1);
if (lua_pcall(L, 3, 0, 0) != LUA_OK) {
Ogre::LogManager::getSingleton().stream()
<< lua_tostring(L, -1);
OgreAssert(false, "Lua error");
}
}
return 0;
}
// ---------------------------------------------------------------------------
// Call all handlers with an event name and two entities
// ---------------------------------------------------------------------------
int LuaState::callHandler(const Ogre::String &event, flecs::entity e1,
flecs::entity e2)
{
// Entity IDs are mapped through the global idmap (see LuaEntityApi.cpp)
extern int luaEntityToId(flecs::entity e);
extern flecs::entity luaIdToEntity(int id);
for (int ref : setupHandlers) {
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
lua_pushstring(L, event.c_str());
lua_pushinteger(L, luaEntityToId(e1));
lua_pushinteger(L, luaEntityToId(e2));
if (lua_pcall(L, 3, 0, 0) != LUA_OK) {
Ogre::LogManager::getSingleton().stream()
<< lua_tostring(L, -1);
OgreAssert(false, "Lua error");
}
}
return 0;
}
// ---------------------------------------------------------------------------
// Late setup: load data.lua and run initialisation
// ---------------------------------------------------------------------------
void LuaState::lateSetup()
{
Ogre::DataStreamPtr stream =
Ogre::ResourceGroupManager::getSingleton().openResource(
"data.lua", "LuaScripts");
std::cout << "stream: " << stream->getAsString() << "\n";
if (luaL_dostring(L, stream->getAsString().c_str()) != LUA_OK) {
std::cout << "error: " << lua_tostring(L, -1) << "\n";
OgreAssert(false, "Script failure");
}
const char *lua_code = "\n\
function stuff()\n\
return 4\n\
end\n\
x = stuff()\n\
";
luaL_dostring(L, lua_code);
lua_getglobal(L, "x");
int x = lua_tonumber(L, 1);
std::cout << "lua: " << x << "\n";
}
} // namespace editScene

View File

@@ -0,0 +1,137 @@
#ifndef EDITSCENE_LUA_STATE_HPP
#define EDITSCENE_LUA_STATE_HPP
#pragma once
#include <Ogre.h>
#include <lua.hpp>
#include <flecs.h>
#include <string>
#include <vector>
/**
* @file LuaState.hpp
* @brief Lua state management for the editScene editor.
*
* Manages the Lua virtual machine instance, library loading,
* script loading from OGRE resource groups, and the custom
* package.searchers entry that loads Lua modules from
* the "LuaScripts" resource group.
*
* Usage:
* LuaState lua;
* lua.installLibraryLoader(); // Register custom searcher
* lua.lateSetup(); // Load data.lua and run startup
* lua.callHandler("event_name", e1, e2);
*/
namespace editScene
{
/**
* @brief Manages a single lua_State instance.
*
* Opens standard Lua libraries (base, table, math, package, string, io)
* plus LPEG. Installs a custom package.searchers entry that loads
* .lua files from OGRE's "LuaScripts" resource group.
*
* Provides a handler/callback system: Lua scripts can register
* callback functions via setup_handler(), and C++ code can invoke
* them with event names and optional entity parameters.
*/
class LuaState {
public:
/**
* @brief Construct a new Lua state.
*
* Opens all standard libraries and installs the custom
* library loader for OGRE resource-based Lua modules.
*/
LuaState();
/**
* @brief Destroy the Lua state and close the VM.
*/
~LuaState();
/**
* @brief Get the underlying lua_State pointer.
*/
lua_State *getState() const
{
return L;
}
/**
* @brief Install the custom library loader into package.searchers.
*
* Inserts luaLibraryLoader at position 1 of the searchers table
* so it takes precedence over the default file-system loader.
* This allows Lua's require() to load modules from OGRE resource
* archives.
*/
void installLibraryLoader();
/**
* @brief Late setup: load data.lua and run initialisation.
*
* Opens "data.lua" from the "LuaScripts" resource group and
* executes it. Also runs a simple inline Lua test snippet.
* Called once after the ECS world is fully initialised.
*/
void lateSetup();
/**
* @brief Register a Lua callback function for event handling.
*
* Expects a Lua function on the stack (at index 1).
* Stores a reference to it in the registry so it can be
* called later via callHandler().
*
* @return int Reference index (stored internally).
*/
int setupHandler();
/**
* @brief Call all registered handlers with an event name.
*
* @param event The event name string.
* @return int 0 on success.
*/
int callHandler(const Ogre::String &event);
/**
* @brief Call all registered handlers with an event name and
* two entity IDs.
*
* Entity IDs are mapped through the global idmap (see LuaEntityApi).
*
* @param event The event name string.
* @param e1 First entity (e.g. subject).
* @param e2 Second entity (e.g. object).
* @return int 0 on success.
*/
int callHandler(const Ogre::String &event, flecs::entity e1,
flecs::entity e2);
private:
lua_State *L;
/** Registry references for registered Lua callback functions. */
std::vector<int> setupHandlers;
/**
* @brief Custom Lua loader function for OGRE resource-based modules.
*
* Registered in package.searchers. Translates dots to path
* separators, appends ".lua", and loads the file from the
* "LuaScripts" OGRE resource group.
*
* @param L Lua state.
* @return int 1 (pushes loaded chunk onto stack) or error.
*/
static int luaLibraryLoader(lua_State *L);
};
} // namespace editScene
#endif // EDITSCENE_LUA_STATE_HPP