Skip to content

Commit

Permalink
ElunaTemplate refactor (#492)
Browse files Browse the repository at this point in the history
* Merge the ElunaGlobal and the ElunaTemplate classes
* Misc. refactoring of SetMethods and thunk to accommodate the merge
* Clean up the method headers and remove no longer needed end of array entries
* Add method names to output for wrong state and not implemented errors
  • Loading branch information
Foereaper authored Aug 7, 2024
1 parent 0844d57 commit de28e84
Show file tree
Hide file tree
Showing 78 changed files with 452 additions and 637 deletions.
169 changes: 68 additions & 101 deletions ElunaTemplate.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,80 +26,6 @@ extern "C"
#include "UniqueTrackablePtr.h"
#endif

class ElunaGlobal
{
public:
struct ElunaRegister
{
const char* name;
int(*func)(Eluna*);
MethodRegisterState regState = METHOD_REG_ALL;
};

static int thunk(lua_State* L)
{
ElunaRegister* l = static_cast<ElunaRegister*>(lua_touserdata(L, lua_upvalueindex(1)));
Eluna* E = static_cast<Eluna*>(lua_touserdata(L, lua_upvalueindex(2)));
int top = lua_gettop(L);
int expected = l->func(E);
int args = lua_gettop(L) - top;
if (args < 0 || args > expected)
{
ELUNA_LOG_ERROR("[Eluna]: %s returned unexpected amount of arguments %i out of %i. Report to devs", l->name, args, expected);
ASSERT(false);
}
lua_settop(L, top + expected);
return expected;
}

static void SetMethods(Eluna* E, ElunaRegister* methodTable)
{
ASSERT(E);
ASSERT(methodTable);

lua_pushglobaltable(E->L);

for (; methodTable && methodTable->name; ++methodTable)
{
lua_pushstring(E->L, methodTable->name);

// if the method should not be registered, push a closure to error output function
if (methodTable->regState == METHOD_REG_NONE)
{
lua_pushcclosure(E->L, MethodUnimpl, 0);
lua_rawset(E->L, -3);
continue;
}

// if we're in multistate mode, we need to check whether a method is flagged as a world or a map specific method
if (!E->GetCompatibilityMode() && methodTable->regState != METHOD_REG_ALL)
{
// if the method should not be registered, push a closure to error output function
if ((E->GetBoundMapId() == -1 && methodTable->regState == METHOD_REG_MAP) ||
(E->GetBoundMapId() != -1 && methodTable->regState == METHOD_REG_WORLD))
{
lua_pushcclosure(E->L, MethodWrongState, 0);
lua_rawset(E->L, -3);
continue;
}
}

// push method table and Eluna object pointers as light user data
lua_pushlightuserdata(E->L, (void*)methodTable);
lua_pushlightuserdata(E->L, (void*)E);

// push a closure to the thunk function with 2 upvalues (method table and Eluna object)
lua_pushcclosure(E->L, thunk, 2);
lua_rawset(E->L, -3);
}

lua_remove(E->L, -1);
}

static int MethodWrongState(lua_State* L) { luaL_error(L, "attempt to call a method that does not exist for state: %d", Eluna::GetEluna(L)->GetBoundMapId()); return 0; }
static int MethodUnimpl(lua_State* L) { luaL_error(L, "attempt to call a method that is not implemented for this emulator"); return 0; }
};

class ElunaObject
{
public:
Expand Down Expand Up @@ -226,15 +152,27 @@ MAKE_ELUNA_OBJECT_VALUE_IMPL(ObjectGuid);
MAKE_ELUNA_OBJECT_VALUE_IMPL(WorldPacket);
MAKE_ELUNA_OBJECT_VALUE_IMPL(ElunaQuery);

template<typename T>
template<typename T = void>
struct ElunaRegister
{
const char* name;
int(*mfunc)(Eluna*, T*);
MethodRegisterState regState = METHOD_REG_ALL;
typename std::conditional<std::is_same_v<T, void>, int(*)(Eluna*), int(*)(Eluna*, T*)>::type mfunc;
MethodRegisterState regState;

// constructor for non-globals (with T*)
ElunaRegister(const char* name, int(*func)(Eluna*, T*), MethodRegisterState state = METHOD_REG_ALL)
: name(name), mfunc(func), regState(state) {}

// constructor for globals (without T*)
ElunaRegister(const char* name, int(*func)(Eluna*), MethodRegisterState state = METHOD_REG_ALL)
: name(name), mfunc(func), regState(state) {}

// constructor for nullptr functions and METHOD_REG_NONE (unimplemented methods)
ElunaRegister(const char* name, MethodRegisterState state = METHOD_REG_NONE)
: name(name), mfunc(nullptr), regState(state) {}
};

template<typename T>
template<typename T = void>
class ElunaTemplate
{
public:
Expand All @@ -260,7 +198,7 @@ class ElunaTemplate

// create metatable for userdata of this type
luaL_newmetatable(E->L, tname);
int metatable = lua_gettop(E->L);
int metatable = lua_gettop(E->L);

// push methodtable to stack to be accessed and modified by users
lua_pushvalue(E->L, metatable);
Expand Down Expand Up @@ -339,47 +277,62 @@ class ElunaTemplate
lua_pop(E->L, 1);
}

template<typename C>
static void SetMethods(Eluna* E, ElunaRegister<C>* methodTable)
template<typename C, size_t N>
static void SetMethods(Eluna* E, ElunaRegister<C> const (&methodTable)[N])
{
ASSERT(E);
ASSERT(tname);
ASSERT(methodTable);

// get metatable
lua_pushstring(E->L, tname);
lua_rawget(E->L, LUA_REGISTRYINDEX);
ASSERT(lua_istable(E->L, -1));
// determine if the method table functions are global or non-global
constexpr bool isGlobal = std::is_same_v<C, void>;

if constexpr (isGlobal)
{
lua_pushglobaltable(E->L);
}
else
{
ASSERT(tname);

// get metatable
lua_pushstring(E->L, tname);
lua_rawget(E->L, LUA_REGISTRYINDEX);
ASSERT(lua_istable(E->L, -1));
}

// load all core-specific methods
for (; methodTable && methodTable->name; ++methodTable)
for (std::size_t i = 0; i < N; i++)
{
const auto& method = methodTable + i;

// push the method name to the Lua stack
lua_pushstring(E->L, methodTable->name);
lua_pushstring(E->L, method->name);

// if the method should not be registered, push a closure to error output function
if (methodTable->regState == METHOD_REG_NONE)
if (method->regState == METHOD_REG_NONE)
{
lua_pushcclosure(E->L, MethodUnimpl, 0);
lua_pushstring(E->L, method->name);
lua_pushcclosure(E->L, MethodUnimpl, 1);
lua_rawset(E->L, -3);
continue;
}

// if we're in multistate mode, we need to check whether a method is flagged as a world or a map specific method
if (!E->GetCompatibilityMode() && methodTable->regState != METHOD_REG_ALL)
if (!E->GetCompatibilityMode() && method->regState != METHOD_REG_ALL)
{
// if the method should not be registered, push a closure to error output function
if ((E->GetBoundMapId() == -1 && methodTable->regState == METHOD_REG_MAP) ||
(E->GetBoundMapId() != -1 && methodTable->regState == METHOD_REG_WORLD))
if ((E->GetBoundMapId() == -1 && method->regState == METHOD_REG_MAP) ||
(E->GetBoundMapId() != -1 && method->regState == METHOD_REG_WORLD))
{
lua_pushcclosure(E->L, MethodWrongState, 0);
lua_pushstring(E->L, method->name);
lua_pushcclosure(E->L, MethodWrongState, 1);
lua_rawset(E->L, -3);
continue;
}
}

// push method table and Eluna object pointers as light user data
lua_pushlightuserdata(E->L, (void*)methodTable);
lua_pushlightuserdata(E->L, (void*)method);
lua_pushlightuserdata(E->L, (void*)E);

// push a closure to the thunk function with 2 upvalues (method table and Eluna object)
Expand Down Expand Up @@ -462,12 +415,26 @@ class ElunaTemplate
ElunaRegister<T>* l = static_cast<ElunaRegister<T>*>(lua_touserdata(L, lua_upvalueindex(1)));
Eluna* E = static_cast<Eluna*>(lua_touserdata(L, lua_upvalueindex(2)));

T* obj = E->CHECKOBJ<T>(1); // get self
if (!obj)
return 0;
// determine if the method table functions are global or non-global
constexpr bool isGlobal = std::is_same_v<T, void>;

// we only check self if the method is not a global
T* obj;
if constexpr (!isGlobal)
{
obj = E->CHECKOBJ<T>(1);
if (!obj)
return 0;
}

int top = lua_gettop(L);
int expected = l->mfunc(E, obj);

int expected = 0;
if constexpr (isGlobal)
expected = l->mfunc(E); // global method
else
expected = l->mfunc(E, obj); // non-global method

int args = lua_gettop(L) - top;
if (args < 0 || args > expected)
{
Expand Down Expand Up @@ -516,8 +483,8 @@ class ElunaTemplate
static int LessOrEqual(lua_State* L) { return CompareError(L); }
static int Call(lua_State* L) { return luaL_error(L, "attempt to call a %s value", tname); }

static int MethodWrongState(lua_State* L) { luaL_error(L, "attempt to call a method that does not exist for state: %d", Eluna::GetEluna(L)->GetBoundMapId()); return 0; }
static int MethodUnimpl(lua_State* L) { luaL_error(L, "attempt to call a method that is not implemented for this emulator"); return 0; }
static int MethodWrongState(lua_State* L) { luaL_error(L, "attempt to call method '%s' that does not exist for state: %d", lua_tostring(L, lua_upvalueindex(1)), Eluna::GetEluna(L)->GetBoundMapId()); return 0; }
static int MethodUnimpl(lua_State* L) { luaL_error(L, "attempt to call method '%s' that is not implemented for this emulator", lua_tostring(L, lua_upvalueindex(1))); return 0; }
};

template<typename T> const char* ElunaTemplate<T>::tname = NULL;
Expand Down
2 changes: 1 addition & 1 deletion LuaFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ template<> int ElunaTemplate<ObjectGuid>::ToString(lua_State* L)

void RegisterFunctions(Eluna* E)
{
ElunaGlobal::SetMethods(E, LuaGlobalFunctions::GlobalMethods);
ElunaTemplate<>::SetMethods(E, LuaGlobalFunctions::GlobalMethods);

ElunaTemplate<Object>::Register(E, "Object");
ElunaTemplate<Object>::SetMethods(E, LuaObject::ObjectMethods);
Expand Down
4 changes: 1 addition & 3 deletions methods/CMangos/AuraMethods.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,7 @@ namespace LuaAura
{ "SetStackAmount", &LuaAura::SetStackAmount },

// Other
{ "Remove", &LuaAura::Remove },

{ NULL, NULL, METHOD_REG_NONE }
{ "Remove", &LuaAura::Remove }
};
};
#endif
4 changes: 1 addition & 3 deletions methods/CMangos/BattleGroundMethods.h
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,7 @@ namespace LuaBattleGround
{ "GetMaxPlayersPerTeam", &LuaBattleGround::GetMaxPlayersPerTeam },
{ "GetMinPlayersPerTeam", &LuaBattleGround::GetMinPlayersPerTeam },
{ "GetWinner", &LuaBattleGround::GetWinner },
{ "GetStatus", &LuaBattleGround::GetStatus },

{ NULL, NULL, METHOD_REG_NONE }
{ "GetStatus", &LuaBattleGround::GetStatus }
};
};
#endif
4 changes: 1 addition & 3 deletions methods/CMangos/CorpseMethods.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,7 @@ namespace LuaCorpse

// Other
{ "ResetGhostTime", &LuaCorpse::ResetGhostTime },
{ "SaveToDB", &LuaCorpse::SaveToDB },

{ NULL, NULL, METHOD_REG_NONE }
{ "SaveToDB", &LuaCorpse::SaveToDB }
};
};
#endif
44 changes: 21 additions & 23 deletions methods/CMangos/CreatureMethods.h
Original file line number Diff line number Diff line change
Expand Up @@ -1222,7 +1222,7 @@ namespace LuaCreature
#ifndef CATA
{ "GetShieldBlockValue", &LuaCreature::GetShieldBlockValue },
#else
{ "GetShieldBlockValue", nullptr, METHOD_REG_NONE },
{ "GetShieldBlockValue", METHOD_REG_NONE },
#endif

// Setters
Expand All @@ -1243,7 +1243,7 @@ namespace LuaCreature
#ifndef CATA
{ "SetDisableReputationGain", &LuaCreature::SetDisableReputationGain },
#else
{ "SetDisableReputationGain", nullptr, METHOD_REG_NONE },
{ "SetDisableReputationGain", METHOD_REG_NONE },
#endif

// Boolean
Expand Down Expand Up @@ -1271,7 +1271,7 @@ namespace LuaCreature
#ifndef CATA
{ "IsReputationGainDisabled", &LuaCreature::IsReputationGainDisabled },
#else
{ "IsReputationGainDisabled", nullptr, METHOD_REG_NONE },
{ "IsReputationGainDisabled", METHOD_REG_NONE },
#endif

// Other
Expand All @@ -1291,26 +1291,24 @@ namespace LuaCreature
{ "RemoveFromWorld", &LuaCreature::RemoveFromWorld },

// Not implemented methods
{ "GetWaypointPath", nullptr, METHOD_REG_NONE }, // TC/Acore
{ "GetLootMode", nullptr, METHOD_REG_NONE }, // TC/Acore
{ "SetRegeneratingHealth", nullptr, METHOD_REG_NONE }, // TC/Acore
{ "SetLootMode", nullptr, METHOD_REG_NONE }, // TC/Acore
{ "SetReactState", nullptr, METHOD_REG_NONE }, // TC/Acore
{ "IsDungeonBoss", nullptr, METHOD_REG_NONE }, // TC/Acore
{ "IsTrigger", nullptr, METHOD_REG_NONE }, // TC/Acore
{ "CanStartAttack", nullptr, METHOD_REG_NONE }, // TC/Acore
{ "IsDamageEnoughForLootingAndReward", nullptr, METHOD_REG_NONE }, // TC/Acore
{ "HasLootMode", nullptr, METHOD_REG_NONE }, // TC/Acore
{ "AddLootMode", nullptr, METHOD_REG_NONE }, // TC/Acore
{ "ResetLootMode", nullptr, METHOD_REG_NONE }, // TC/Acore
{ "RemoveLootMode", nullptr, METHOD_REG_NONE }, // TC/Acore
{ "GetThreat", nullptr, METHOD_REG_NONE }, // TC/Acore
{ "ClearThreat", nullptr, METHOD_REG_NONE }, // TC/Acore
{ "ResetAllThreat", nullptr, METHOD_REG_NONE }, // TC/Acore
{ "FixateTarget", nullptr, METHOD_REG_NONE }, // TC/Acore
{ "ClearFixate", nullptr, METHOD_REG_NONE }, // TC/Acore

{ NULL, NULL, METHOD_REG_NONE }
{ "GetWaypointPath", METHOD_REG_NONE }, // TC/Acore
{ "GetLootMode", METHOD_REG_NONE }, // TC/Acore
{ "SetRegeneratingHealth", METHOD_REG_NONE }, // TC/Acore
{ "SetLootMode", METHOD_REG_NONE }, // TC/Acore
{ "SetReactState", METHOD_REG_NONE }, // TC/Acore
{ "IsDungeonBoss", METHOD_REG_NONE }, // TC/Acore
{ "IsTrigger", METHOD_REG_NONE }, // TC/Acore
{ "CanStartAttack", METHOD_REG_NONE }, // TC/Acore
{ "IsDamageEnoughForLootingAndReward", METHOD_REG_NONE }, // TC/Acore
{ "HasLootMode", METHOD_REG_NONE }, // TC/Acore
{ "AddLootMode", METHOD_REG_NONE }, // TC/Acore
{ "ResetLootMode", METHOD_REG_NONE }, // TC/Acore
{ "RemoveLootMode", METHOD_REG_NONE }, // TC/Acore
{ "GetThreat", METHOD_REG_NONE }, // TC/Acore
{ "ClearThreat", METHOD_REG_NONE }, // TC/Acore
{ "ResetAllThreat", METHOD_REG_NONE }, // TC/Acore
{ "FixateTarget", METHOD_REG_NONE }, // TC/Acore
{ "ClearFixate", METHOD_REG_NONE } // TC/Acore
};
};
#endif
4 changes: 1 addition & 3 deletions methods/CMangos/ElunaQueryMethods.h
Original file line number Diff line number Diff line change
Expand Up @@ -333,9 +333,7 @@ namespace LuaQuery

// Boolean
{ "NextRow", &LuaQuery::NextRow },
{ "IsNull", &LuaQuery::IsNull },

{ NULL, NULL, METHOD_REG_NONE }
{ "IsNull", &LuaQuery::IsNull }
};
};
#undef RESULT
Expand Down
4 changes: 1 addition & 3 deletions methods/CMangos/GameObjectMethods.h
Original file line number Diff line number Diff line change
Expand Up @@ -332,9 +332,7 @@ namespace LuaGameObject
{ "SaveToDB", &LuaGameObject::SaveToDB },

// Not implemented methods
{ "IsDestructible", nullptr, METHOD_REG_NONE }, // Not implemented

{ NULL, NULL, METHOD_REG_NONE }
{ "IsDestructible", METHOD_REG_NONE } // Not implemented
};
};
#endif
6 changes: 2 additions & 4 deletions methods/CMangos/GlobalMethods.h
Original file line number Diff line number Diff line change
Expand Up @@ -3180,7 +3180,7 @@ namespace LuaGlobalFunctions
return 0;
}

ElunaGlobal::ElunaRegister GlobalMethods[] =
ElunaRegister<> GlobalMethods[] =
{
// Hooks
{ "RegisterPacketEvent", &LuaGlobalFunctions::RegisterPacketEvent },
Expand Down Expand Up @@ -3295,9 +3295,7 @@ namespace LuaGlobalFunctions
{ "CreateInt64", &LuaGlobalFunctions::CreateLongLong },
{ "CreateUint64", &LuaGlobalFunctions::CreateULongLong },
{ "StartGameEvent", &LuaGlobalFunctions::StartGameEvent },
{ "StopGameEvent", &LuaGlobalFunctions::StopGameEvent },

{ NULL, NULL, METHOD_REG_NONE }
{ "StopGameEvent", &LuaGlobalFunctions::StopGameEvent }
};
}
#endif
Loading

0 comments on commit de28e84

Please sign in to comment.