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