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等待发掘。。

时间: 2024-12-26 12:21:42

ue4 SNew背后的逻辑的相关文章

ue4 SNew补遗

上一篇分析了SNew背后的实现,但是有一个关键问题遗漏了,那就是: #define SNew( WidgetType, ... ) \ MakeTDecl<WidgetType>( #WidgetType, __FILE__, __LINE__, RequiredArgs::MakeRequiredArgs(__VA_ARGS__) ) <<= TYPENAME_OUTSIDE_TEMPLATE WidgetType::FArguments() 为何这里要用一个特别奇怪的操作符重载

交易中 你的加仓策略是怎样的?背后的逻辑是什么?

建仓是赌,加仓也是赌.建仓是赌自己是对的,加仓,是证明自己赌对了以后,赌自己这次是大大地对了. 如果是一个不加仓的系统,那么做完一百笔单子,统计一下,归类一下,假设说有70笔是亏的,但是经过止损,都只亏1.有30笔是赚的,其中有5笔赚了10,有10笔赚了5,有15笔赚了2.一共赚了130.平均获利4.3.很显然这是一个正期望系统,即便不加仓,也能过好日子了. 但是,我们不满足.如果在那些能够赚10的5笔单子中,仓位放大一倍,或者50%,或者甚至更低25%,那么业绩是不是会有提升?--是的. 如果

浅谈GAIAWORLD独家自研技术背后的逻辑及意义

微信公众号:GAIAWorld 前言: 我们认为区块链不仅是世界的操作系统,它本身就应该是一个自治世界!在此系统中,每个节点相互连接与交互,整个系统自治管理自己的行为,形成一个去中心化的自治的数字世界.GAIAWORLD致力于夯实公链的基础,打造一个基于区块链的自治世界.在这个新的世界里,一切都应该是在链上进行的,而GAIA链会是这个新世界的基石! 现实逃不过富者越富穷者越穷的马太效应 我们同样选择POS而不是POW,因为这避免了大量无意义的资源消耗.但是我们不认可以太坊casper的理念:投入

Hive项目实战:用Hive分析“余额宝”躺着赚大钱背后的逻辑

一.项目背景 前两年,支付宝推出的“余额宝”赚尽无数人的眼球,同时也吸引的大量的小额资金进入.“余额宝”把用户的散钱利息提高到了年化收益率4.0%左右,比起银行活期存储存款0.3%左右高出太多了,也正在撼动着银行躺着赚钱的地位. 在金融市场,如果想获得年化收益率4%-5%左右也并非难事,通过“逆回购”一样可以.一旦遇到货币紧张时(银行缺钱),更可达到50%一天隔夜回够利率.我们就可以美美地在家里数钱了!! 所谓逆回购:通俗来讲,就是你(A)把钱借给别人(B),到期时,B按照约定利息,还给你(A)

MySQL索引背后的数据结构及算法原理

摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎 对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BTree索引,哈希索引,全文索引等等.为了避免混乱,本文将只关注于BTree索 引,因为这是平常使用MySQL时主要打交道的索引,至于哈希索引和全文索引本文暂不讨论. 数据结构及算法基础 索引的本质 MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构.提取句子主干

浅谈MySQL索引背后的数据结构及算法

摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持 也各不相同,因此MySQL数据库支持多种索引类型,如BTree索引,哈希索引,全文索引等等.为了避免混乱,本文将只关注于BTree索引,因为这是 平常使用MySQL时主要打交道的索引,至于哈希索引和全文索引本文暂不讨论. 文章主要内容分为四个部分. 第一部分主要从数据结构及算法理论层面讨论MySQL数据库索引的数理基础. 第二部分结合MySQL数据库中

[纯干货] MySQL索引背后的数据结构及算法原理

摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BTree索引,哈希索引,全文索引等等.为了避免混乱,本文将只关注于BTree索引,因为这是平常使用MySQL时主要打交道的索引,至于哈希索引和全文索引本文暂不讨论. 文章主要内容分为三个部分. 第一部分主要从数据结构及算法理论层面讨论MySQL数据库索引的数理基础. 第二部分结合MySQL数据库中My

从window.console&amp;&amp;console.log(123)浅谈JS的且运算逻辑(&amp;&amp;)

从window.console&&console.log(123)浅谈JS的且运算逻辑(&&) 作者:www.cnblogs.com  来源:www.cnblogs.com  发布日期:2015-03-01 一.JS的且运算记得最开始看到window.console&&console.log(123),当时知道能起什么作用但是没有深入研究,最近在研究后总算弄明白了.要理解这个,首先得明白三个知识点第一:短路原则这个大家都非常清楚的了,在做且运算的时候,“同真

浅谈MySQL索引背后的数据结构及算法(转载)

转自:http://blogread.cn/it/article/4088?f=wb1 摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BTree索引,哈希索引,全文索引等等.为了避免混乱,本文将只关注于BTree索引,因为这是平常使用MySQL时主要打交道的索引,至于哈希索引和全文索引本文暂不讨论. 文章主要内容分为三个部分. 第一部分主要从数据结