当前位置: 首页 > wzjs >正文

游戏评测网站怎么做网店推广平台

游戏评测网站怎么做,网店推广平台,黄山手机网站建设公司排名,阿里妈妈用哪个软件做网站UnLua源码分析(二)IUnLuaInterface 使用尝试绑定真正绑定注册C类加载Lua模块将Lua模块绑定到C创建Lua instance调用Initialize 总结Reference 上一篇:UnLua源码分析(一)初始化流程 在UnLua中,我们可以通过…

UnLua源码分析(二)IUnLuaInterface

  • 使用
  • 尝试绑定
  • 真正绑定
    • 注册C++类
    • 加载Lua模块
    • 将Lua模块绑定到C++
    • 创建Lua instance
    • 调用`Initialize`
  • 总结
  • Reference

上一篇:UnLua源码分析(一)初始化流程

在UnLua中,我们可以通过Lua来编写或者覆盖蓝图的逻辑。要实现这一步的关键是,C++或者蓝图的类需要实现IUnLuaInterface接口。

使用

在任意的Actor蓝图中,点击UnLua工具栏的Bind选项,UnLua就会自动为蓝图实现IUnLuaInterface接口。
在这里插入图片描述

然后,在蓝图中实现接口的GetModuleName方法,这个方法就是获取蓝图对应Lua文件的路径。
在这里插入图片描述

在UnLua工具栏中,点击Create Lua Template,就会在这个路径下自动生成一个Lua的模板文件,直接打开这个文件,就可以编写Lua逻辑了。
在这里插入图片描述

一个最简单的Lua文件可能长这样:

local M = UnLua.Class()-- 所有绑定到Lua的对象初始化时都会调用Initialize的实例方法
function M:Initialize()local msg = "Hello World!"print(msg)
endreturn M

运行引擎,可以看到控制台输出了Hello World!,说明Lua逻辑已经成功绑定到蓝图中了。
在这里插入图片描述

下面我们来分析一下UnLua是如何实现这个功能的。

尝试绑定

上一节我们提到,UnLua的入口是在FUnLuaModule模块。这个模块它还继承自FUObjectArray::FUObjectCreateListener这一接口,实现了NotifyUObjectCreated方法:

virtual void NotifyUObjectCreated(const UObjectBase* ObjectBase, int32 Index) override
{if (!bIsActive)return;UObject* Object = (UObject*)ObjectBase;const auto Env = EnvLocator->Locate(Object);Env->TryBind(Object);
}

也就是说,在每次创建UObject的时候,UnLua都会尝试绑定这个对象。EnvLocator->Locate(Object)会返回一个FUnLuaEnv对象,这个对象负责管理Lua虚拟机。由于Lua层的Initialize方法会在对象创建时调用,所以可以猜测,UnLua会在TryBind方法中,尝试绑定Lua模块,并且执行Initialize方法。

bool FLuaEnv::TryBind(UObject* Object)
{const auto Class = Object->IsA<UClass>() ? static_cast<UClass*>(Object) : Object->GetClass();static UClass* InterfaceClass = UUnLuaInterface::StaticClass();const bool bImplUnluaInterface = Class->ImplementsInterface(InterfaceClass);if (!bImplUnluaInterface){// dynamic bindingif (!GLuaDynamicBinding.IsValid(Class))return false;return GetManager()->Bind(Object, *GLuaDynamicBinding.ModuleName, GLuaDynamicBinding.InitializerTableRef);}const auto ModuleName = ModuleLocator->Locate(Object);if (ModuleName.IsEmpty())return false;return GetManager()->Bind(Object, *ModuleName, GLuaDynamicBinding.InitializerTableRef);
}

TryBind方法中首先判断了这个对象是否实现了IUnLuaInterface接口,如果没有实现,就会使用动态绑定的方式进行绑定。所谓动态绑定,这是UnLua的一个特性,它允许将Lua模块绑定到运行时Spawn出来的Actor和Object,这一块我们后续再讨论。否则,就会通过ModuleLocator->Locate(Object)获取到Lua模块的名称,然后调用GetManager()->Bind方法进行绑定。那么相应地,这种方式就被称作为静态绑定。

上文中提到,IUnLuaInterface接口的GetModuleName方法会返回Lua模块的路径,那么显然就能猜测到,ModuleLocator->Locate(Object)的实现中一定会调用到此方法:

FString ULuaModuleLocator::Locate(const UObject* Object)
{const UObject* CDO;if (Object->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject)){CDO = Object;}else{const auto Class = Cast<UClass>(Object);CDO = Class ? Class->GetDefaultObject() : Object->GetClass()->GetDefaultObject();}if (CDO->HasAnyFlags(RF_NeedInitialization)){// CDO还没有初始化完成return "";}if (!CDO->GetClass()->ImplementsInterface(UUnLuaInterface::StaticClass())){return "";}return IUnLuaInterface::Execute_GetModuleName(CDO);
}

函数实现比较简单,就是先获取到合法的CDO对象,如果CDO对象没有初始化完成,或者没有实现IUnLuaInterface接口,就返回空字符串。否则就调用GetModuleName来获取Lua模块的名称。

真正绑定

绕来绕去,最后还是UUnLuaManager::Bind函数负责最终的绑定逻辑,我们来看看这个函数的实现:

bool UUnLuaManager::Bind(UObject *Object, const TCHAR *InModuleName, int32 InitializerTableRef)
{check(Object);const auto Class = Object->IsA<UClass>() ? static_cast<UClass*>(Object) : Object->GetClass();lua_State *L = Env->GetMainState();if (!Env->GetClassRegistry()->Register(Class))return false;// try bind lua if not bind or use a copyed tableUnLua::FLuaRetValues RetValues = UnLua::Call(L, "require", TCHAR_TO_UTF8(InModuleName));FString Error;if (!RetValues.IsValid() || RetValues.Num() == 0){Error = "invalid return value of require()";}else if (RetValues[0].GetType() != LUA_TTABLE){Error = FString("table needed but got ");if(RetValues[0].GetType() == LUA_TSTRING)Error += UTF8_TO_TCHAR(RetValues[0].Value<const char*>());elseError += UTF8_TO_TCHAR(lua_typename(L, RetValues[0].GetType()));}else{BindClass(Class, InModuleName, Error);}if (!Error.IsEmpty()){UE_LOG(LogUnLua, Warning, TEXT("Failed to attach %s module for object %s,%p!\n%s"), InModuleName, *Object->GetName(), Object, *Error);return false;}// create a Lua instance for this UObjectEnv->GetObjectRegistry()->Bind(Class);Env->GetObjectRegistry()->Bind(Object);// try call user first user function handlerint32 FunctionRef = PushFunction(L, Object, "Initialize");                  // push hard coded Lua function 'Initialize'if (FunctionRef != LUA_NOREF){if (InitializerTableRef != LUA_NOREF){lua_rawgeti(L, LUA_REGISTRYINDEX, InitializerTableRef);             // push a initializer table if necessary}else{lua_pushnil(L);}bool bResult = ::CallFunction(L, 2, 0);                                 // call 'Initialize'if (!bResult){UE_LOG(LogUnLua, Warning, TEXT("Failed to call 'Initialize' function!"));}luaL_unref(L, LUA_REGISTRYINDEX, FunctionRef);}return true;
}

这个函数稍微复杂一些,大致可以分为以下几个步骤:

  • 注册C++类的信息到Lua层
  • 调用require函数加载Lua模块
  • 将Lua模块绑定到C++
  • 创建Lua instance,把上述所有信息绑定到instance
  • 调用Lua模块的Initialize方法,如果存在的话

注册C++类

这一步骤的关键在FClassRegistry::Register函数,这个函数会将C++类的信息注册到Lua的元表中,以便后续可以通过Lua来访问这个类的属性和方法。

FClassDesc* FClassRegistry::Register(const char* MetatableName)
{const auto L = Env->GetMainState();if (!PushMetatable(L, MetatableName))return nullptr;// TODO: refactorlua_pop(L, 1);FName Key = FName(UTF8_TO_TCHAR(MetatableName));return Name2Classes.FindChecked(Key);
}FClassDesc* FClassRegistry::Register(const UStruct* Class)
{const auto MetatableName = LowLevel::GetMetatableName(Class);return Register(TCHAR_TO_UTF8(*MetatableName));
}

如果使用的是蓝图类,LowLevel::GetMetatableName返回的是蓝图资源的完整路径。
在这里插入图片描述

加载Lua模块

接下来我们自定义的Lua模块将会被加载。Lua模块里目前只有一个Initialize函数,不过可以发现,Lua模板里有一句UnLua.Class(),这个函数又是在哪里定义的呢?
让我们回到FLuaEnv的构造函数,其中有一句

UnLuaLib::Open(L);

来看下UnLuaLib::Open函数的实现,这里也只截取我们当前关心的部分:

static void LegacySupport(lua_State* L)
{static const char* Chunk = R"(local rawget = _G.rawgetlocal rawset = _G.rawsetlocal rawequal = _G.rawequallocal type = _G.typelocal getmetatable = _G.getmetatablelocal require = _G.requirelocal GetUProperty = GetUPropertylocal SetUProperty = SetUPropertylocal NotExist = {}local function Index(t, k)local mt = getmetatable(t)local super = mtwhile super dolocal v = rawget(super, k)if v ~= nil and not rawequal(v, NotExist) thenrawset(t, k, v)return vendsuper = rawget(super, "Super")endlocal p = mt[k]if p ~= nil thenif type(p) == "userdata" thenreturn GetUProperty(t, p)elseif type(p) == "function" thenrawset(t, k, p)elseif rawequal(p, NotExist) thenreturn nilendelserawset(mt, k, NotExist)endreturn pendlocal function NewIndex(t, k, v)local mt = getmetatable(t)local p = mt[k]if type(p) == "userdata" thenreturn SetUProperty(t, p, v)endrawset(t, k, v)endlocal function Class(super_name)local super_class = nilif super_name ~= nil thensuper_class = require(super_name)endlocal new_class = {}new_class.__index = Indexnew_class.__newindex = NewIndexnew_class.Super = super_classreturn new_classend_G.Class = Class)";luaL_loadstring(L, Chunk);lua_newtable(L);lua_getglobal(L, LUA_GNAME);lua_setfield(L, -2, LUA_GNAME);luaL_setfuncs(L, UnLua_LegacyFunctions, 0);lua_setupvalue(L, -2, 1);lua_pcall(L, 0, LUA_MULTRET, 0);lua_getglobal(L, "Class");lua_setfield(L, -2, "Class");
}static int LuaOpen(lua_State* L)
{lua_newtable(L);luaL_setfuncs(L, UnLua_Functions, 0);lua_pushstring(L, "Content/Script/?.lua;Plugins/UnLua/Content/Script/?.lua");lua_setfield(L, -2, PACKAGE_PATH_KEY);return 1;
}int Open(lua_State* L)
{luaL_requiref(L, "UnLua", LuaOpen, 1);LegacySupport(L);lua_pop(L, 1);return 1;
}

Open函数注册了UnLua的全局模块,而LegacySupport函数中定义了一个Class函数,这个函数就是我们在Lua模板中使用的UnLua.Class()。这里就是Lua的元表机制的应用,通过Class函数,我们可以创建一个新的类,并且可以继承自其他类。

将Lua模块绑定到C++

有了Lua模块之后,我们就可以将Lua模块绑定到C++类上了。BindClass函数的实现如下:

bool UUnLuaManager::BindClass(UClass* Class, const FString& InModuleName, FString& Error)
{const auto  L = Env->GetMainState();const auto Top = lua_gettop(L);if (!Class->IsChildOf<UBlueprintFunctionLibrary>()){// 一个LuaModule可能会被绑定到一个UClass和它的子类,复制一个出来作为它们的实例的元表lua_newtable(L);lua_pushnil(L);while (lua_next(L, -3) != 0){lua_pushvalue(L, -2);lua_insert(L, -2);lua_settable(L, -4);}}lua_pushvalue(L, -1);const auto Ref = luaL_ref(L, LUA_REGISTRYINDEX);lua_settop(L, Top);auto& BindInfo = Classes.Add(Class);BindInfo.Class = Class;BindInfo.ModuleName = InModuleName;BindInfo.TableRef = Ref;return true;
}

可以看到,加载的Lua模块table并不是直接拿来使用,而是复制了一份出来作为实例的类元表。这样做的好处是,Lua模块可以被多个C++类共享,而每个C++类都可以有自己的状态。被复制出来的table随后会记录到Lua的registry表中,C++层也会将这个table的ref存储在Classes中。

创建Lua instance

如果我们在Initialize方法中打印self的type,会发现它是一个table。这个table就是Lua instance。UnLua的设计是不直接把C++对象以userdata的形式push到Lua层,而是用table做了一层封装。我们来看下负责创建Lua instance的代码:

int FObjectRegistry::Bind(UObject* Object)
{const auto L = Env->GetMainState();int OldTop = lua_gettop(L);lua_getfield(L, LUA_REGISTRYINDEX, REGISTRY_KEY);lua_pushlightuserdata(L, Object);lua_newtable(L); // create a Lua table ('INSTANCE')PushObjectCore(L, Object); // push UObject ('RAW_UOBJECT')lua_pushstring(L, "Object");lua_pushvalue(L, -2);lua_rawset(L, -4); // INSTANCE.Object = RAW_UOBJECT// in some case may occur module or object metatable can // not be found problemconst auto Class = Object->IsA<UClass>() ? static_cast<UClass*>(Object) : Object->GetClass();const auto ClassBoundRef = Env->GetManager()->GetBoundRef(Class);int32 TypeModule = lua_rawgeti(L, LUA_REGISTRYINDEX, ClassBoundRef); // push the required module/table ('REQUIRED_MODULE') to the top of the stackint32 TypeMetatable = lua_getmetatable(L, -2); // get the metatable ('METATABLE_UOBJECT') of 'RAW_UOBJECT' if (TypeModule != LUA_TTABLE || TypeMetatable == LUA_TNIL){lua_pop(L, lua_gettop(L) - OldTop);return LUA_REFNIL;}lua_setmetatable(L, -2); // REQUIRED_MODULE.metatable = METATABLE_UOBJECTlua_setmetatable(L, -3); // INSTANCE.metatable = REQUIRED_MODULElua_pop(L, 1);lua_pushvalue(L, -1);const auto Ret = luaL_ref(L, LUA_REGISTRYINDEX);ObjectRefs.Add(Object, Ret);lua_rawset(L, -3);lua_pop(L, 1);return Ret;
}

这段代码很长,但核心就做了两件事情。第一件事情就是创建了一个table作为Lua instance,然后把前面几个步骤中创建的对象都关联了起来。
在这里插入图片描述

如图所示,Lua class table就是通过require加载进来的Lua模块的复制表,UObject metatable就是在注册C++类过程中生成的元表,它既是表示原始UObject的userdata元表,也是Lua class table的元表。这意味着,
Lua instance table中字段的查找顺序,是先从Lua模块找起,找不到再去C++层找,这样倒是也挺合理。

第二件事就是存储了,Lua层会把instance table存到registry表中,并把UObject和instance table的关系,存到一个UnLua_ObjectMap的表中。这个表的key是light userdata,也就是UObject指针,value则是registry表返回的ref。同样,C++层也类似,ObjectRefs保存的也是同样的key和value。

调用Initialize

这一步就比较简单了,根据前面的分析,我们很容易就能猜测到,UnLua会在Lua层递归查找Initialize方法,这里的PushFunction函数会将Lua函数和Lua instance table压入栈顶,然后通过CallFunction函数来真正执行这个函数。

/*** Push a Lua function (by a function name) and push a UObject instance as its first parameter*/
int32 PushFunction(lua_State *L, UObjectBaseUtility *Object, const char *FunctionName)
{int32 N = lua_gettop(L);lua_pushcfunction(L, UnLua::ReportLuaCallError);const auto& Env = UnLua::FLuaEnv::FindEnv(L);const auto Ref = Env->GetObjectRegistry()->GetBoundRef((UObject*)Object);if (Ref != LUA_NOREF){lua_rawgeti(L, LUA_REGISTRYINDEX, Ref);int32 Type = lua_type(L, -1);if (Type == LUA_TTABLE /*|| Type == LUA_TUSERDATA*/){if (lua_getmetatable(L, -1) == 1){do{lua_pushstring(L, FunctionName);lua_rawget(L, -2);if (lua_isfunction(L, -1)){lua_pushvalue(L, -3);lua_remove(L, -3);lua_remove(L, -3);lua_pushvalue(L, -2);return luaL_ref(L, LUA_REGISTRYINDEX);}else{lua_pop(L, 1);lua_pushstring(L, "Super");lua_rawget(L, -2);lua_remove(L, -2);}} while (lua_istable(L, -1));}}}if (int32 NumToPop = lua_gettop(L) - N){lua_pop(L, NumToPop);}return LUA_NOREF;
}

总结

UnLua会通过IUnLuaInterface接口来获取蓝图对应的Lua模块路径,并在创建UObject时尝试绑定Lua模块,然后自动尝试执行Lua层的Initialize方法。这一套流程也被称之为静态绑定,有关静态绑定的各种细节,我们将在后续继续分析。

Reference

[1] UnLua与UE4的UObject绑定原理深入分析

[2] UnLua解析(一)Object绑定lua

[3] UnLua_Programming_Guide

http://www.dtcms.com/wzjs/158008.html

相关文章:

  • 企查查官网入口网页版seo软件服务
  • 18元套餐深圳网站seo公司
  • 网站是怎么搭建的产品软文怎么写
  • 平台网站如何做推广专业软文发稿平台
  • 高州网站建设公司做网站推广
  • 盘锦网站建设多少钱短视频代运营方案模板
  • 网站建设APP的软件室内设计培训班学费一般多少
  • 未来网站建设想法上海城市分站seo
  • 免费网站建设合同书阿里云万网域名注册
  • 张店免费做网站厦门人才网个人登录
  • 政府网站制作平台建个网站费用多少
  • 模板网站可以做备案吗seo公司排名教程
  • 国内网建公司排名seo关键词选择及优化
  • 信用中国 网站 建设方案手机系统流畅神器
  • 铭做网站建设刷粉网站推广快点
  • 淘宝买模板注浆做网站保定seo外包服务商
  • 昆明网站建设-中国互联北京网络营销推广外包
  • wordpress都有哪些权限广州seo网站开发
  • 嘉兴做网站设计友情链接交换的作用在于
  • 西安做网站的公司有阿里云空间+1对1私人专属设计师
  • 中国建筑集团有限公司官网招聘安卓优化大师手机版
  • 国内python 做的网站免费b站软件推广网站
  • 网站建设开发原代码归属抖音营销推广怎么做
  • 网站建设 徐州网上写文章用什么软件
  • 如何让新网站快速收录出售外链
  • 免费无版权图片网站百度优化关键词
  • 淘宝做网站为什么那么便宜营销网络的建设有哪些
  • 重庆市招标网seo搜索引擎优化总结
  • 套别人的网站模板吗自媒体是如何赚钱的
  • 电子商务网站建设课后习题无锡网站优化公司