diff --git a/include/Nazara/Lua/LuaClass.hpp b/include/Nazara/Lua/LuaClass.hpp index 1d4aea327..64acc3763 100644 --- a/include/Nazara/Lua/LuaClass.hpp +++ b/include/Nazara/Lua/LuaClass.hpp @@ -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; using InstanceGetter = std::function; diff --git a/include/Nazara/Lua/LuaClass.inl b/include/Nazara/Lua/LuaClass.inl index 3d0befeaa..44cb4ee0c 100644 --- a/include/Nazara/Lua/LuaClass.inl +++ b/include/Nazara/Lua/LuaClass.inl @@ -69,149 +69,25 @@ namespace Nz { m_info = std::make_shared(); m_info->name = name; + + m_info->instanceGetters[m_info->name] = [info = m_info] (LuaInstance& instance) + { + return static_cast(instance.CheckUserdata(1, info->name)); + }; } template void LuaClass::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* info = static_cast*>(lua.PushUserdata(sizeof(std::shared_ptr))); - 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(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 @@ -338,6 +214,145 @@ namespace Nz m_info->staticSetter = setter; } + template + void LuaClass::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* info = static_cast*>(lua.PushUserdata(sizeof(std::shared_ptr))); + 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 + void LuaClass::SetupConstructor(LuaInstance& lua) + { + lua.PushValue(1); // ClassInfo + lua.PushCFunction(ConstructorProxy, 1); + lua.SetField("__call"); // ClassMeta.__call = ConstructorProxy + } + + template + void LuaClass::SetupDefaultToString(LuaInstance& lua) + { + lua.PushValue(1); // shared_ptr on UserData + lua.PushCFunction(ToStringProxy, 1); + lua.SetField("__tostring"); + } + + template + void LuaClass::SetupFinalizer(LuaInstance& lua) + { + lua.PushValue(1); // ClassInfo + lua.PushCFunction(FinalizerProxy, 1); + lua.SetField("__gc"); + } + + template + void LuaClass::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 + void LuaClass::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 + void LuaClass::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 + void LuaClass::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 + void LuaClass::SetupSetter(LuaInstance& lua, LuaCFunction proxy) + { + lua.PushValue(1); // ClassInfo + lua.PushCFunction(proxy, 1); + + lua.SetField("__newindex"); // Setter + } + template int LuaClass::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 +#include "LuaClass.hpp"