Slate 组件问题排查总结简介
首先是一个工作中遇到的BUG: 用slua添加子节点到父节点上的时候,第二次打开无法显示对应的子节点Widget。对应Lua代码如下

  1. local comboBox = ui_manager.ShowUI(ui_manager.UI_Config.ui_coupon_combobox,2,price,buyUIInfo.shopInfo.id)
  2. if comboBox then
  3. self:AttachChildWindow("ScaleBox_Coupon",comboBox)
  4. end

因为我们引入了UI对象的内存池概念,很自然而然的想到了缓存住的控件是不是释放有问题。检查slua的release代码之后发现没有任何问题。基于ScaleBox 只能有1个子物体这一个机制,在猜测BUG原因的时候,尝试用CanvasPanel替换ScaleBox组件。发现果然能解决这个问题。
但是也带来了2个新问题:
1、ScaleBox子物体在第一次关闭和第二次打开的时候经历了什么?
2、为什么CanvasPanel就可以而ScaleBox不可以?
为了解决以上疑问,开启我们的排查过程。
为了能使大家更好的了解这个流程,先对Slate组件创建销毁流程进行介绍。Slate组件创建销毁流程
1、将我们日常使用的一个组件,ScaleBox拆出来看(如下图):有如下的对应关系。里面包含了 UScaleBox,UScaleBoxSlot,SscaleBox

2、创建过程

3、销毁过程

问题排查
针对ScaleBox,进入断点调试。
在第一次关闭界面的时候,发现UscaleBox Release 有正常跑到(下面代码部分)。

  1. void UScaleBox::ReleaseSlateResources(bool bReleaseChildren)
  2. {
  3. Super::ReleaseSlateResources(bReleaseChildren);
  4. MyScaleBox.Reset();
  5. }

但是在第二次打开的时候,发现没有进入RebuildWidget(Swidget的实例化部分,也就是下面代码部分)

  1. TSharedRef<SWidget> UScaleBox::RebuildWidget()
  2. {
  3. MyScaleBox = SNew(SScaleBox)
  4. .SingleLayoutPass(bSingleLayoutPass);
  5. if ( GetChildrenCount() > 0 )
  6. {
  7. CastChecked<UScaleBoxSlot>(GetContentSlot())->BuildSlot(MyScaleBox.ToSharedRef());
  8. }
  9. return MyScaleBox.ToSharedRef();
  10. }

这是为什么呢? 原来,在UWidget的BUILD过程中,有一个查找共享指针MyWidget的操作。这个指针虽然被UscaleBox Reset了一次。但是在ScaleBox 第二次打开时候,这个指针依然存在引用。所以该实例并没有被销毁。那么在第二次打开的时候,由于没有走RebuildWidget。导致了子类UScaleBox 的指针引用已经被销毁,在AddChild时候调用添加了个空Object。(下图代码部分)

  1. TSharedRef<SWidget> UWidget::TakeWidget_Private(ConstructMethodType ConstructMethod)
  2. {
  3. bool bNewlyCreated = false;
  4. TSharedPtr<SWidget> PublicWidget;
  5. // If the underlying widget doesn't exist we need to construct and cache the widget for the first run.
  6. if (!MyWidget.IsValid())
  7. {
  8. PublicWidget = RebuildWidget();
  9. #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
  10. ensureMsgf(PublicWidget.Get() != &SNullWidget::NullWidget.Get(), TEXT("Don't return SNullWidget from RebuildWidget, because we mutate the state of the return. Return a SSpacer if you need to return a no-op widget."));
  11. #endif
  12. MyWidget = PublicWidget;
  13. bNewlyCreated = true;
  14. }
  15. else
  16. {
  17. PublicWidget = MyWidget.Pin();
  18. }

那这个指针是被谁Hold住了呢? 经过排查与调试,发现是被UScaleBoxSlot给Hold住了导致了这份资源没有被释放。

为什么在UScaleBox中的共享指针已经释放了,但是UScaleBoxSlot所持有的相同指针没有被释放呢?请看Slot释放的地方(下面代码模块):

  1. // If the child is a UserWidget, we should let it manage it's own slate resources instead of forcing a clear here. This fixes issues such as UE-39106
  2. if (PanelSlot->Content && !PanelSlot->Content->IsA<UUserWidget>())
  3. {
  4. const bool bReleaseChildren = true;
  5. PanelSlot->ReleaseSlateResources(bReleaseChildren);
  6. }

UE的注释写的很清楚了,我们所Add 的子物体,正是UserWidget 界面。在UE-39106版本中,让UserWidget 的Slate释放控制交给了上层。在这里没有自动释放。下面是UE 39106的改动链接。https://issues.unrealengine.com/issue/UE-39106

虚幻引擎UE为了补锅一个slate 被释放的问题,导致了这个新问题。 我们上层缓存了这份实例,销毁子物体的时候调用了RemoveFromParent。在ScaleBox被销毁的时候,因为其子物体为userwidget,而不去释放PanelSlot的SlateResourse。那么在第二次打开的时候,因为PanelSlot中共享指针的存在,没有New一份新的子物体实例。
OK,问题定位了。那么之前提到的第二个问题,为什么换成CanvasPanel就没有这个问题了呢?,既然查到了在ScaleBox 中是 ScaleBoxSlot 的指针Hold住了这份资源,那么我们去CanvasPanel对应的Slot 看一下。(下面代码)

  1. void UCanvasPanelSlot::BuildSlot(TSharedRef<SConstraintCanvas> Canvas)
  2. {
  3. Slot = &Canvas->AddSlot()
  4. [
  5. Content == nullptr ? SNullWidget::NullWidget : Content->TakeWidget()
  6. ];
  7. SynchronizeProperties();
  8. }

明显可以看出:原来CanvasPanelSlot 在Build过程中没有缓存那份实例!只是做了常规引用!在上面有贴出过ScaleBoxSlot build 过程的代码。所以就是ScaleBoxSlot 存住了这份指针,而CanvasPanelSlot 并没有。所以在都没有Release对应掉这份资源的情况下,CanvasPanel会走rebuild过程,而ScaleBox不会!
到这里,我们基本上就定位出了问题全部原因所在。解决办法
这里想到了4种解决方案,都能解决这个问题,这里简单说一下:
1、不判断指针是否还存在引用,全部走Rebuild过程。
2、在Remove判断是否为UserWidget过程中,对本身PanelSlot做一次释放。
3、删除UScaleBoxSlot中对SscaleBox指针的引用。
4、将UScaleBoxSlot中的共享引用改为弱引用。
第一种风险较大,因为所有UMG都会走的创建过程,不太保险。
第二种风险小于第一种,但是也在所有UMG都会走的创建过程,不太保险。
第三种和第四种个人感觉都是有效的方案,总的来看还是第四种改动小并优雅一点。
所以目前采用了第四种。

if 组件是否存在_UE4 UMG简介+Slate组件问题排查相关推荐

  1. 【Flutter】Flutter 布局组件 ( 布局组件简介 | Row 组件 | Column 组件 | SizedBox 组件 | ClipOval 组件 )

    文章目录 一.Flutter 布局相关的组件简介 二.Row 和 Column 组件 三.SizedBox 组件 四.ClipOval 组件 五. 完整代码示例 六. 相关资源 一.Flutter 布 ...

  2. UE4中HUD、UMG、Slate之间的区别

    这是目录 Slate UMG HUD Slate Slate是为编辑器专门制作的窗口UI框架(以前的UE编辑器使用了Window的库),但它也可以用在游戏中. 但是Slate类不在UObject环境之 ...

  3. [react] 写一个react的高阶组件并说明你对高阶组件的理解

    [react] 写一个react的高阶组件并说明你对高阶组件的理解 定义高阶组件 import React, { Component } from 'react';const simpleHoc = ...

  4. [vue] 怎么缓存当前打开的路由组件,缓存后想更新当前组件怎么办呢?

    [vue] 怎么缓存当前打开的路由组件,缓存后想更新当前组件怎么办呢? 可以在路由meta中加入参数, 对打开的路由进行keep-alive的判断, 通过钩子active等 个人简介 我是歌谣,欢迎和 ...

  5. ionic 修改组件默认样式_开源Magpie:组件库详解

    开源项目专题系列(八)1.开源项目名称:magpie_fly2.github地址: https://github.com/wuba/magpie_fly 3.简介:magpie_fly 是58集体出品 ...

  6. android 使用4大组件的源码,Android Jetpack架构组件之 Paging(使用、源码篇)

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  7. 小程序基础开发(四):weui-wxss组件表单页面,WxValidate.js组件表单验证上传

    一,使用weui-wxss组件做一个表单页面 WeUI组件库简介 这是一套基于样式库weui-wxss开发的小程序扩展组件库,同微信原生视觉体验一致的UI组件库,由微信官方设计团队和小程序团队为微信小 ...

  8. VUE之组件(插槽slot与可复用组件)

    插槽slot 首先创建个基础组件,然后在页面调用显示,如下所示 <div id="app"><blog></blog></div>& ...

  9. 『参考』.net CF组件编程(4)——为自定义组件添加工具箱图标!

    前言: 在前三篇的文章中,和大家一起创建了一个用于TCP连接检测的小组件,如果你记不得了,可以通过以下链接去回顾一下: 『参考』.net CF组件编程(1)--基础之后 『参考』.net CF组件编程 ...

最新文章

  1. 慕课网基于ElasticSearch的找房网实战开发企业级房屋搜索网项目学习心得(一)
  2. [转]Sandcastle初探——官方版的NDoc
  3. 在CentOS上安装Java环境—openjdk1.7 解决https配置问题
  4. OFDM专题之输入的复信号从何而来?
  5. Symantec Backup Exec System Recovery还原向导
  6. python代码需要背吗-Python面试必须要看的15个问题
  7. Linux进程状态解析
  8. Devops (1)
  9. se16 and include table entries into TR SAT trace - Gross time and Net time
  10. java excel文件读取的内容_java读取Excel文件指定内容
  11. 重读GhostNet:使用轻量操作代替部分传统卷积层生成冗余特征以减少计算量
  12. chrome vue插件_「Vue学习记录一」开发环境准备
  13. eclipse中导入SVN项目步骤
  14. getconf 取系统配制 --CPU
  15. 用pcl读ply文件_一分钟详解PCL中点云配准技术
  16. default在php定义什么,default(T)的含义
  17. Protel99SE多张原理图的设计步骤
  18. 梅宫主:聊聊创业路上关于韭菜的事儿。。
  19. ES6阮一峰笔记部分对象新增方法、字符串的扩展和新增方法
  20. 1.1.1.1校园网_突破校园网限制,开启寝室Wifi

热门文章

  1. python调用手机蓝牙_米家生态出品,易锁宝蓝牙U型锁,让开锁更灵活
  2. 掘金浏览器插件安装图文教程
  3. MacOS安装过程需要注意的几个问题
  4. Centos7.5 lnmp+mongodb扩展
  5. Xcode9的xib只支持iOS7.0及以上版本
  6. Atcoder Contest069F:Flag
  7. BZOJ 1878 HH的项链 | 主席树
  8. Summary Day30
  9. 面向对象chapter1
  10. Thread类和Runnable接口