• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2025 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "core/components_ng/syntax/repeat_virtual_scroll_2_caches.h"
17 
18 #include <cstdint>
19 #include <optional>
20 #include <unordered_map>
21 #include <unordered_set>
22 
23 #include "base/log/log_wrapper.h"
24 #include "base/memory/referenced.h"
25 #include "core/components_ng/base/frame_node.h"
26 #include "core/components_ng/base/view_stack_processor.h"
27 
28 namespace OHOS::Ace::NG {
29 
30 using CacheItem = RepeatVirtualScroll2Caches::CacheItem;
31 using OptCacheItem = RepeatVirtualScroll2Caches::OptCacheItem;
32 using GetFrameChildResult = RepeatVirtualScroll2Caches::GetFrameChildResult;
33 
MakeCacheItem(RefPtr<UINode> & node,bool isL1)34 RefPtr<RepeatVirtualScroll2CacheItem> RepeatVirtualScroll2CacheItem::MakeCacheItem(RefPtr<UINode>& node, bool isL1)
35 {
36     return MakeRefPtr<RepeatVirtualScroll2CacheItem>(node, isL1);
37 }
38 
RepeatVirtualScroll2Caches(const std::function<std::pair<RIDType,uint32_t> (IndexType)> & onGetRid4Index)39 RepeatVirtualScroll2Caches::RepeatVirtualScroll2Caches(
40     const std::function<std::pair<RIDType, uint32_t>(IndexType)>& onGetRid4Index)
41     : onGetRid4Index_(onGetRid4Index)
42 {}
43 
44 /**
45  * Return a FrameNode child for give index
46  */
GetFrameChild(IndexType index,bool needBuild)47 GetFrameChildResult RepeatVirtualScroll2Caches::GetFrameChild(IndexType index, bool needBuild)
48 {
49     TAG_LOGD(AceLogTag::ACE_REPEAT, "GetFrameChild(index %{public}d, needBuild: %{public}d)", index,
50         static_cast<int32_t>(needBuild));
51     index = ConvertFromToIndex(index);
52     OptCacheItem optCacheItem = GetL1CacheItem4Index(index);
53     if (optCacheItem.has_value()) {
54         return std::pair<uint32_t, CacheItem>(OnGetRid4IndexResult::UNCHANGED_NODE, optCacheItem.value());
55     }
56 
57     if (!needBuild) {
58         return std::pair<uint32_t, CacheItem>(OnGetRid4IndexResult::NO_NODE, nullptr);
59     }
60 
61     optCacheItem = CallOnGetRid4Index(index);
62     return optCacheItem.has_value()
63                ? std::pair<uint32_t, CacheItem>(OnGetRid4IndexResult::UPDATED_NODE, optCacheItem.value())
64                : std::pair<uint32_t, CacheItem>(OnGetRid4IndexResult::NO_NODE, nullptr);
65 }
66 
67 /**
68  * Function called from TS:
69  * purge UINode with given id
70  * i.e. remove map entry rid -> CacheItem
71  */
RemoveNode(RIDType rid)72 void RepeatVirtualScroll2Caches::RemoveNode(RIDType rid)
73 {
74     TAG_LOGD(AceLogTag::ACE_REPEAT, "RemoveNode(rid : %{public}d)", static_cast<int32_t>(rid));
75 
76     DropFromL1ByRid(rid);
77 
78     const auto iter = cacheItem4Rid_.find(rid);
79     if (iter != cacheItem4Rid_.end()) {
80         cacheItem4Rid_.erase(iter);
81     }
82 }
83 
84 /**
85  * Function called from TS:
86  * drop RID from L1
87  * mark CacheItem for rid as invalid
88  */
SetInvalid(RIDType rid)89 void RepeatVirtualScroll2Caches::SetInvalid(RIDType rid)
90 {
91     TAG_LOGD(AceLogTag::ACE_REPEAT, "SetInvalid(rid : %{public}d)", static_cast<int32_t>(rid));
92     DropFromL1ByRid(rid);
93 }
94 
95 /**
96  * drop given rid from the L1
97  * see DropFromL1ByIndex
98  */
DropFromL1ByRid(RIDType rid)99 void RepeatVirtualScroll2Caches::DropFromL1ByRid(RIDType rid)
100 {
101     for (const auto iter : l1Rid4Index_) {
102         if (iter.second == rid) {
103             DropFromL1ByIndex(iter.first);
104             return;
105         }
106     }
107 }
108 
109 /**
110  * drop given index from the L1
111  * update CacheItem.isL1
112  * do not modify IsActive or render tree status
113  *
114  */
DropFromL1ByIndex(IndexType index)115 void RepeatVirtualScroll2Caches::DropFromL1ByIndex(IndexType index)
116 {
117     const std::optional<RIDType>& ridOpt = GetRID4Index(index);
118     if (ridOpt.has_value()) {
119         const auto cacheIter = cacheItem4Rid_.find(ridOpt.value());
120         if (cacheIter != cacheItem4Rid_.end()) {
121             UpdateIsL1(cacheIter->second, false);
122         }
123     }
124 
125     const auto iter = l1Rid4Index_.find(index);
126     if (iter != l1Rid4Index_.end()) {
127         l1Rid4Index_.erase(iter);
128         TAG_LOGD(AceLogTag::ACE_REPEAT, "DropFromL1ByIndex: removed index %{public}d -> rid %{public}d from L1",
129             static_cast<int32_t>(index), static_cast<int32_t>(ridOpt.value()));
130     }
131 }
132 
133 /**
134  * returns index if given rid is in L1Rid4Index
135  */
GetL1Index4Rid(RIDType rid)136 std::optional<IndexType> RepeatVirtualScroll2Caches::GetL1Index4Rid(RIDType rid)
137 {
138     for (auto l1Iter : l1Rid4Index_) {
139         if (l1Iter.second == rid) {
140             return l1Iter.first;
141         }
142     }
143     return std::nullopt;
144 }
145 
UpdateL1Rid4Index(std::map<int32_t,uint32_t> l1Rd4Index,std::unordered_set<uint32_t> ridNeedToRecycle)146 void RepeatVirtualScroll2Caches::UpdateL1Rid4Index(std::map<int32_t, uint32_t> l1Rd4Index,
147     std::unordered_set<uint32_t> ridNeedToRecycle)
148 {
149     TAG_LOGD(AceLogTag::ACE_REPEAT, "UpdateL1Rid4Index");
150 
151     l1Rid4Index_ = l1Rd4Index;
152     TAG_LOGD(AceLogTag::ACE_REPEAT, "L1Rid4Index: %{public}s", DumpL1Rid4Index().c_str());
153 
154     ForEachCacheItem([&](RIDType rid, const CacheItem& cacheItem) {
155         if (ridNeedToRecycle.find(rid) != ridNeedToRecycle.end()) {
156             UpdateIsL1(cacheItem, false);
157         }
158         UpdateIsL1(cacheItem, GetL1Index4RID(rid) != std::nullopt);
159         TAG_LOGD(AceLogTag::ACE_REPEAT, "DumpCacheItem: %{public}s", DumpCacheItem(cacheItem).c_str());
160     });
161 }
162 
GetL1Index4RID(RIDType rid) const163 std::optional<IndexType> RepeatVirtualScroll2Caches::GetL1Index4RID(RIDType rid) const
164 {
165     for (const auto iter : l1Rid4Index_) {
166         if (iter.second == rid) {
167             return iter.first;
168         }
169     }
170     return std::nullopt;
171 }
172 
173 /**
174  * Get the Index of frameNode in L1 / active range
175  * if UINode found
176  */
GetL1Index4Node(const RefPtr<FrameNode> & frameNode) const177 std::optional<IndexType> RepeatVirtualScroll2Caches::GetL1Index4Node(const RefPtr<FrameNode>& frameNode) const
178 {
179     if (frameNode == nullptr) {
180         return std::nullopt;
181     }
182 
183     for (const auto iter : l1Rid4Index_) {
184         const IndexType index = iter.first;
185         const RIDType rid = iter.second;
186 
187         OptCacheItem cacheItemOpt = GetCacheItem4RID(rid);
188         if (cacheItemOpt.has_value() && cacheItemOpt.value()->node_ == frameNode) {
189             return ConvertFromToIndexRevert(index); // index in index -> RID
190         }
191     }
192     return std::nullopt;
193 }
194 
CallOnGetRid4Index(IndexType index)195 OptCacheItem RepeatVirtualScroll2Caches::CallOnGetRid4Index(IndexType index)
196 {
197     TAG_LOGD(AceLogTag::ACE_REPEAT, "CallOnGetRid4Index(index %{public}d: calling TS", index);
198 
199     // swap the ViewStackProcessor instance for secondary while we run the item builder function
200     // so that its results can easily be obtained from it, does not disturb main ViewStackProcessor
201     NG::ScopedViewStackProcessor scopedViewStackProcessor;
202     auto* viewStack = NG::ViewStackProcessor::GetInstance();
203 
204     const std::pair<RIDType, uint32_t> result = onGetRid4Index_(index);
205     if (result.second == OnGetRid4IndexResult::CREATED_NEW_NODE) {
206         // case: new node was created successfully
207         // get it from ViewStackProcessor
208         RefPtr<UINode> node4Index = viewStack->Finish();
209         if (node4Index == nullptr) {
210             TAG_LOGE(AceLogTag::ACE_REPEAT,
211                 "CallOnGetRid4Index(index %{public}d: New Node creation failed. No node on ViewStackProcessor. "
212                 "Internal error!", static_cast<int32_t>(index));
213             return nullptr;
214         }
215         const RIDType rid = result.first;
216         return GetNewRid4Index(index, rid, node4Index);
217     } // case OnGetRid4IndexResult::CREATED_NEW_NODE)
218 
219     if (result.second == OnGetRid4IndexResult::UPDATED_NODE) {
220         // case: TS updated existing node that's in the cache already
221         const RIDType rid = result.first;
222         return GetUpdatedRid4Index(index, rid);
223     } // case OnGetRid4IndexResult::UPDATED_NODE
224 
225     // TS was not able to deliver UINode
226     TAG_LOGE(AceLogTag::ACE_REPEAT, "New node creation failed for index %{public}d. TS unable to create/update node. "
227         "Application error!", static_cast<int32_t>(index));
228     return std::nullopt;
229 }
230 
GetNewRid4Index(IndexType index,RIDType rid,RefPtr<UINode> & node4Index)231 OptCacheItem RepeatVirtualScroll2Caches::GetNewRid4Index(IndexType index, RIDType rid, RefPtr<UINode>& node4Index)
232 {
233     if (rid == 0) {
234         TAG_LOGE(AceLogTag::ACE_REPEAT, "CallOnGetRid4Index(index %{public}d: Node creation failed. Invalid rid. "
235             "Internal error!", static_cast<int32_t>(index));
236         return std::nullopt;
237     }
238     // add to L1 index -> rid
239     l1Rid4Index_[index] = rid;
240     // add to cache
241     cacheItem4Rid_[rid] = RepeatVirtualScroll2CacheItem::MakeCacheItem(node4Index, true);
242     TAG_LOGD(AceLogTag::ACE_REPEAT, "REPEAT TRACE ABNORMAL (after startup) ...CallOnGetRid4Index"
243         "(index %{public}d -> rid %{public}d) returns CacheItem with newly created node %{public}s .",
244         index, static_cast<int32_t>(rid), DumpCacheItem(cacheItem4Rid_[rid]).c_str());
245     ACE_SCOPED_TRACE("RepeatVirtualScroll:CallOnGetRid4Index CREATED_NEW_NODE [index] %d, [rid] %d, [item] %s",
246         index, static_cast<int32_t>(rid), DumpCacheItem(cacheItem4Rid_[rid]).c_str());
247     return cacheItem4Rid_[rid];
248 }
249 
GetUpdatedRid4Index(IndexType index,RIDType rid)250 OptCacheItem RepeatVirtualScroll2Caches::GetUpdatedRid4Index(IndexType index, RIDType rid)
251 {
252     if (rid == 0) {
253         TAG_LOGE(AceLogTag::ACE_REPEAT, "Node update for index %{public}d failed. Invalid rid. "
254             "Internal error!", static_cast<int32_t>(index));
255         return std::nullopt;
256     }
257     const auto& optCacheItem = GetCacheItem4RID(rid);
258     if (!optCacheItem.has_value() || optCacheItem.value()->node_ == nullptr) {
259         TAG_LOGE(AceLogTag::ACE_REPEAT, "Node update for index %{public}d failed. No node in CacheItem. "
260             "Internal error!", static_cast<int32_t>(index));
261         return std::nullopt;
262     }
263     // add to L1 but do not Set Active or add to render tree.
264     l1Rid4Index_[index] = rid;
265     UpdateIsL1(optCacheItem.value(), true);
266     TAG_LOGD(AceLogTag::ACE_REPEAT,
267         "CallOnGetRid4Index(index %{public}d -> rid %{public}d): returns CacheItem with updated node "
268         "%{public}s .", index, static_cast<int32_t>(rid), DumpCacheItem(cacheItem4Rid_[rid]).c_str());
269     ACE_SCOPED_TRACE("RepeatVirtualScroll:CallOnGetRid4Index UPDATED_NODE [index] %d, [rid] %d, [item] %s",
270         index, static_cast<int32_t>(rid), DumpCacheItem(cacheItem4Rid_[rid]).c_str());
271     return optCacheItem;
272 }
273 
274 /**
275  * return CacheItem for RID, if it exists
276  * do not check and CacheItem flags
277  */
GetCacheItem4RID(RIDType rid) const278 OptCacheItem RepeatVirtualScroll2Caches::GetCacheItem4RID(RIDType rid) const
279 {
280     auto cacheIter = cacheItem4Rid_.find(rid);
281     if (cacheIter != cacheItem4Rid_.end()) {
282         return cacheIter->second;
283     }
284     return std::nullopt;
285 }
286 
287 /**
288  * if L1 includes RID for index, return it
289  */
GetRID4Index(IndexType index) const290 std::optional<RIDType> RepeatVirtualScroll2Caches::GetRID4Index(IndexType index) const
291 {
292     auto ridIter = l1Rid4Index_.find(index);
293     if (ridIter != l1Rid4Index_.end()) {
294         return ridIter->second;
295     }
296     return std::nullopt;
297 }
298 
GetL1CacheItem4Index(IndexType index)299 OptCacheItem RepeatVirtualScroll2Caches::GetL1CacheItem4Index(IndexType index)
300 {
301     const auto iter = l1Rid4Index_.find(index);
302     if (iter == l1Rid4Index_.end()) {
303         TAG_LOGE(AceLogTag::ACE_REPEAT, "GetL1CacheItem4Index(index: %{public}d) returns nullptr, new index", index);
304         return std::nullopt;
305     }
306 
307     const RIDType rid = iter->second;
308     const auto cacheItemIter = cacheItem4Rid_.find(rid);
309     if (cacheItemIter != cacheItem4Rid_.end() && cacheItemIter->second->node_ != nullptr &&
310         cacheItemIter->second->isL1_) {
311         TAG_LOGD(AceLogTag::ACE_REPEAT, "GetL1CacheItem4Index(index: %{public}d) returns L1 %{public}s .",
312             index, DumpCacheItem(cacheItemIter->second).c_str());
313         return cacheItemIter->second;
314     }
315     TAG_LOGE(
316         AceLogTag::ACE_REPEAT, "GetL1CacheItem4Index(index: %{public}d) returns nullptr. Invalid CacheItem.", index);
317     return std::nullopt;
318 }
319 
RebuildL1(const std::function<bool (int32_t index,CacheItem & node)> & cbFunc)320 bool RepeatVirtualScroll2Caches::RebuildL1(const std::function<bool(int32_t index, CacheItem& node)>& cbFunc)
321 {
322     std::map<IndexType, RIDType> l1Copy;
323     std::swap(l1Copy, l1Rid4Index_);
324     bool modified = false;
325 
326     for (const auto l1Iter : l1Copy) {
327         const IndexType index = l1Iter.first;
328         const RIDType rid = l1Iter.second;
329 
330         OptCacheItem optCacheItem = GetCacheItem4RID(rid);
331         if (optCacheItem.has_value()) {
332             IndexType indexMapped = ConvertFromToIndexRevert(index);
333             if (optCacheItem.value()->node_ != nullptr && cbFunc(indexMapped, optCacheItem.value())) {
334                 // keep in L1
335                 l1Rid4Index_[index] = rid;
336                 // don't trigger reuse here
337                 UpdateIsL1(optCacheItem.value(), true, false);
338             } else {
339                 UpdateIsL1(optCacheItem.value(), false);
340                 optCacheItem.value()->isActive_ = false;
341                 optCacheItem.value()->isOnRenderTree_ = false;
342                 modified = true;
343             }
344         }
345     }
346     return modified;
347 }
348 
ForEachL1Node(const std::function<void (IndexType index,RIDType rid,const RefPtr<UINode> & node)> & cbFunc)349 void RepeatVirtualScroll2Caches::ForEachL1Node(
350     const std::function<void(IndexType index, RIDType rid, const RefPtr<UINode>& node)>& cbFunc)
351 {
352     for (const auto l1Iter : l1Rid4Index_) {
353         const IndexType index = l1Iter.first;
354         const RIDType rid = l1Iter.second;
355         OptCacheItem optCacheItem = GetCacheItem4RID(rid);
356         if (optCacheItem.has_value()) {
357             cbFunc(index, rid, optCacheItem.value()->node_);
358         }
359     }
360 }
361 
ForEachL1NodeWithOnMove(const std::function<void (const RefPtr<UINode> & node)> & cbFunc)362 void RepeatVirtualScroll2Caches::ForEachL1NodeWithOnMove(const std::function<void(const RefPtr<UINode>& node)>& cbFunc)
363 {
364     std::map<IndexType, RIDType> mappedL1Rid4Index;
365     for (const auto& iter : l1Rid4Index_) {
366         const IndexType index = ConvertFromToIndex(iter.first);
367         const RIDType rid = iter.second;
368         mappedL1Rid4Index.emplace(index, rid);
369     }
370     for (const auto& iter : mappedL1Rid4Index) {
371         const RIDType rid = iter.second;
372         OptCacheItem optCacheItem = GetCacheItem4RID(rid);
373         if (optCacheItem.has_value()) {
374             cbFunc(optCacheItem.value()->node_);
375         }
376     }
377 }
378 
ForEachCacheItem(const std::function<void (RIDType rid,const CacheItem & cacheItem)> & cbFunc)379 void RepeatVirtualScroll2Caches::ForEachCacheItem(
380     const std::function<void(RIDType rid, const CacheItem& cacheItem)>& cbFunc)
381 {
382     for (auto cacheItemIter : cacheItem4Rid_) {
383         cbFunc(cacheItemIter.first, cacheItemIter.second);
384     }
385 }
386 
UpdateMoveFromTo(int32_t from,int32_t to)387 void RepeatVirtualScroll2Caches::UpdateMoveFromTo(int32_t from, int32_t to)
388 {
389     if (moveFromTo_) {
390         moveFromTo_.value().second = to;
391         if (moveFromTo_.value().second == moveFromTo_.value().first) {
392             moveFromTo_.reset();
393         }
394     } else {
395         moveFromTo_ = { from, to };
396     }
397 }
398 
DumpUINode(const RefPtr<UINode> & node) const399 std::string RepeatVirtualScroll2Caches::DumpUINode(const RefPtr<UINode>& node) const
400 {
401     return (node == nullptr) ? "UINode: nullptr"
402                              : "UINode: " + node->GetTag() + "(" + std::to_string(node->GetId()) + ")";
403 }
404 
DumpCacheItem(const CacheItem & cacheItem) const405 std::string RepeatVirtualScroll2Caches::DumpCacheItem(const CacheItem& cacheItem) const
406 {
407     return (cacheItem->node_ == nullptr)
408                ? "UINode nullptr"
409                : DumpUINode(cacheItem->node_) + ", isL1: " + std::to_string(cacheItem->isL1_) +
410                      ", isActive: " + std::to_string(cacheItem->isActive_);
411 }
412 
DumpUINodeCache() const413 std::string RepeatVirtualScroll2Caches::DumpUINodeCache() const
414 {
415     std::string result =
416         "All UINodes in cache (cacheItem4Rid_): size=" + std::to_string(cacheItem4Rid_.size()) + "--------------\n";
417     for (const auto& cacheItemIIter : cacheItem4Rid_) {
418         const auto optIndex4Rid = GetL1Index4RID(cacheItemIIter.first);
419         result += "  rid: " + std::to_string(cacheItemIIter.first) + " -> " +
420                   DumpCacheItem(cacheItemIIter.second).c_str() + " L1 active node index " +
421                   (optIndex4Rid.has_value() ? std::to_string(optIndex4Rid.value()) : "N/A") + "\n";
422     }
423     return result;
424 }
425 
DumpL1Rid4Index() const426 std::string RepeatVirtualScroll2Caches::DumpL1Rid4Index() const
427 {
428     std::string result = "l1Rid4Index size=" + std::to_string(l1Rid4Index_.size()) + "--------------\n";
429 
430     for (const auto mapIter : l1Rid4Index_) {
431         const OptCacheItem optCacheItem = GetCacheItem4RID(mapIter.second);
432         result +=
433             " index " + std::to_string(mapIter.first) + " -> cacheItem(rid: " + std::to_string(mapIter.second) +
434             "): " + (optCacheItem.has_value() ? DumpCacheItem(optCacheItem.value()).c_str() : "CacheItem N/A ERROR") +
435             "\n";
436     }
437     return result;
438 }
439 
UpdateIsL1(const CacheItem & cacheItem,bool isL1,bool shouldTriggerRecycleOrReuse)440 void RepeatVirtualScroll2Caches::UpdateIsL1(const CacheItem& cacheItem, bool isL1, bool shouldTriggerRecycleOrReuse)
441 {
442     cacheItem->isL1_ = isL1;
443     if (!shouldTriggerRecycleOrReuse) {
444         return;
445     }
446     CHECK_NULL_VOID(cacheItem->node_);
447     auto child = cacheItem->node_->GetFrameChildByIndex(0, false);
448     CHECK_NULL_VOID(child);
449     if (isL1) {
450         if (recycledNodeIds_.erase(child->GetId()) == 0) {
451             return;
452         }
453         child->OnReuse();
454     } else {
455         if (recycledNodeIds_.emplace(child->GetId()).second == false) {
456             return;
457         }
458         child->OnRecycle();
459     }
460 }
461 
462 } // namespace OHOS::Ace::NG
463