Lua/LuaClass: Refactor Register() implementation
This commit is contained in:
parent
373b8a7069
commit
3f7f12b625
|
|
@ -66,6 +66,16 @@ namespace Nz
|
|||
void SetStaticSetter(StaticIndexFunc getter);
|
||||
|
||||
private:
|
||||
void PushClassInfo(LuaInstance& lua);
|
||||
void SetupConstructor(LuaInstance& lua);
|
||||
void SetupDefaultToString(LuaInstance& lua);
|
||||
void SetupFinalizer(LuaInstance& lua);
|
||||
void SetupGetter(LuaInstance& lua, LuaCFunction proxy);
|
||||
void SetupGlobalTable(LuaInstance& lua);
|
||||
void SetupMetatable(LuaInstance& lua);
|
||||
void SetupMethod(LuaInstance& lua, LuaCFunction proxy, const String& name, std::size_t methodIndex);
|
||||
void SetupSetter(LuaInstance& lua, LuaCFunction proxy);
|
||||
|
||||
using ParentFunc = std::function<void(LuaInstance& lua, T* instance)>;
|
||||
using InstanceGetter = std::function<T*(LuaInstance& lua)>;
|
||||
|
||||
|
|
|
|||
|
|
@ -69,149 +69,25 @@ namespace Nz
|
|||
{
|
||||
m_info = std::make_shared<ClassInfo>();
|
||||
m_info->name = name;
|
||||
|
||||
m_info->instanceGetters[m_info->name] = [info = m_info] (LuaInstance& instance)
|
||||
{
|
||||
return static_cast<T*>(instance.CheckUserdata(1, info->name));
|
||||
};
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void LuaClass<T>::Register(LuaInstance& lua)
|
||||
{
|
||||
// Le ClassInfo doit rester en vie jusqu'à la fin du script
|
||||
// Obliger l'instance de LuaClass à rester en vie dans cette fin serait contraignant pour l'utilisateur
|
||||
// J'utilise donc une astuce, la stocker dans une UserData associée avec chaque fonction de la metatable du type,
|
||||
// cette UserData disposera d'un finalizer qui libérera le ClassInfo
|
||||
// Ainsi c'est Lua qui va s'occuper de la destruction pour nous :-)
|
||||
// De même, l'utilisation d'un shared_ptr permet de garder la structure en vie même si l'instance est libérée avant le LuaClass
|
||||
std::shared_ptr<ClassInfo>* info = static_cast<std::shared_ptr<ClassInfo>*>(lua.PushUserdata(sizeof(std::shared_ptr<ClassInfo>)));
|
||||
PlacementNew(info, m_info);
|
||||
PushClassInfo(lua);
|
||||
|
||||
// On créé la table qui contiendra une méthode (Le finalizer) pour libérer le ClassInfo
|
||||
lua.PushTable(0, 1);
|
||||
lua.PushLightUserdata(info);
|
||||
lua.PushCFunction(InfoDestructor, 1);
|
||||
lua.SetField("__gc");
|
||||
lua.SetMetatable(-2); // La table devient la metatable de l'UserData
|
||||
|
||||
// Maintenant, nous allons associer l'UserData avec chaque fonction, de sorte qu'il reste en vie
|
||||
// aussi longtemps que nécessaire, et que le pointeur soit transmis à chaque méthode
|
||||
|
||||
if (!lua.NewMetatable(m_info->name))
|
||||
NazaraWarning("Class \"" + m_info->name + "\" already registred in this instance");
|
||||
{
|
||||
// Set the type in a __type field
|
||||
lua.PushString(m_info->name);
|
||||
lua.SetField("__type");
|
||||
|
||||
// In case a __tostring method is missing, add a default implementation returning the type
|
||||
if (m_methods.find("__tostring") == m_methods.end())
|
||||
{
|
||||
// Define the Finalizer
|
||||
lua.PushValue(1); // shared_ptr on UserData
|
||||
lua.PushCFunction(ToStringProxy, 1);
|
||||
lua.SetField("__tostring");
|
||||
}
|
||||
|
||||
// Define the Finalizer
|
||||
lua.PushValue(1);
|
||||
lua.PushCFunction(FinalizerProxy, 1);
|
||||
lua.SetField("__gc");
|
||||
|
||||
if (m_info->getter || !m_info->parentGetters.empty())
|
||||
{
|
||||
lua.PushValue(1); // shared_ptr on UserData
|
||||
lua.PushValue(-2); // Metatable
|
||||
lua.PushCFunction(GetterProxy, 2);
|
||||
}
|
||||
else
|
||||
// Optimisation, plutôt que de rediriger vers une fonction C qui ne fera rien d'autre que rechercher
|
||||
// dans la table, nous envoyons directement la table, de sorte que Lua fasse directement la recherche
|
||||
// Ceci n'est possible que si nous n'avons ni getter, ni parent
|
||||
lua.PushValue(-1); // Metatable
|
||||
|
||||
lua.SetField("__index"); // Getter
|
||||
|
||||
if (m_info->setter)
|
||||
{
|
||||
lua.PushValue(1); // shared_ptr on UserData
|
||||
lua.PushCFunction(SetterProxy, 1);
|
||||
lua.SetField("__newindex"); // Setter
|
||||
}
|
||||
|
||||
m_info->methods.reserve(m_methods.size());
|
||||
for (auto& pair : m_methods)
|
||||
{
|
||||
std::size_t methodIndex = m_info->methods.size();
|
||||
m_info->methods.push_back(pair.second);
|
||||
|
||||
lua.PushValue(1); // shared_ptr on UserData
|
||||
lua.PushInteger(methodIndex);
|
||||
|
||||
lua.PushCFunction(MethodProxy, 2);
|
||||
lua.SetField(pair.first); // Method name
|
||||
}
|
||||
|
||||
m_info->instanceGetters[m_info->name] = [info = m_info] (LuaInstance& instance)
|
||||
{
|
||||
return static_cast<T*>(instance.CheckUserdata(1, info->name));
|
||||
};
|
||||
}
|
||||
lua.Pop(); // On pop la metatable
|
||||
// Let's create the metatable which will be associated with every instance.
|
||||
SetupMetatable(lua);
|
||||
|
||||
if (m_info->constructor || m_info->staticGetter || m_info->staticSetter || !m_info->staticMethods.empty())
|
||||
{
|
||||
// Création de l'instance globale
|
||||
lua.PushTable(); // Class = {}
|
||||
SetupGlobalTable(lua);
|
||||
|
||||
// Création de la metatable associée à la table globale
|
||||
lua.PushTable(); // ClassMeta = {}
|
||||
|
||||
if (m_info->constructor)
|
||||
{
|
||||
lua.PushValue(1); // ClassInfo
|
||||
lua.PushCFunction(ConstructorProxy, 1);
|
||||
lua.SetField("__call"); // ClassMeta.__call = ConstructorProxy
|
||||
}
|
||||
|
||||
if (m_info->staticGetter)
|
||||
{
|
||||
lua.PushValue(1); // shared_ptr on UserData
|
||||
lua.PushValue(-2); // ClassMeta
|
||||
lua.PushCFunction(StaticGetterProxy, 2);
|
||||
}
|
||||
else
|
||||
// Optimisation, plutôt que de rediriger vers une fonction C qui ne fera rien d'autre que rechercher
|
||||
// dans la table, nous envoyons directement la table, de sorte que Lua fasse directement la recherche
|
||||
// Ceci n'est possible que si nous n'avons ni getter, ni parent
|
||||
lua.PushValue(-1); // ClassMeta
|
||||
|
||||
lua.SetField("__index"); // ClassMeta.__index = StaticGetterProxy/ClassMeta
|
||||
|
||||
if (m_info->staticSetter)
|
||||
{
|
||||
lua.PushValue(1); // shared_ptr on UserData
|
||||
lua.PushCFunction(StaticSetterProxy, 1);
|
||||
lua.SetField("__newindex"); // ClassMeta.__newindex = StaticSetterProxy
|
||||
}
|
||||
|
||||
m_info->staticMethods.reserve(m_staticMethods.size());
|
||||
for (auto& pair : m_staticMethods)
|
||||
{
|
||||
std::size_t methodIndex = m_info->staticMethods.size();
|
||||
m_info->staticMethods.push_back(pair.second);
|
||||
|
||||
lua.PushValue(1); // shared_ptr on UserData
|
||||
lua.PushInteger(methodIndex);
|
||||
|
||||
lua.PushCFunction(StaticMethodProxy, 2);
|
||||
lua.SetField(pair.first); // ClassMeta.method = StaticMethodProxy
|
||||
}
|
||||
|
||||
lua.SetMetatable(-2); // setmetatable(Class, ClassMeta)
|
||||
|
||||
lua.PushValue(-1); // Copie
|
||||
lua.SetGlobal(m_info->name); // Class
|
||||
|
||||
m_info->globalTableRef = lua.CreateReference();
|
||||
}
|
||||
lua.Pop(); // On pop l'Userdata (contenant nos informations)
|
||||
lua.Pop(); // Pop our ClassInfo, which is now referenced by all our functions
|
||||
}
|
||||
|
||||
template<class T>
|
||||
|
|
@ -338,6 +214,145 @@ namespace Nz
|
|||
m_info->staticSetter = setter;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void LuaClass<T>::PushClassInfo(LuaInstance& lua)
|
||||
{
|
||||
// Our ClassInfo has to outlive the LuaClass, because we don't want to force the user to keep the LuaClass alive
|
||||
// To do that, each Registration creates a tiny shared_ptr wrapper whose life is directly managed by Lua.
|
||||
// This shared_ptr object gets pushed as a up-value for every proxy function set in the metatable.
|
||||
// This way, there is no way our ClassInfo gets freed before any instance and the global class gets destroyed.
|
||||
std::shared_ptr<ClassInfo>* info = static_cast<std::shared_ptr<ClassInfo>*>(lua.PushUserdata(sizeof(std::shared_ptr<ClassInfo>)));
|
||||
PlacementNew(info, m_info);
|
||||
|
||||
// Setup a tiny metatable to let Lua know how to destroy our ClassInfo
|
||||
lua.PushTable(0, 1);
|
||||
lua.PushLightUserdata(info);
|
||||
lua.PushCFunction(InfoDestructor, 1);
|
||||
lua.SetField("__gc");
|
||||
lua.SetMetatable(-2);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void LuaClass<T>::SetupConstructor(LuaInstance& lua)
|
||||
{
|
||||
lua.PushValue(1); // ClassInfo
|
||||
lua.PushCFunction(ConstructorProxy, 1);
|
||||
lua.SetField("__call"); // ClassMeta.__call = ConstructorProxy
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void LuaClass<T>::SetupDefaultToString(LuaInstance& lua)
|
||||
{
|
||||
lua.PushValue(1); // shared_ptr on UserData
|
||||
lua.PushCFunction(ToStringProxy, 1);
|
||||
lua.SetField("__tostring");
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void LuaClass<T>::SetupFinalizer(LuaInstance& lua)
|
||||
{
|
||||
lua.PushValue(1); // ClassInfo
|
||||
lua.PushCFunction(FinalizerProxy, 1);
|
||||
lua.SetField("__gc");
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void LuaClass<T>::SetupGetter(LuaInstance& lua, LuaCFunction proxy)
|
||||
{
|
||||
if (m_info->getter || !m_info->parentGetters.empty())
|
||||
{
|
||||
lua.PushValue(1); // ClassInfo
|
||||
lua.PushValue(-2); // Metatable
|
||||
lua.PushCFunction(proxy, 2);
|
||||
}
|
||||
else
|
||||
// Optimize by assigning the metatable instead of a search function
|
||||
// This is only possible if we have no custom getter nor parent
|
||||
lua.PushValue(-1); // Metatable
|
||||
|
||||
lua.SetField("__index"); // Getter
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void LuaClass<T>::SetupGlobalTable(LuaInstance& lua)
|
||||
{
|
||||
// Create a metatable which will be used for our global table
|
||||
lua.PushTable(); // ClassMeta = {}
|
||||
|
||||
if (m_info->constructor)
|
||||
SetupConstructor(lua);
|
||||
|
||||
SetupGetter(lua, StaticGetterProxy);
|
||||
|
||||
if (m_info->staticSetter)
|
||||
SetupSetter(lua, StaticSetterProxy);
|
||||
|
||||
m_info->staticMethods.reserve(m_staticMethods.size());
|
||||
for (auto& pair : m_staticMethods)
|
||||
{
|
||||
std::size_t methodIndex = m_info->staticMethods.size();
|
||||
m_info->staticMethods.push_back(pair.second);
|
||||
|
||||
SetupMethod(lua, StaticMethodProxy, pair.first, methodIndex);
|
||||
}
|
||||
|
||||
// Now let's create the global table
|
||||
lua.PushTable(); // Class = {}
|
||||
lua.SetMetatable(-2); // setmetatable(Class, ClassMeta)
|
||||
|
||||
lua.PushValue(-1); // As CreateReference() pops the table, push a copy
|
||||
m_info->globalTableRef = lua.CreateReference();
|
||||
|
||||
lua.SetGlobal(m_info->name); // _G["Class"] = Class
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void LuaClass<T>::SetupMetatable(LuaInstance& lua)
|
||||
{
|
||||
if (!lua.NewMetatable(m_info->name))
|
||||
NazaraWarning("Class \"" + m_info->name + "\" already registred in this instance");
|
||||
{
|
||||
SetupFinalizer(lua);
|
||||
SetupGetter(lua, GetterProxy);
|
||||
|
||||
if (m_info->setter)
|
||||
SetupSetter(lua, SetterProxy);
|
||||
|
||||
// In case a __tostring method is missing, add a default implementation returning the class name
|
||||
if (m_methods.find("__tostring") == m_methods.end())
|
||||
SetupDefaultToString(lua);
|
||||
|
||||
m_info->methods.reserve(m_methods.size());
|
||||
for (auto& pair : m_methods)
|
||||
{
|
||||
std::size_t methodIndex = m_info->methods.size();
|
||||
m_info->methods.push_back(pair.second);
|
||||
|
||||
SetupMethod(lua, MethodProxy, pair.first, methodIndex);
|
||||
}
|
||||
}
|
||||
lua.Pop(); //< Pops the metatable, it won't be collected before it's referenced by the Lua registry.
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void LuaClass<T>::SetupMethod(LuaInstance& lua, LuaCFunction proxy, const String& name, std::size_t methodIndex)
|
||||
{
|
||||
lua.PushValue(1); // ClassInfo
|
||||
lua.PushInteger(methodIndex);
|
||||
lua.PushCFunction(proxy, 2);
|
||||
|
||||
lua.SetField(name); // Method name
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void LuaClass<T>::SetupSetter(LuaInstance& lua, LuaCFunction proxy)
|
||||
{
|
||||
lua.PushValue(1); // ClassInfo
|
||||
lua.PushCFunction(proxy, 1);
|
||||
|
||||
lua.SetField("__newindex"); // Setter
|
||||
}
|
||||
|
||||
template<class T>
|
||||
int LuaClass<T>::ConstructorProxy(lua_State* state)
|
||||
{
|
||||
|
|
@ -445,7 +460,7 @@ namespace Nz
|
|||
T* instance = nullptr;
|
||||
if (lua.GetMetatable(1))
|
||||
{
|
||||
LuaType type = lua.GetField("__type");
|
||||
LuaType type = lua.GetField("__name");
|
||||
if (type == LuaType_String)
|
||||
{
|
||||
String name = lua.ToString(-1);
|
||||
|
|
@ -554,3 +569,4 @@ namespace Nz
|
|||
}
|
||||
|
||||
#include <Nazara/Lua/DebugOff.hpp>
|
||||
#include "LuaClass.hpp"
|
||||
|
|
|
|||
Loading…
Reference in New Issue