diff --git a/dom/include/dom/dom_manager.h b/dom/include/dom/dom_manager.h index 9de80ee4f42..9f34f51104c 100644 --- a/dom/include/dom/dom_manager.h +++ b/dom/include/dom/dom_manager.h @@ -96,7 +96,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, diff --git a/dom/include/dom/dom_node.h b/dom/include/dom/dom_node.h index 24519607de8..858b1d4e14b 100644 --- a/dom/include/dom/dom_node.h +++ b/dom/include/dom/dom_node.h @@ -106,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(); } @@ -142,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); diff --git a/dom/include/dom/root_node.h b/dom/include/dom/root_node.h index 1b329f13fe9..2b8f505565b 100644 --- a/dom/include/dom/root_node.h +++ b/dom/include/dom/root_node.h @@ -74,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); 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/src/dom/dom_manager.cc b/dom/src/dom/dom_manager.cc index f14eeb827ef..138b06bfdd2 100644 --- a/dom/src/dom/dom_manager.cc +++ b/dom/src/dom/dom_manager.cc @@ -71,13 +71,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(); } @@ -267,7 +268,7 @@ bool DomManager::SetSnapShot(const std::shared_ptr& root_node, const b 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 094566a0236..f6987b3dba7 100644 --- a/dom/src/dom/dom_manager_unittests.cc +++ b/dom/src/dom/dom_manager_unittests.cc @@ -134,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(); @@ -156,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}}},{}]]"; @@ -174,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 861c617d3f2..18a7dc56d9b 100644 --- a/dom/src/dom/dom_node.cc +++ b/dom/src/dom/dom_node.cc @@ -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); diff --git a/dom/src/dom/root_node.cc b/dom/src/dom/root_node.cc index 0225647656c..6e737cd9f01 100644 --- a/dom/src/dom/root_node.cc +++ b/dom/src/dom/root_node.cc @@ -59,13 +59,13 @@ footstone::utils::PersistentObjectMap> RootN // | 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. +// 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. +// 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, @@ -122,7 +122,7 @@ void RootNode::RemoveEventListener(const std::string& name, uint64_t listener_id 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); } @@ -142,8 +142,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); 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& weak_dom_manager, const std::weak_ptr& root_node, - std::vector>&& nodes) { + std::vector>&& nodes, + bool needSortByIndex) { auto dom_manager = weak_dom_manager.lock(); if (dom_manager) { dom_manager->RecordDomStartTimePoint(); - dom_manager->CreateDomNodes(root_node, std::move(nodes)); + dom_manager->CreateDomNodes(root_node, std::move(nodes), needSortByIndex); } } diff --git a/driver/js/packages/hippy-vue-next/src/runtime/render/index.ts b/driver/js/packages/hippy-vue-next/src/runtime/render/index.ts index 326a74effad..c42702db667 100644 --- a/driver/js/packages/hippy-vue-next/src/runtime/render/index.ts +++ b/driver/js/packages/hippy-vue-next/src/runtime/render/index.ts @@ -143,12 +143,14 @@ function endBatch() { const { rootViewId } = getHippyCachedInstance(); // create Scene Builder with rootView id const sceneBuilder = new global.Hippy.SceneBuilder(rootViewId); + // nodes need sort by index + const needSortByIndex = true; // batch operations on nodes based on operation type chunks.forEach((chunk) => { switch (chunk.type) { case NodeOperateType.CREATE: printNodeOperation(chunk.printedNodes, 'createNode'); - sceneBuilder.create(chunk.nodes); + sceneBuilder.create(chunk.nodes, needSortByIndex); handleEventListeners(chunk.eventNodes, sceneBuilder); break; case NodeOperateType.UPDATE: diff --git a/driver/js/src/modules/scene_builder_module.cc b/driver/js/src/modules/scene_builder_module.cc index 661015c0df9..efc1c044e0f 100644 --- a/driver/js/src/modules/scene_builder_module.cc +++ b/driver/js/src/modules/scene_builder_module.cc @@ -435,8 +435,12 @@ std::shared_ptr> RegisterSceneBuilder(const std::wea if (!scope) { return nullptr; } - auto ret = HandleJsValue(scope->GetContext(), arguments[0], scope); - SceneBuilder::Create(scope->GetDomManager(), scope->GetRootNode(), std::move(std::get<2>(ret))); + auto nodes = HandleJsValue(scope->GetContext(), arguments[0], scope); + bool needSortByIndex = false; + if (argument_count == 2) { + scope->GetContext()->GetValueBoolean(arguments[1], &needSortByIndex); + } + SceneBuilder::Create(scope->GetDomManager(), scope->GetRootNode(), std::move(std::get<2>(nodes)), needSortByIndex); return nullptr; }; class_template.functions.emplace_back(std::move(create_func_def));