/* * Tencent is pleased to support the open source community by making ScriptX available. * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "LuaEngine.hpp" #include #include #include #include #include "../../src/Engine.hpp" #include "../../src/Native.h" #include "../../src/Native.hpp" #include "../../src/Utils.h" #include "LuaByteBufferImpl.h" #include "LuaHelper.hpp" #include "LuaReference.hpp" #include "LuaScope.hpp" // ref https://www.lua.org/manual/5.1/manual.html // https://www.lua.org/wshop14/Zykov.pdf namespace script::lua_backend { namespace { lua_State* newCommonLua() { auto lua = luaL_newstate(); luaL_openlibs(lua); return lua; } } // namespace const void* const LuaEngine::kLuaTableNativeThisPtrToken_ = reinterpret_cast(&kLuaTableNativeThisPtrToken_); const void* const LuaEngine::kLuaTableNativeClassDefinePtrToken_ = reinterpret_cast(&kLuaTableNativeClassDefinePtrToken_); const void* const LuaEngine::kLuaNativeConstructorMarker_ = reinterpret_cast(&kLuaNativeConstructorMarker_); const void* const LuaEngine::kLuaNativeInternalStorageToken_ = reinterpret_cast(&kLuaNativeInternalStorageToken_); const void* const LuaEngine::kLuaBuiltinDefinedClassMetaDataToken_ = reinterpret_cast(&kLuaBuiltinDefinedClassMetaDataToken_); const void* const LuaEngine::kLuaGlobalRegistryToken_ = reinterpret_cast(&kLuaGlobalRegistryToken_); const void* const LuaEngine::kLuaWeakRegistryToken_ = reinterpret_cast(&kLuaWeakRegistryToken_); LuaEngine::LuaEngine(std::shared_ptr<::script::utils::MessageQueue> queue, const std::function& luaStateFactory, std::unique_ptr byteBufferDelegate) : messageQueue_(queue ? std::move(queue) : std::make_shared()), byteBufferDelegate_(byteBufferDelegate ? std::move(byteBufferDelegate) : std::make_unique()) { if (luaStateFactory) { lua_ = luaStateFactory(); assert(lua_); } else { lua_ = newCommonLua(); } { EngineScope engineScope(this); initGlobalRegistry(); byteBufferDelegate_->init(this); registerNativeClass(builtInFunctions()); } } LuaEngine::~LuaEngine() = default; void LuaEngine::destroy() { ScriptEngine::destroyUserData(); { // clean anything we used EngineScope scope(this); isDestroying_ = true; nativeDefineRegistry_.clear(); globalWeakBookkeeping_.clear(); } messageQueue()->removeMessageByTag(this); lua_close(lua_); delete this; } bool lua_backend::LuaEngine::isDestroying() const { return isDestroying_; } void LuaEngine::initGlobalRegistry() { lua_getglobal(lua_, kLuaGlobalEnvName); auto global = lua_gettop(lua_); luaStackScope(lua_, [this, global]() { // global lua_newtable(lua_); lua_rawsetp(lua_, global, kLuaGlobalRegistryToken_); }); luaStackScope(lua_, [this, global] { // weak lua_newtable(lua_); luaStackScope(lua_, [this] { lua_newtable(lua_); luaStackScope(lua_, [this] { lua_pushstring(lua_, kLuaMetaMethodMode); lua_pushstring(lua_, "v"); lua_rawset(lua_, -3); }); lua_setmetatable(lua_, -2); }); lua_rawsetp(lua_, global, kLuaWeakRegistryToken_); }); } size_t LuaEngine::putGlobalOrWeakTable(const Local& localReference, const void* registryToken) { size_t id = 0; if (localReference.val_ != 0) { StackFrameScope scope; lua_getglobal(lua_, kLuaGlobalEnvName); auto global = lua_gettop(lua_); lua_rawgetp(lua_, global, registryToken); auto globalTable = lua_gettop(lua_); auto counter = globalIdCounter(); make>(globalTable).set(counter, localReference); // pop globalTable lua_pop(this->lua_, 1); id = counter; if (registryToken == kLuaGlobalRegistryToken_) { globalRefCount_++; } else { weakRefCount_++; } } return id; } void LuaEngine::removeGlobalOrWeakTable(size_t id, const void* registryToken) { StackFrameScope scope; lua_getglobal(lua_, kLuaGlobalEnvName); auto global = lua_gettop(lua_); lua_rawgetp(lua_, global, registryToken); auto globalTable = lua_gettop(lua_); make>(globalTable).set(id, {}); // pop globalTable lua_pop(this->lua_, 1); if (registryToken == kLuaGlobalRegistryToken_) { globalRefCount_--; } else { weakRefCount_--; } } Local LuaEngine::getGlobalOrWeakTable(size_t index, const void* registryToken) const { if (index == 0) return {}; StackFrameScope stack; lua_backend::luaEnsureStack(lua_, 2); lua_getglobal(lua_, kLuaGlobalEnvName); auto global = lua_gettop(lua_); lua_rawgetp(lua_, global, registryToken); auto globalTable = lua_gettop(lua_); return stack.returnValue(make>(globalTable).get(index)); } size_t LuaEngine::globalIdCounter() { while (++globalIdCounter_ == 0) { } return globalIdCounter_; } Local LuaEngine::get(const Local& key) { auto lua = lua_backend::currentLua(); auto keyString = lua_tostring(lua, key.val_); return get(keyString); } void LuaEngine::set(const Local& key, const Local& value) { auto lua = lua_backend::currentLua(); auto keyString = lua_tostring(lua, key.val_); set(keyString, value); } Local LuaEngine::get(const char* key) { auto lua = lua_backend::currentLua(); lua_backend::luaEnsureStack(lua, 1); lua_getglobal(lua, key); return Local{lua_gettop(lua)}; } void LuaEngine::set(const char* key, const Local& value) { auto lua = lua_backend::currentLua(); lua_backend::luaStackScope(lua, [lua, key, &value]() { lua_backend::luaEnsureStack(lua, 2); lua_backend::pushValue(lua, value); lua_setglobal(lua, key); }); } Local LuaEngine::eval(const Local& script) { return eval(script, Local()); } Local LuaEngine::eval(const Local& script, const Local& sourceFile) { return eval(script, sourceFile.asValue()); } Local LuaEngine::eval(const Local& script, const Local& sourceFile) { Tracer trace(this, "LuaEngine::eval"); auto sourceStringHolder = script.toString(); std::string sourceFileName; if (sourceFile.isString()) { sourceFileName = sourceFile.asString().toString(); } if (sourceFileName.empty()) { sourceFileName = "unknown.lua"; } if (luaL_loadbuffer(lua_, sourceStringHolder.c_str(), sourceStringHolder.length(), sourceFileName.c_str()) != LUA_OK) { lua_backend::rethrowException(lua_); } return lua_backend::callFunction({}, {}, 0, nullptr); } Arguments LuaEngine::makeArguments(LuaEngine* engine, int stackBase, size_t paramCount, bool isInstanceFunc) { lua_backend::ArgumentsData argumentsData{engine, stackBase, paramCount, isInstanceFunc}; return Arguments(argumentsData); } void LuaEngine::registerStaticDefine(const internal::StaticDefine& staticDefine, int tableIndex, int metaTableIndex) { defineStaticFunctions(staticDefine, tableIndex); defineStaticProperties(staticDefine, tableIndex, metaTableIndex); } void LuaEngine::defineStaticFunctions(const internal::StaticDefine& staticDefine, int tableIndex) { for (auto& fun : staticDefine.functions) { pushFunction(static_cast(&fun), [](lua_State*, void* func, const Arguments& arguments) -> Local { auto fun = static_cast(func); Tracer trace(arguments.engine(), fun->traceName); return fun->callback(arguments); }); lua_setfield(lua_, tableIndex, fun.name.c_str()); } } void LuaEngine::defineStaticProperties(const internal::StaticDefine& staticDefine, int tableIndex, int metaIndex) { if (staticDefine.properties.empty()) { return; } { lua_pushvalue(lua_, metaIndex); lua_setmetatable(lua_, tableIndex); } StackFrameScope stack; // property registry lua_newtable(lua_); auto getterRegistryIndex = lua_gettop(lua_); lua_newtable(lua_); auto setterRegistryIndex = lua_gettop(lua_); setupMetaTableForProperties(metaIndex, 0, getterRegistryIndex, setterRegistryIndex); for (auto& prop : staticDefine.properties) { pushFunction(static_cast((&prop)), [](lua_State* lua, void* func, const Arguments&) -> Local { // __index(table, index) auto p = static_cast(func); std::optional exception; try { if (p->getter) { return p->getter(); } } catch (const Exception& e) { exception = e.message(); } luaThrow(lua, exception); return {}; }); lua_setfield(lua_, getterRegistryIndex, prop.name.c_str()); pushFunction(static_cast((&prop)), [](lua_State* lua, void* func, const Arguments& arguments) -> Local { // __newindex(table, index, value) auto p = static_cast(func); std::optional exception; try { if (p->setter) { p->setter(arguments[2]); } } catch (const Exception& e) { exception = e.message(); } luaThrow(lua, exception); return {}; }); lua_setfield(lua_, setterRegistryIndex, prop.name.c_str()); } } void LuaEngine::setupMetaTableForProperties(int metaIndex, int instanceFunction, int getterRegistryIndex, int setterRegistryIndex) const { { // key lua_pushstring(lua_, kLuaMetaMethodIndex); // value lua_pushvalue(lua_, getterRegistryIndex); lua_pushvalue(lua_, instanceFunction); // __index(table, index) lua_pushcclosure( lua_, [](lua_State* lua) -> int { lua_pushvalue(lua, -1); lua_gettable(lua, lua_upvalueindex(1)); if (!lua_isnoneornil(lua, -1)) { // found the getter, call it lua_insert(lua, 1); lua_call(lua, 2, LUA_MULTRET); return lua_gettop(lua); } else { lua_pop(lua, 1); // get from function table lua_rawget(lua, lua_upvalueindex(2)); return 1; } }, 2); lua_rawset(lua_, metaIndex); } { // key lua_pushstring(lua_, kLuaMetaMethodNewIndex); // value lua_pushvalue(lua_, setterRegistryIndex); // __newindex(table, index, value) lua_pushcclosure( lua_, [](lua_State* lua) -> int { lua_pushvalue(lua, 2); lua_gettable(lua, lua_upvalueindex(1)); if (!lua_isnoneornil(lua, -1)) { // found the setter, call it lua_insert(lua, 1); lua_call(lua, 3, LUA_MULTRET); return lua_gettop(lua); } else { lua_pop(lua, 1); // normal table set lua_rawset(lua, 1); return 0; } }, 1); lua_rawset(lua_, metaIndex); } } void LuaEngine::pushFunction(const void* data, PushFunctionCallback callable) { lua_backend::luaEnsureStack(lua_, 4); lua_pushlightuserdata(lua_, const_cast(data)); lua_pushlightuserdata(lua_, reinterpret_cast(callable)); lua_pushlightuserdata(lua_, this); lua_pushcclosure( lua_, [](lua_State* lua) -> int { auto data = lua_touserdata(lua, lua_upvalueindex(1)); auto callable = reinterpret_cast(lua_touserdata(lua, lua_upvalueindex(2))); auto engine = static_cast(lua_touserdata(lua, lua_upvalueindex(3))); std::optional exception; try { auto ret = callable(lua, data, makeArguments(engine, 1, lua_gettop(lua), false)); auto retIndex = localRefIndex(ret); return handleReturnToLua(lua, retIndex); } catch (const Exception& e) { exception = e.message(); } luaThrow(lua, exception); return 0; }, 3); } const ClassDefine& LuaEngine::builtInFunctions() { static auto define = defineClass("ScriptX") // isInstanceOf(value, Class); .function("isInstanceOf", [](const Arguments& args) -> Local { if (args.size() == 2) { return Boolean::newBoolean(isInstanceOf( args.callbackInfo_.engine->lua_, localRefIndex(args[1]), localRefIndex(args[0]))); } return Boolean::newBoolean(false); }) .function("getInstanceMeta", [](const Arguments& args) -> Local { if (args.size() > 0) { return getClassMetaTable(args.callbackInfo_.engine->lua_, localRefIndex(args[0])); } return {}; }) .build(); return define; } Local LuaEngine::getClassMetaTable(lua_State* lua, int classIndex) { if (classIndex == 0 || !lua_istable(lua, classIndex)) { return {}; } lua_backend::luaEnsureStack(lua, 1); lua_rawgetp(lua, classIndex, kLuaBuiltinDefinedClassMetaDataToken_); auto meta = lua_gettop(lua); if (!lua_istable(lua, meta)) { return {}; } return make>(meta); } bool LuaEngine::isInstanceOf(lua_State* lua, int classIndex, int selfIndex) { if (classIndex == 0) { return false; } if (!lua_istable(lua, classIndex)) { return false; } lua_backend::luaEnsureStack(lua, 1); lua_rawgetp(lua, classIndex, kLuaTableNativeClassDefinePtrToken_); auto classDefine = lua_touserdata(lua, -1); lua_pop(lua, 1); return isInstanceOf(lua, classDefine, selfIndex); } bool LuaEngine::isInstanceOf(lua_State* lua, const void* classDefine, int selfIndex) { if (selfIndex == 0 || classDefine == nullptr) { return false; } if (!lua_istable(lua, selfIndex)) { return false; } lua_backend::luaEnsureStack(lua, 1); lua_rawgetp(lua, selfIndex, kLuaTableNativeClassDefinePtrToken_); auto ptr = lua_touserdata(lua, -1); lua_pop(lua, 1); return ptr == classDefine; } void* LuaEngine::getNativeThis(lua_State* lua, const void* classDefine, int selfIndex) { lua_backend::luaEnsureStack(lua, 2); if (!isInstanceOf(lua, classDefine, selfIndex)) { return nullptr; } lua_rawgetp(lua, selfIndex, kLuaTableNativeThisPtrToken_); auto ptr = lua_touserdata(lua, -1); lua_pop(lua, 1); return ptr; } void LuaEngine::pushInstanceFunction(const void* data, const void* classDefine, PushInstanceFunctionCallback callable) const { lua_backend::luaEnsureStack(lua_, 4); lua_pushlightuserdata(lua_, const_cast(data)); lua_pushlightuserdata(lua_, reinterpret_cast(callable)); lua_pushlightuserdata(lua_, const_cast(classDefine)); lua_pushlightuserdata(lua_, const_cast(this)); lua_pushcclosure( lua_, [](lua_State* lua) -> int { std::optional exception; try { auto classDefine = lua_touserdata(lua, lua_upvalueindex(3)); auto engine = static_cast(lua_touserdata(lua, lua_upvalueindex(4))); void* thiz = nullptr; if (lua_gettop(lua) >= 1) { thiz = getNativeThis(lua, classDefine, 1); } if (thiz == nullptr) { luaThrow(lua, "call instance function on non-native Object"); } auto data = lua_touserdata(lua, lua_upvalueindex(1)); auto callable = reinterpret_cast( lua_touserdata(lua, lua_upvalueindex(2))); auto ret = callable(lua, data, thiz, makeArguments(engine, 1, lua_gettop(lua), true)); auto retIndex = localRefIndex(ret); return handleReturnToLua(lua, retIndex); } catch (const Exception& e) { exception = e.message(); } luaThrow(lua, exception); return 0; }, 4); } std::shared_ptr LuaEngine::messageQueue() { return messageQueue_; } void LuaEngine::gc() { lua_gc(lua_, LUA_GCCOLLECT, 0); } size_t LuaEngine::getHeapSize() { return lua_gc(lua_, LUA_GCCOUNT, 0) * 1024; // NOLINT } void LuaEngine::adjustAssociatedMemory(int64_t count) {} ScriptLanguage LuaEngine::getLanguageType() { return ScriptLanguage::kLua; } std::string LuaEngine::getEngineVersion() { return LUA_RELEASE; } } // namespace script::lua_backend