1 /*
2  * Copyright (c) 2022 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 "frameworks/core/components_part_upd/foreach/foreach_element.h"
17 
18 #include <cstdint>
19 #include <list>
20 #include <set>
21 
22 #include "frameworks/core/components_part_upd/foreach/foreach_component.h"
23 
24 namespace OHOS::Ace::PartUpd {
25 
CanUpdate(const RefPtr<Component> & newComponent)26 bool ForEachElement::CanUpdate(const RefPtr<Component>& newComponent)
27 {
28     return AceType::InstanceOf<PartUpd::ForEachComponent>(newComponent);
29 }
30 
CompareSlots(const RefPtr<Element> & first,const RefPtr<Element> & second)31 bool ForEachElement::CompareSlots(const RefPtr<Element>& first, const RefPtr<Element>& second)
32 {
33     // sort lift of child Elements by their slot
34     return first->GetSlot() < second->GetSlot();
35 }
36 
37 // adds elements from given list into a Map with key Element.GetSlot
MakeElementByIdMap(const std::list<RefPtr<Element>> & elmts,const std::list<std::string> & ids,std::map<std::string,Ace::RefPtr<Element>> & result)38 void ForEachElement::MakeElementByIdMap(const std::list<RefPtr<Element>>& elmts, const std::list<std::string>& ids,
39     std::map<std::string, Ace::RefPtr<Element>>& result)
40 {
41     ACE_SCOPED_TRACE("ForEachElement::UpdateWithComponent makeElmtByIdMap");
42 
43     ACE_DCHECK(ids.size() == elmts.size());
44 
45     // 1. step map Elements by their slot, because elmts is not sorted by slot
46     std::map<int, Ace::RefPtr<Element>> elmtsBySlotMap;
47     for (const auto& elmt : elmts) {
48         ACE_DCHECK(elmt->GetSlot() >= 0);
49         elmtsBySlotMap.emplace(elmt->GetSlot(), elmt);
50     }
51     ACE_DCHECK(elmtsBySlotMap.size() == elmts.size());
52 
53     // 2. map elmts by their id. Note ids list is in slot order
54     auto idsIter = ids.begin();
55     int slot = 0;
56     while (idsIter != ids.end()) {
57         auto elmtIter = elmtsBySlotMap.find(slot);
58         ACE_DCHECK(elmtIter != elmtsBySlotMap.end());
59         result.emplace(*idsIter, (*elmtIter).second);
60         idsIter++;
61         slot++;
62     }
63 }
64 
RemoveUnusedChildElementsFromRegistery(const std::list<std::string> & newIds) const65 void ForEachElement::RemoveUnusedChildElementsFromRegistery(const std::list<std::string>& newIds) const
66 {
67     ACE_SCOPED_TRACE("ForEachElement::RemoveUnusedChildElementsFromRegistery");
68 
69     // ID array before update
70     std::list<std::string> oldIds = GetIdArray();
71 
72     if (oldIds.empty()) {
73         return;
74     }
75 
76     // construct a set from newIds list for faster find/search
77     std::unordered_set<std::string> newIdsSet(newIds.begin(), newIds.end());
78 
79     // Element children before update
80     const auto& oldChildElementsRef = GetChildren();
81     std::list<RefPtr<Element>> oldChildElements(oldChildElementsRef); // make a copy of the list
82     oldChildElements.sort(CompareSlots); // needs sorting by their slot to match the order of oldIds array
83 
84     ACE_DCHECK((oldIds.size() == oldChildElements.size()) &&
85                "Number of IDs generated during previous render and number of ForEach child Elements must match");
86 
87     auto oldElementIter = oldChildElements.begin();
88     for (const auto& oldId : oldIds) {
89         // check if oldId still in newIds array
90         if (newIdsSet.find(oldId) == newIdsSet.end()) {
91             (*oldElementIter)->UnregisterForPartialUpdates();
92         }
93         oldElementIter++;
94     }
95 }
96 
Update()97 void ForEachElement::Update()
98 {
99     RefPtr<PartUpd::ForEachComponent> newFEComp = AceType::DynamicCast<PartUpd::ForEachComponent>(component_);
100     if (!newFEComp) {
101         LOGE("ForEachElement elmtId : %{public}d, no ForEachComponent set to update from, internal error",
102             GetElementId());
103         return;
104     }
105     MultiComposedElement::Update();
106 
107     SetIdArray(newFEComp->GetIdArray());
108 }
109 
LocalizedUpdate()110 void ForEachElement::LocalizedUpdate()
111 {
112     ACE_SCOPED_TRACE("ForEachElement::LocalizedUpdate");
113 
114     RefPtr<PartUpd::ForEachComponent> newFEComp = AceType::DynamicCast<PartUpd::ForEachComponent>(component_);
115     if (!newFEComp) {
116         LOGE("ForEachElement elmtId : %{public}d, no ForEachComponent set to update from, internal error",
117             GetElementId());
118         return;
119     }
120     // result of id gen function of most re-recent render
121     // create a map for quicker find/search
122     std::list<std::string> newIds = newFEComp->GetIdArray();
123     std::unordered_set<std::string> newIdsSet(newIds.begin(), newIds.end());
124 
125     // result of id gen function of previous render/re-render
126     // create a map for quicker find/search
127     const auto& oldIds = GetIdArray();
128     std::unordered_set<std::string> oldIdsSet(oldIds.begin(), oldIds.end());
129 
130     // ForEachComponent only includes children for _newly created_ array items
131     // it does _not_ include children of array items that were rendered on a previous
132     // render
133     const auto& additionalChildComps = newFEComp->GetChildren();
134 
135     // create map id gen result -> Element
136     // old children
137     std::map<std::string, Ace::RefPtr<Element>> oldElmtsByIdMap;
138     MakeElementByIdMap(GetChildren(), oldIds, oldElmtsByIdMap);
139 
140     ACE_DCHECK((oldIds.size() == GetChildren().size()) &&
141                "Number of IDs generated during previous render and number of ForEach child Elements must match");
142     ACE_DCHECK(oldIdsSet.size() == oldIds.size());
143     ACE_DCHECK(GetChildren().size() == oldElmtsByIdMap.size());
144 
145     auto firstChildElement = GetChildren().begin();
146     int renderSlot = GetRenderSlot();
147 
148     bool needRequestLayout = false;
149     int32_t slot = ((*firstChildElement) != nullptr) ? (*firstChildElement)->GetSlot() : 0;
150     int additionalChildIndex = 0;
151     for (const auto& newId : newIds) {
152         if (oldIdsSet.find(newId) == oldIdsSet.end()) {
153             // found a newly added ID
154             // insert component into 'slot'
155             auto newCompsIter = additionalChildComps.begin();
156             std::advance(newCompsIter, additionalChildIndex++);
157             InflateComponent(*newCompsIter, slot, renderSlot);
158             needRequestLayout = false;
159         } else {
160             // the ID was used before, only need to update the child Element's slot
161             auto iter = oldElmtsByIdMap.find(newId);
162             auto oldElmt = (*iter).second;
163             ChangeChildSlot(oldElmt, slot);
164             ChangeChildRenderSlot(oldElmt, renderSlot, true);
165             needRequestLayout = true;
166         }
167         slot++;
168         renderSlot++;
169     }
170 
171     for (const auto& oldId : oldIds) {
172         // check if oldId still in newIds array
173         if (newIdsSet.find(oldId) == newIdsSet.end()) {
174             // the ID is no longer used, delete the child Element
175             auto iter = oldElmtsByIdMap.find(oldId);
176             auto oldElmt = iter->second;
177             // no new child component
178             UpdateChild(oldElmt, nullptr);
179             needRequestLayout = false;
180         }
181     }
182     SetIdArray(newFEComp->GetIdArray());
183     Update();
184     if (needRequestLayout) {
185         auto renderNode = GetRenderNode();
186         if (renderNode != nullptr) {
187             renderNode->MarkNeedLayout();
188         }
189     }
190     SetNewComponent(nullptr);
191 }
192 } // namespace OHOS::Ace::PartUpd
193