// Copyright (C) 2017 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::LuaState& state, T* instance, std::size_t argumentCount) { NazaraUnused(state); 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] (LuaState& state) -> P* { return convertFunc(static_cast(state.CheckUserdata(1, info->name))); }; m_info->parentGetters.emplace_back([parentInfo, convertFunc] (LuaState& state, T* instance) { LuaClass

::Get(parentInfo, state, 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] (LuaState& state) { return static_cast(state.CheckUserdata(1, info->name)); }; } template void LuaClass::Register(LuaState& state) { int classInfoRef = PushClassInfo(state); // Let's create the metatable which will be associated with every state. SetupMetatable(state, classInfoRef); if (m_info->constructor || m_info->staticGetter || m_info->staticSetter || !m_staticMethods.empty()) SetupGlobalTable(state, classInfoRef); state.DestroyReference(classInfoRef); } template void LuaClass::PushGlobalTable(LuaState& state) { state.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] (LuaState& state, T& object, std::size_t /*argumentCount*/) -> int { handler.ProcessArguments(state); return handler.Invoke(state, 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] (LuaState& state, T& object, std::size_t /*argumentCount*/) -> int { handler.ProcessArguments(state); return handler.Invoke(state, 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] (LuaState& state, T& object, std::size_t /*argumentCount*/) -> int { handler.ProcessArguments(state); return handler.Invoke(state, 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] (LuaState& state, T& object, std::size_t /*argumentCount*/) -> int { handler.ProcessArguments(state); return handler.Invoke(state, 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] (LuaState& state) -> int { handler.ProcessArguments(state); return handler.Invoke(state, func); }); } template void LuaClass::SetStaticSetter(StaticIndexFunc setter) { m_info->staticSetter = setter; } template int LuaClass::PushClassInfo(LuaState& state) { // 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*>(state.PushUserdata(sizeof(std::shared_ptr))); PlacementNew(info, m_info); // Setup a tiny metatable to let Lua know how to destroy our ClassInfo state.PushTable(0, 1); state.PushLightUserdata(info); state.PushCFunction(InfoDestructor, 1); state.SetField("__gc"); state.SetMetatable(-2); return state.CreateReference(); } template void LuaClass::SetupConstructor(LuaState& state, int classInfoRef) { state.PushReference(classInfoRef); state.PushCFunction(ConstructorProxy, 1); state.SetField("__call"); // ClassMeta.__call = ConstructorProxy } template void LuaClass::SetupDefaultToString(LuaState& state, int classInfoRef) { state.PushReference(classInfoRef); state.PushCFunction(ToStringProxy, 1); state.SetField("__tostring"); } template struct LuaClassImplFinalizerSetupProxy; template struct LuaClassImplFinalizerSetupProxy { static void Setup(LuaState& state, int classInfoRef) { state.PushReference(classInfoRef); state.PushCFunction(LuaClass::FinalizerProxy, 1); state.SetField("__gc"); } }; template struct LuaClassImplFinalizerSetupProxy { static void Setup(LuaState&, int) { } }; template void LuaClass::SetupFinalizer(LuaState& state, int classInfoRef) { LuaClassImplFinalizerSetupProxy::value>::Setup(state, classInfoRef); } template void LuaClass::SetupGetter(LuaState& state, LuaCFunction proxy, int classInfoRef) { state.PushReference(classInfoRef); state.PushValue(-2); // Metatable state.PushCFunction(proxy, 2); state.SetField("__index"); // Getter } template void LuaClass::SetupGlobalTable(LuaState& state, int classInfoRef) { // Create the global table state.PushTable(); // Class = {} // Create a metatable which will be used for our global table state.PushTable(); // ClassMeta = {} if (m_info->constructor) SetupConstructor(state, classInfoRef); if (m_info->staticGetter) SetupGetter(state, StaticGetterProxy, classInfoRef); else { // Optimize by assigning the metatable instead of a search function state.PushValue(-1); // Metatable state.SetField("__index"); } if (m_info->staticSetter) SetupSetter(state, StaticSetterProxy, classInfoRef); 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(state, StaticMethodProxy, pair.first, methodIndex, classInfoRef); } state.SetMetatable(-2); // setmetatable(Class, ClassMeta), pops ClassMeta state.PushValue(-1); // As CreateReference() pops the table, push a copy m_info->globalTableRef = state.CreateReference(); state.SetGlobal(m_info->name); // _G["Class"] = Class } template void LuaClass::SetupMetatable(LuaState& state, int classInfoRef) { if (!state.NewMetatable(m_info->name)) NazaraWarning("Class \"" + m_info->name + "\" already registred in this instance"); { SetupFinalizer(state, classInfoRef); if (m_info->getter || !m_info->parentGetters.empty()) SetupGetter(state, GetterProxy, classInfoRef); else { // Optimize by assigning the metatable instead of a search function // This is only possible if we have no custom getter nor parent state.PushValue(-1); // Metatable state.SetField("__index"); } if (m_info->setter) SetupSetter(state, SetterProxy, classInfoRef); // In case a __tostring method is missing, add a default implementation returning the class name if (m_methods.find("__tostring") == m_methods.end()) SetupDefaultToString(state, classInfoRef); 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(state, MethodProxy, pair.first, methodIndex, classInfoRef); } } state.Pop(); //< Pops the metatable, it won't be collected before it's referenced by the Lua registry. } template void LuaClass::SetupMethod(LuaState& state, LuaCFunction proxy, const String& name, std::size_t methodIndex, int classInfoRef) { state.PushReference(classInfoRef); state.PushInteger(methodIndex); state.PushCFunction(proxy, 2); state.SetField(name); // Method name } template void LuaClass::SetupSetter(LuaState& state, LuaCFunction proxy, int classInfoRef) { state.PushReference(classInfoRef); state.PushCFunction(proxy, 1); state.SetField("__newindex"); // Setter } template int LuaClass::ConstructorProxy(lua_State* internalState) { LuaState state = LuaInstance::GetState(internalState); std::shared_ptr& info = *static_cast*>(state.ToUserdata(state.GetIndexOfUpValue(1))); const ConstructorFunc& constructor = info->constructor; state.Remove(1); // On enlève l'argument "table" du stack std::size_t argCount = state.GetStackTop(); T* instance = static_cast(state.PushUserdata(sizeof(T))); if (!constructor(state, instance, argCount)) { state.Error("Constructor failed"); return 0; // Normalement jamais exécuté (l'erreur provoquant une exception) } state.SetMetatable(info->name); return 1; } template int LuaClass::FinalizerProxy(lua_State* internalState) { LuaState state = LuaInstance::GetState(internalState); std::shared_ptr& info = *static_cast*>(state.ToUserdata(state.GetIndexOfUpValue(1))); const FinalizerFunc& finalizer = info->finalizer; T* instance = static_cast(state.CheckUserdata(1, info->name)); state.Remove(1); //< Remove the instance from the Lua stack if (!finalizer || finalizer(state, *instance)) instance->~T(); return 0; } template int LuaClass::InfoDestructor(lua_State* internalState) { LuaState state = LuaInstance::GetState(internalState); std::shared_ptr& info = *static_cast*>(state.ToUserdata(state.GetIndexOfUpValue(1))); state.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, LuaState& state, T* instance) { const ClassIndexFunc& getter = info->getter; if (!getter || !getter(state, *instance)) { // Query from the metatable state.GetMetatable(info->name); //< Metatable state.PushValue(2); //< Field state.GetTable(); // Metatable[Field] state.Remove(-2); // Remove Metatable if (!state.IsValid(-1)) { for (const ParentFunc& parentGetter : info->parentGetters) { state.Pop(); //< Pop the last nil value parentGetter(state, instance); if (state.IsValid(-1)) return; } } } } template int LuaClass::GetterProxy(lua_State* internalState) { LuaState state = LuaInstance::GetState(internalState); std::shared_ptr& info = *static_cast*>(state.ToUserdata(state.GetIndexOfUpValue(1))); T* instance = static_cast(state.CheckUserdata(1, info->name)); Get(info, state, instance); return 1; } template int LuaClass::MethodProxy(lua_State* internalState) { LuaState state = LuaInstance::GetState(internalState); std::shared_ptr& info = *static_cast*>(state.ToUserdata(state.GetIndexOfUpValue(1))); T* instance = nullptr; if (state.GetMetatable(1)) { LuaType type = state.GetField("__name"); if (type == LuaType_String) { String name = state.ToString(-1); auto it = info->instanceGetters.find(name); if (it != info->instanceGetters.end()) instance = it->second(state); } state.Pop(2); } if (!instance) { state.Error("Method cannot be called without an object"); return 0; } std::size_t argCount = state.GetStackTop() - 1U; unsigned int index = static_cast(state.ToInteger(state.GetIndexOfUpValue(2))); const ClassFunc& method = info->methods[index]; return method(state, *instance, argCount); } template int LuaClass::SetterProxy(lua_State* internalState) { LuaState state = LuaInstance::GetState(internalState); std::shared_ptr& info = *static_cast*>(state.ToUserdata(state.GetIndexOfUpValue(1))); const ClassIndexFunc& setter = info->setter; T& instance = *static_cast(state.CheckUserdata(1, info->name)); if (!setter(state, instance)) { std::size_t length; const char* str = state.ToString(2, &length); state.Error("Class \"" + info->name + "\" has no field \"" + String(str, length) + "\")"); } return 1; } template int LuaClass::StaticGetterProxy(lua_State* internalState) { LuaState state = LuaInstance::GetState(internalState); std::shared_ptr& info = *static_cast*>(state.ToUserdata(state.GetIndexOfUpValue(1))); const StaticIndexFunc& getter = info->staticGetter; if (!getter(state)) { // On accède alors à la table state.PushValue(state.GetIndexOfUpValue(2)); state.PushValue(-2); state.GetTable(); } return 1; } template int LuaClass::StaticMethodProxy(lua_State* internalState) { LuaState state = LuaInstance::GetState(internalState); std::shared_ptr& info = *static_cast*>(state.ToUserdata(state.GetIndexOfUpValue(1))); unsigned int index = static_cast(state.ToInteger(state.GetIndexOfUpValue(2))); const StaticFunc& method = info->staticMethods[index]; return method(state); } template int LuaClass::StaticSetterProxy(lua_State* internalState) { LuaState state = LuaInstance::GetState(internalState); std::shared_ptr& info = *static_cast*>(state.ToUserdata(state.GetIndexOfUpValue(1))); const StaticIndexFunc& setter = info->staticSetter; if (!setter(state)) { std::size_t length; const char* str = state.ToString(2, &length); state.Error("Class \"" + info->name + "\" has no static field \"" + String(str, length) + ')'); } return 1; } template int LuaClass::ToStringProxy(lua_State* internalState) { LuaState state = LuaInstance::GetState(internalState); std::shared_ptr& info = *static_cast*>(state.ToUserdata(state.GetIndexOfUpValue(1))); state.PushString(info->name); return 1; } } #include