1 /*
2 * Copyright (c) 2024 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_caches.h"
17
18 #include "core/components_ng/base/view_stack_processor.h"
19
20 namespace OHOS::Ace::NG {
21
RepeatVirtualScrollCaches(const std::map<std::string,std::pair<bool,uint32_t>> & cacheCountL24ttype,const std::function<void (uint32_t)> & onCreateNode,const std::function<void (const std::string &,uint32_t)> & onUpdateNode,const std::function<std::list<std::string> (uint32_t,uint32_t)> & onGetKeys4Range,const std::function<std::list<std::string> (uint32_t,uint32_t)> & onGetTypes4Range,bool reusable)22 RepeatVirtualScrollCaches::RepeatVirtualScrollCaches(
23 const std::map<std::string, std::pair<bool, uint32_t>>& cacheCountL24ttype,
24 const std::function<void(uint32_t)>& onCreateNode,
25 const std::function<void(const std::string&, uint32_t)>& onUpdateNode,
26 const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetKeys4Range,
27 const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetTypes4Range,
28 bool reusable)
29 : cacheCountL24ttype_(cacheCountL24ttype), // each ttype incl default has own L2 cache size
30 // request TS to create new sub-tree for given index or update existing
31 // update subtree cached for (old) index
32 // API might need to change to tell which old item to update
33 onCreateNode_(onCreateNode), onUpdateNode_(onUpdateNode), onGetTypes4Range_(onGetTypes4Range),
34 onGetKeys4Range_(onGetKeys4Range),
35 reusable_(reusable)
36 {
37 }
38
GetKey4Index(uint32_t index,bool allowFetch)39 std::optional<std::string> RepeatVirtualScrollCaches::GetKey4Index(uint32_t index, bool allowFetch)
40 {
41 if (key4index_.find(index) != key4index_.end()) {
42 return key4index_[index];
43 }
44
45 if (!allowFetch) {
46 return std::nullopt;
47 }
48
49 // need to rebuild L1 after fetch ?
50 const bool rebuildL1 =
51 key4index_.size() == 0 && HasOverlapWithLastActiveRange(index, index);
52 TAG_LOGD(AceLogTag::ACE_REPEAT, "GetKey4Index key4index_.size():%{public}d, HasOverlap:%{public}d",
53 static_cast<int32_t>(key4index_.size()), static_cast<int32_t>(HasOverlapWithLastActiveRange(index, index)));
54
55 // allow to fetch extended range of keys if rebuildL1 is needed
56 FetchMoreKeysTTypes(index, index, rebuildL1 == true);
57
58 if (rebuildL1) {
59 // check for each L1 entry if its key is includes in newly received keys
60 // only keep these in L1
61 isModified_ = RebuildL1WithKey([&](const std::string &key) {
62 return index4Key_.find(key) != index4Key_.end();
63 });
64 }
65
66 return GetKey4Index(index, false);
67 }
68
69 /**
70 * does given range overlap the last active range?
71 */
HasOverlapWithLastActiveRange(uint32_t from,uint32_t to)72 bool RepeatVirtualScrollCaches::HasOverlapWithLastActiveRange(uint32_t from, uint32_t to)
73 {
74 const auto lastFrom = lastActiveRanges_[0].first;
75 const auto lastTo = lastActiveRanges_[0].second;
76 if (lastFrom <= lastTo) {
77 return to >= lastFrom && from <= lastTo;
78 } else {
79 return to <= lastTo || to >= lastFrom || from <= lastTo || from >= lastFrom;
80 }
81 }
82
83 /**
84 * get more index -> key and index -> ttype from TS side
85 */
FetchMoreKeysTTypes(uint32_t from,uint32_t to,bool allowFetchMore)86 bool RepeatVirtualScrollCaches::FetchMoreKeysTTypes(uint32_t from, uint32_t to, bool allowFetchMore)
87 {
88 if (from > to) {
89 return false;
90 }
91
92 if (allowFetchMore) {
93 // following a key4index_/ttype4index_ purge fetch the whole range
94 const auto rangeStart = lastActiveRanges_[0].first;
95 const auto rangeEnd = lastActiveRanges_[0].second;
96
97 if (rangeStart <= rangeEnd) {
98 return FetchMoreKeysTTypes(reusable_?from:rangeStart, std::max(to, rangeEnd), false);
99 } else {
100 const bool v1 = FetchMoreKeysTTypes(0, rangeEnd, false);
101 const bool v2 = FetchMoreKeysTTypes(rangeStart, std::numeric_limits<int>::max(), false);
102 return v1 || v2;
103 }
104 }
105
106 ACE_SCOPED_TRACE("RepeatVirtualScrollCaches::FetchMoreKeysTTypes from[%d] to[%d] allowFetchMore[%d]",
107 static_cast<int32_t>(from), static_cast<int32_t>(to), static_cast<int32_t>(allowFetchMore));
108 TAG_LOGD(AceLogTag::ACE_REPEAT, "FetchMoreKeysTTypes from:%{public}d, to:%{public}d, allowFetchMore:%{public}d",
109 static_cast<int32_t>(from), static_cast<int32_t>(to), static_cast<int32_t>(allowFetchMore));
110
111 // always request the same range for keys and ttype
112 // optimism by merging the two calls into one
113 const std::list<std::string> keysFrom = onGetKeys4Range_(from, to);
114 const std::list<std::string> ttypesFrom = onGetTypes4Range_(from, to);
115 if ((keysFrom.size() == 0) || (ttypesFrom.size() == 0) || (keysFrom.size() != ttypesFrom.size())) {
116 TAG_LOGE(AceLogTag::ACE_REPEAT,
117 "fail to fetch keys and/or ttyypes: requested range %{public}d - %{public}d. "
118 "Received number of keys: %{public}d, of ttypes: %{public}d",
119 static_cast<int32_t>(from), static_cast<int32_t>(to),
120 static_cast<int32_t>(keysFrom.size()), static_cast<int32_t>(ttypesFrom.size()));
121 return false;
122 }
123 TAG_LOGD(AceLogTag::ACE_REPEAT,
124 "Received number of keys: %{public}d, of ttypes: %{public}d:",
125 static_cast<int32_t>(keysFrom.size()), static_cast<int32_t>(ttypesFrom.size()));
126
127 // fill-in index <-> key maps
128 auto from1 = from;
129 for (const auto& key : keysFrom) {
130 key4index_[from1] = key;
131 index4Key_[key] = from1;
132
133 const auto cacheItemIter = node4key_.find(key);
134 if (cacheItemIter != node4key_.end()) {
135 // TS onGetKeys4Range_ has made any needed updates for this key -> UINode
136 cacheItemIter->second.isValid = true;
137 }
138 from1++;
139 }
140
141 // fill-in index <-> ttype maps
142 from1 = from;
143 for (const auto& ttype : ttypesFrom) {
144 ttype4index_[from1] = ttype;
145 index4ttype_[ttype] = from1;
146 from1++;
147 }
148
149 // false when nothing was fetched
150 return from1 > from;
151 }
152
153 // get UINode for given index without create.
GetCachedNode4Index(uint32_t index)154 RefPtr<UINode> RepeatVirtualScrollCaches::GetCachedNode4Index(uint32_t index)
155 {
156 TAG_LOGD(AceLogTag::ACE_REPEAT, "GetCachedNode4Index index %{public}d", static_cast<int32_t>(index));
157
158 const auto key = GetKey4Index(index, false);
159 const auto node4Key = GetCachedNode4Key(key);
160 const auto& ttype = GetTType4Index(index);
161
162 if (!key.has_value() || !ttype.has_value() || !node4Key.has_value()) {
163 TAG_LOGD(AceLogTag::ACE_REPEAT, "no CachedItem for index %{public}d", static_cast<int32_t>(index));
164 return nullptr;
165 }
166
167 if (!reusable_ && !IsInL1Cache(key.value())) {
168 return nullptr;
169 }
170
171 auto uiNode = node4Key.value().item;
172 const auto& node4Ttype = GetCachedNode4Key4Ttype(key, ttype);
173 if (node4Ttype != uiNode) {
174 // STATE_MGMT_NOTE check if the node4Ttype is a nullptr instead
175 // UINode for key exists, but the requested ttype is different from the ttype used
176 // to render the returned UINode
177 // STATE_MGMT_NOTE how to handle ?
178 TAG_LOGD(AceLogTag::ACE_REPEAT,
179 "index %{public}d -> %{public}s, templateId %{public}s, "
180 "found UINode %{public}s that has been created from a different template.",
181 static_cast<int32_t>(index), key.value().c_str(),
182 ttype.value().c_str(), DumpUINode(uiNode).c_str());
183 // mark as not valid, needs fresh render or update other UINode
184 // what to do with existing item?
185 // STATE_MGMT_NOTE: Can not just del like this?
186 // how to fix: call to RepeatVirtualScrollNode::DropFromL1
187 node4key_.erase(key.value());
188 RemoveKeyFromL1(key.value());
189 return nullptr;
190 }
191
192 if (!node4Key.value().isValid) {
193 // impossible situation: TS onKeyIndex shoul;d have updated repeatItem.index already!
194 TAG_LOGW(AceLogTag::ACE_REPEAT,
195 "index %{public}d -> %{public}s, templateId %{public}s, "
196 "found UINode %{public}s marked inValid. Internal error!",
197 static_cast<int32_t>(index), key.value().c_str(), ttype.value().c_str(), DumpUINode(uiNode).c_str());
198 UpdateSameKeyItem(key.value(), index);
199 node4key_[key.value()].isValid = true;
200 }
201
202 return node4Key.value().item;
203 }
204
AddKeyToL1(const std::string & key,bool shouldTriggerReuse)205 void RepeatVirtualScrollCaches::AddKeyToL1(const std::string& key, bool shouldTriggerReuse)
206 {
207 TAG_LOGD(AceLogTag::ACE_REPEAT, "AddKeyToL1 key:%{public}s", key.c_str());
208 activeNodeKeysInL1_.emplace(key);
209
210 if (!shouldTriggerReuse) {
211 return;
212 }
213
214 RefPtr<UINode> child;
215 const auto& it = node4key_.find(key);
216 if (it != node4key_.end() && it->second.item) {
217 child = it->second.item->GetFrameChildByIndex(0, false);
218 }
219 CHECK_NULL_VOID(child);
220
221 // if node is already reused, do nothing
222 if (reusedNodeIds_.emplace(child->GetId()).second == false) {
223 return;
224 }
225
226 // fire OnReuse to trigger node pattern handlers
227 TAG_LOGD(AceLogTag::ACE_REPEAT, "OnReuse() nodeId:%{public}d key:%{public}s", child->GetId(), key.c_str());
228 child->OnReuse();
229 }
230
AddKeyToL1WithNodeUpdate(const std::string & key,uint32_t index,bool shouldTriggerRecycle)231 void RepeatVirtualScrollCaches::AddKeyToL1WithNodeUpdate(const std::string& key, uint32_t index,
232 bool shouldTriggerRecycle)
233 {
234 onUpdateNode_(key, index);
235 AddKeyToL1(key, shouldTriggerRecycle);
236 }
237
RemoveKeyFromL1(const std::string & key,bool shouldTriggerRecycle)238 void RepeatVirtualScrollCaches::RemoveKeyFromL1(const std::string& key, bool shouldTriggerRecycle)
239 {
240 TAG_LOGD(AceLogTag::ACE_REPEAT, "RemoveKeyFromL1 key:%{public}s", key.c_str());
241 activeNodeKeysInL1_.erase(key);
242
243 if (!shouldTriggerRecycle) {
244 return;
245 }
246
247 RefPtr<UINode> child;
248 const auto& it = node4key_.find(key);
249 if (it != node4key_.end() && it->second.item) {
250 child = it->second.item->GetFrameChildByIndex(0, false);
251 }
252 CHECK_NULL_VOID(child);
253
254 // if node is not reused currently, do nothing
255 if (reusedNodeIds_.erase(child->GetId()) == 0) {
256 return;
257 }
258
259 // fire OnRecycle to trigger node pattern handlers
260 TAG_LOGD(AceLogTag::ACE_REPEAT, "OnRecycle() nodeId:%{public}d key:%{public}s", child->GetId(), key.c_str());
261 child->OnRecycle();
262 }
263
CheckTTypeChanged(uint32_t index)264 bool RepeatVirtualScrollCaches::CheckTTypeChanged(uint32_t index)
265 {
266 std::string oldTType;
267 if (auto iter = ttype4index_.find(index); iter != ttype4index_.end()) {
268 oldTType = iter->second;
269 }
270
271 FetchMoreKeysTTypes(index, index, false);
272
273 std::string newTType;
274 if (auto iter = ttype4index_.find(index); iter != ttype4index_.end()) {
275 newTType = iter->second;
276 }
277
278 return oldTType != newTType;
279 }
280
281 /** scenario:
282 * Repeat gets updated due to data change.
283 * 1. TS calls RepeatVirtualScrollNode,
284 * then calls this function.
285 * 2. RepeatVirtualScrollNode requests layout to rebuild the UI
286 * 3. layout sends RepeatVirtualScrollNode::GetFrameChild calls
287 * 4. how to service GetFrameChild call:
288 * - first check for L1 keys (same template type) if any can be updated.
289 * These UINodes remain in the render tree.
290 * - if no L1 item, the look for L2 keys (same template type)
291 */
InvalidateKeyAndTTypeCaches()292 void RepeatVirtualScrollCaches::InvalidateKeyAndTTypeCaches()
293 {
294 TAG_LOGD(AceLogTag::ACE_REPEAT,
295 "invalidating index-> key and index->ttype mapping, layout needs to request all UINodes again .. ");
296 key4index_.clear();
297 index4Key_.clear();
298 ttype4index_.clear();
299 index4ttype_.clear();
300 // mark all cached UINodes need to update.
301 // TS onKey4Index will fetch key-> RepeatItem and uupdate RepeatItem.index if changed
302 // Call UpdateDirty2 to make partial updates right away
303 // Therefore, on return GetKeys4Range can set node4key -> CacheItem back to isValid.true
304 // for all retained keys.
305 for (auto& [key, item] : node4key_) {
306 item.isValid = false;
307 }
308
309 // we do not request new index <-> because we are still
310 // inside observed Repeat rerender.
311 // instead for FetchMoreKeysTTypes will fetch the entire range
312 }
313
314 /**
315 * scenario: scroll, try to update an existing UINode
316 *
317 * find an key / UINode in L2 and update the UINode to
318 * render the data source item 'forIndex'.
319 */
UpdateFromL2(uint32_t forIndex)320 RefPtr<UINode> RepeatVirtualScrollCaches::UpdateFromL2(uint32_t forIndex)
321 {
322 TAG_LOGD(AceLogTag::ACE_REPEAT, "forIndex: %{public}d", static_cast<int32_t>(forIndex));
323
324 const auto iterTType = ttype4index_.find(forIndex);
325 if (iterTType == ttype4index_.end()) {
326 TAG_LOGD(AceLogTag::ACE_REPEAT, "no ttype for index %{public}d", static_cast<int32_t>(forIndex));
327 return nullptr;
328 }
329 const auto ttype = iterTType->second;
330 const auto iterNewKey = key4index_.find(forIndex);
331 if (iterNewKey == key4index_.end()) {
332 TAG_LOGD(AceLogTag::ACE_REPEAT, "no key for index %{public}d", static_cast<int32_t>(forIndex));
333 return nullptr;
334 }
335 const std::string forKey = iterNewKey->second;
336
337 const auto& oldKey = GetL2KeyToUpdate(ttype);
338 if (!oldKey) {
339 // no key for this ttype available to update
340 TAG_LOGD(AceLogTag::ACE_REPEAT,
341 "for index %{public}d, ttype %{public}s, no UINode found to update",
342 static_cast<int32_t>(forIndex), ttype.c_str());
343 return nullptr;
344 }
345
346 TAG_LOGD(AceLogTag::ACE_REPEAT,
347 "for index %{public}d, from old key %{public}s requesting TS to update child UINodes ....",
348 static_cast<int32_t>(forIndex), oldKey.value().c_str());
349
350 // call TS to do the RepeatItem update
351 onUpdateNode_(oldKey.value(), forIndex);
352
353 TAG_LOGD(AceLogTag::ACE_REPEAT,
354 "for index %{public}d, from old key %{public}s requesting TS to update child UINodes - done ",
355 static_cast<int32_t>(forIndex), oldKey.value().c_str());
356
357 return UINodeHasBeenUpdated(ttype, oldKey.value(), forKey);
358 }
359
UpdateSameKeyItem(const std::string & key,uint32_t index)360 void RepeatVirtualScrollCaches::UpdateSameKeyItem(const std::string& key, uint32_t index)
361 {
362 // call TS to do the RepeatItem update
363 onUpdateNode_(key, index);
364 }
365
CreateNewNode(uint32_t forIndex)366 RefPtr<UINode> RepeatVirtualScrollCaches::CreateNewNode(uint32_t forIndex)
367 {
368 TAG_LOGD(AceLogTag::ACE_REPEAT, "forIndex: %{public}d", static_cast<int32_t>(forIndex));
369
370 // get key
371 const auto iter = key4index_.find(forIndex);
372 if (iter == key4index_.end()) {
373 TAG_LOGE(AceLogTag::ACE_REPEAT, "fail to create node of %{public}d", static_cast<int32_t>(forIndex));
374 return nullptr;
375 }
376 const auto& forKey = iter->second;
377
378 ACE_SCOPED_TRACE("RepeatVirtualScrollCaches::CreateNewNode index[%d] -> key[%s]",
379 static_cast<int32_t>(forIndex), forKey.c_str());
380
381 // see if node already created, just for safety
382 const auto nodeIter = node4key_.find(forKey);
383 if (nodeIter != node4key_.end()) {
384 // have a node for this key already, just return
385 return nodeIter->second.item;
386 }
387
388 // need to create a new node for key
389
390 // get ttype
391 const auto ttypeIter = ttype4index_.find(forIndex);
392 if (ttypeIter == ttype4index_.end()) {
393 TAG_LOGE(AceLogTag::ACE_REPEAT, "fail to create %{public}d node due to type is missing", forIndex);
394 return nullptr;
395 }
396 const auto& ttype = ttypeIter->second;
397
398 // swap the ViewStackProcessor instance for secondary while we run the item builder function
399 // so that its results can easily be obtained from it, does not disturb main ViewStackProcessor
400 NG::ScopedViewStackProcessor scopedViewStackProcessor;
401 auto* viewStack = NG::ViewStackProcessor::GetInstance();
402
403 // call TS side
404 onCreateNode_(forIndex);
405
406 const auto& node4Index = viewStack->Finish();
407
408 if (!node4Index) {
409 TAG_LOGE(AceLogTag::ACE_REPEAT,
410 "New Node create: For index %{public}d -> key %{public}s -> ttype %{public}s "
411 "item builder FAILED to gen FrameNode. ERROR",
412 forIndex, forKey.c_str(), ttype.c_str());
413 return nullptr;
414 }
415
416 // add node to node4key4ttype_
417 const auto node4KeyIter = node4key4ttype_.find(ttype);
418 if (node4KeyIter != node4key4ttype_.end()) {
419 node4KeyIter->second[forKey] = node4Index;
420 } else {
421 std::unordered_map<std::string, RefPtr<UINode>> node4Key;
422 node4Key[forKey] = node4Index;
423 node4key4ttype_[ttype] = std::move(node4Key);
424 }
425
426 // add node to node4key_
427 node4key_[forKey] = CacheItem { true, node4Index };
428 TAG_LOGD(AceLogTag::ACE_REPEAT,
429 "New Node create: For index %{public}d -> key %{public}s -> ttype %{public}s created new Node %{public}s . ",
430 forIndex, forKey.c_str(), ttype.c_str(), DumpUINode(node4Index).c_str());
431 return node4Index;
432 }
433
ForEachL1IndexUINode(std::map<int32_t,RefPtr<UINode>> & children)434 void RepeatVirtualScrollCaches::ForEachL1IndexUINode(std::map<int32_t, RefPtr<UINode>>& children)
435 {
436 for (const auto& key : activeNodeKeysInL1_) {
437 const auto& cacheItem = node4key_[key];
438 const auto& indexIter = index4Key_.find(key);
439 if (indexIter == index4Key_.end()) {
440 TAG_LOGE(AceLogTag::ACE_REPEAT, "fail to get index for %{public}s key", key.c_str());
441 continue;
442 }
443 children.emplace(indexIter->second, cacheItem.item);
444 }
445 }
446
RecycleItemsByIndex(int32_t index)447 void RepeatVirtualScrollCaches::RecycleItemsByIndex(int32_t index)
448 {
449 if (!reusable_) {
450 return;
451 }
452 auto keyIter = key4index_.find(index);
453 if (keyIter != key4index_.end()) {
454 // STATE_MGMT_NOTE
455 // can not just remove from L1, also need to detach from tree!
456 // how to fix cause a call to RepeatVirtualScrollNode::DropFromL1 in
457 TAG_LOGD(
458 AceLogTag::ACE_REPEAT, "remove index %{public}d -> key %{public}s from L1", index, keyIter->second.c_str());
459
460 ACE_SCOPED_TRACE(
461 "RepeatVirtualScrollCaches::RecycleItemsByIndex index[%d] -> key [%s]", index, keyIter->second.c_str());
462
463 // don't fire OnRecycle here, as we manage reuse/recycle indepedently
464 RemoveKeyFromL1(keyIter->second, false);
465 isModified_ = true;
466 }
467 }
468
469 /**
470 * iterate over all entries of L1 and call function for each entry
471 * if function returns true, entry is added to rebuild L1
472 * cbFunc return true, key
473 * cbFunc returns false drop from L1
474 */
RebuildL1(const std::function<bool (int32_t index,const RefPtr<UINode> & node)> & cbFunc)475 bool RepeatVirtualScrollCaches::RebuildL1(const std::function<bool(int32_t index, const RefPtr<UINode>& node)>& cbFunc)
476 {
477 ACE_SCOPED_TRACE("RepeatVirtualScrollCaches::RebuildL1 activeNodeKeysInL1_.size()=%d",
478 static_cast<int32_t>(activeNodeKeysInL1_.size()));
479
480 std::unordered_set<std::string> l1Copy;
481 std::swap(l1Copy, activeNodeKeysInL1_);
482 bool modified = false;
483 for (const auto& key : l1Copy) {
484 const auto& indexIter = index4Key_.find(key);
485 if (indexIter == index4Key_.end()) {
486 continue;
487 }
488 const auto& cacheItem = node4key_[key];
489 int32_t index = static_cast<int32_t>(indexIter->second);
490 if (cbFunc(index, cacheItem.item)) {
491 AddKeyToL1(key, false);
492 } else {
493 RemoveKeyFromL1(key);
494 modified = true;
495 }
496 }
497
498 std::string result = "activeNodeKeysInL1_: ";
499 for (const auto& l1Key : activeNodeKeysInL1_) {
500 result += l1Key + ",";
501 }
502 TAG_LOGD(AceLogTag::ACE_REPEAT, "RebuildL1 done. %{public}s", result.c_str());
503 if (isModified_) {
504 modified = isModified_;
505 isModified_ = false;
506 }
507 return modified;
508 }
509
RebuildL1WithKey(const std::function<bool (const std::string & key)> & cbFunc)510 bool RepeatVirtualScrollCaches::RebuildL1WithKey(const std::function<bool(const std::string& key)>& cbFunc)
511 {
512 ACE_SCOPED_TRACE("RepeatVirtualScrollCaches::RebuildL1WithKey activeNodeKeysInL1_.size()=%d",
513 static_cast<int32_t>(activeNodeKeysInL1_.size()));
514 std::unordered_set<std::string> l1Copy;
515 std::swap(l1Copy, activeNodeKeysInL1_);
516 bool modified = false;
517 for (const auto& key : l1Copy) {
518 if (cbFunc(key)) {
519 AddKeyToL1(key, false);
520 } else {
521 RemoveKeyFromL1(key);
522 modified = true;
523 }
524 }
525
526 std::string result = "activeNodeKeysInL1_: ";
527 for (const auto& l1Key : activeNodeKeysInL1_) {
528 result += l1Key + ",";
529 }
530 TAG_LOGD(AceLogTag::ACE_REPEAT, "RebuildL1WithKey done. %{public}s", result.c_str());
531 return modified;
532 }
533
DropFromL1(const std::string & key)534 RefPtr<UINode> RepeatVirtualScrollCaches::DropFromL1(const std::string& key)
535 {
536 const auto& cacheItem4Key = GetCachedNode4Key(key);
537 if (!cacheItem4Key.has_value()) {
538 return nullptr;
539 }
540 auto uiNode = cacheItem4Key.value().item;
541 RemoveKeyFromL1(key);
542 return uiNode;
543 }
544
SetLastActiveRange(uint32_t from,uint32_t to)545 void RepeatVirtualScrollCaches::SetLastActiveRange(uint32_t from, uint32_t to)
546 {
547 ACE_SCOPED_TRACE("RepeatVirtualScrollCaches::SetLastActiveRange from[%d] to[%d]",
548 static_cast<int32_t>(from), static_cast<int32_t>(to));
549
550 // STATE_MGMT_NOTE, only update when from or to != stActiveRanges_[0] ?
551 lastActiveRanges_[1] = lastActiveRanges_[0];
552 lastActiveRanges_[0] = { from, to };
553
554 const auto updatedPermissableCacheCount = to - from + 1;
555 for (auto iter = cacheCountL24ttype_.begin(); iter != cacheCountL24ttype_.end(); iter++) {
556 std::pair<bool, uint32_t>& optCacheCount = iter->second;
557 if (optCacheCount.first == false) {
558 // Repeat.template({ cachedCount }) options not specified
559 if (optCacheCount.second < updatedPermissableCacheCount) {
560 TAG_LOGD(AceLogTag::ACE_REPEAT,
561 "Growing permissable size of spare nodes cache for ttype '%{public}s' to %{public}d .",
562 iter->first.c_str(), updatedPermissableCacheCount);
563 optCacheCount.second = updatedPermissableCacheCount;
564 }
565 }
566 }
567 }
568
569 /**
570 * Get the Index of frameNode in L1 / active range
571 * return -1 if not find the frameNode
572 */
GetFrameNodeIndex(const RefPtr<FrameNode> & frameNode) const573 int32_t RepeatVirtualScrollCaches::GetFrameNodeIndex(const RefPtr<FrameNode>& frameNode) const
574 {
575 for (const auto& key : activeNodeKeysInL1_) {
576 const auto nodeIter = node4key_.find(key);
577 if (nodeIter == node4key_.end() || !nodeIter->second.item) {
578 continue;
579 }
580 const auto& node = nodeIter->second.item->GetFrameChildByIndex(0, true);
581 if (node != frameNode) {
582 continue;
583 }
584 const auto& indexIter = index4Key_.find(key);
585 if (indexIter == index4Key_.end()) {
586 return -1;
587 }
588 return indexIter->second;
589 }
590 return -1;
591 }
592
593 /**
594 * intended scenario: scroll
595 * servicing GetFrameChild, search for key that can be updated.
596 *
597 * return a key whose UINode can be updated
598 * the key must not be in L1, i.e. activeNodeKeysInL1_
599 * the given ttype must match the template type the UINode for this key
600 * has been rendered for (this info is available from node4key4ttype_)
601 */
GetL2KeyToUpdate(const std::optional<std::string> & ttype) const602 std::optional<std::string> RepeatVirtualScrollCaches::GetL2KeyToUpdate(
603 const std::optional<std::string>& ttype) const
604 {
605 if (!ttype.has_value()) {
606 return std::nullopt;
607 }
608
609 const auto itNodes = node4key4ttype_.find(ttype.value());
610 if (itNodes == node4key4ttype_.end()) {
611 return std::nullopt;
612 }
613 const auto& keys2UINode = itNodes->second;
614 std::set<std::string> l2Keys = GetL2KeysForTType(keys2UINode);
615 auto keyIter = l2Keys.rbegin();
616 if (keyIter == l2Keys.rend()) {
617 TAG_LOGD(AceLogTag::ACE_REPEAT,
618 "GetL2KeyToUpdate for ttype %{public}s no key in L2 that could be updated. ",
619 ttype.value().c_str());
620 return std::nullopt;
621 }
622 TAG_LOGD(AceLogTag::ACE_REPEAT,
623 "GetL2KeyToUpdate for ttype %{public}s found key '%{public}s' from L2 to update. ",
624 ttype.value().c_str(), keyIter->c_str());
625 return *keyIter;
626 }
627
628 /**
629 * scenario: UI rebuild following key invalidation by TS side
630 * L1 includes keys that are no longer used, the linked UINodes
631 * should be updated.
632 *
633 * This function checks all L1 keys (of active UINodes) if the key
634 * can still be found from
635 * (previously updated following invalidation) key -> index map and
636 *
637 */
GetL1KeyToUpdate(const std::string & ttype) const638 std::optional<std::string> RepeatVirtualScrollCaches::GetL1KeyToUpdate(const std::string& ttype) const
639 {
640 for (const auto& keyIter : activeNodeKeysInL1_) {
641 const std::string& key = keyIter;
642 if (index4Key_.find(key) == index4Key_.end()) {
643 // key is no longer used
644 // check if key rendered the expected ttype
645 const auto ttypeIter = node4key4ttype_.find(ttype);
646 if (ttypeIter != node4key4ttype_.end()) {
647 const std::unordered_map<std::string, RefPtr<UINode>>& node4Key = ttypeIter->second;
648 if (node4Key.find(key) != node4Key.end()) {
649 TAG_LOGD(AceLogTag::ACE_REPEAT,
650 "GetL1KeyToUpdate for ttype %{public}s found key to update %{public}s in L1. ",
651 ttype.c_str(), key.c_str());
652 return key;
653 }
654 }
655 }
656 }
657 TAG_LOGD(AceLogTag::ACE_REPEAT,
658 "GetL1KeyToUpdate for ttype %{public}s no key in L1 that could be updated. ",
659 ttype.c_str());
660 return std::nullopt;
661 }
662
663 /**
664 * scenario: UINode of fromKey has been updated to render data for 'forKey'
665 * the template type (ttype) remains unchanged
666 * update node4key4ttype_ and node4key_ entries to use new key point to same UINode
667 */
UINodeHasBeenUpdated(const std::string & ttype,const std::string & fromKey,const std::string & forKey)668 RefPtr<UINode> RepeatVirtualScrollCaches::UINodeHasBeenUpdated(
669 const std::string& ttype, const std::string& fromKey, const std::string& forKey)
670 {
671 ACE_SCOPED_TRACE(
672 "RepeatVirtualScrollCaches::UINodeHasBeenUpdated ttype[%s] fromKey[%s] -> forKey[%s]",
673 ttype.c_str(), fromKey.c_str(), forKey.c_str());
674
675 // 1. update fromKey -> forKey in node4key4ttype_
676 for (auto& node4KeyIter : node4key4ttype_) {
677 node4KeyIter.second.erase(forKey);
678 }
679 const auto nodesIter = node4key4ttype_.find(ttype);
680 if (nodesIter != node4key4ttype_.end()) {
681 auto& node4key = nodesIter->second;
682 auto iter = node4key.find(fromKey);
683 if (iter != node4key.end()) {
684 auto node = iter->second;
685 node4key.erase(iter);
686 node4key.emplace(forKey, node);
687 }
688 }
689
690 // 2. update the key: fromKey to forKey in node4key_
691 auto iter = node4key_.find(fromKey);
692 if (iter != node4key_.end()) {
693 auto cachedItem = iter->second;
694 cachedItem.isValid = true;
695 node4key_.erase(iter);
696 node4key_.emplace(forKey, cachedItem);
697 return cachedItem.item;
698 }
699 TAG_LOGE(AceLogTag::ACE_REPEAT, "fail to update L2 : %{public}s, %{public}s, %{public}s, ", ttype.c_str(),
700 fromKey.c_str(), forKey.c_str());
701 return nullptr;
702 }
703
704 /** scenario: keys cache has been updated
705 *
706 * find which keys in key -> UINode map are no longer used
707 * returned set entries are pairs:
708 * pair.first: is this key a L1 item,
709 * pair.second: key
710 */
FindUnusedKeys(std::set<std::pair<bool,std::string>> & result) const711 void RepeatVirtualScrollCaches::FindUnusedKeys(std::set<std::pair<bool, std::string>>& result) const
712 {
713 for (const auto& iter : node4key_) {
714 const std::string key = iter.first;
715 const auto indexIter = index4Key_.find(key);
716 if (indexIter == index4Key_.end()) {
717 // key is no longer used
718 // is it in L1 ?
719 const bool keyInL1 = (index4Key_.find(key) != index4Key_.end());
720 result.emplace(keyInL1, key);
721 }
722 }
723 }
724
725 /**
726 * scenario: in idle process , following GetChildren()
727 * execute purge()
728 *
729 * enforce L2 cacheCount for each ttype
730 * logical L2 cache is map key->UINode map filtered out L1 keys
731 * purge by by deleting UINodes, delete their entry from
732 * node4key4ttype_ and node4key_
733 *
734 */
Purge()735 bool RepeatVirtualScrollCaches::Purge()
736 {
737 uint32_t deletedCount = 0;
738 for (auto& itTType : node4key4ttype_) {
739 const auto& ttype = itTType.first;
740 auto& uiNode4Key = itTType.second;
741
742 // size of the unused node pool L2
743 // defined either in template { caacheCount }
744 // or set dynamically by framework to maximum number of items in L1
745 uint32_t cacheCount = (cacheCountL24ttype_.find(ttype) == cacheCountL24ttype_.end())
746 ? 0 // unknown ttype should never happen
747 : cacheCountL24ttype_[ttype].second;
748 TAG_LOGD(AceLogTag::ACE_REPEAT, "RepeatCaches::Purge cacheCount %{public}d", static_cast<int32_t>(cacheCount));
749 std::set<std::string> l2Keys = GetL2KeysForTType(uiNode4Key);
750
751 // l2_keys is sorted by increasing distance from lastActiveRange
752 // will drop those keys and their UINodes with largest distance
753 // improvement idea: in addition to distance from range use the
754 // scroll direction for selecting these keys
755 auto safeDist = std::min(cacheCount, static_cast<uint32_t>(l2Keys.size()));
756 auto itL2Key = l2Keys.begin();
757 auto itDivider = std::next(l2Keys.begin(), safeDist);
758
759 auto greatOrEqualApi18 = Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_EIGHTEEN);
760 while (itL2Key != itDivider) {
761 if (greatOrEqualApi18) {
762 // freeze the remaining nodes in L2
763 const auto& uiNodeIter = uiNode4Key.find(*itL2Key);
764 if (uiNodeIter != uiNode4Key.end()) {
765 TAG_LOGD(AceLogTag::ACE_REPEAT,
766 "... freezing spare node cache item old key '%{public}s' -> "
767 "node %{public}s, ttype: '%{public}s'",
768 itL2Key->c_str(), DumpUINodeWithKey(*itL2Key).c_str(), ttype.c_str());
769 uiNodeIter->second->SetJSViewActive(false);
770 }
771 }
772 itL2Key++;
773 }
774 while (itL2Key != l2Keys.end()) {
775 // delete remaining keys
776 TAG_LOGD(AceLogTag::ACE_REPEAT,
777 "... purging spare node cache item old key '%{public}s' -> node %{public}s, ttype: '%{public}s', "
778 "permissable spare nodes count %{public}d",
779 itL2Key->c_str(), DumpUINodeWithKey(*itL2Key).c_str(), ttype.c_str(),
780 static_cast<int32_t>(cacheCount));
781 uiNode4Key.erase(*itL2Key);
782 node4key_.erase(*itL2Key);
783 // check out transition case.
784 itL2Key++;
785 deletedCount += 1;
786 }
787 }
788 if (deletedCount > 0) {
789 TAG_LOGD(AceLogTag::ACE_REPEAT, "Purged total %d items.", static_cast<int32_t>(deletedCount));
790 ACE_SCOPED_TRACE("RepeatVirtualScrollCaches::Purge %d items", static_cast<int32_t>(deletedCount));
791 }
792 return (deletedCount > 0);
793 }
794
795 /**
796 * given key return the index position (reverse lookup)
797 * invalidated keys (after Repeat rerender/ data change)
798 * are keys for which no index exists anymore,
799 * method returns uint max value for these.
800 * int max value causes that distance from active range is max
801 * these keys will be selected for update first.
802 */
GetIndex4Key(const std::string & key) const803 uint32_t RepeatVirtualScrollCaches::GetIndex4Key(const std::string& key) const
804 {
805 auto it = index4Key_.find(key);
806 if (it != index4Key_.end()) {
807 return it->second;
808 }
809 // key is no longer used
810 // return max uint32_t value
811 return UINT32_MAX;
812 }
813
GetTType4Index(uint32_t index)814 std::optional<std::string> RepeatVirtualScrollCaches::GetTType4Index(uint32_t index)
815 {
816 const auto it = ttype4index_.find(index);
817 if (it == ttype4index_.end()) {
818 return std::nullopt;
819 }
820 return it->second;
821 }
822
GetCachedNode4Key(const std::optional<std::string> & key)823 std::optional<CacheItem> RepeatVirtualScrollCaches::GetCachedNode4Key(const std::optional<std::string>& key)
824 {
825 if (!key.has_value()) {
826 return std::nullopt;
827 }
828 const auto it = node4key_.find(key.value());
829 if (it == node4key_.end()) {
830 return std::nullopt;
831 }
832 return it->second;
833 }
834
GetCachedNode4Key4Ttype(const std::optional<std::string> & key,const std::optional<std::string> & ttype)835 RefPtr<UINode> RepeatVirtualScrollCaches::GetCachedNode4Key4Ttype(
836 const std::optional<std::string>& key, const std::optional<std::string>& ttype)
837 {
838 // if key and ttype given, search for UINode of givenkey and ttype
839 // in caches, i.e. in node4key4ttype
840 if (!key.has_value() || !ttype.has_value()) {
841 return nullptr;
842 }
843 const auto it0 = node4key4ttype_.find(ttype.value());
844 if (it0 == node4key4ttype_.end()) {
845 return nullptr;
846 }
847 const auto it1 = it0->second.find(key.value());
848 if (it1 == it0->second.end()) {
849 return nullptr;
850 }
851 return it1->second;
852 }
853
854 /**
855 * for given index return distance from active range,
856 * or 0 if within active range
857 * distance is uint max for invalidated keys
858 *
859 * instead of just using previous active range
860 * use the ranges informed by previous two SetActiveRaneg calls.
861 * Obtain the scroll direction and use it to calc the distance.
862 * Items left 'behind' when scrolling get larger distance and are more
863 * likely updated or purged from L2 cache.
864 */
GetDistanceFromRange(uint32_t index) const865 uint32_t RepeatVirtualScrollCaches::GetDistanceFromRange(uint32_t index) const
866 {
867 // distance is uint max for invalidated keys
868 if (index == UINT32_MAX) {
869 return UINT32_MAX;
870 }
871 uint32_t last[2] = { lastActiveRanges_[0].first, lastActiveRanges_[0].second };
872 uint32_t prev[2] = { lastActiveRanges_[1].first, lastActiveRanges_[1].second };
873
874 // this is experimental optimization, based on scrolling detection
875 // here we assume this is a scrolling, if previous range and last range has
876 // not empty intersection
877
878 // if scrolling up, return 0 for any lower index
879 if (last[0] < prev[0] && prev[0] < last[1]) {
880 if (index < last[0]) {
881 return 0;
882 }
883 }
884
885 // if scrolling down, return 0 for any greater index
886 if (last[0] < prev[1] && prev[1] < last[1]) {
887 if (index > last[1]) {
888 return 0;
889 }
890 }
891
892 // this is not scrolling
893 if (index < last[0]) {
894 return last[0] - index;
895 }
896
897 if (index > last[1]) {
898 return index - last[1];
899 }
900
901 return 0;
902 }
903
904 /**
905 * scenario: find L1 key that should be updated
906 * choose the key whose index is the furthest away from active range
907 * given two keys compare their distFromRange
908 */
CompareKeyByIndexDistance(const std::string & key1,const std::string & key2) const909 bool RepeatVirtualScrollCaches::CompareKeyByIndexDistance(const std::string& key1, const std::string& key2) const
910 {
911 return GetDistanceFromRange(GetIndex4Key(key1)) < GetDistanceFromRange(GetIndex4Key(key2));
912 }
913
914 /**
915 * scenario: find L1 key(s) that should be updated
916 *
917 * return a sorted set of L2 keys, sorted by increasing distance from active range
918 */
GetL2KeysForTType(const std::unordered_map<std::string,RefPtr<UINode>> & uiNode4Key) const919 std::set<std::string> RepeatVirtualScrollCaches::GetL2KeysForTType(
920 const std::unordered_map<std::string, RefPtr<UINode>>& uiNode4Key) const
921 {
922 std::set<std::string> l2Keys;
923 for (const auto& itUINode : uiNode4Key) {
924 const auto& key = itUINode.first;
925 if (activeNodeKeysInL1_.find(key) == activeNodeKeysInL1_.end()) {
926 // key is not in L1
927 // add to l2Keys
928 l2Keys.emplace(key);
929 }
930 }
931 return l2Keys;
932 }
933
DumpL1() const934 std::string RepeatVirtualScrollCaches::DumpL1() const
935 {
936 std::string result =
937 "L1 (visible + pre-rendered items, their count defined by List/Grid.cacheCount: total number=" +
938 std::to_string(activeNodeKeysInL1_.size()) + "--------------\n";
939 for (const auto& it : activeNodeKeysInL1_) {
940 const std::string& key = it;
941 auto indexIter = index4Key_.find(key);
942 if (indexIter == index4Key_.end()) {
943 continue;
944 }
945 result += " index " + std::to_string(indexIter->second) + " -> key: '" + key +
946 "', node: " + DumpUINodeWithKey(key) + "\n";
947 }
948 return result;
949 }
950
DumpL2() const951 std::string RepeatVirtualScrollCaches::DumpL2() const
952 {
953 std::unordered_map<std::string, RefPtr<UINode>> allCaches;
954 for (const auto& [item, cacheItem] : node4key_) {
955 allCaches.try_emplace(item, cacheItem.item);
956 }
957 std::set<std::string> l2KeyResult = GetL2KeysForTType(allCaches);
958
959 std::string result = "RecycleItem: Spare items available for update, not on render tree: size=" +
960 std::to_string(l2KeyResult.size()) + "--------------\n";
961 for (const auto& it : l2KeyResult) {
962 result += " old key '" + it + "', node: " + DumpUINodeWithKey(it) + "\n";
963 }
964 return result;
965 }
966
DumpKey4Index() const967 std::string RepeatVirtualScrollCaches::DumpKey4Index() const
968 {
969 std::string result = "key4index_: size=" + std::to_string(key4index_.size()) + "--------------\n";
970 for (const auto& it : key4index_) {
971 result += " " + std::to_string(it.first) + " -> \"" + it.second +
972 "\", node: " + DumpUINodeWithKey(it.second) + "\n";
973 }
974 result += "index4Key_: size=" + std::to_string(index4Key_.size()) + "--------------\n";
975 for (const auto& it : index4Key_) {
976 result += " \"" + it.first + "\" -> " + std::to_string(it.second) + "\n";
977 }
978 return result;
979 }
980
DumpTType4Index() const981 std::string RepeatVirtualScrollCaches::DumpTType4Index() const
982 {
983 std::string result = "ttype4index_: size=" + std::to_string(ttype4index_.size()) + "--------------\n";
984 for (const auto& it : ttype4index_) {
985 result += " " + std::to_string(it.first) + " -> \"" + it.second + "\n";
986 }
987 result += "index4ttype_: size=" + std::to_string(index4ttype_.size()) + "--------------\n";
988 for (const auto& it : index4ttype_) {
989 result += " \"" + it.first + "\" -> " + std::to_string(it.second) + "\n";
990 }
991 return result;
992 }
993
DumpUINode4Key() const994 std::string RepeatVirtualScrollCaches::DumpUINode4Key() const
995 {
996 std::string result = "node4key_: size=" + std::to_string(node4key_.size()) + "--------------\n";
997 for (const auto& it : node4key_) {
998 result += " \"" + it.first + "\" -> node: " + it.second.item->GetTag() + "(" +
999 std::to_string(it.second.item->GetId()) + ") \n";
1000 }
1001 return result;
1002 }
1003
DumpUINode4Key4TType() const1004 std::string RepeatVirtualScrollCaches::DumpUINode4Key4TType() const
1005 {
1006 std::string result = "node4key4ttype_: size=" + std::to_string(node4key4ttype_.size()) + "--------------\n";
1007 for (const auto& ttypeIter : node4key4ttype_) {
1008 const auto& ttype = ttypeIter.first;
1009 const auto& node4key = ttypeIter.second;
1010 result += "ttype " + ttype + ": node4key: size=" + std::to_string(node4key.size()) + "--------------\n";
1011 for (const auto& it : node4key) {
1012 result += " \"" + it.first + "\" -> node: " + it.second->GetTag() + "(" +
1013 std::to_string(it.second->GetId()) + ") \n";
1014 }
1015 }
1016 return result;
1017 }
1018
DumpUINodeWithKey(const std::string & key) const1019 std::string RepeatVirtualScrollCaches::DumpUINodeWithKey(const std::string& key) const
1020 {
1021 const auto it = node4key_.find(key);
1022 return (it == node4key_.end()) ? "no UINode on file"
1023 : it->second.item->GetTag() + "(" + std::to_string(it->second.item->GetId()) + ")";
1024 }
1025
DumpUINode(const RefPtr<UINode> & node) const1026 std::string RepeatVirtualScrollCaches::DumpUINode(const RefPtr<UINode>& node) const
1027 {
1028 return (node == nullptr) ? "UINode nullptr" : node->GetTag() + "(" + std::to_string(node->GetId()) + ")";
1029 }
1030 } // namespace OHOS::Ace::NG
1031