This commit is contained in:
Jan Racek
2026-03-24 13:01:47 +01:00
parent 55c1d2c39b
commit e51c06b012
51 changed files with 8111 additions and 491 deletions

107
src/lua.h
View File

@@ -17,13 +17,50 @@ struct LuaFile {
uint32_t length;
};
/**
* Event bitmask flags - each bit represents whether an object handles that event.
* This allows O(1) checking before calling into Lua VM.
*
* CRITICAL: The PS1 cannot afford to call into Lua for events objects don't handle.
* When registering a GameObject, we scan its script and set these bits.
* During dispatch, we check the bit FIRST before any Lua VM access.
*/
enum EventMask : uint32_t {
EVENT_NONE = 0,
EVENT_ON_CREATE = 1 << 0,
EVENT_ON_COLLISION = 1 << 1,
EVENT_ON_INTERACT = 1 << 2,
EVENT_ON_TRIGGER_ENTER = 1 << 3,
EVENT_ON_TRIGGER_STAY = 1 << 4,
EVENT_ON_TRIGGER_EXIT = 1 << 5,
EVENT_ON_UPDATE = 1 << 6,
EVENT_ON_DESTROY = 1 << 7,
EVENT_ON_ENABLE = 1 << 8,
EVENT_ON_DISABLE = 1 << 9,
EVENT_ON_BUTTON_PRESS = 1 << 10,
EVENT_ON_BUTTON_RELEASE = 1 << 11,
};
class Lua {
public:
void Init();
void Reset(); // Destroy and recreate the Lua VM (call on scene load)
void Shutdown(); // Close the Lua VM without recreating (call on scene unload)
void LoadLuaFile(const char* code, size_t len, int index);
void RegisterSceneScripts(int index);
void RegisterGameObject(GameObject* go);
// Get the underlying psyqo::Lua state for API registration
psyqo::Lua& getState() { return m_state; }
/**
* Check if a GameObject handles a specific event.
* Call this BEFORE attempting to dispatch any event.
*/
bool hasEvent(GameObject* go, EventMask event) const {
return (go->eventMask & event) != 0;
}
void OnSceneCreationStart() {
onSceneCreationStartFunctionWrapper.callFunction(*this);
@@ -31,8 +68,19 @@ class Lua {
void OnSceneCreationEnd() {
onSceneCreationEndFunctionWrapper.callFunction(*this);
}
// Event dispatchers - these check the bitmask before calling Lua
void OnCollision(GameObject* self, GameObject* other);
void OnInteract(GameObject* self);
void OnTriggerEnter(GameObject* trigger, GameObject* other);
void OnTriggerStay(GameObject* trigger, GameObject* other);
void OnTriggerExit(GameObject* trigger, GameObject* other);
void OnUpdate(GameObject* go, int deltaFrames); // Per-object update
void OnDestroy(GameObject* go);
void OnEnable(GameObject* go);
void OnDisable(GameObject* go);
void OnButtonPress(GameObject* go, int button);
void OnButtonRelease(GameObject* go, int button);
private:
template <int methodId, typename methodName>
@@ -40,26 +88,31 @@ class Lua {
template <int methodId, char... C>
struct FunctionWrapper<methodId, irqus::typestring<C...>> {
typedef irqus::typestring<C...> methodName;
// Needs the methods table at index 1, and the script environment table at index 3
static void resolveGlobal(psyqo::Lua L) {
// Push the method name string to access the environment table
// Returns true if the function was found and stored
static bool resolveGlobal(psyqo::Lua L) {
L.push(methodName::data(), methodName::size());
L.getTable(3);
if (L.isFunction(-1)) {
// Store the function in methods table using numeric ID as key
L.pushNumber(methodId); // Push numeric key for methods table
L.copy(-2); // Push the function (copy from top -2)
L.setTable(1); // methodsTable[methodId] = function
L.pushNumber(methodId);
L.copy(-2);
L.setTable(1);
L.pop(); // Pop the function
return true;
} else {
L.pop(); // Pop the non-function value
L.pop();
return false;
}
}
template <typename... Args>
static void pushArgs(psxsplash::Lua& lua, Args... args) {
(push(lua, args), ...);
}
static void push(psxsplash::Lua& lua, GameObject* go) { lua.PushGameObject(go); }
static void push(psxsplash::Lua& lua, int val) { lua.m_state.pushNumber(val); }
template <typename... Args>
static void callMethod(psxsplash::Lua& lua, GameObject* go, Args... args) {
auto L = lua.m_state;
@@ -78,11 +131,16 @@ class Lua {
}
L.clearStack();
}
template <typename... Args>
static void callFunction(psxsplash::Lua& lua, Args... args) {
auto L = lua.m_state;
L.push(methodName::data(), methodName::size());
L.rawGetI(LUA_REGISTRYINDEX, lua.m_metatableReference);
L.rawGetI(LUA_REGISTRYINDEX, lua.m_luaSceneScriptsReference);
if (!L.isTable(-1)) {
L.clearStack();
return;
}
L.rawGetI(-1, methodId);
if (!L.isFunction(-1)) {
L.clearStack();
return;
@@ -95,17 +153,34 @@ class Lua {
}
};
// Scene-level events (methodId 1-2)
[[no_unique_address]] FunctionWrapper<1, typestring_is("onSceneCreationStart")> onSceneCreationStartFunctionWrapper;
[[no_unique_address]] FunctionWrapper<2, typestring_is("onSceneCreationEnd")> onSceneCreationEndFunctionWrapper;
[[no_unique_address]] FunctionWrapper<1, typestring_is("onCreate")> onCreateMethodWrapper;
[[no_unique_address]] FunctionWrapper<2, typestring_is("onCollision")> onCollisionMethodWrapper;
[[no_unique_address]] FunctionWrapper<3, typestring_is("onInteract")> onInteractMethodWrapper;
// Object-level events (methodId 100-111, offset to avoid collision with scene events)
[[no_unique_address]] FunctionWrapper<100, typestring_is("onCreate")> onCreateMethodWrapper;
[[no_unique_address]] FunctionWrapper<101, typestring_is("onCollision")> onCollisionMethodWrapper;
[[no_unique_address]] FunctionWrapper<102, typestring_is("onInteract")> onInteractMethodWrapper;
[[no_unique_address]] FunctionWrapper<103, typestring_is("onTriggerEnter")> onTriggerEnterMethodWrapper;
[[no_unique_address]] FunctionWrapper<104, typestring_is("onTriggerStay")> onTriggerStayMethodWrapper;
[[no_unique_address]] FunctionWrapper<105, typestring_is("onTriggerExit")> onTriggerExitMethodWrapper;
[[no_unique_address]] FunctionWrapper<106, typestring_is("onUpdate")> onUpdateMethodWrapper;
[[no_unique_address]] FunctionWrapper<107, typestring_is("onDestroy")> onDestroyMethodWrapper;
[[no_unique_address]] FunctionWrapper<108, typestring_is("onEnable")> onEnableMethodWrapper;
[[no_unique_address]] FunctionWrapper<109, typestring_is("onDisable")> onDisableMethodWrapper;
[[no_unique_address]] FunctionWrapper<110, typestring_is("onButtonPress")> onButtonPressMethodWrapper;
[[no_unique_address]] FunctionWrapper<111, typestring_is("onButtonRelease")> onButtonReleaseMethodWrapper;
void PushGameObject(GameObject* go);
private:
psyqo::Lua m_state;
int m_metatableReference;
int m_luascriptsReference;
int m_luaSceneScriptsReference;
int m_metatableReference = LUA_NOREF;
int m_luascriptsReference = LUA_NOREF;
int m_luaSceneScriptsReference = LUA_NOREF;
// Event mask now stored inline in GameObject::eventMask
template <int methodId, typename methodName>
friend struct FunctionWrapper;