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