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 LOGD("ID '%{public}s' no more used, removing %{public}s(%{public}d) from ElementRegister", oldId.c_str(),
92 AceType::TypeName((*oldElementIter)), (*oldElementIter)->GetElementId());
93 (*oldElementIter)->UnregisterForPartialUpdates();
94 }
95 oldElementIter++;
96 }
97 }
98
Update()99 void ForEachElement::Update()
100 {
101 RefPtr<PartUpd::ForEachComponent> newFEComp = AceType::DynamicCast<PartUpd::ForEachComponent>(component_);
102 if (!newFEComp) {
103 LOGE("ForEachElement elmtId : %{public}d, no ForEachComponent set to update from, internal error",
104 GetElementId());
105 return;
106 }
107 LOGD("Update for ForEachElement, elmtId: %{public}d ....", GetElementId());
108 MultiComposedElement::Update();
109
110 SetIdArray(newFEComp->GetIdArray());
111 }
112
LocalizedUpdate()113 void ForEachElement::LocalizedUpdate()
114 {
115 ACE_SCOPED_TRACE("ForEachElement::LocalizedUpdate");
116
117 RefPtr<PartUpd::ForEachComponent> newFEComp = AceType::DynamicCast<PartUpd::ForEachComponent>(component_);
118 if (!newFEComp) {
119 LOGE("ForEachElement elmtId : %{public}d, no ForEachComponent set to update from, internal error",
120 GetElementId());
121 return;
122 }
123
124 LOGD("Local update for ForEachElement, elmtId: %{public}d ....", GetElementId());
125
126 // result of id gen function of most re-recent render
127 // create a map for quicker find/search
128 std::list<std::string> newIds = newFEComp->GetIdArray();
129 std::unordered_set<std::string> newIdsSet(newIds.begin(), newIds.end());
130
131 // result of id gen function of previous render/re-render
132 // create a map for quicker find/search
133 const auto& oldIds = GetIdArray();
134 std::unordered_set<std::string> oldIdsSet(oldIds.begin(), oldIds.end());
135
136 // ForEachComponent only includes children for _newly created_ array items
137 // it does _not_ include children of array items that were rendered on a previous
138 // render
139 const auto& additionalChildComps = newFEComp->GetChildren();
140
141 // create map id gen result -> Element
142 // old children
143 std::map<std::string, Ace::RefPtr<Element>> oldElmtsByIdMap;
144 MakeElementByIdMap(GetChildren(), oldIds, oldElmtsByIdMap);
145
146 ACE_DCHECK((oldIds.size() == GetChildren().size()) &&
147 "Number of IDs generated during previous render and number of ForEach child Elements must match");
148 ACE_DCHECK(oldIdsSet.size() == oldIds.size());
149 ACE_DCHECK(GetChildren().size() == oldElmtsByIdMap.size());
150
151 #ifdef ACE_DEBUG
152 std::string idS = "[";
153 for (const auto& oldId : oldIds) {
154 idS += oldId + ", ";
155 }
156 idS += "]";
157 auto idIter = oldIds.begin();
158 LOGD(" ... old Ids %{public}s .", idS.c_str());
159 LOGD("ForEachElement children before change: ");
160 for (const auto& childElmt : GetChildren()) {
161 LOGD(" ... child arr-id %{public}s / renderSlot %{public}d, %{public}d", (*idIter).c_str(),
162 childElmt->GetSlot(), childElmt->GetRenderSlot());
163 }
164 LOGD(" ... total children Elements: %{public}d .", static_cast<int32_t>(GetChildren().size()));
165 idS = "[";
166 for (const auto& newId : newIds) {
167 idS += newId + ", ";
168 }
169 idS += "]";
170 LOGD(" ... new Ids %{public}s .", idS.c_str());
171 LOGD(" ... newly added child Components: %{public}u .", (uint32_t)additionalChildComps.size());
172 #endif
173
174 auto firstChildElement = GetChildren().begin();
175 int renderSlot = GetRenderSlot();
176
177 bool needRequestLayout = false;
178 int32_t slot = ((*firstChildElement) != nullptr) ? (*firstChildElement)->GetSlot() : 0;
179 int additionalChildIndex = 0;
180 for (const auto& newId : newIds) {
181 if (oldIdsSet.find(newId) == oldIdsSet.end()) {
182 // found a newly added ID
183 // insert component into 'slot'
184 auto newCompsIter = additionalChildComps.begin();
185 std::advance(newCompsIter, additionalChildIndex++);
186 LOGD("Elmt with New arr-id '%{public}s', inserting to slot %{public}d / renderSlot %{public}d",
187 newId.c_str(), slot, renderSlot);
188 InflateComponent(*newCompsIter, slot, renderSlot);
189 needRequestLayout = false;
190 } else {
191 // the ID was used before, only need to update the child Element's slot
192 auto iter = oldElmtsByIdMap.find(newId);
193 auto oldElmt = (*iter).second;
194 LOGD("Elmt with arr-id %{public}s retained, update its slot %{public}d->%{public}d /"
195 " renderSlot %{public}d->%{public}d",
196 newId.c_str(), oldElmt->GetSlot(), slot, oldElmt->GetRenderSlot(), renderSlot);
197 ChangeChildSlot(oldElmt, slot);
198 ChangeChildRenderSlot(oldElmt, renderSlot, true);
199 needRequestLayout = true;
200 }
201 slot++;
202 renderSlot++;
203 }
204
205 for (const auto& oldId : oldIds) {
206 // check if oldId still in newIds array
207 if (newIdsSet.find(oldId) == newIdsSet.end()) {
208 // the ID is no longer used, delete the child Element
209 auto iter = oldElmtsByIdMap.find(oldId);
210 auto oldElmt = iter->second;
211 LOGD("Array Id %{public}s no more used, deleting %{public}s(%{public}d)", oldId.c_str(),
212 AceType::TypeName(oldElmt), oldElmt->GetElementId());
213 // no new child component
214 UpdateChild(oldElmt, nullptr);
215 needRequestLayout = false;
216 }
217 }
218 SetIdArray(newFEComp->GetIdArray());
219 Update();
220 if (needRequestLayout) {
221 auto renderNode = GetRenderNode();
222 if (renderNode != nullptr) {
223 renderNode->MarkNeedLayout();
224 }
225 }
226 SetNewComponent(nullptr);
227 #ifdef ACE_DEBUG
228 LOGD("ForEachElement children after change: ");
229 for (const auto& childElmt : GetChildren()) {
230 LOGD(" ... slot %{public}d / renderSlot %{public}d.", childElmt->GetSlot(), childElmt->GetRenderSlot());
231 }
232 LOGD(" ... total children Elements: %{public}d .", static_cast<int32_t>(GetChildren().size()));
233 #endif
234 }
235 } // namespace OHOS::Ace::PartUpd
236