+```
+
+# 接口定义变更
+
+1. hippy-react 不再导出RNfqb、RNfqbRegister、RNfqbEventEmitter、RNfqbEventListener 方法
+
+2. hippy-react animation 模块不再有 destory() 方法的错误写法兼容,统一用 destroy()
+
+3. hippy-react animation 事件监听不再支持 onRNfqbAnimationXX 兼容写法,统一用 onHippyAnimationXX 或者 onAnimationXX
+
+4. hippy-react 初始化动画对象(new Animation),需要在根节点渲染之后,否则会因为 Dom Manager未创建提示报错
+ hippy-vue/hippy-vue-next 初始化动画对象(new Animation),需要在 Vue.start 回调之后,否则会因为 Dom Manager未创建提示报错
+
+5. hippy-react/hippy-vue/hippy-vue-next 如果使用了颜色属性的渐变动画,需要显示指定 color 单位,添加 valueType:'color' 字段,例如:
+
+``` javascript
+ animation: new Animation({
+ startValue: 'red',
+ toValue: 'yellow',
+ valueType: 'color', // 颜色动画需显式指定color单位
+ duration: 1000,
+ delay: 0,
+ mode: 'timing',
+ timingFunction: 'linear',
+ }),
+```
+
+
+# 验证关注点
+
+一、Hippy 3.0 前端架构升级主要有如下改动点:
+
+
+1. JS 驱动上屏的方式由 UIManagerModule 变为了 SceneBuilder。
+2. Node API 重新实现了 Move 计算逻辑。
+3. Event 由前端分发变为 DOM 分发。
+4. 动画由 bridge 模块变为 C++ DOM 模块实现。
+
+二、需要验证关注点:
+
+
+1. 界面的UI视图渲染正常 (UI结构、样式属性等),特别关注 Hippy-React/Vue 中因为条件渲染语句,产生的节点`Move`操作,表现是否正常。
+2. UI事件(点击、滑动)等表现正常,特别关注事件`冒泡`、`捕获`等表现是否正常。
+3. 关注`动画`表现是否正常。
+
+
+
+
+# 新特性
+
+## Performance API
+
+Hippy 3.0 我们实现了基于前端规范设计的性能 API,接入方式可参考 [Performance](feature/feature3.0/performance.md)。
+
+## Layout 引擎支持切换
+
+Hippy 3.0 我们支持了 Layout 引擎的无缝切换,项目可保持`Yoga`引擎,也可以选择Hippy自研的`Taitank`引擎。详情可参考 [Layout](feature/feature3.0/layout.md)
diff --git a/docs/development/voltron-flutter-integration-guidelines.md b/docs/development/voltron-flutter-integration-guidelines.md
new file mode 100644
index 00000000000..c2b5b0ff5d8
--- /dev/null
+++ b/docs/development/voltron-flutter-integration-guidelines.md
@@ -0,0 +1,78 @@
+# Voltron Flutter 集成指引
+
+这篇教程,讲述了如何将 Hippy 3.x SDK 集成到 Flutter 工程。
+
+> 注:以下文档都是假设您已经具备一定的 Flutter 开发经验。
+
+---
+
+## 前期准备
+
+- 已经安装了 Flutter version>=3.0 并配置了环境变量
+
+## Demo 体验
+
+若想快速体验,可以直接基于我们的 `Voltron Demo` 来开发,我们提供以下两种 `Demo`
+
+- 如果您的应用完全通过 `Flutter` 进行开发,可以参考[flutter_proj](https://github.com/Tencent/Hippy/tree/master/framework/example/voltron-demo/flutter_proj),3.0正式发布前,请使用 [flutter_proj](https://github.com/Tencent/Hippy/tree/v3.0-dev/framework/example/voltron-demo/flutter_proj)
+
+- 如果您希望将 `Voltron` 集成进您的原生 `IOS` 或 `Android` 应用,可以使用 `flutter module` 进行集成
+
+ - `Android` 应用请参考[android-proj](https://github.com/Tencent/Hippy/tree/master/framework/example/voltron-demo/android-proj),3.0正式发布前,请使用 [flutter_proj](https://github.com/Tencent/Hippy/tree/v3.0-dev/framework/example/voltron-demo/android-proj)
+ - `IOS` 应用请参考[IOSProj](https://github.com/Tencent/Hippy/tree/master/framework/example/voltron-demo/IOSProj),3.0正式发布前,请使用 [flutter_proj](https://github.com/Tencent/Hippy/tree/v3.0-dev/framework/example/voltron-demo/IOSProj)
+
+> 注意,使用 `flutter_module` 方式进行开发时,原生工程和 `Flutter` 工程在两个目录,上面所提到的 `android-proj` 和 `IOSProj` 均需要配合 [flutter_module](https://github.com/Tencent/Hippy/tree/master/framework/example/voltron-demo/flutter_module)进行使用,3.0正式发布前,请使用 [flutter_proj](https://github.com/Tencent/Hippy/tree/v3.0-dev/framework/example/voltron-demo/flutter_module)
+
+## 快速接入
+
+### 如果您的应用完全通过 `Flutter` 进行开发
+
+1. 创建一个 Flutter 工程
+
+2. Pub 集成
+
+ 在 `pubspec.yaml` 中添加 `Voltron` 依赖
+
+ ```yaml
+ dependencies:
+ voltron: ^0.0.1
+ ```
+
+3. 本地集成(可选)
+
+ 1. 克隆 Hippy 源码
+
+ ```shell
+ git clone https://github.com/Tencent/Hippy.git
+ ```
+
+ > 注意使用相应的分支及tag,未合入主干前,请使用v3.0-dev分支
+
+ 2. 打开 Flutter 工程根目录下的 `pubspec.yaml`
+
+ 在 `dependencies` 下添加 `voltron` 依赖
+
+ ```yaml
+ voltron:
+ path: Hippy路径/framework/voltron
+ ```
+
+4. 安装依赖
+
+ ```shell
+ flutter pub get
+ ```
+
+5. 使用 `Voltron`
+
+ 建议参考[flutter_proj](https://github.com/Tencent/Hippy/tree/master/framework/example/voltron-demo/flutter_proj),3.0正式发布前,请使用 [flutter_proj](https://github.com/Tencent/Hippy/tree/v3.0-dev/framework/example/voltron-demo/flutter_proj)
+
+ > Pub 集成方式在 Android 平台默认支持 `arm64-v8a` 和 `armeabi-v7a`,如需支持 `x86` 和 `x86_64`,请使用本地集成,iOS 无影响。
+
+ > 需要注意,如果 **debugMode** 为YES的情况下,会忽略所有参数,直接使用 npm 本地服务加载测试 bundle,
+
+### 如果您希望将 `Voltron` 集成进您的原生 `IOS` 或 `Android` 应用
+
+1. 使用该方式进行集成时,要首先集成 Flutter Module,该部分可直接参考官网[Add Flutter to an existing app](https://docs.flutter.dev/add-to-app)
+
+2. 后续流程与完全通过 `Flutter` 进行开发保持一致即可。也可直接参考我们的[Demo](#demo-体验-1)工程
diff --git a/docs/development/web-integration-guidelines.md b/docs/development/web-integration-guidelines.md
new file mode 100644
index 00000000000..6c2bf0812fa
--- /dev/null
+++ b/docs/development/web-integration-guidelines.md
@@ -0,0 +1,151 @@
+# Web同构指引
+
+这篇教程,讲述了如何将 Hippy 集成到 Web 页面中。
+
+> 不同于 @hippy/react-web 和 @hippy/vue-web 方案,本方案(Web Renderer)不会替换 @hippy/react 和 @hippy/vue,而是将运行在原生环境下的 bundle 原封不动运行到 Web 上,与转译 Web 的方案各有利弊,业务可根据具体场景采用合适的方案
+
+---
+
+## 前期准备
+
+- 模板文件:Web 运行需要一个 HTML 文件作为入口
+- 入口文件:WebRenderer 是作为 Hippy bundle 的一个运行环境,因此不共享入口 JS 文件,应为其创建独立的入口文件
+
+### npm script
+
+在 demo 项目中,通过 `web:dev` 命令启动 WebRenderer 调试服务,通过 `web:build` 打包编译。
+
+```json
+ "scripts": {
+ "web:dev": "npm run hippy:dev & node ./scripts/env-polyfill.js webpack serve --config ./scripts/hippy-webpack.web-renderer.dev.js",
+ "web:build": "node ./scripts/env-polyfill.js
+webpack --config ./scripts/hippy-webpack.web-renderer.js"
+ }
+```
+
+### 启动调试
+
+执行 `npm run web:dev` 启动 WebRenderer 调试,根据 demo 的 webpack 配置,WebRenderer 的 web 服务运行在`3000`端口,浏览器通过 `http://localhost:3000` 访问页面。
+
+## 快速接入
+
+WebRenderer 的执行应符合以下流程:
+
+1. 导入 WebRenderer:该阶段会初始化 Hippy 代码运行的环境
+2. 加载业务 bundle:这个 bundle 与 Native 侧运行的 bundle 包保持一致
+3. 启动 WebRenderer:该阶段会加载 Hippy 内置组件和模块,也可以加载自定义组件和模块
+
+### 导入 WebRenderer
+
+#### 以 CDN 方式使用
+
+在模板文件内添加:
+
+```html
+
+
+
+
+
+
Example
+
+
+
+
+
+
+
+
+
+```
+
+#### 以 NPM 包方式使用
+
+```shell
+npm install -S @hippy/web-renderer
+```
+
+在入口文件内添加:
+
+```javascript
+// 1. 导入 web renderer
+import { HippyWebEngine, HippyWebModule } from '@hippy/web-renderer';
+
+// 2. 导入业务 bundle 的入口文件,需放在 web renderer 导入之后
+
+// 3. 创建 web engine,如果有业务自定义模块和组件,从此处传入
+```
+
+### 加载业务 Bundle
+
+加载 bundle 包有多种方式,可根据业务需要灵活选择,只需要确保引入顺序在 WebRenderer 之后即可
+
+#### 在模板文件内引用加载
+
+```html
+
+
+
+
+
+```
+
+#### 在入口文件内动态加载
+
+```javascript
+import { HippyWebEngine } from '@hippy/web-renderer';
+
+const engine = HippyWebEngine.create();
+
+ engine.load('https://xxxx.com/hippy-bundle/index.bundle.js').then(() => {
+ engine.start({
+ id: 'root',
+ name: 'example',
+ });
+});
+```
+
+#### 业务源码直接引用
+
+```javascript
+import { HippyCallBack, HippyWebEngine, HippyWebModule, View } from '@hippy/web-renderer';
+// 导入业务 bundle 的入口文件,需放在 web renderer 导入之后
+import './main';
+
+
+const engine = HippyWebEngine.create();
+```
+
+### 启动 WebRenderer
+
+加载完业务 bundle 后,调用相关 API 创建并启动 WebRenderer
+
+```js
+// 创建 web engine,如果有业务自定义模块和组件,从此处传入
+// 如果只使用官方模块和组件,则直接使用 const engine = HippyWebEngine.create() 即可
+const engine = HippyWebEngine.create({
+ modules: {
+ CustomCommonModule,
+ },
+ components: {
+ CustomPageView,
+ },
+});
+
+// 启动 web renderer
+engine.start({
+ // 挂载的 dom id
+ id: 'root',
+ // 模块名
+ name: 'module-name',
+ // 模块启动参数,业务自定义,
+ // hippy-react 可以从 入口文件props里获取,hippy-vue可以从 app.$options.$superProps 里获取
+ params: {
+ path: '/home',
+ singleModule: true,
+ isSingleMode: true,
+ business: '',
+ data: { },
+ },
+});
+```
diff --git a/docs/feature/_sidebar.md b/docs/feature/_sidebar.md
index eb5c3870814..a2369a7d15f 100644
--- a/docs/feature/_sidebar.md
+++ b/docs/feature/_sidebar.md
@@ -1,9 +1,10 @@
-- 3.0+
+- 3.x
- [VFS](feature/feature3.0/vfs.md)
- [Layout 引擎切换](feature/feature3.0/layout.md)
- [Snapshot](feature/feature3.0/render-node-snapshot.md)
- [双端一致性](feature/feature3.0/cross-platform-consistency.md)
-- 2.0+
+ - [Performance API](feature/feature3.0/performance.md)
+- 2.x
- [动画](feature/feature2.0/animation.md)
- [日志](feature/feature2.0/console.md)
- [自定义字体](feature/feature2.0/custom-font.md)
diff --git a/docs/feature/feature3.0/_sidebar.md b/docs/feature/feature3.0/_sidebar.md
index eb5c3870814..6e346381eef 100755
--- a/docs/feature/feature3.0/_sidebar.md
+++ b/docs/feature/feature3.0/_sidebar.md
@@ -3,6 +3,8 @@
- [Layout 引擎切换](feature/feature3.0/layout.md)
- [Snapshot](feature/feature3.0/render-node-snapshot.md)
- [双端一致性](feature/feature3.0/cross-platform-consistency.md)
+ - [Performance API](feature/feature3.0/performance.md)
+ - [Screenshot](feature/feature3.0/screenshot.md)
- 2.0+
- [动画](feature/feature2.0/animation.md)
- [日志](feature/feature2.0/console.md)
diff --git a/docs/feature/feature3.0/image-decoder-adapter.md b/docs/feature/feature3.0/image-decoder-adapter.md
new file mode 100644
index 00000000000..988c67dd144
--- /dev/null
+++ b/docs/feature/feature3.0/image-decoder-adapter.md
@@ -0,0 +1,35 @@
+# ImageDecoderAdapter
+
+---
+
+## 背景
+
+3.0我们在HippyEngine引擎初始化参数中增加了ImageDecoderAdapter的设置,如果有开发者业务中有使用到特殊格式的图片,如SharpP、avif等,可以通过设置ImageDecoderAdapter来对接你的自定义图片解码器,具体接口描述如下:
+
+## 3.0 图片解码Adapter接口定义
+
+### ImageDecoderAdapter Public methods
+
+ ```java
+ boolean preDecode(@NonNull byte[] data,
+ @Nullable Map
initProps,
+ @NonNull ImageDataHolder imageHolder,
+ @NonNull BitmapFactory.Options options);
+ ```
+
+ 该接口在拉取到图片原始数据的时候会调用,解码的结果可以通过image data holder提供的setBitmap或者setDrawable接口设置到holder中,并且返回true,表示不需要sdk再做解码操作,如果返回false表示需要sdk做默认的解码操作。
+
+ ```java
+ void afterDecode(@Nullable Map initProps,
+ @NonNull ImageDataHolder imageHolder,
+ @NonNull BitmapFactory.Options options);
+ ```
+
+
+ 该接口在解码结束后会调用,为开发者提供二次处理bitmap的机会, 比如要对bitmap做高斯模糊。
+
+ ```java
+ void destroyIfNeeded();
+ ```
+
+ 引擎退出销毁时调用,释放adapter可能占用的资源
diff --git a/docs/feature/feature3.0/performance.md b/docs/feature/feature3.0/performance.md
index d7c6bd069e7..5778a9a682e 100644
--- a/docs/feature/feature3.0/performance.md
+++ b/docs/feature/feature3.0/performance.md
@@ -1 +1,90 @@
# Performance API
+
+## 背景
+
+过去 Hippy SDK 缺乏对关键性能指标的获取和监控机制,各个业务都需自行打点或者魔改 SDK 进行统计,导致 Hippy 团队和接入业务均没有一个针对性能指标的统一基准,数据解读混乱,因此由 SDK 统一提供标准化的性能监控和指标显得非常有必要。
+
+## 指标
+
+### 2\.1 启动耗时
+
+Web 设计了 Performance API ,其中包含了 PerformanceResourceTiming 和 PerformanceNavigationTiming 接口,用于检索和分析有关加载应用程序资源的详细网络计时数据和首屏加载耗时数据
+
+
+
+
+Hippy 3\.0 新架构参考 Web 标准设计了新的性能 API:
+
+
+
+性能数据获取示例:
+
+global\.performance\.getEntries\(\): 获取所有的性能指标对象 (PerformanceResource、PerformanceNavigation等)
+
+global\.performance\.getEntriesByType\('navigation'\): 获取启动加载性能指标对象
+
+global\.performance\.getEntriesByType\('resource'\): 获取资源加载性能指标对象
+
+
+
+>PerformanceNavigationTiming:
+
+| 指标 | 对应 Key |
+|----------------|---------------------|
+| Hippy 引擎加载开始 | hippyNativeInitStart |
+| JS 引擎加载开始 | hippyJsEngineInitStart |
+| JS 引擎加载结束 | hippyJsEngineInitEnd |
+| Hippy 引擎加载结束 | hippyNativeInitEnd |
+| JS Bundle 自执行耗时 | bundleInfo[] |
+| 业务入口执行开始 | hippyRunApplicationStart |
+| 业务入口执行结束 | hippyRunApplicationEnd |
+| 首帧绘制开始 | hippyFirstFrameStart |
+| 首帧绘制结束 | hippyFirstFrameEnd |
+| 启动耗时 | duration |
+| 指标名称 | name |
+| 指标类型 | entryType |
+
+>bundleInfo:
+
+| 指标 | 对应 Key |
+|----------------|---------------------|
+| 主包/分包地址 | url |
+| 执行js包开始时间 | executeSourceStart |
+| 执行js包结束时间 | executeSourceEnd |
+
+>PerformanceResourceTiming:
+
+| 指标 | 对应 Key |
+|----------------|---------------------|
+| 资源地址 | name |
+| 请求资源开始时间 | loadSourceStart |
+| 请求资源结束时间 | loadSourceEnd |
+| 请求耗时 | duration |
+| 指标类型 | entryType |
+
+
+- 适用版本:3\.1
+
+### 2\.2 内存
+
+- 现状:2\.0 已支持 JS 层通过 Performance\.memory 获取到 V8 引擎的内存数据(Hermes 待定)
+
+
+
+- 适用版本:2\.0、3\.1
+
+### 2\.3 流畅度
+
+流畅度可通过 FPS 和 Janky Frame 指标来衡量
+
+浏览器里提供了 requestAnimationFrame API,浏览器会在屏幕刷新(一帧)时机调用回调函数,JS 可在回调函数中执行动画等逻辑,也可用来计算 FPS 和 janky frame。Hippy 由于 JS 线程与 UI 线程独立,渲染异步执行,若通过终端实现 requestAnimationFrame 无法做到与浏览器一致的数据精确度(JS 层获取到的帧率会小于终端的真实帧率),但也可作为一个通用能力提供给业务作为参考。
+
+
+
+- Hippy 3\.0 基于vsync信号重新实现了 requestAnimationFrame API
+- 适用版本:3\.1
+
+## 三、Aegis\-Hippy 接入
+
+aegis\-sdk:1\.42\.4
+
diff --git a/docs/feature/feature3.0/screenshot.md b/docs/feature/feature3.0/screenshot.md
new file mode 100644
index 00000000000..3b5c59d7e01
--- /dev/null
+++ b/docs/feature/feature3.0/screenshot.md
@@ -0,0 +1,42 @@
+# Screenshot for specific views
+
+---
+
+## 背景
+
+过去在一些业务的使用场景中需要针对指定的Hippy view进行截图并做分享,之前截图逻辑都是由开发者自己实现,在3.0中我们针对图片解码使用了Android高版本的api,导致部分开发者自己创建canvas和bitmap,并用draw方法截图的方式无法正常截取图片,为了进一步降低开发者的适配成本,我们把截图能力下沉到了SDK,为开发者提供更为便捷的使用方式。
+
+## 3.0 Screenshot接口定义
+
+### HippyEngine Public methods
+
+```java
+/**
+ * @throws IllegalArgumentException
+ */
+public abstract void getScreenshotBitmapForView(@Nullable Context context,
+ int id, @NonNull ScreenshotBuildCallback callback);
+```
+
+ 针对指定id的View进行截图,context非空情况下需要是HippyRootView挂载容器所属的Activity
+
+```java
+/**
+ * @throws IllegalArgumentException
+ */
+public abstract void getScreenshotBitmapForView(@Nullable Context context,
+ @NonNull View view, @NonNull ScreenshotBuildCallback callback);
+```
+
+ 针对指定的View进行截图,context非空情况下需要是HippyRootView挂载容器所属的Activity
+
+ > 注意:以上2个接口中的context参数如果传入null,会默认使用view的context,view设置的context是loadModule时候在ModuleLoadParams中传入的context,针对一些预加载场景,开发者有可能设置的是app的context,当context不是Activiy的时候会抛出IllegalArgumentException异常导致截图失败。除了context不满足条件,还有其它view不存在或者执行PixelCopy.request都有可能抛出IllegalArgumentException类型异常,需要开发者自行捕获处理。
+
+```java
+public interface ScreenshotBuildCallback {
+
+ void onScreenshotBuildCompleted(Bitmap bitmap, int result);
+}
+```
+
+ 返回截图结果的回调,result为0代表截图成功,非0代表截图失败,失败错误值可以参考系统PixelCopy类中定义的错误码
diff --git a/dom/include/dom/animation/animation_manager.h b/dom/include/dom/animation/animation_manager.h
index 05de7478bd0..af60274e547 100644
--- a/dom/include/dom/animation/animation_manager.h
+++ b/dom/include/dom/animation/animation_manager.h
@@ -61,6 +61,7 @@ class AnimationManager
}
void RemoveVSyncEventListener();
+
void OnDomNodeCreate(const std::vector>& nodes) override;
void OnDomNodeUpdate(const std::vector>& nodes) override;
void OnDomNodeMove(const std::vector>& nodes) override;
diff --git a/dom/include/dom/diff_utils.h b/dom/include/dom/diff_utils.h
index 8f8ec570b0e..5b8c0b9f679 100644
--- a/dom/include/dom/diff_utils.h
+++ b/dom/include/dom/diff_utils.h
@@ -53,7 +53,7 @@ class DiffUtils {
* k: 11,
* }
*/
- static DiffValue DiffProps(const DomValueMap& old_props_map, const DomValueMap& new_props_map);
+ static DiffValue DiffProps(const DomValueMap& old_props_map, const DomValueMap& new_props_map, bool skip_style_diff);
};
} // namespace dom
} // namespace hippy
diff --git a/dom/include/dom/dom_manager.h b/dom/include/dom/dom_manager.h
index 9de80ee4f42..da57955f68e 100644
--- a/dom/include/dom/dom_manager.h
+++ b/dom/include/dom/dom_manager.h
@@ -41,6 +41,8 @@
#include "footstone/base_timer.h"
#include "footstone/worker.h"
+#define HIPPY_EXPERIMENT_LAYER_OPTIMIZATION
+
namespace hippy {
inline namespace dom {
@@ -96,7 +98,7 @@ class DomManager : public std::enable_shared_from_this {
uint32_t id) ;
static void CreateDomNodes(const std::weak_ptr& weak_root_node,
- std::vector>&& nodes);
+ std::vector>&& nodes, bool needSortByIndex);
static void UpdateDomNodes(const std::weak_ptr& weak_root_node,
std::vector>&& nodes);
static void MoveDomNodes(const std::weak_ptr& weak_root_node,
@@ -140,8 +142,12 @@ class DomManager : public std::enable_shared_from_this {
friend class DomNode;
uint32_t id_;
+#ifdef HIPPY_EXPERIMENT_LAYER_OPTIMIZATION
std::shared_ptr optimized_render_manager_;
- std::weak_ptr render_manager_;
+ std::shared_ptr render_manager_;
+#else
+ std::shared_ptr render_manager_;
+#endif
std::unordered_map> timer_map_;
std::shared_ptr task_runner_;
std::shared_ptr worker_;
diff --git a/dom/include/dom/dom_node.h b/dom/include/dom/dom_node.h
index e56964c9deb..858b1d4e14b 100644
--- a/dom/include/dom/dom_node.h
+++ b/dom/include/dom/dom_node.h
@@ -55,6 +55,14 @@ enum RelativeType {
kBack = 1,
};
+struct DiffInfo {
+ bool skip_style_diff;
+ DiffInfo(bool skip_style_diff) : skip_style_diff(skip_style_diff) {}
+
+ private:
+ friend std::ostream& operator<<(std::ostream& os, const DiffInfo& diff_info);
+};
+
struct RefInfo {
uint32_t ref_id;
int32_t relative_to_ref = RelativeType::kDefault;
@@ -67,7 +75,8 @@ struct RefInfo {
struct DomInfo {
std::shared_ptr dom_node;
std::shared_ptr ref_info;
- DomInfo(std::shared_ptr node, std::shared_ptr ref) : dom_node(node), ref_info(ref) {}
+ std::shared_ptr diff_info;
+ DomInfo(std::shared_ptr node, std::shared_ptr ref, std::shared_ptr diff) : dom_node(node), ref_info(ref), diff_info(diff) {}
private:
friend std::ostream& operator<<(std::ostream& os, const DomInfo& dom_info);
@@ -97,6 +106,7 @@ class DomNode : public std::enable_shared_from_this {
uint32_t id = kInvalidId; // RenderNode的id
uint32_t pid = kInvalidId; // 父RenderNode的id
int32_t index = kInvalidIndex; // 本节点在父RenderNode上的索引
+ int32_t depth = kInvalidIndex; // 本节点在父RenderNode上的深度
};
inline std::shared_ptr GetParent() { return parent_.lock(); }
@@ -119,6 +129,8 @@ class DomNode : public std::enable_shared_from_this {
inline void SetLayoutOnly(bool layout_only) { layout_only_ = layout_only; }
inline bool IsVirtual() { return is_virtual_; }
inline void SetIsVirtual(bool is_virtual) { is_virtual_ = is_virtual; }
+ inline bool IsEnableEliminated() { return enable_eliminated_; }
+ inline void SetEnableEliminated(bool enable_eliminated) { enable_eliminated_ = enable_eliminated; }
inline void SetIndex(int32_t index) { index_ = index; }
inline int32_t GetIndex() const { return index_; }
inline void SetRootNode(std::weak_ptr root_node) { root_node_ = root_node; }
@@ -131,6 +143,7 @@ class DomNode : public std::enable_shared_from_this {
void MarkWillChange(bool flag);
int32_t GetSelfIndex();
int32_t GetChildIndex(uint32_t id);
+ int32_t GetSelfDepth();
int32_t IndexOf(const std::shared_ptr& child);
std::shared_ptr GetChildAt(size_t index);
@@ -230,6 +243,10 @@ class DomNode : public std::enable_shared_from_this {
bool is_virtual_{};
bool layout_only_ = false;
+ // Node can only be eliminated for the first time,
+ // and if they cannot be eliminated for the first time, they cannot be eliminated at all times.
+ bool enable_eliminated_ = true;
+
std::weak_ptr parent_;
std::vector> children_;
diff --git a/dom/include/dom/layer_optimized_render_manager.h b/dom/include/dom/layer_optimized_render_manager.h
index a060bad94df..ce683c0cc47 100644
--- a/dom/include/dom/layer_optimized_render_manager.h
+++ b/dom/include/dom/layer_optimized_render_manager.h
@@ -28,6 +28,7 @@ inline namespace dom {
class LayerOptimizedRenderManager : public RenderManager {
public:
LayerOptimizedRenderManager(std::shared_ptr render_manager);
+ inline std::shared_ptr GetInternalNativeRenderManager() { return render_manager_; }
void CreateRenderNode(std::weak_ptr root_node, std::vector>&& nodes) override;
void UpdateRenderNode(std::weak_ptr root_node, std::vector>&& nodes) override;
@@ -73,10 +74,6 @@ class LayerOptimizedRenderManager : public RenderManager {
void FindValidChildren(const std::shared_ptr& node,
std::vector>& valid_children_nodes);
-
- // Record nodes that cannot be eliminated. Nodes can only be eliminated for the first time,
- // and if they cannot be eliminated for the first time, they cannot be eliminated at all times.
- std::set not_eliminated_node_ids_;
};
} // namespace dom
diff --git a/dom/include/dom/layout_node.h b/dom/include/dom/layout_node.h
index 75ac510bc51..01a0ec3f684 100644
--- a/dom/include/dom/layout_node.h
+++ b/dom/include/dom/layout_node.h
@@ -20,6 +20,7 @@
#pragma once
+#include
#include
#include "footstone/hippy_value.h"
@@ -93,14 +94,14 @@ class LayoutNode {
/**
* @brief 插入子节点
- * @param child
- * @param index
+ * @param child LayoutNode ptr
+ * @param index int32
*/
virtual void InsertChild(std::shared_ptr child, uint32_t index) = 0;
/**
* @brief 删除子节点
- * @param child
+ * @param child LayoutNode ptr
*/
virtual void RemoveChild(const std::shared_ptr child) = 0;
@@ -116,13 +117,14 @@ class LayoutNode {
/**
* @brief 设置属性
- * @param style_map 属性的map
+ * @param style_update 属性的map
*/
virtual void SetLayoutStyles(
const std::unordered_map>& style_update,
const std::vector& style_delete) = 0;
};
+void InitLayoutConsts();
std::shared_ptr CreateLayoutNode();
} // namespace dom
diff --git a/dom/include/dom/root_node.h b/dom/include/dom/root_node.h
index a711fc8e578..2b8f505565b 100644
--- a/dom/include/dom/root_node.h
+++ b/dom/include/dom/root_node.h
@@ -22,13 +22,37 @@
#include
+#include "dom/diff_utils.h"
#include "dom/dom_node.h"
-#include "footstone/task_runner.h"
#include "footstone/persistent_object_map.h"
+#include "footstone/task_runner.h"
namespace hippy {
inline namespace dom {
+class RootNode;
+
+/**
+ * In HippyVue/HippyReact, updating node styles can be intricate.
+ * This class is specifically designed to compute the differences when updating DOM node styles.
+ */
+class DomNodeStyleDiffer {
+ public:
+ DomNodeStyleDiffer() = default;
+ ~DomNodeStyleDiffer() = default;
+
+ bool Calculate(const std::shared_ptr& root_node, const std::shared_ptr& dom_info,
+ hippy::dom::DiffValue& style_diff, hippy::dom::DiffValue& ext_style_diff);
+ void Reset() {
+ node_ext_style_map_.clear();
+ node_style_map_.clear();
+ }
+
+ private:
+ std::unordered_map>> node_style_map_;
+ std::unordered_map>> node_ext_style_map_;
+};
+
class RootNode : public DomNode {
public:
using TaskRunner = footstone::runner::TaskRunner;
@@ -50,7 +74,7 @@ class RootNode : public DomNode {
virtual void RemoveEventListener(const std::string& name, uint64_t listener_id) override;
void ReleaseResources();
- void CreateDomNodes(std::vector>&& nodes);
+ void CreateDomNodes(std::vector>&& nodes, bool needSortByIndex);
void UpdateDomNodes(std::vector>&& nodes);
void MoveDomNodes(std::vector>&& nodes);
void DeleteDomNodes(std::vector>&& nodes);
@@ -71,7 +95,6 @@ class RootNode : public DomNode {
void Traverse(const std::function&)>& on_traverse);
void AddInterceptor(const std::shared_ptr& interceptor);
-
static footstone::utils::PersistentObjectMap>& PersistentMap() {
return persistent_map_;
}
@@ -80,16 +103,12 @@ class RootNode : public DomNode {
static void MarkLayoutNodeDirty(const std::vector>& nodes);
struct DomOperation {
- enum class Op {
- kOpCreate, kOpUpdate, kOpDelete, kOpMove
- } op;
+ enum class Op { kOpCreate, kOpUpdate, kOpDelete, kOpMove } op;
std::vector> nodes;
};
struct EventOperation {
- enum class Op {
- kOpAdd, kOpRemove
- } op;
+ enum class Op { kOpAdd, kOpRemove } op;
uint32_t id;
std::string name;
};
@@ -107,6 +126,7 @@ class RootNode : public DomNode {
std::weak_ptr dom_manager_;
std::vector> interceptors_;
std::shared_ptr animation_manager_;
+ std::unique_ptr style_differ_;
static footstone::utils::PersistentObjectMap> persistent_map_;
};
diff --git a/dom/include/dom/scene_builder.h b/dom/include/dom/scene_builder.h
index 5e594dfc45d..e55a668a033 100644
--- a/dom/include/dom/scene_builder.h
+++ b/dom/include/dom/scene_builder.h
@@ -49,7 +49,8 @@ class SceneBuilder {
static void Create(const std::weak_ptr& dom_manager,
const std::weak_ptr& root_node,
- std::vector>&& nodes);
+ std::vector>&& nodes,
+ bool needSortByIndex);
static void Update(const std::weak_ptr& dom_manager,
const std::weak_ptr& root_node,
std::vector>&& nodes);
diff --git a/dom/include/dom/taitank_layout_node.h b/dom/include/dom/taitank_layout_node.h
index c7ea9380f39..25c906344a4 100644
--- a/dom/include/dom/taitank_layout_node.h
+++ b/dom/include/dom/taitank_layout_node.h
@@ -49,7 +49,7 @@ class TaitankLayoutNode : public LayoutNode, public std::enable_shared_from_this
/**
* @brief 设置 Taitank Layout 的属性
- * @param style_map 属性的map
+ * @param style_update 属性的map
*/
void SetLayoutStyles(
const std::unordered_map>& style_update,
@@ -69,7 +69,7 @@ class TaitankLayoutNode : public LayoutNode, public std::enable_shared_from_this
/**
* @brief 设置 position 属性
- * @param css_direction(EdgeLeft|EdgeTop|EdgeRight|EdgeBottom|EdgeStart|EdgeEnd)
+ * @param edge (EdgeLeft|EdgeTop|EdgeRight|EdgeBottom|EdgeStart|EdgeEnd)
* @param position 位置
*/
void SetPosition(Edge edge, float position) override;
@@ -126,21 +126,21 @@ class TaitankLayoutNode : public LayoutNode, public std::enable_shared_from_this
/**
* @brief 获取 margin 属性
- * @param edge
+ * @param edge Edge
* @return left 属性
*/
float GetMargin(Edge edge) override;
/**
* @brief 获取 padding 属性
- * @param edge
+ * @param edge Edge
* @return padding 属性
*/
float GetPadding(Edge edge) override;
/**
* @brief 获取 border 属性
- * @param edge
+ * @param edge Edge
* @return border 属性
*/
float GetBorder(Edge edge) override;
@@ -159,7 +159,6 @@ class TaitankLayoutNode : public LayoutNode, public std::enable_shared_from_this
/**
* @brief 是否 overflow
- * @param overflow
* @return border 属性
*/
bool LayoutHadOverflow();
@@ -172,14 +171,14 @@ class TaitankLayoutNode : public LayoutNode, public std::enable_shared_from_this
/**
* @brief 插入子节点
- * @param child
- * @param index
+ * @param child LayoutNode ptr
+ * @param index uint32
*/
void InsertChild(std::shared_ptr child, uint32_t index) override;
/**
* @brief 删除子节点
- * @param child
+ * @param child LayoutNode ptr
*/
void RemoveChild(const std::shared_ptr child) override;
@@ -191,7 +190,7 @@ class TaitankLayoutNode : public LayoutNode, public std::enable_shared_from_this
/**
* @brief 设置 has new layout 属性
- * @param has_new_layout
+ * @param has_new_layout bool
*/
void SetHasNewLayout(bool has_new_layout) override;
@@ -218,8 +217,6 @@ class TaitankLayoutNode : public LayoutNode, public std::enable_shared_from_this
*/
bool Reset();
- int64_t GetKey() { return key_; }
-
private:
/**
* @brief 解析属性
@@ -259,7 +256,7 @@ class TaitankLayoutNode : public LayoutNode, public std::enable_shared_from_this
/**
* @brief 设置 flex basis 属性
- * @param flex basis
+ * @param flex_basis flex basis
*/
void SetFlexBasis(float flex_basis);
@@ -271,59 +268,59 @@ class TaitankLayoutNode : public LayoutNode, public std::enable_shared_from_this
/**
* @brief 设置 flex grow属性
- * @param flex grow
+ * @param flex_grow flex grow
*/
void SetFlexGrow(float flex_grow);
/**
* @brief 设置 flex shrink属性
- * @param flex shrink
+ * @param flex_shrink flex shrink
*/
void SetFlexShrink(float flex_shrink);
/**
* @brief 设置 flex direction 属性
- * @param flex direction (FLexDirectionRow|FLexDirectionRowReverse|FLexDirectionColumn|FLexDirectionColumnReverse)
+ * @param flex_direction (FLexDirectionRow|FLexDirectionRowReverse|FLexDirectionColumn|FLexDirectionColumnReverse)
*/
void SetFlexDirection(FlexDirection flex_direction);
/**
* @brief 设置 position type 属性
- * @param position_type(PositionTypeRelative|PositionTypeAbsolute)
+ * @param position_type (PositionTypeRelative|PositionTypeAbsolute)
*/
void SetPositionType(PositionType position_type);
/**
* @brief 设置 position 属性
- * @param css_direction(CSSLeft|CSSTop|CSSRight|CSSBottom|CSSStart|CSSEnd)
- * @param position
+ * @param css_direction (CSSLeft|CSSTop|CSSRight|CSSBottom|CSSStart|CSSEnd)
+ * @param position float
*/
void SetPosition(CSSDirection css_direction, float position);
/**
* @brief 设置 margin 属性
- * @param css_direction
- * @param margin
+ * @param css_direction CSSDirection
+ * @param margin float
*/
void SetMargin(CSSDirection css_direction, float margin);
/**
* @brief 设置 margin auto属性
- * @param css_direction
+ * @param css_direction CSSDirection
*/
void SetMarginAuto(CSSDirection css_direction);
/**
* @brief 设置 padding 属性
- * @param css_direction
- * @param padding
+ * @param css_direction CSSDirection
+ * @param padding float
*/
void SetPadding(CSSDirection css_direction, float padding);
/**
* @brief 设置 border 属性
- * @param css_direction
- * @param border
+ * @param css_direction CSSDirection
+ * @param border float
*/
void SetBorder(CSSDirection css_direction, float border);
@@ -335,25 +332,25 @@ class TaitankLayoutNode : public LayoutNode, public std::enable_shared_from_this
/**
* @brief 设置 justify content 属性
- * @param justify
+ * @param justify FlexAlign
*/
void SetJustifyContent(FlexAlign justify);
/**
* @brief 设置 align content 属性
- * @param align_content
+ * @param align_content FlexAlign
*/
void SetAlignContent(FlexAlign align_content);
/**
* @brief 设置 align items 属性
- * @param align_items
+ * @param align_items FlexAlign
*/
void SetAlignItems(FlexAlign align_items);
/**
* @brief 设置 align self 属性
- * @param align_self
+ * @param align_self FlexAlign
*/
void SetAlignSelf(FlexAlign align_self);
@@ -377,7 +374,6 @@ class TaitankLayoutNode : public LayoutNode, public std::enable_shared_from_this
/**
* @brief 分配节点
- * @param overflow_type (OverflowVisible|OverflowHidden|OverflowScroll)
*/
void Allocate();
@@ -391,7 +387,7 @@ class TaitankLayoutNode : public LayoutNode, public std::enable_shared_from_this
std::vector> children_;
TaitankNodeRef engine_node_;
- int64_t key_;
+ MeasureFunction measure_function_ = nullptr;
};
} // namespace dom
diff --git a/dom/src/dom/animation/animation.cc b/dom/src/dom/animation/animation.cc
index 08dde41a476..3b4b49b9bf3 100644
--- a/dom/src/dom/animation/animation.cc
+++ b/dom/src/dom/animation/animation.cc
@@ -254,7 +254,8 @@ void Animation::Run(uint64_t now, const AnimationOnRun& on_run) {
case Animation::Status::kDestroy:
default: {
FOOTSTONE_LOG(ERROR) << "animation status = " << static_cast(status_);
- FOOTSTONE_UNREACHABLE();
+ FOOTSTONE_DCHECK(false);
+ return;
}
}
diff --git a/dom/src/dom/animation/animation_manager.cc b/dom/src/dom/animation/animation_manager.cc
index 66356b9979b..566c0379155 100644
--- a/dom/src/dom/animation/animation_manager.cc
+++ b/dom/src/dom/animation/animation_manager.cc
@@ -69,7 +69,9 @@ void AnimationManager::EmplaceNodeProp(const std::shared_ptr& node, con
animation_nodes_map_.insert({animation_id, nodeIds});
}
auto animation = GetAnimation(animation_id);
- node->EmplaceStyleMap(prop, HippyValue(animation->GetStartValue()));
+ if (animation) {
+ node->EmplaceStyleMap(prop, HippyValue(animation->GetStartValue()));
+ }
}
void AnimationManager::ParseAnimation(const std::shared_ptr& node) {
diff --git a/dom/src/dom/deserializer_unittests.cc b/dom/src/dom/deserializer_unittests.cc
index 07e425d4a6d..40cd696b3d6 100644
--- a/dom/src/dom/deserializer_unittests.cc
+++ b/dom/src/dom/deserializer_unittests.cc
@@ -44,6 +44,7 @@ void CheckUint32(uint32_t value) {
footstone::value::HippyValue hippy_value;
deserializer.ReadObject(hippy_value);
+ footstone::value::SerializerHelper::DestroyBuffer(buffer);
EXPECT_TRUE(hippy_value.GetType() == footstone::value::HippyValue::Type::kNumber);
EXPECT_TRUE(hippy_value.GetNumberType() == footstone::value::HippyValue::NumberType::kUInt32);
EXPECT_TRUE(hippy_value.ToUint32Checked() == value);
@@ -60,6 +61,7 @@ void CheckInt32(int32_t value) {
footstone::value::HippyValue hippy_value;
deserializer.ReadObject(hippy_value);
+ footstone::value::SerializerHelper::DestroyBuffer(buffer);
EXPECT_TRUE(hippy_value.GetType() == footstone::value::HippyValue::Type::kNumber);
EXPECT_TRUE(hippy_value.GetNumberType() == footstone::value::HippyValue::NumberType::kInt32);
EXPECT_TRUE(hippy_value.ToInt32Checked() == value);
@@ -76,6 +78,7 @@ void CheckDouble(double value) {
footstone::value::HippyValue hippy_value;
deserializer.ReadObject(hippy_value);
+ footstone::value::SerializerHelper::DestroyBuffer(buffer);
EXPECT_TRUE(hippy_value.GetType() == footstone::value::HippyValue::Type::kNumber);
EXPECT_TRUE(hippy_value.GetNumberType() == footstone::value::HippyValue::NumberType::kDouble);
EXPECT_TRUE(hippy_value.ToDoubleChecked() == value);
@@ -92,6 +95,7 @@ void CheckString(std::string value) {
footstone::value::HippyValue hippy_value;
deserializer.ReadObject(hippy_value);
+ footstone::value::SerializerHelper::DestroyBuffer(buffer);
EXPECT_TRUE(hippy_value.GetType() == footstone::value::HippyValue::Type::kString);
EXPECT_TRUE(hippy_value.ToStringChecked() == value);
EXPECT_TRUE(hippy_value.ToStringChecked().length() == value.length());
@@ -108,6 +112,7 @@ void CheckMap(footstone::value::HippyValue::HippyValueObjectType value) {
footstone::value::HippyValue hippy_value;
deserializer.ReadObject(hippy_value);
+ footstone::value::SerializerHelper::DestroyBuffer(buffer);
EXPECT_TRUE(hippy_value.GetType() == footstone::value::HippyValue::Type::kObject);
EXPECT_TRUE(hippy_value.IsObject());
EXPECT_TRUE(hippy_value.ToObjectChecked().size() == value.size());
@@ -134,6 +139,7 @@ void CheckArray(footstone::value::HippyValue::HippyValueArrayType value) {
footstone::value::HippyValue hippy_value;
deserializer.ReadObject(hippy_value);
+ footstone::value::SerializerHelper::DestroyBuffer(buffer);
EXPECT_TRUE(hippy_value.GetType() == footstone::value::HippyValue::Type::kArray);
EXPECT_TRUE(hippy_value.IsArray());
EXPECT_TRUE(hippy_value.ToArrayChecked().size() == value.size());
@@ -157,6 +163,7 @@ TEST(DeserializerTest, ReadHeader) {
footstone::value::Deserializer deserializer(buffer.first, buffer.second);
deserializer.ReadHeader();
EXPECT_EQ(deserializer.version_, tdf::base::kLatestVersion);
+ footstone::value::SerializerHelper::DestroyBuffer(buffer);
}
TEST(DeserializerTest, Uint32) {
diff --git a/dom/src/dom/diff_utils.cc b/dom/src/dom/diff_utils.cc
index d101a840bc6..7b11fa274a2 100644
--- a/dom/src/dom/diff_utils.cc
+++ b/dom/src/dom/diff_utils.cc
@@ -69,9 +69,13 @@ static bool ShouldUpdateProperty(const std::string& key, const DomValueMap& old_
return false;
}
-DiffValue DiffUtils::DiffProps(const DomValueMap& old_props_map, const DomValueMap& new_props_map) {
+DiffValue DiffUtils::DiffProps(const DomValueMap& old_props_map, const DomValueMap& new_props_map, bool skip_style_diff) {
std::shared_ptr update_props = std::make_shared();
std::shared_ptr> delete_props = std::make_shared>();
+ if (skip_style_diff) {
+ // 跳过 style diff 计算
+ return std::make_tuple(update_props, delete_props);
+ }
// delete props
// Example:
diff --git a/dom/src/dom/dom_argument.cc b/dom/src/dom/dom_argument.cc
index 10bd581d03e..e0b84233723 100644
--- a/dom/src/dom/dom_argument.cc
+++ b/dom/src/dom/dom_argument.cc
@@ -65,6 +65,7 @@ bool DomArgument::ConvertObjectToBson(const footstone::value::HippyValue& hippy_
std::pair pair = serializer.Release();
bson.resize(pair.second);
memcpy(&bson[0], pair.first, sizeof(uint8_t) * pair.second);
+ footstone::value::SerializerHelper::DestroyBuffer(pair);
return true;
}
diff --git a/dom/src/dom/dom_manager.cc b/dom/src/dom/dom_manager.cc
index 3a9e947dd26..74d81826d92 100644
--- a/dom/src/dom/dom_manager.cc
+++ b/dom/src/dom/dom_manager.cc
@@ -18,8 +18,6 @@
* limitations under the License.
*/
-#define EXPERIMENT_LAYER_OPTIMIZATION
-
#include "dom/dom_manager.h"
#include
@@ -54,11 +52,11 @@ using Deserializer = footstone::value::Deserializer;
using HippyValueArrayType = footstone::value::HippyValue::HippyValueArrayType;
void DomManager::SetRenderManager(const std::weak_ptr& render_manager) {
-#ifdef EXPERIMENT_LAYER_OPTIMIZATION
+#ifdef HIPPY_EXPERIMENT_LAYER_OPTIMIZATION
optimized_render_manager_ = std::make_shared(render_manager.lock());
render_manager_ = optimized_render_manager_;
#else
- render_manager_ = render_manager;
+ render_manager_ = render_manager.lock();
#endif
}
@@ -71,13 +69,14 @@ std::shared_ptr DomManager::GetNode(const std::weak_ptr& weak
}
void DomManager::CreateDomNodes(const std::weak_ptr& weak_root_node,
- std::vector>&& nodes) {
+ std::vector>&& nodes,
+ bool needSortByIndex) {
auto root_node = weak_root_node.lock();
if (!root_node) {
return;
}
size_t create_size = nodes.size();
- root_node->CreateDomNodes(std::move(nodes));
+ root_node->CreateDomNodes(std::move(nodes), needSortByIndex);
FOOTSTONE_DLOG(INFO) << "[Hippy Statistic] create node size = " << create_size << ", total node size = " << root_node->GetChildCount();
}
@@ -124,7 +123,7 @@ void DomManager::DeleteDomNodes(const std::weak_ptr& weak_root_node,
}
void DomManager::EndBatch(const std::weak_ptr& weak_root_node) {
- auto render_manager = render_manager_.lock();
+ auto render_manager = render_manager_;
FOOTSTONE_DCHECK(render_manager);
if (!render_manager) {
return;
@@ -186,7 +185,7 @@ void DomManager::DoLayout(const std::weak_ptr& weak_root_node) {
if (!root_node) {
return;
}
- auto render_manager = render_manager_.lock();
+ auto render_manager = render_manager_;
// check render_manager, measure text dependent render_manager
FOOTSTONE_DCHECK(render_manager);
if (!render_manager) {
@@ -223,8 +222,10 @@ DomManager::byte_string DomManager::GetSnapShot(const std::shared_ptr&
Serializer serializer;
serializer.WriteHeader();
serializer.WriteValue(HippyValue(array));
- auto ret = serializer.Release();
- return {reinterpret_cast(ret.first), ret.second};
+ auto buffer_pair = serializer.Release();
+ byte_string bs = {reinterpret_cast(buffer_pair.first), buffer_pair.second};
+ footstone::value::SerializerHelper::DestroyBuffer(buffer_pair);
+ return bs;
}
bool DomManager::SetSnapShot(const std::shared_ptr& root_node, const byte_string& buffer) {
@@ -262,10 +263,10 @@ bool DomManager::SetSnapShot(const std::shared_ptr& root_node, const b
if (dom_node->GetPid() == orig_root_id) {
dom_node->SetPid(root_node->GetId());
}
- nodes.push_back(std::make_shared(dom_node, nullptr));
+ nodes.push_back(std::make_shared(dom_node, nullptr, nullptr));
}
- CreateDomNodes(root_node, std::move(nodes));
+ CreateDomNodes(root_node, std::move(nodes), false);
EndBatch(root_node);
return true;
diff --git a/dom/src/dom/dom_manager_unittests.cc b/dom/src/dom/dom_manager_unittests.cc
index 4c76cf745fb..f6987b3dba7 100644
--- a/dom/src/dom/dom_manager_unittests.cc
+++ b/dom/src/dom/dom_manager_unittests.cc
@@ -110,7 +110,8 @@ std::vector> ParserJson(const std::string& json_
ref = std::make_shared(id, ref_id);
}
- std::shared_ptr dom_info = std::make_shared(dom_node, ref);
+ auto diff_info = std::make_shared(false);
+ std::shared_ptr dom_info = std::make_shared(dom_node, ref, diff_info);
nodes.push_back(dom_info);
}
return nodes;
@@ -133,7 +134,7 @@ TEST(DomManagerTest, CreateDomNodes) {
std::shared_ptr root_node = manager->GetNode(10);
root_node->SetDomManager(manager);
std::vector> infos = ParserFile("create_node.json", manager);
- manager->CreateDomNodes(std::move(infos));
+ manager->CreateDomNodes(std::move(infos), false);
ASSERT_EQ(root_node->GetChildren().size(), 1);
auto child = root_node->GetChildren();
@@ -155,7 +156,7 @@ TEST(DomManagerTest, UpdateDomNodes) {
std::shared_ptr root_node = manager->GetNode(10);
root_node->SetDomManager(manager);
std::vector> infos = ParserFile("create_node.json", manager);
- manager->CreateDomNodes(std::move(infos));
+ manager->CreateDomNodes(std::move(infos), false);
std::string json =
"[[{\"id\":59,\"pId\":61,\"name\":\"Text\",\"props\":{\"numberOfLines\":1,\"text\":\"本地调试\","
"\"style\":{\"color\":4280558628,\"fontSize\":26}}},{}]]";
@@ -173,7 +174,7 @@ TEST(DomManagerTest, DeleteDomNodes) {
std::shared_ptr root_node = manager->GetNode(10);
root_node->SetDomManager(manager);
std::vector> infos = ParserFile("create_node.json", manager);
- manager->CreateDomNodes(std::move(infos));
+ manager->CreateDomNodes(std::move(infos), false);
std::string json = "[[{\"id\":63,\"pId\":10,\"name\":\"View\"},{}]]";
std::vector> delete_nodes = ParserJson(json, manager);
diff --git a/dom/src/dom/dom_node.cc b/dom/src/dom/dom_node.cc
index 97abf0bb850..18a7dc56d9b 100644
--- a/dom/src/dom/dom_node.cc
+++ b/dom/src/dom/dom_node.cc
@@ -103,13 +103,13 @@ std::shared_ptr DomNode::GetChildAt(size_t index) {
}
int32_t DomNode::AddChildByRefInfo(const std::shared_ptr& dom_info) {
- std::shared_ptr ref_info = dom_info->ref_info;
+ std::shared_ptr& ref_info = dom_info->ref_info;
if (ref_info) {
if (children_.size() == 0) {
children_.push_back(dom_info->dom_node);
} else {
for (uint32_t i = 0; i < children_.size(); ++i) {
- auto child = children_[i];
+ auto& child = children_[i];
if (ref_info->ref_id == child->GetId()) {
if (ref_info->relative_to_ref == RelativeType::kFront) {
children_.insert(
@@ -145,7 +145,7 @@ int32_t DomNode::AddChildByRefInfo(const std::shared_ptr& dom_info) {
int32_t DomNode::GetChildIndex(uint32_t id) {
int32_t index = -1;
for (uint32_t i = 0; i < children_.size(); ++i) {
- auto child = children_[i];
+ auto& child = children_[i];
if (child && child->GetId() == id) {
index = static_cast(i);
break;
@@ -169,6 +169,13 @@ int32_t DomNode::GetSelfIndex() {
return -1;
}
+int32_t DomNode::GetSelfDepth() {
+ if (auto parent = parent_.lock()) {
+ return 1 + parent->GetSelfDepth();
+ }
+ return 1;
+}
+
std::shared_ptr DomNode::RemoveChildAt(int32_t index) {
auto child = children_[footstone::check::checked_numeric_cast(index)];
child->SetParent(nullptr);
@@ -322,11 +329,14 @@ LayoutResult DomNode::GetLayoutInfoFromRoot() {
void DomNode::TransferLayoutOutputsRecursive(std::vector>& changed_nodes) {
auto not_equal = std::not_equal_to<>();
bool changed = layout_node_->IsDirty() || layout_node_->HasNewLayout();
-
- layout_.left = layout_node_->GetLeft();
- layout_.top = layout_node_->GetTop();
- layout_.width = layout_node_->GetWidth();
- layout_.height = layout_node_->GetHeight();
+ bool trigger_layout_event =
+ not_equal(layout_.left, layout_node_->GetLeft()) || not_equal(layout_.top, layout_node_->GetTop()) ||
+ not_equal(layout_.width, layout_node_->GetWidth()) || not_equal(layout_.height, layout_node_->GetHeight());
+
+ layout_.left = std::isnan(layout_node_->GetLeft()) ? 0 : layout_node_->GetLeft();
+ layout_.top = std::isnan(layout_node_->GetTop()) ? 0 : layout_node_->GetTop();
+ layout_.width = std::isnan(layout_node_->GetWidth()) ? 0 : std::max(layout_node_->GetWidth(), .0);
+ layout_.height = std::isnan(layout_node_->GetHeight()) ? 0 : std::max(layout_node_->GetHeight(), .0);
layout_.marginLeft = layout_node_->GetMargin(Edge::EdgeLeft);
layout_.marginTop = layout_node_->GetMargin(Edge::EdgeTop);
layout_.marginRight = layout_node_->GetMargin(Edge::EdgeRight);
@@ -364,19 +374,19 @@ void DomNode::TransferLayoutOutputsRecursive(std::vector(kLayoutEvent,
- weak_from_this(),
- std::make_shared(std::move(layout_obj)));
- auto root = root_node_.lock();
- if (root != nullptr) {
- auto manager = root->GetDomManager().lock();
- if (manager != nullptr) {
- std::vector> ops = {[WEAK_THIS, event] {
- DEFINE_AND_CHECK_SELF(DomNode)
- self->HandleEvent(event);
- }};
- manager->PostTask(Scene(std::move(ops)));
+ if (trigger_layout_event) {
+ auto event = std::make_shared(kLayoutEvent, weak_from_this(),
+ std::make_shared(std::move(layout_obj)));
+ auto root = root_node_.lock();
+ if (root != nullptr) {
+ auto manager = root->GetDomManager().lock();
+ if (manager != nullptr) {
+ std::vector> ops = {[WEAK_THIS, event] {
+ DEFINE_AND_CHECK_SELF(DomNode)
+ self->HandleEvent(event);
+ }};
+ manager->PostTask(Scene(std::move(ops)));
+ }
}
}
}
@@ -487,8 +497,8 @@ void DomNode::UpdateDiff(const std::unordered_map>& update_style,
const std::unordered_map>& update_dom_ext) {
- auto style_diff_value = DiffUtils::DiffProps(*this->GetStyleMap(), update_style);
- auto ext_diff_value = DiffUtils::DiffProps(*this->GetExtStyle(), update_dom_ext);
+ auto style_diff_value = DiffUtils::DiffProps(*this->GetStyleMap(), update_style, false);
+ auto ext_diff_value = DiffUtils::DiffProps(*this->GetExtStyle(), update_dom_ext, false);
auto style_update = std::get<0>(style_diff_value);
auto ext_update = std::get<0>(ext_diff_value);
std::shared_ptr diff_value = std::make_shared();
@@ -604,6 +614,13 @@ std::ostream& operator<<(std::ostream& os, const RefInfo& ref_info) {
return os;
}
+std::ostream& operator<<(std::ostream& os, const DiffInfo& diff_info) {
+ os << "{";
+ os << "\"skip_style_diff\": " << diff_info.skip_style_diff << ", ";
+ os << "}";
+ return os;
+}
+
std::ostream& operator<<(std::ostream& os, const DomNode& dom_node) {
os << "{";
os << "\"id\": " << dom_node.id_ << ", ";
@@ -630,10 +647,14 @@ std::ostream& operator<<(std::ostream& os, const DomNode& dom_node) {
std::ostream& operator<<(std::ostream& os, const DomInfo& dom_info) {
auto dom_node = dom_info.dom_node;
auto ref_info = dom_info.ref_info;
+ auto diff_info = dom_info.diff_info;
os << "{";
if (ref_info != nullptr) {
os << "\"ref info\": " << *ref_info << ", ";
}
+ if (diff_info != nullptr) {
+ os << "\"diff info\": " << *diff_info << ", ";
+ }
if (dom_node != nullptr) {
os << "\"dom node\": " << *dom_node << ", ";
}
diff --git a/dom/src/dom/layer_optimized_render_manager.cc b/dom/src/dom/layer_optimized_render_manager.cc
index fa70cf2686f..3c744dc0010 100644
--- a/dom/src/dom/layer_optimized_render_manager.cc
+++ b/dom/src/dom/layer_optimized_render_manager.cc
@@ -78,6 +78,7 @@ void LayerOptimizedRenderManager::UpdateRenderNode(std::weak_ptr root_
std::vector moved_ids;
moved_ids.reserve(moved_children.size());
for (const auto& moved_node : moved_children) {
+ UpdateRenderInfo(moved_node);
moved_ids.push_back(footstone::check::checked_numeric_cast(moved_node->GetId()));
}
MoveRenderNode(root_node, std::move(moved_ids),
@@ -112,7 +113,7 @@ void LayerOptimizedRenderManager::MoveRenderNode(std::weak_ptr root_no
}
FOOTSTONE_DLOG(INFO) << "[Hippy Statistic] move node size before optimize = " << nodes.size()
<< ", move node size after optimize = " << nodes_to_move.size();
- render_manager_->MoveRenderNode(root_node, std::move(nodes));
+ render_manager_->MoveRenderNode(root_node, std::move(nodes_to_move));
}
void LayerOptimizedRenderManager::DeleteRenderNode(std::weak_ptr root_node,
@@ -128,19 +129,6 @@ void LayerOptimizedRenderManager::DeleteRenderNode(std::weak_ptr root_
FOOTSTONE_DLOG(INFO) << "[Hippy Statistic] delete node size before optimize = " << nodes.size()
<< ", delete node size after optimize = " << nodes_to_delete.size();
if (!nodes_to_delete.empty()) {
- for (auto& node : nodes_to_delete) {
- // Recursively delete all ids on the node tree.
- std::vector> node_stack;
- node_stack.push_back(node);
- while (!node_stack.empty()) {
- auto back_node = node_stack.back();
- node_stack.pop_back();
- not_eliminated_node_ids_.erase(back_node->GetId());
- for (auto& child : back_node->GetChildren()) {
- node_stack.push_back(child);
- }
- }
- }
render_manager_->DeleteRenderNode(root_node, std::move(nodes_to_delete));
}
}
@@ -289,10 +277,9 @@ bool LayerOptimizedRenderManager::IsJustLayoutProp(const char *prop_name) const
}
bool LayerOptimizedRenderManager::CanBeEliminated(const std::shared_ptr& node) {
- bool eliminated = (node->IsLayoutOnly() || node->IsVirtual()) &&
- (not_eliminated_node_ids_.find(node->GetId()) == not_eliminated_node_ids_.end());
+ bool eliminated = (node->IsLayoutOnly() || node->IsVirtual()) && node->IsEnableEliminated();
if (!eliminated) {
- not_eliminated_node_ids_.insert(node->GetId());
+ node->SetEnableEliminated(false);
}
return eliminated;
}
diff --git a/dom/src/dom/root_node.cc b/dom/src/dom/root_node.cc
index 31059bcf9bf..eb94863ea70 100644
--- a/dom/src/dom/root_node.cc
+++ b/dom/src/dom/root_node.cc
@@ -23,7 +23,6 @@
#include
#include "dom/animation/animation_manager.h"
-#include "dom/diff_utils.h"
#include "dom/render_manager.h"
#include "footstone/deserializer.h"
#include "footstone/hippy_value.h"
@@ -45,10 +44,68 @@ using Task = footstone::Task;
footstone::utils::PersistentObjectMap> RootNode::persistent_map_;
+// In Hippy Vue, there are some special cases where there are multiple update instructions for the same node. This can
+// cause issues with the diff algorithm and lead to incorrect results.
+// Example:
+//
+// Dom Node:
+// |------|--------------------------------------|
+// | id | style: {text: "a", color: "red"} |
+// | 1 | diff: {} |
+// |------|--------------------------------------|
+//
+// Previous update algorithm:
+// |------|-----------------------| update instructions: |------|-----------------------| update instructions: |------|-------------------------------------|
+// | id | style: {text: "a"} | { text: "b"} | id | style: {text: "b"} | { text: "b", fontsize: 12} | id | style: {text: "b", fontsize: 12} |
+// | 1 | diff: {} | -------------------> | 1 | diff: {text: "b"} | --------------------------> | 1 | diff: {fontsize: "b"} |
+// |------|-----------------------| |------|-----------------------| |------|-------------------------------------|
+// In the previous diff algorithm, the differences were generated by comparing the DOM styles and update instructions.
+// However, in Hippy Vue, two update instructions might be generated within the same batch. This can lead to incorrect diff results.
+// The diff should be {text: "b", fontsize: 12}, but the previous diff algorithm cacluate {fontsize: "b"}
+//
+// To address this issue, the new update algorithm is as follows:
+// 1. When a node's style needs to be updated for the first time, we save the current style.
+// 2. Subsequent update differences are generated by comparing the saved styles with the update instructions.
+// 3. At the end of the batch, we clear the saved styles.
+bool DomNodeStyleDiffer::Calculate(const std::shared_ptr& root_node,
+ const std::shared_ptr& dom_info, hippy::dom::DiffValue& style_diff,
+ hippy::dom::DiffValue& ext_style_diff) {
+ if (!root_node) return false;
+ if (dom_info == nullptr || dom_info->dom_node == nullptr) return false;
+
+ auto dom_node = root_node->GetNode(dom_info->dom_node->GetId());
+ if (dom_node == nullptr) return false;
+ uint32_t dom_id = dom_node->GetId();
+
+ // 保存 batch 最早的 style 和 ext_style, 该批次中的所有的 diff 都由这个 style 比较产生
+ if (node_style_map_.find(dom_id) == node_style_map_.end()) {
+ std::unordered_map> style;
+ std::unordered_map> ext_style;
+ auto dom_style = dom_node->GetStyleMap();
+ for (const auto& pair : *dom_style) {
+ style[pair.first] = std::make_shared(*pair.second);
+ }
+ node_style_map_.insert({dom_id, style});
+ auto dom_ext_style = dom_node->GetExtStyle();
+ for (const auto& pair : *dom_ext_style) {
+ ext_style[pair.first] = std::make_shared(*pair.second);
+ }
+ node_ext_style_map_.insert({dom_id, ext_style});
+ }
+
+ auto base_style = node_style_map_.at(dom_id);
+ auto base_ext_style = node_ext_style_map_.at(dom_id);
+ style_diff = DiffUtils::DiffProps(base_style, *dom_info->dom_node->GetStyleMap(), false);
+ ext_style_diff = DiffUtils::DiffProps(base_ext_style, *dom_info->dom_node->GetExtStyle(), false);
+ return true;
+}
+
RootNode::RootNode(uint32_t id) : DomNode(id, 0, 0, "", "", nullptr, nullptr, {}) {
+ InitLayoutConsts();
SetRenderInfo({id, 0, 0});
animation_manager_ = std::make_shared();
interceptors_.push_back(animation_manager_);
+ style_differ_ = std::make_unique();
}
RootNode::RootNode() : RootNode(0) {}
@@ -64,17 +121,15 @@ void RootNode::RemoveEventListener(const std::string& name, uint64_t listener_id
RemoveEvent(GetId(), name);
}
-void RootNode::ReleaseResources() {
- animation_manager_->RemoveVSyncEventListener();
-}
+void RootNode::ReleaseResources() {}
-void RootNode::CreateDomNodes(std::vector>&& nodes) {
+void RootNode::CreateDomNodes(std::vector>&& nodes, bool needSortByIndex) {
for (const auto& interceptor : interceptors_) {
interceptor->OnDomNodeCreate(nodes);
}
std::vector> nodes_to_create;
for (const auto& node_info : nodes) {
- auto node = node_info->dom_node;
+ auto& node = node_info->dom_node;
std::shared_ptr parent_node = GetNode(node->GetPid());
if (parent_node == nullptr) {
continue;
@@ -88,8 +143,33 @@ void RootNode::CreateDomNodes(std::vector>&& nodes) {
OnDomNodeCreated(node);
}
for (const auto& node : nodes_to_create) {
- node->SetRenderInfo({node->GetId(), node->GetPid(), node->GetSelfIndex()});
+ if (needSortByIndex) {
+ node->SetRenderInfo({node->GetId(), node->GetPid(), node->GetSelfIndex(), node->GetSelfDepth()});
+ } else {
+ // 如果不需要对 index 排序,其他场景目前没有用到 depth,避免冗余计算
+ node->SetRenderInfo({node->GetId(), node->GetPid(), node->GetSelfIndex(), -1});
+ }
+ }
+
+ if (needSortByIndex) {
+ // 针对反向插入的场景 (比如先查 index = 15的节点,再插入 index = 14,13,12.. 的节点),先做排序。否则会导致 renderNode 节点位置错乱。详见:
+ // https://doc.weixin.qq.com/doc/w3_ANsAsgZ1ACckOPazHXERJqKHOCbP1?scode=AJEAIQdfAAogJJ2RicAMgAvQZ1ACc
+ // 排序要保证两个原则:1. 父节点在子节点前;2. 同一父节点的子节点,必须按照 index 从小到大的顺序排序
+ // 同一层级,不同父节点的子节点,位置可以交叉,但要保证原则2,即同一父节点子节点 index 是从小到大的顺序
+ std::stable_sort(
+ nodes_to_create.begin(),
+ nodes_to_create.end(),
+ [](const std::shared_ptr& a, const std::shared_ptr& b)
+ {
+ auto render_info_a = a->GetRenderInfo();
+ auto render_info_b = b->GetRenderInfo();
+ if (render_info_a.depth == render_info_b.depth) {
+ return render_info_a.index < render_info_b.index;
+ }
+ return render_info_a.depth < render_info_b.depth;
+ });
}
+
auto event = std::make_shared(kDomTreeCreated, weak_from_this(), nullptr);
HandleEvent(event);
@@ -103,51 +183,21 @@ void RootNode::UpdateDomNodes(std::vector>&& nodes) {
interceptor->OnDomNodeUpdate(nodes);
}
- // In Hippy Vue, there are some special cases where there are multiple update instructions for the same node. This can
- // cause issues with the diff algorithm and lead to incorrect results.
- // Example:
- //
- // Dom Node Style:
- // |------|----------------|
- // | id | style |
- // | 1 | text : {} |
- // | 2 | some style |
- //
- // Update instructions:
- // |------|-------------------------------|----------------|------------------------|
- // | id | style | operation | diff style result |
- // | 1 | text : { "color": "blue" } | compare | { "color": "blue" } |
- // | 2 | some style | | |
- // | 1 | text : { "color": "red" } | compare | { "color": "red" } |
- // | 1 | text : { "color": "red" } | compare | { } |
- // In last diff algroithm the diff_style = {}
- //
- // To Solve this case we should use the last update instruction to generate the diff style.
- // Update instructions:
- // |------|-------------------------------|----------------|------------------------|
- // | id | style | operation | diff style result |
- // | 1 | text : { "color": "blue" } | skip | { } |
- // | 2 | some style | | |
- // | 1 | text : { "color": "red" } | skip | { } |
- // | 1 | text : { "color": "red" } | compare | { "color": "red" } |
- // In new diff algroithm the diff_style = { "color": "red" }
- std::unordered_map> skipped_instructions;
- for (const auto& node_info : nodes) {
- auto id = node_info->dom_node->GetId();
- skipped_instructions[id] = node_info;
- }
-
std::vector> nodes_to_update;
- for (const auto& [id, node_info] : skipped_instructions) {
- std::shared_ptr dom_node = GetNode(node_info->dom_node->GetId());
+ for (const auto& node : nodes) {
+ std::shared_ptr dom_node = GetNode(node->dom_node->GetId());
if (dom_node == nullptr) {
continue;
}
- // diff props
- auto style_diff_value = DiffUtils::DiffProps(*dom_node->GetStyleMap(), *node_info->dom_node->GetStyleMap());
- auto ext_diff_value = DiffUtils::DiffProps(*dom_node->GetExtStyle(), *node_info->dom_node->GetExtStyle());
- auto style_update = std::get<0>(style_diff_value);
- auto ext_update = std::get<0>(ext_diff_value);
+
+ hippy::dom::DiffValue style_diff, ext_style_diff;
+ if (!style_differ_->Calculate(std::static_pointer_cast(shared_from_this()), node, style_diff,
+ ext_style_diff)) {
+ continue;
+ }
+
+ auto style_update = std::get<0>(style_diff);
+ auto ext_update = std::get<0>(ext_style_diff);
std::shared_ptr diff_value = std::make_shared();
if (!style_update->empty()) {
diff_value->insert(style_update->begin(), style_update->end());
@@ -155,12 +205,12 @@ void RootNode::UpdateDomNodes(std::vector>&& nodes) {
if (!ext_update->empty()) {
diff_value->insert(ext_update->begin(), ext_update->end());
}
- dom_node->SetStyleMap(node_info->dom_node->GetStyleMap());
- dom_node->SetExtStyleMap(node_info->dom_node->GetExtStyle());
+ dom_node->SetStyleMap(node->dom_node->GetStyleMap());
+ dom_node->SetExtStyleMap(node->dom_node->GetExtStyle());
dom_node->SetDiffStyle(diff_value);
- auto style_delete = std::get<1>(style_diff_value);
- auto ext_delete = std::get<1>(ext_diff_value);
+ auto style_delete = std::get<1>(style_diff);
+ auto ext_delete = std::get<1>(ext_style_diff);
std::shared_ptr> delete_value = std::make_shared>();
if (!style_delete->empty()) {
delete_value->insert(delete_value->end(), style_delete->begin(), style_delete->end());
@@ -170,8 +220,6 @@ void RootNode::UpdateDomNodes(std::vector>&& nodes) {
delete_value->insert(delete_value->end(), ext_delete->begin(), ext_delete->end());
}
dom_node->SetDeleteProps(delete_value);
- node_info->dom_node->SetDiffStyle(diff_value);
- node_info->dom_node->SetDeleteProps(delete_value);
if (!style_update->empty() || !style_delete->empty()) {
dom_node->UpdateLayoutStyleInfo(*style_update, *style_delete);
}
@@ -207,7 +255,7 @@ void RootNode::MoveDomNodes(std::vector>&& nodes) {
continue;
}
nodes_to_move.push_back(node);
- parent_node->AddChildByRefInfo(std::make_shared(node, node_info->ref_info));
+ parent_node->AddChildByRefInfo(std::make_shared(node, node_info->ref_info, nullptr));
}
for (const auto& node : nodes_to_move) {
node->SetRenderInfo({node->GetId(), node->GetPid(), node->GetSelfIndex()});
@@ -274,14 +322,21 @@ void RootNode::CallFunction(uint32_t id, const std::string& name, const DomArgum
}
void RootNode::SyncWithRenderManager(const std::shared_ptr& render_manager) {
+ TDF_PERF_DO_STMT_AND_LOG(unsigned long domCnt = dom_operations_.size();, "RootNode::SyncWithRenderManager");
+ if (style_differ_ != nullptr) style_differ_->Reset();
FlushDomOperations(render_manager);
+ TDF_PERF_DO_STMT_AND_LOG(unsigned long evCnt = event_operations_.size();
+ , "RootNode::FlushDomOperations Done, dom op count:%lld", domCnt);
FlushEventOperations(render_manager);
+ TDF_PERF_LOG("RootNode::FlushEventOperations Done, event op count:%d", evCnt);
DoAndFlushLayout(render_manager);
+ TDF_PERF_LOG("RootNode::DoAndFlushLayout Done");
auto dom_manager = dom_manager_.lock();
if (dom_manager) {
dom_manager->RecordDomEndTimePoint();
}
render_manager->EndBatch(GetWeakSelf());
+ TDF_PERF_LOG("RootNode::SyncWithRenderManager End");
}
void RootNode::AddEvent(uint32_t id, const std::string& event_name) {
@@ -446,7 +501,7 @@ void RootNode::FlushDomOperations(const std::shared_ptr& render_m
}
void RootNode::MarkLayoutNodeDirty(const std::vector>& nodes) {
- for (const auto& node: nodes) {
+ for (const auto& node : nodes) {
if (node && node->GetLayoutNode() && !node->GetLayoutNode()->HasParentEngineNode()) {
auto parent = node->GetParent();
while (parent) {
diff --git a/dom/src/dom/scene_builder.cc b/dom/src/dom/scene_builder.cc
index 5a79c09cb30..a122f8d87d9 100644
--- a/dom/src/dom/scene_builder.cc
+++ b/dom/src/dom/scene_builder.cc
@@ -30,11 +30,12 @@ inline namespace dom {
void SceneBuilder::Create(const std::weak_ptr