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