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 #ifndef FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_CACHES_H
17 #define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_CACHES_H
18 
19 #include <cstdint>
20 #include <functional>
21 #include <map>
22 #include <optional>
23 #include <set>
24 #include <string>
25 #include <unordered_map>
26 #include <unordered_set>
27 
28 #include "base/memory/referenced.h"
29 #include "core/components_ng/base/ui_node.h"
30 
31 namespace OHOS::Ace::NG {
32 
33 // custom sorting for std::set only works with struct
34 // with operator() inside
35 class RepeatVirtualScrollCaches;
36 struct KeySorterClass {
37     const RepeatVirtualScrollCaches* virtualScroll_;
38 
KeySorterClassKeySorterClass39     explicit KeySorterClass(const RepeatVirtualScrollCaches* virtualScroll) : virtualScroll_(virtualScroll) {}
40     bool operator()(const std::string& left, const std::string& right) const;
41 };
42 
43 struct CacheItem {
44     bool isValid = false;
45     RefPtr<UINode> item;
46 };
47 
48 class RepeatVirtualScrollCaches {
49     friend struct KeySorterClass;
50 
51 public:
52     RepeatVirtualScrollCaches(const std::map<std::string, std::pair<bool, uint32_t>>& cacheCountL24ttype,
53         const std::function<void(uint32_t)>& onCreateNode,
54         const std::function<void(const std::string&, uint32_t)>& onUpdateNode,
55         const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetKeys4Range,
56         const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetTypes4Range);
57 
58     /** scenario:
59      *         Repeat gets updated due to data change.
60      *         1. TS calls RepeatVirtualScrollNode,
61      *            then calls this function.
62      * 2. RepeatVirtualScrollNode requests layout to rebuild the UI
63      * 3. layout sends RepeatVirtualScrollNode::GetFrameChild calls
64      * 4. how to service GetFrameChild  call:
65      *   - first check for L1 keys (same template type) if any can be updated.
66      *     These UINodes remain in the render tree.
67      *   - if no L1 item, the look for L2 keys (same template type)
68      */
69     void InvalidateKeyAndTTypeCaches();
70 
71     /**
72      * scenario: scroll, try to update an existing UINode
73      *
74      * find an key / UINode in L2 and update the UINode to
75      *   render the data source item 'forIndex'.
76      */
77     RefPtr<UINode> UpdateFromL2(uint32_t forIndex);
78 
79     void UpdateSameKeyItem(const std::string& key, uint32_t index);
80 
81     /**
82      * request TS to create a new node for given index / key/
83      */
84     RefPtr<UINode> CreateNewNode(uint32_t forIndex);
85 
86     // iterate over L1 keys, not allowed to modify L1
87     void ForEachL1IndexUINode(const std::function<void(uint32_t index, const RefPtr<UINode>& node)>& cbFunc);
88 
89     void RecycleItemsByIndex(int32_t index);
90 
91     /**
92      * for given index get key
93      * fetch from TS if not in cache (if allowFetch == true)
94      * return false if index out of range
95      */
96     std::optional<std::string> GetKey4Index(uint32_t index, bool allowFetch);
97 
98     /**
99      * iterate over all entries of L1 and call function for each entry
100      * if function returns true, entry is added to rebuild L1, otherwise it is moved to L2
101      * cbFunc return true, [index, key] pair stays in L1 (index remains unchanged)
102      * cbFunc returns false, enqueue key in L2
103      */
104     bool RebuildL1(const std::function<bool(int32_t index, const RefPtr<UINode>& node)>& cbFunc);
105 
106     /**
107      * dito with only key as cb function parameter
108      */
109     bool RebuildL1WithKey(const std::function<bool(const std::string& key)>& cbFunc);
110 
111     /*
112         drop L1 entry with given index from L1
113         keep it in L2
114         return the affected UINode
115         caller responsibility to detach this UINode from the UI tree!
116     */
117     RefPtr<UINode> DropFromL1(const std::string& key);
118 
119     int32_t GetFrameNodeIndex(const RefPtr<FrameNode>& frameNode) const;
120 
121     /**
122      * scenario: in idle process , following GetChildren()
123      *
124      * enforce L2 cacheCount for each ttype
125      * by deleting UINodes, delete their entry from
126      * node4key4ttype_ and node4key_
127      * any other processing steps needed before UINode
128      * tree can be deleted
129      */
130     bool Purge();
131 
132     /**
133      * return the cached UINode for given index
134      *
135      * resolve index -> key -> UINode
136      *
137      */
138     RefPtr<UINode> GetCachedNode4Index(uint32_t forIndex);
139 
140     void AddKeyToL1(const std::string& key, bool shouldTriggerReuse = true);
141 
142     void AddKeyToL1WithNodeUpdate(const std::string& key, uint32_t index, bool shouldTriggerRecycle);
143 
144     void RemoveKeyFromL1(const std::string& key, bool shouldTriggerRecycle = true);
145 
146     bool CheckTTypeChanged(uint32_t index);
147 
IsInL1Cache(const std::string & key)148     bool IsInL1Cache(const std::string& key) const
149     {
150         return activeNodeKeysInL1_.find(key) != activeNodeKeysInL1_.end();
151     }
152 
GetAllNodes()153     const std::unordered_map<std::string, CacheItem>& GetAllNodes() const
154     {
155         return node4key_;
156     }
157 
158     /**
159      * memorize last active range(s)
160      */
161     void SetLastActiveRange(uint32_t from, uint32_t to);
GetLastActiveRange()162     std::pair<uint32_t, uint32_t> GetLastActiveRange() { return lastActiveRanges_[0]; };
163 
164     // formatting internal structures to string for debug output
165     // and possibly in modified form for DFX in the future
166     std::string DumpL1() const;
167     std::string DumpL2() const;
168     std::string DumpKey4Index() const;
169     std::string DumpTType4Index() const;
170     std::string DumpUINode4Key4TType() const;
171     std::string DumpUINode4Key() const;
172 
173     std::string DumpUINodeWithKey(const std::string& key) const;
174     std::string DumpUINode(const RefPtr<UINode>& node) const;
175 
176 private:
177     /**
178      * intended scenario: scroll
179      * servicing GetFrameChild, search for key that can be updated.
180      *
181      * return a key whose UINode can be updated
182      * the key must not be in L1, i.e. activeNodeKeysInL1_
183      * the given ttype must match the template type the UINode for this key
184      * has been rendered for (this info is available from node4key4ttype_)
185      */
186     std::optional<std::string> GetL2KeyToUpdate(const std::optional<std::string>& ttype) const;
187 
188     /**
189      * scenario: UI rebuild following key invalidation by TS side
190      * L1 includes keys that are no longer used, the linked UINodes
191      * should be updated.
192      *
193      * This function checks all L1 keys (of active UINodes) if the key
194      * can still be found from
195      * (previously updated following invalidation) key -> index map and
196      *
197      */
198     std::optional<std::string> GetL1KeyToUpdate(const std::string& ttype) const;
199 
200     /**
201      * scenario: UINode of fromKey has been updated to render data for 'forKey'
202      *     the template type (ttype) remains unchanged
203      *     update node4key4ttype_ and node4key_ entries to use new key point to same UINode
204      */
205     RefPtr<UINode> UINodeHasBeenUpdated(
206         const std::string& ttype, const std::string& fromKey, const std::string& forKey);
207 
208     /** scenario: keys cache has been updated
209      *
210      * find which keys in key -> UINode map are no longer used
211      * returned set entries are pairs:
212      *   pair.first: is this key a L1 item,
213      *   pair.second: key
214      */
215     void FindUnusedKeys(std::set<std::pair<bool, std::string>>& result) const;
216 
217     /**
218      * given key return the index position (reverse lookup)
219      * invalidated keys (after Repeat rerender/ data change)
220      * are keys for which no index exists anymore,
221      * method returns int max value for these.
222      * int max value causes that distance from active range is max
223      * these keys will be selected for update first.
224      */
225     uint32_t GetIndex4Key(const std::string& key) const;
226 
227     /**
228      * find UINode for given key, irrespective of ttype in ttype4index_
229      */
230     std::optional<std::string> GetTType4Index(uint32_t index);
231 
232     /** find in node4key_ */
233     std::optional<CacheItem> GetCachedNode4Key(const std::optional<std::string>& key);
234 
235     /**
236      *  if key and ttype given, search for UINode of given key and ttype
237      * in caches, i.e. in node4key4ttype
238     * return nullptr in all other cases
239     */
240     RefPtr<UINode> GetCachedNode4Key4Ttype(
241         const std::optional<std::string>& key, const std::optional<std::string>& ttype);
242 
243     /**
244      *  for given index return distance from active range,
245      *  or 0 if within active range
246      *  distance is int max for invalidated keys
247      */
248     uint32_t GetDistanceFromRange(uint32_t index) const;
249     /**
250      * scenario: find L1 key that should be updated
251      * choose the key whose index is the furthest away from active range
252      * given two keys compare their distFromRange
253      */
254     bool CompareKeyByIndexDistance(const std::string& key1, const std::string& key2) const;
255 
256     std::set<std::string, KeySorterClass> GetSortedL2KeysForTType(
257         const std::unordered_map<std::string, RefPtr<UINode>>& uiNode4Key) const;
258 
259     /**
260      * does given range overlap the last active range?
261      */
262     bool HasOverlapWithLastActiveRange(uint32_t from, uint32_t to);
263 
264     /**
265      * get more index -> key and index -> ttype from TS side
266      * may request additional keys if allowFetchMore is true
267      */
268     bool FetchMoreKeysTTypes(uint32_t from, uint32_t to, bool allowFetchMore = true);
269 
270     // Map ttype -> cacheSize. Each ttype incl default has own L2 size
271     std::map<std::string, std::pair<bool, uint32_t>> cacheCountL24ttype_;
272 
273     // request TS to create new sub-tree for given index or update existing
274     // update subtree cached for (old) index
275     // API might need to change to tell which old item to update
276     std::function<void(uint32_t)> onCreateNode_;
277     std::function<void(const std::string&, uint32_t)> onUpdateNode_;
278 
279     // get index -> key for given range
280     // resulting list starts with 'from' but might end before 'to' if Array shorter
281     std::function<std::list<std::string>(uint32_t, uint32_t)> onGetTypes4Range_;
282 
283     // get index -> ttype for given range
284     // resulting list starts with 'from' but might end before 'to' if Array shorter
285     std::function<std::list<std::string>(uint32_t, uint32_t)> onGetKeys4Range_;
286 
287     // memorize active ranges of past 2 (last, prev)
288     // from SetActiveChildRange calls and use to calc scroll direction
289     // active range:= visible range + pre-render items above and below
290     // number of pre-render items defined by Gird/List.cacheCount and informed
291     // as cacheStart and cacheEnd in SetActiveChildRange
292     std::pair<uint32_t, uint32_t> lastActiveRanges_[2] = { { 0, 0 }, { 0, 0 } };
293 
294     // keys of active nodes, UINodes must be on the UI tree,
295     // this list is also known as L1
296     // all keys not in this set are in "L2"
297     std::unordered_set<std::string> activeNodeKeysInL1_;
298 
299     // L1
300     // index -> key and reverse
301     // lazy request from TS side can be invalidated
302     std::unordered_map<uint32_t, std::string> key4index_;
303     std::unordered_map<std::string, uint32_t> index4Key_;
304 
305     // index -> ttype
306     // lazy request from TS side can be invalidated
307     std::unordered_map<uint32_t, std::string> ttype4index_;
308     std::unordered_map<std::string, uint32_t> index4ttype_;
309 
310     // Map ttype -> Map key -> UINode
311     std::unordered_map<std::string, std::unordered_map<std::string, RefPtr<UINode>>> node4key4ttype_;
312 
313     // Map Map key -> UINode
314     std::unordered_map<std::string, CacheItem> node4key_;
315 
316     // for tracking reused/recycled nodes
317     std::unordered_set<int32_t> reusedNodeIds_;
318 }; // class NodeCache
319 
320 } // namespace OHOS::Ace::NG
321 
322 #endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_CACHES_H
323