Lua/LuaClass: Refactor Register() implementation

This commit is contained in:
Lynix 2016-10-21 16:50:49 +02:00
parent 373b8a7069
commit 3f7f12b625
2 changed files with 161 additions and 135 deletions

View File

@ -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)>;

View File

@ -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"