• 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 
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