// Copyright (C) 2015 Jérôme Leclercq // This file is part of the "Nazara Engine - Lua scripting module" // For conditions of distribution and use, see copyright notice in Config.hpp #include #include #include #include #include namespace Nz { template LuaClass::LuaClass(const String& name) { Reset(name); } template inline void LuaClass::BindDefaultConstructor() { SetConstructor([] (Nz::LuaInstance& lua, T* instance, std::size_t argumentCount) { NazaraUnused(lua); NazaraUnused(argumentCount); PlacementNew(instance); return true; }); } template template inline void LuaClass::Inherit(LuaClass

& parent) { Inherit

(parent, [] (T* instance) -> P* { return static_cast(instance); }); } template template inline void LuaClass::Inherit(LuaClass

& parent, ConvertToParent

convertFunc) { static_assert(!std::is_same::value || std::is_base_of::value, "P must be a base of T"); std::shared_ptr::ClassInfo>& parentInfo = parent.m_info; parentInfo->instanceGetters[m_info->name] = [info = m_info, convertFunc] (LuaInstance& lua) -> P* { return convertFunc(static_cast(lua.CheckUserdata(1, info->name))); }; m_info->parentGetters.emplace_back([parentInfo, convertFunc] (LuaInstance& lua, T* instance) { LuaClass

::Get(parentInfo, lua, convertFunc(instance)); }); } template void LuaClass::Reset() { m_info.reset(); } template void LuaClass::Reset(const String& name) { 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) { PushClassInfo(lua); // 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_staticMethods.empty()) SetupGlobalTable(lua); lua.Pop(); // Pop our ClassInfo, which is now referenced by all our functions } template void LuaClass::PushGlobalTable(LuaInstance& lua) { lua.PushReference(m_info->globalTableRef); } template void LuaClass::SetConstructor(ConstructorFunc constructor) { m_info->constructor = constructor; } template void LuaClass::SetFinalizer(FinalizerFunc finalizer) { m_info->finalizer = finalizer; } template void LuaClass::SetGetter(ClassIndexFunc getter) { m_info->getter = getter; } template void LuaClass::BindMethod(const String& name, ClassFunc method) { m_methods[name] = method; } template template std::enable_if_t::value> LuaClass::BindMethod(const String& name, R(P::*func)(Args...), DefArgs&&... defArgs) { typename LuaImplMethodProxy::template Impl handler(std::forward(defArgs)...); BindMethod(name, [func, handler] (LuaInstance& lua, T& object, std::size_t /*argumentCount*/) -> int { handler.ProcessArguments(lua); return handler.Invoke(lua, object, func); }); } template template std::enable_if_t::value> LuaClass::BindMethod(const String& name, R(P::*func)(Args...) const, DefArgs&&... defArgs) { typename LuaImplMethodProxy::template Impl handler(std::forward(defArgs)...); BindMethod(name, [func, handler] (LuaInstance& lua, T& object, std::size_t /*argumentCount*/) -> int { handler.ProcessArguments(lua); return handler.Invoke(lua, object, func); }); } template template std::enable_if_t::type>::value> LuaClass::BindMethod(const String& name, R(P::*func)(Args...), DefArgs&&... defArgs) { typename LuaImplMethodProxy::template Impl handler(std::forward(defArgs)...); BindMethod(name, [func, handler] (LuaInstance& lua, T& object, std::size_t /*argumentCount*/) -> int { handler.ProcessArguments(lua); return handler.Invoke(lua, object, func); }); } template template std::enable_if_t::type>::value> LuaClass::BindMethod(const String& name, R(P::*func)(Args...) const, DefArgs&&... defArgs) { typename LuaImplMethodProxy::template Impl handler(std::forward(defArgs)...); BindMethod(name, [func, handler] (LuaInstance& lua, T& object, std::size_t /*argumentCount*/) -> int { handler.ProcessArguments(lua); return handler.Invoke(lua, object, func); }); } template void LuaClass::SetSetter(ClassIndexFunc setter) { m_info->setter = setter; } template void LuaClass::SetStaticGetter(StaticIndexFunc getter) { m_info->staticGetter = getter; } template void LuaClass::BindStaticMethod(const String& name, StaticFunc method) { m_staticMethods[name] = method; } template template void LuaClass::BindStaticMethod(const String& name, R(*func)(Args...), DefArgs&&... defArgs) { typename LuaImplFunctionProxy::template Impl handler(std::forward(defArgs)...); BindStaticMethod(name, [func, handler] (LuaInstance& lua) -> int { handler.ProcessArguments(lua); return handler.Invoke(lua, func); }); } template void LuaClass::SetStaticSetter(StaticIndexFunc setter) { 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 struct LuaClassImplFinalizerSetupProxy; template struct LuaClassImplFinalizerSetupProxy { static void Setup(LuaInstance& lua) { lua.PushValue(1); // ClassInfo lua.PushCFunction(LuaClass::FinalizerProxy, 1); lua.SetField("__gc"); } }; template struct LuaClassImplFinalizerSetupProxy { static void Setup(LuaInstance&) { } }; template void LuaClass::SetupFinalizer(LuaInstance& lua) { LuaClassImplFinalizerSetupProxy::value>::Setup(lua); } template void LuaClass::SetupGetter(LuaInstance& lua, LuaCFunction proxy) { lua.PushValue(1); // ClassInfo lua.PushValue(-2); // Metatable lua.PushCFunction(proxy, 2); lua.SetField("__index"); // Getter } template void LuaClass::SetupGlobalTable(LuaInstance& lua) { // Create the global table lua.PushTable(); // Class = {} // Create a metatable which will be used for our global table lua.PushTable(); // ClassMeta = {} if (m_info->constructor) SetupConstructor(lua); if (m_info->staticGetter) SetupGetter(lua, StaticGetterProxy); else { // Optimize by assigning the metatable instead of a search function lua.PushValue(-1); // Metatable lua.SetField("__index"); } 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); } lua.SetMetatable(-2); // setmetatable(Class, ClassMeta), pops 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); if (m_info->getter || !m_info->parentGetters.empty()) SetupGetter(lua, GetterProxy); 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"); } 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) { LuaInstance& lua = *LuaInstance::GetInstance(state); std::shared_ptr& info = *static_cast*>(lua.ToUserdata(lua.GetIndexOfUpValue(1))); const ConstructorFunc& constructor = info->constructor; lua.Remove(1); // On enlève l'argument "table" du stack std::size_t argCount = lua.GetStackTop(); T* instance = static_cast(lua.PushUserdata(sizeof(T))); if (!constructor(lua, instance, argCount)) { lua.Error("Constructor failed"); return 0; // Normalement jamais exécuté (l'erreur provoquant une exception) } lua.SetMetatable(info->name); return 1; } template int LuaClass::FinalizerProxy(lua_State* state) { LuaInstance& lua = *LuaInstance::GetInstance(state); std::shared_ptr& info = *static_cast*>(lua.ToUserdata(lua.GetIndexOfUpValue(1))); const FinalizerFunc& finalizer = info->finalizer; T* instance = static_cast(lua.CheckUserdata(1, info->name)); lua.Remove(1); //< Remove the instance from the Lua stack if (!finalizer || finalizer(lua, *instance)) instance->~T(); return 0; } template int LuaClass::InfoDestructor(lua_State* state) { LuaInstance& lua = *LuaInstance::GetInstance(state); std::shared_ptr& info = *static_cast*>(lua.ToUserdata(lua.GetIndexOfUpValue(1))); lua.DestroyReference(info->globalTableRef); using namespace std; // Obligatoire pour le destructeur info.~shared_ptr(); // Si vous voyez une autre façon de faire, je suis preneur return 0; } template void LuaClass::Get(const std::shared_ptr& info, LuaInstance& lua, T* instance) { const ClassIndexFunc& getter = info->getter; if (!getter || !getter(lua, *instance)) { // Query from the metatable lua.GetMetatable(info->name); //< Metatable lua.PushValue(2); //< Field lua.GetTable(); // Metatable[Field] lua.Remove(-2); // Remove Metatable if (!lua.IsValid(-1)) { for (const ParentFunc& parentGetter : info->parentGetters) { lua.Pop(); //< Pop the last nil value parentGetter(lua, instance); if (lua.IsValid(-1)) return; } } } } template int LuaClass::GetterProxy(lua_State* state) { LuaInstance& lua = *LuaInstance::GetInstance(state); std::shared_ptr& info = *static_cast*>(lua.ToUserdata(lua.GetIndexOfUpValue(1))); T* instance = static_cast(lua.CheckUserdata(1, info->name)); Get(info, lua, instance); return 1; } template int LuaClass::MethodProxy(lua_State* state) { LuaInstance& lua = *LuaInstance::GetInstance(state); std::shared_ptr& info = *static_cast*>(lua.ToUserdata(lua.GetIndexOfUpValue(1))); T* instance = nullptr; if (lua.GetMetatable(1)) { LuaType type = lua.GetField("__name"); if (type == LuaType_String) { String name = lua.ToString(-1); auto it = info->instanceGetters.find(name); if (it != info->instanceGetters.end()) instance = it->second(lua); } lua.Pop(2); } if (!instance) { lua.Error("Method cannot be called without an object"); return 0; } std::size_t argCount = lua.GetStackTop() - 1U; unsigned int index = static_cast(lua.ToInteger(lua.GetIndexOfUpValue(2))); const ClassFunc& method = info->methods[index]; return method(lua, *instance, argCount); } template int LuaClass::SetterProxy(lua_State* state) { LuaInstance& lua = *LuaInstance::GetInstance(state); std::shared_ptr& info = *static_cast*>(lua.ToUserdata(lua.GetIndexOfUpValue(1))); const ClassIndexFunc& setter = info->setter; T& instance = *static_cast(lua.CheckUserdata(1, info->name)); if (!setter(lua, instance)) { std::size_t length; const char* str = lua.ToString(2, &length); lua.Error("Class \"" + info->name + "\" has no field \"" + String(str, length) + "\")"); } return 1; } template int LuaClass::StaticGetterProxy(lua_State* state) { LuaInstance& lua = *LuaInstance::GetInstance(state); std::shared_ptr& info = *static_cast*>(lua.ToUserdata(lua.GetIndexOfUpValue(1))); const StaticIndexFunc& getter = info->staticGetter; if (!getter(lua)) { // On accède alors à la table lua.PushValue(lua.GetIndexOfUpValue(2)); lua.PushValue(-2); lua.GetTable(); } return 1; } template int LuaClass::StaticMethodProxy(lua_State* state) { LuaInstance& lua = *LuaInstance::GetInstance(state); std::shared_ptr& info = *static_cast*>(lua.ToUserdata(lua.GetIndexOfUpValue(1))); unsigned int index = static_cast(lua.ToInteger(lua.GetIndexOfUpValue(2))); const StaticFunc& method = info->staticMethods[index]; return method(lua); } template int LuaClass::StaticSetterProxy(lua_State* state) { LuaInstance& lua = *LuaInstance::GetInstance(state); std::shared_ptr& info = *static_cast*>(lua.ToUserdata(lua.GetIndexOfUpValue(1))); const StaticIndexFunc& setter = info->staticSetter; if (!setter(lua)) { std::size_t length; const char* str = lua.ToString(2, &length); lua.Error("Class \"" + info->name + "\" has no static field \"" + String(str, length) + ')'); } return 1; } template int LuaClass::ToStringProxy(lua_State* state) { LuaInstance& lua = *LuaInstance::GetInstance(state); std::shared_ptr& info = *static_cast*>(lua.ToUserdata(lua.GetIndexOfUpValue(1))); lua.PushString(info->name); return 1; } } #include