ue4 SNew背后的逻辑
ue4的ui库Slate体系非常庞大,即使是在创建对象这一小事上,也是相当复杂:
SLATECORE_API TSharedRef<SWidget> SNullWidget::NullWidget = SNew(SNullWidgetContent).Visibility(EVisibility::Hidden);
所有SWidget体系内的对象,都要用SNew这个宏来创建,它的内容是:
#define SNew( WidgetType, ... ) \
MakeTDecl<WidgetType>( #WidgetType, __FILE__, __LINE__, RequiredArgs::MakeRequiredArgs(__VA_ARGS__) ) <<= TYPENAME_OUTSIDE_TEMPLATE WidgetType::FArguments()
这里做了两件事:
1、先是调用MakeTDecl创建了一个【widget wrapper】
2、再调用该wrapper重载的<<=操作符,实并将一个FArguments类型的默认对象做为参数传进去。注意这里这个FArguments类型不是全局类,而是内嵌于WidgetType里的,也就是说每个widget子类都可以定义自己的初始化参数类。
那么继续跟踪1,看看MakeTDecl在干啥:
template<typename WidgetType, typename RequiredArgsPayloadType>
TDecl<WidgetType, RequiredArgsPayloadType> MakeTDecl( const ANSICHAR* InType, const ANSICHAR* InFile, int32 OnLine, RequiredArgsPayloadType&& InRequiredArgs )
{
return TDecl<WidgetType, RequiredArgsPayloadType>(InType, InFile, OnLine, Forward<RequiredArgsPayloadType>(InRequiredArgs));
}
它实际就是一层模块包装,生成了一个TDecl类型对象,也就是上面说的wrapper,<<=重载也就是在它身上调的。
这里值得注意的是第4个参数,实参是:RequiredArgs::MakeRequiredArgs(__VA_ARGS__),由于__VA_ARGS__是个变参宏,就意味着MakeRequiredArgs必须要有支持多个参数的版本
事实上确实如此,UE4已经写了从无参到5个参数的6种变体,如果今后有更多参数的调用出现了,自然还要再加,下面贴出前几个看看:
FORCEINLINE T0RequiredArgs MakeRequiredArgs()
{
return T0RequiredArgs();
} template<typename Arg0Type>
T1RequiredArgs<Arg0Type&&> MakeRequiredArgs(Arg0Type&& InArg0)
{
return T1RequiredArgs<Arg0Type&&>(Forward<Arg0Type>(InArg0));
} template<typename Arg0Type, typename Arg1Type>
T2RequiredArgs<Arg0Type&&, Arg1Type&&> MakeRequiredArgs(Arg0Type&& InArg0, Arg1Type&& InArg1)
{
return T2RequiredArgs<Arg0Type&&, Arg1Type&&>(Forward<Arg0Type>(InArg0), Forward<Arg1Type>(InArg1));
}
不同版本的返回值类型也是不一样的,也分别定义了从T0RequiredArgs 到 T5RequiredArgs 共6个类型,也都是简单的参数打包结构体,只看一个T2吧:
template<typename Arg0Type, typename Arg1Type>
struct T2RequiredArgs
{
T2RequiredArgs(Arg0Type&& InArg0, Arg1Type&& InArg1)
: Arg0(InArg0)
, Arg1(InArg1)
{
} template<class WidgetType>
void CallConstruct(const TSharedRef<WidgetType>& OnWidget, const typename WidgetType::FArguments& WithNamedArgs) const
{
// YOUR WIDGET MUST IMPLEMENT Construct(const FArguments& InArgs)
OnWidget->Construct(WithNamedArgs, Forward<Arg0Type>(Arg0), Forward<Arg1Type>(Arg1));
OnWidget->CacheVolatility();
} Arg0Type& Arg0;
Arg1Type& Arg1;
};
回到前面第1点,MakeTDecl返回了一个TDecl对象,而这个对象的类型也是模板化的,除了要SNew的Widget类型本身,还有一个打包着变参参数的RequiredArgsPayloadType,而该类型是T0RequiredArgs 到 T5RequiredArgs其中之一。
那么TDecl是什么,又如何构造的呢:
template<class WidgetType, typename RequiredArgsPayloadType>
struct TDecl
{
TDecl( const ANSICHAR* InType, const ANSICHAR* InFile, int32 OnLine, RequiredArgsPayloadType&& InRequiredArgs )
: _Widget( TWidgetAllocator<WidgetType, TIsDerivedFrom<WidgetType, SUserWidget>::IsDerived >::PrivateAllocateWidget() )
, _RequiredArgs(InRequiredArgs)
{
_Widget->SetDebugInfo( InType, InFile, OnLine );
} const TSharedRef<WidgetType> _Widget;
RequiredArgsPayloadType& _RequiredArgs;
};
在这里也是做了两件事,一是把SNew传入的参数存起来放在_RequiredArgs属性上以备后用,二是通过PrivateAllocateWidget创建了widget实例
而这个PrivateAllocateWidget的前缀也是非常拗口:
TWidgetAllocator<WidgetType, TIsDerivedFrom<WidgetType, SUserWidget>::IsDerived >
这个TWidgetAllocator就是起到一个【萃取器】的作用,给各个Widget子类提供了一个模板重载的机会,让它们可以定义自身实例的特殊创建方式
下面是该萃取器的默认实现:
template<typename WidgetType, bool IsDerived>
struct TWidgetAllocator
{
static TSharedRef<WidgetType> PrivateAllocateWidget()
{
return MakeShareable( new WidgetType() );
}
};
也就是没做什么特殊处理,直接new出来了。然而全局搜索,并未发现有任何子类重载过以给予特殊逻辑。当然这里留下了接口,方便以后扩展。
再看第2点,也就是对 TDecl.operator<<=(FArguments& InArgs) 的调用:
TSharedRef<WidgetType> operator<<=( const typename WidgetType::FArguments& InArgs ) const
{
//@todo UMG: This should be removed in favor of all widgets calling their superclass construct.
_Widget->SWidgetConstruct(
InArgs._ToolTipText,
InArgs._ToolTip ,
InArgs._Cursor ,
InArgs._IsEnabled ,
InArgs._Visibility,
InArgs._RenderTransform,
InArgs._RenderTransformPivot,
InArgs._Tag,
InArgs._ForceVolatile,
InArgs.MetaData ); _RequiredArgs.CallConstruct(_Widget, InArgs); return _Widget;
}
这里也有两个关注点:
一是_RequiredArgs.CallConstruct调用,代码上上面已经贴过,再回顾一下:
template<class WidgetType>
void CallConstruct(const TSharedRef<WidgetType>& OnWidget, const typename WidgetType::FArguments& WithNamedArgs) const
{
// YOUR WIDGET MUST IMPLEMENT Construct(const FArguments& InArgs)
OnWidget->Construct(WithNamedArgs, Forward<Arg0Type>(Arg0), Forward<Arg1Type>(Arg1));
OnWidget->CacheVolatility();
}
内部就是将自己保存的参数再转调到Widget->Construct(...)
然而_RequiredArgs是个模板类型,可能表示0~5个参数,那么每一个类型里要转调的Widget->Construct的参数个数(和类型)也是不一样的
这就是说,当你用SNew(YourWidget,A,B,C)去创建一个YourWidget实例时,最终层层展开的模板代码,会要求YourWidget类上,必须有一个接受【FArguments,A,B,C】为参数的Construct版本。
这种用法很有趣,其规则就是:谁调用谁实现,而中间层只管转发,如果匹配不上,那是使用方的责任。
搜索代码可以找到其中的例子:
class SPaperExtractSpritesViewport : public SPaperEditorViewport
{
void Construct(const FArguments& InArgs, UTexture2D* Texture, const TArray<FPaperExtractedSprite>& ExtractedSprites, const class UPaperExtractSpritesSettings* Settings, class SPaperExtractSpritesDialog* InDialog);
...
} TSharedRef<SPaperExtractSpritesViewport> Viewport = SNew(SPaperExtractSpritesViewport, SourceTexture, ExtractedSprites, ExtractSpriteSettings, this);
只有自己先实现了相应参数版本的Construct,才能在SNew中传递相应的实参来构造。
二是关于SWidget上的SWidgetConstruct和Construct的关系。
这个<<=重载里的代码正是先后调了俩:首先是->SWidgetConstruct,然后通过转发又调了->Construct
SWidgetConstruct里面并没有什么东西,直接就调了Construct,然而此Construct并非上面说的与SNew参数匹配的那版,而仅仅是一个与SWidgetConstruct签名完全相同的壳,其中的内容也非常简单,只是把参数保存到相应成员变量里。
而真正的SNew对应版Construct,才是各Widget子类真正做初始化的地方
挖了这么多,就是一个小小SNew的实现。。还有更多的Slate Trick等待发掘。。
ue4 SNew背后的逻辑的更多相关文章
- ue4 SNew补遗
上一篇分析了SNew背后的实现,但是有一个关键问题遗漏了,那就是: #define SNew( WidgetType, ... ) \ MakeTDecl<WidgetType>( #Wi ...
- 当你「ping 一下」的时候,你知道它背后的逻辑吗?
我们在遇到网络不通的情况,大家都知道去 ping 一下,看一下网络状况.那你知道「ping」命令后背的逻辑是什么吗?知道它是如何实现的吗? 一.「ping」命令的作用和原理? 简单来说,「ping」是 ...
- 看懂「www.google.com」背后的逻辑
在前两篇文章中,我们完整的描述了计算机网络 OSI 五层模型的相关内容.那么,本篇将会从一个实践案例开始,带你从整体上重新认识我们的计算机网络. 我们以访问 Google 为例,当我们在浏览器地址栏中 ...
- Hive项目实战:用Hive分析“余额宝”躺着赚大钱背后的逻辑
一.项目背景 前两年,支付宝推出的“余额宝”赚尽无数人的眼球,同时也吸引的大量的小额资金进入.“余额宝”把用户的散钱利息提高到了年化收益率4.0%左右,比起银行活期存储存款0.3%左右高出太多了,也正 ...
- 【转】MySQL索引背后的数据结构及算法原理
摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BT ...
- [转]MySQL索引背后的数据结构及算法原理
摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BT ...
- MySQL索引背后的数据结构及算法原理
摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BT ...
- [纯干货] MySQL索引背后的数据结构及算法原理
摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BT ...
- 从window.console&&console.log(123)浅谈JS的且运算逻辑(&&)
一.JS的且运算记得最开始看到window.console&&console.log(123),当时知道能起什么作用但是没有深入研究,最近在研究后总算弄明白了.要理解这个,首先得明白三 ...
随机推荐
- 在同一个机器上运行两个jboss修改配置
http://blog.sina.com.cn/s/blog_8ebe17aa0101mnft.html 解决办法:修改 \jboss-4.0.4.GA\server\default\conf 目录下 ...
- Hibernate5.2关联关系之单向一对多(一)
Hibernate5.2之单向一对多 一. 简介 Hibernate中最复杂的应该就是各种关联(单向一对多.单向多对一.双向一对多.一对一.多对多)关系的映射,于是笔者就想着去写一些关于Hibe ...
- MapReduce Shuffle原理 与 Spark Shuffle原理
MapReduce的Shuffle过程介绍 Shuffle的本义是洗牌.混洗,把一组有一定规则的数据尽量转换成一组无规则的数据,越随机越好.MapReduce中的Shuffle更像是洗牌的逆过程,把一 ...
- 自动装箱(boxing)和自动拆箱(unboxing)
摘自:http://www.codeceo.com/article/java-boxing-unboxing.html Java的四类八种基本数据类型 基本类型 占用空间(Byte) 表示范围 包装器 ...
- 112、两个Activity切换黑屏问题
<activity android:name=".main.select.ActDoyenActivity" android:screenOrientation=" ...
- GIS理论(墨卡托投影、地理坐标系、地面分辨率、地图比例尺、Bing Maps Tile System)
[注]原文 http://www.cnblogs.com/beniao/archive/2010/04/18/1714544.html 墨卡托投影(Mercator Projection),又名&qu ...
- 异步SRAM控制器的Verilog建模
前言:sram顾名思义静态随机存储器,分为asram异步型和ssram同步型.这里驱动DE2上一块ISSI公司的512KB的asram. 设计思路:因为实际应用中单字节读写效率不高,所以本设计中仿照s ...
- Clustering with the ArcGIS API for Flex
Clustering is an excellent technique for visualizing lotss of point data. We've all seen application ...
- tinyxml学习5
读取和设置xml配置文件是最常用的操作,试用了几个C++的XML解析器,个人感觉TinyXML是使用起来最舒服的,因为它的API接口和Java的十分类似,面向对象性很好. TinyXML是一个开源的解 ...
- Response.Redirect引起的“无法在发送HTTP标头之后进行重定向”
博客后台切换至i.cnblogs.com之后,在日志中发现大量的“无法在发送HTTP标头之后进行重定向”(Cannot redirect after HTTP headers have been se ...