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/lazy_for_each_builder.h" 17 #include "core/components_ng/pattern/recycle_view/recycle_dummy_node.h" 18 19 namespace OHOS::Ace::NG { GetChildByIndex(int32_t index,bool needBuild,bool isCache)20 std::pair<std::string, RefPtr<UINode>> LazyForEachBuilder::GetChildByIndex( 21 int32_t index, bool needBuild, bool isCache) 22 { 23 auto iter = cachedItems_.find(index); 24 if (iter != cachedItems_.end()) { 25 if (iter->second.second) { 26 return iter->second; 27 } 28 auto keyIter = expiringItem_.find(iter->second.first); 29 if (keyIter != expiringItem_.end() && keyIter->second.second) { 30 if (!isCache) { 31 iter->second.second = keyIter->second.second; 32 expiringItem_.erase(keyIter); 33 return iter->second; 34 } else { 35 return { keyIter->first, keyIter->second.second }; 36 } 37 } 38 } 39 40 if (needBuild) { 41 ACE_SCOPED_TRACE("Builder:BuildLazyItem [%d]", index); 42 std::pair<std::string, RefPtr<UINode>> itemInfo; 43 if (useNewInterface_) { 44 itemInfo = OnGetChildByIndexNew(ConvertFormToIndex(index), cachedItems_, expiringItem_); 45 } else { 46 itemInfo = OnGetChildByIndex(ConvertFormToIndex(index), expiringItem_); 47 } 48 CHECK_NULL_RETURN(itemInfo.second, itemInfo); 49 if (isCache) { 50 expiringItem_.emplace(itemInfo.first, LazyForEachCacheChild(index, itemInfo.second)); 51 cachedItems_[index] = LazyForEachChild(itemInfo.first, nullptr); 52 } else { 53 cachedItems_[index] = itemInfo; 54 } 55 return itemInfo; 56 } 57 return {}; 58 } 59 OnDataReloaded()60 void LazyForEachBuilder::OnDataReloaded() 61 { 62 for (auto& [key, node] : expiringItem_) { 63 node.first = -1; 64 } 65 for (auto& [index, node] : cachedItems_) { 66 if (node.second) { 67 expiringItem_.try_emplace(node.first, LazyForEachCacheChild(-1, std::move(node.second))); 68 } 69 } 70 cachedItems_.clear(); 71 needTransition = true; 72 } 73 OnDataAdded(size_t index)74 bool LazyForEachBuilder::OnDataAdded(size_t index) 75 { 76 NotifyDataAdded(index); 77 if (!cachedItems_.empty() && index <= static_cast<size_t>(cachedItems_.rbegin()->first)) { 78 decltype(cachedItems_) temp(std::move(cachedItems_)); 79 80 for (auto& [oldindex, id] : temp) { 81 cachedItems_.try_emplace( 82 index > static_cast<size_t>(oldindex) ? oldindex : oldindex + 1, std::move(id)); 83 } 84 } 85 for (auto& [key, node] : expiringItem_) { 86 if (static_cast<size_t>(node.first) >= index && node.first != -1) { 87 node.first++; 88 } 89 } 90 91 return true; 92 } 93 OnDataBulkAdded(size_t index,size_t count)94 bool LazyForEachBuilder::OnDataBulkAdded(size_t index, size_t count) 95 { 96 if (!cachedItems_.empty() && index <= static_cast<size_t>(cachedItems_.rbegin()->first)) { 97 decltype(cachedItems_) temp(std::move(cachedItems_)); 98 99 for (auto& [oldindex, id] : temp) { 100 cachedItems_.try_emplace( 101 index > static_cast<size_t>(oldindex) ? oldindex : oldindex + count, std::move(id)); 102 } 103 } 104 for (auto& [key, node] : expiringItem_) { 105 if (static_cast<size_t>(node.first) >= index && node.first != -1) { 106 node.first = node.first + static_cast<int32_t>(count); 107 } 108 } 109 110 return true; 111 } 112 OnDataDeleted(size_t index)113 RefPtr<UINode> LazyForEachBuilder::OnDataDeleted(size_t index) 114 { 115 RefPtr<UINode> node; 116 if (cachedItems_.empty()) { 117 return node; 118 } 119 if (index <= static_cast<size_t>(cachedItems_.rbegin()->first)) { 120 decltype(cachedItems_) temp(std::move(cachedItems_)); 121 122 for (auto& [oldindex, child] : temp) { 123 if (static_cast<size_t>(oldindex) == index) { 124 node = child.second; 125 KeepRemovedItemInCache(child, expiringItem_); 126 } else { 127 cachedItems_.try_emplace( 128 index > static_cast<size_t>(oldindex) ? oldindex : oldindex - 1, std::move(child)); 129 } 130 } 131 } 132 NotifyDataDeleted(node, index, false); 133 for (auto& [key, child] : expiringItem_) { 134 if (static_cast<size_t>(child.first) > index) { 135 child.first--; 136 continue; 137 } 138 if (static_cast<size_t>(child.first) == index) { 139 child.first = -1; 140 node = child.second; 141 } 142 } 143 144 return node; 145 } 146 OnDataBulkDeleted(size_t index,size_t count)147 std::list<std::pair<std::string, RefPtr<UINode>>>& LazyForEachBuilder::OnDataBulkDeleted(size_t index, size_t count) 148 { 149 if (cachedItems_.empty()) { 150 return nodeList_; 151 } 152 if (index <= static_cast<size_t>(cachedItems_.rbegin()->first)) { 153 decltype(cachedItems_) temp(std::move(cachedItems_)); 154 155 for (auto& [oldindex, child] : temp) { 156 if (static_cast<size_t>(oldindex) >= index && static_cast<size_t>(oldindex) < index + count) { 157 nodeList_.emplace_back(child.first, child.second); 158 } else { 159 cachedItems_.try_emplace( 160 index > static_cast<size_t>(oldindex) ? oldindex : oldindex - count, std::move(child)); 161 } 162 } 163 } 164 165 if (DeleteExpiringItemImmediately()) { 166 decltype(expiringItem_) expiringTemp(std::move(expiringItem_)); 167 for (auto& [key, child] : expiringTemp) { 168 if (child.first < 0) { 169 nodeList_.emplace_back(key, child.second); 170 continue; 171 } 172 if (static_cast<size_t>(child.first) >= index + count) { 173 child.first -= static_cast<int32_t>(count); 174 expiringItem_.try_emplace(key, child); 175 continue; 176 } 177 if (static_cast<size_t>(child.first) >= index && static_cast<size_t>(child.first) < index + count) { 178 nodeList_.emplace_back(key, child.second); 179 } else { 180 expiringItem_.try_emplace(key, child); 181 } 182 } 183 } else { 184 for (auto& [key, child] : expiringItem_) { 185 if (static_cast<size_t>(child.first) >= index + count) { 186 child.first -= static_cast<int32_t>(count); 187 continue; 188 } 189 if (static_cast<size_t>(child.first) >= index && static_cast<size_t>(child.first) < index + count) { 190 child.first = -1; 191 } 192 } 193 } 194 195 return nodeList_; 196 } 197 OnDataChanged(size_t index)198 bool LazyForEachBuilder::OnDataChanged(size_t index) 199 { 200 auto keyIter = cachedItems_.find(index); 201 if (keyIter != cachedItems_.end()) { 202 if (keyIter->second.second) { 203 NotifyDataChanged(index, keyIter->second.second, false); 204 expiringItem_.try_emplace( 205 keyIter->second.first, LazyForEachCacheChild(-1, std::move(keyIter->second.second))); 206 } else { 207 InvalidIndexOfChangedData(index); 208 } 209 cachedItems_.erase(keyIter); 210 return true; 211 } 212 return false; 213 } 214 OnDataBulkChanged(size_t index,size_t count)215 std::list<std::pair<std::string, RefPtr<UINode>>>& LazyForEachBuilder::OnDataBulkChanged(size_t index, size_t count) 216 { 217 if (cachedItems_.empty()) { 218 return nodeList_; 219 } 220 if (static_cast<size_t>(cachedItems_.rbegin()->first) < index) { 221 return nodeList_; 222 } 223 auto iter = cachedItems_.begin(); 224 while (iter != cachedItems_.end()) { 225 auto itemIndex = iter->first; 226 const auto& child = iter->second; 227 if (static_cast<size_t>(itemIndex) >= index && static_cast<size_t>(itemIndex) < index + count) { 228 NotifyDataChanged(index, child.second, false); 229 nodeList_.emplace_back(child.first, child.second); 230 iter = cachedItems_.erase(iter); 231 } else { 232 iter++; 233 } 234 } 235 for (auto& [key, node] : expiringItem_) { 236 if (static_cast<size_t>(node.first) >= index && static_cast<size_t>(node.first) < index + count) { 237 node.first = -1; 238 } 239 } 240 return nodeList_; 241 } 242 OnDataMoveToNewPlace(size_t from,size_t to)243 void LazyForEachBuilder::OnDataMoveToNewPlace(size_t from, size_t to) 244 { 245 if (from == to) { 246 return; 247 } 248 decltype(cachedItems_) temp(std::move(cachedItems_)); 249 for (const auto& [itemIndex, child] : temp) { 250 auto position = static_cast<size_t>(itemIndex); 251 if (position > from && position <= to && position >= 1) { // from < position <= to 252 cachedItems_.emplace(position - 1, child); 253 } else if (position >= to && position < from) { // to <= position < from 254 cachedItems_.emplace(position + 1, child); 255 } else if (position == from) { 256 cachedItems_.emplace(to, child); 257 } else { 258 cachedItems_.emplace(itemIndex, child); 259 } 260 } 261 262 for (const auto& [key, child] : expiringItem_) { 263 auto position = static_cast<size_t>(child.first); 264 if (position > from && position <= to && position >= 1) { // from < position <= to 265 expiringItem_[key] = LazyForEachCacheChild(position - 1, std::move(child.second)); 266 } else if (position >= to && position < from) { // to <= position < from 267 expiringItem_[key] = LazyForEachCacheChild(position + 1, std::move(child.second)); 268 } else if (position == from) { 269 expiringItem_[key] = LazyForEachCacheChild(to, std::move(child.second)); 270 } 271 } 272 } 273 OnDataMoved(size_t from,size_t to)274 bool LazyForEachBuilder::OnDataMoved(size_t from, size_t to) 275 { 276 if (from == to) { 277 return false; 278 } 279 auto fromIter = cachedItems_.find(from); 280 auto toIter = cachedItems_.find(to); 281 if (fromIter != cachedItems_.end() && toIter != cachedItems_.end()) { 282 std::swap(fromIter->second, toIter->second); 283 } else if (fromIter != cachedItems_.end()) { 284 expiringItem_[fromIter->second.first] = LazyForEachCacheChild(to, std::move(fromIter->second.second)); 285 cachedItems_.erase(fromIter); 286 } else if (toIter != cachedItems_.end()) { 287 expiringItem_[toIter->second.first] = LazyForEachCacheChild(from, std::move(toIter->second.second)); 288 cachedItems_.erase(toIter); 289 } 290 return true; 291 } 292 GetAllItems(std::vector<UINode * > & items)293 void LazyForEachBuilder::GetAllItems(std::vector<UINode*>& items) 294 { 295 for (const auto& item : cachedItems_) { 296 items.emplace_back(RawPtr(item.second.second)); 297 } 298 for (const auto& item : expiringItem_) { 299 items.emplace_back(RawPtr(item.second.second)); 300 } 301 for (const auto& item : nodeList_) { 302 items.emplace_back(RawPtr(item.second)); 303 } 304 } 305 Transit(std::list<std::pair<std::string,RefPtr<UINode>>> & childList)306 void LazyForEachBuilder::Transit(std::list<std::pair<std::string, RefPtr<UINode>>>& childList) 307 { 308 if (needTransition) { 309 for (auto& [key, node] : expiringItem_) { 310 if (!node.second) { 311 continue; 312 } 313 auto frameNode = AceType::DynamicCast<FrameNode>(node.second->GetFrameChildByIndex(0, true)); 314 if (frameNode && frameNode->IsOnMainTree()) { 315 childList.emplace_back(key, node.second); 316 } 317 } 318 needTransition = false; 319 } 320 } 321 GetItems(std::list<std::pair<std::string,RefPtr<UINode>>> & childList)322 std::map<int32_t, LazyForEachChild>& LazyForEachBuilder::GetItems( 323 std::list<std::pair<std::string, RefPtr<UINode>>>& childList) 324 { 325 int32_t startIndex = -1; 326 int32_t endIndex = -1; 327 int32_t lastIndex = -1; 328 bool isCertained = false; 329 330 decltype(cachedItems_) items(std::move(cachedItems_)); 331 332 for (auto& [index, node] : items) { 333 if (!node.second) { 334 cachedItems_.try_emplace(index, std::move(node)); 335 continue; 336 } 337 338 auto frameNode = AceType::DynamicCast<FrameNode>(node.second->GetFrameChildByIndex(0, true)); 339 if (frameNode && !frameNode->IsActive()) { 340 ACE_SYNTAX_SCOPED_TRACE("LazyForEach not active index[%d]", index); 341 frameNode->SetJSViewActive(false, true); 342 expiringItem_.try_emplace(node.first, LazyForEachCacheChild(index, std::move(node.second))); 343 continue; 344 } 345 cachedItems_.try_emplace(index, std::move(node)); 346 if (startIndex == -1) { 347 startIndex = index; 348 } 349 if (isLoop_) { 350 if (isCertained) { 351 continue; 352 } 353 if (lastIndex > -1 && index - lastIndex > 1) { 354 startIndex = index; 355 endIndex = lastIndex; 356 isCertained = true; 357 } else { 358 endIndex = std::max(endIndex, index); 359 } 360 } else { 361 endIndex = std::max(endIndex, index); 362 } 363 lastIndex = index; 364 } 365 366 Transit(childList); 367 368 if (startIndex != -1 && endIndex != -1) { 369 startIndex_ = startIndex; 370 endIndex_ = endIndex; 371 } 372 373 return cachedItems_; 374 } 375 376 /** 377 * Application code normarlly manipulate datasource first then call OnDatasetChange. But there are cases that they 378 * call OnDatasetChange first, then manipulate datasource. So the return of function "GetTotalCount" can both be 379 * total count of current datasource(manipulate datasource first) and original datasource(call OnDatasetChange 380 * first). We need to maintain historicalTotalCount_ to get total count of original datasource. 381 */ GetTotalCountOfOriginalDataset()382 int32_t LazyForEachBuilder::GetTotalCountOfOriginalDataset() 383 { 384 int32_t totalCount = GetTotalCount(); 385 int32_t totalCountOfOriginalDataset = historicalTotalCount_; 386 UpdateHistoricalTotalCount(totalCount); 387 return totalCountOfOriginalDataset; 388 } 389 OnDatasetChange(std::list<V2::Operation> DataOperations)390 std::pair<int32_t, std::list<std::pair<std::string, RefPtr<UINode>>>> LazyForEachBuilder::OnDatasetChange( 391 std::list<V2::Operation> DataOperations) 392 { 393 totalCountOfOriginalDataset_ = GetTotalCountOfOriginalDataset(); 394 int32_t initialIndex = totalCountOfOriginalDataset_; 395 std::map<int32_t, LazyForEachChild> expiringTempItem_; 396 std::list<std::string> expiringKeys; 397 for (auto& [key, cacheChild] : expiringItem_) { 398 if (cacheChild.first > -1) { 399 expiringTempItem_.try_emplace(cacheChild.first, LazyForEachChild(key, cacheChild.second)); 400 expiringKeys.emplace_back(key); 401 } 402 } 403 for (auto& key : expiringKeys) { 404 expiringItem_.erase(key); 405 } 406 decltype(expiringTempItem_) expiringTemp(std::move(expiringTempItem_)); 407 for (auto operation : DataOperations) { 408 bool isReload = ClassifyOperation(operation, initialIndex, cachedItems_, expiringTemp); 409 if (isReload) { 410 initialIndex = 0; 411 return std::pair(initialIndex, nodeList_); 412 } 413 } 414 decltype(cachedItems_) cachedTemp(std::move(cachedItems_)); 415 std::map<int32_t, int32_t> indexChangedMap; 416 CollectIndexChangedCount(indexChangedMap); 417 RepairDatasetItems(cachedTemp, cachedItems_, indexChangedMap); 418 RepairDatasetItems(expiringTemp, expiringTempItem_, indexChangedMap); 419 for (auto& [index, node] : expiringTempItem_) { 420 if (node.second) { 421 expiringItem_.emplace(node.first, LazyForEachCacheChild(index, node.second)); 422 } 423 } 424 operationList_.clear(); 425 return std::pair(initialIndex, nodeList_); 426 } 427 RepairDatasetItems(std::map<int32_t,LazyForEachChild> & cachedTemp,std::map<int32_t,LazyForEachChild> & expiringTempItem_,std::map<int32_t,int32_t> & indexChangedMap)428 void LazyForEachBuilder::RepairDatasetItems(std::map<int32_t, LazyForEachChild>& cachedTemp, 429 std::map<int32_t, LazyForEachChild>& expiringTempItem_, std::map<int32_t, int32_t>& indexChangedMap) 430 { 431 int32_t changedIndex = 0; 432 for (auto& [index, child] : cachedTemp) { 433 auto iter = indexChangedMap.find(index); 434 if (iter == indexChangedMap.end()) { 435 if (!indexChangedMap.empty()) { 436 iter--; 437 if (iter->first < index) { 438 changedIndex = iter->second; 439 } 440 } 441 } else { 442 changedIndex = iter->second; 443 } 444 if (operationList_.find(index) == operationList_.end()) { 445 expiringTempItem_.try_emplace(index + changedIndex, child); 446 continue; 447 } 448 if (!indexChangedMap.empty()) { 449 changedIndex = iter->second; 450 } 451 auto info = operationList_.find(index)->second; 452 if (info.isDeleting) { 453 nodeList_.emplace_back(child.first, child.second); 454 } else if (info.isChanged) { 455 expiringTempItem_.try_emplace(index + changedIndex, LazyForEachChild(info.key, nullptr)); 456 } else if (!info.extraKey.empty()) { 457 expiringTempItem_.try_emplace(index + changedIndex, child); 458 int32_t preChangedIndex = 0; 459 auto preIter = indexChangedMap.find(index - 1); 460 if (preIter != indexChangedMap.end()) { 461 preChangedIndex = preIter->second; 462 } 463 for (int32_t i = 0; i < static_cast<int32_t>(info.extraKey.size()); i++) { 464 expiringTempItem_.try_emplace( 465 index + preChangedIndex + i, LazyForEachChild(info.extraKey[i], nullptr)); 466 } 467 } else if (info.moveIn || info.isExchange) { 468 RepairMoveOrExchange(expiringTempItem_, info, child, index, changedIndex); 469 } else { 470 expiringTempItem_.try_emplace(index + changedIndex, child); 471 } 472 } 473 } 474 RepairMoveOrExchange(std::map<int32_t,LazyForEachChild> & expiringTempItem_,OperationInfo & info,LazyForEachChild & child,int32_t index,int32_t changedIndex)475 void LazyForEachBuilder::RepairMoveOrExchange(std::map<int32_t, LazyForEachChild>& expiringTempItem_, 476 OperationInfo& info, LazyForEachChild& child, int32_t index, int32_t changedIndex) 477 { 478 if (info.isExchange) { 479 // if the child will be exchanged with a null node, this child should be deleted 480 if (info.node == nullptr && child.second != nullptr) { 481 nodeList_.emplace_back(child.first, child.second); 482 } 483 // null node should not be put in expiringTempItem_ then expiringTempItem_ 484 if (info.node != nullptr) { 485 expiringTempItem_.try_emplace(index + changedIndex, LazyForEachChild(info.key, info.node)); 486 } 487 return; 488 } 489 if (info.moveIn) { 490 int32_t fromIndex = index + changedIndex - 1; 491 int32_t toIndex = index + changedIndex; 492 if (info.fromDiffTo > 0) { 493 fromIndex = index + changedIndex; 494 toIndex = index + changedIndex - 1; 495 } 496 expiringTempItem_.try_emplace(toIndex, LazyForEachChild(info.key, info.node)); 497 expiringTempItem_.try_emplace(fromIndex, child); 498 } 499 } 500 CollectIndexChangedCount(std::map<int32_t,int32_t> & indexChangedMap)501 void LazyForEachBuilder::CollectIndexChangedCount(std::map<int32_t, int32_t>& indexChangedMap) 502 { 503 int32_t changedIndex = 0; 504 for (auto& [index, operationInfo] : operationList_) { 505 if (indexChangedMap.size() >= static_cast<size_t>(1)) { 506 for (int32_t i = indexChangedMap.rbegin()->first + 1; i < index; i++) { 507 indexChangedMap.try_emplace(i, changedIndex); 508 } 509 } 510 operationInfo.changeCount += changedIndex; 511 changedIndex = operationInfo.changeCount; 512 indexChangedMap.try_emplace(index, changedIndex); 513 } 514 } 515 ClassifyOperation(V2::Operation & operation,int32_t & initialIndex,std::map<int32_t,LazyForEachChild> & cachedTemp,std::map<int32_t,LazyForEachChild> & expiringTemp)516 bool LazyForEachBuilder::ClassifyOperation(V2::Operation& operation, int32_t& initialIndex, 517 std::map<int32_t, LazyForEachChild>& cachedTemp, std::map<int32_t, LazyForEachChild>& expiringTemp) 518 { 519 switch (operationTypeMap[operation.type]) { 520 case OP::ADD: 521 OperateAdd(operation, initialIndex); 522 break; 523 case OP::DEL: 524 OperateDelete(operation, initialIndex); 525 break; 526 case OP::CHANGE: 527 OperateChange(operation, initialIndex, cachedTemp, expiringTemp); 528 break; 529 case OP::MOVE: 530 OperateMove(operation, initialIndex, cachedTemp, expiringTemp); 531 break; 532 case OP::EXCHANGE: 533 OperateExchange(operation, initialIndex, cachedTemp, expiringTemp); 534 break; 535 case OP::RELOAD: 536 OperateReload(expiringTemp); 537 return true; 538 } 539 return false; 540 } 541 ValidateIndex(int32_t index,const std::string & type)542 bool LazyForEachBuilder::ValidateIndex(int32_t index, const std::string& type) 543 { 544 bool isValid = true; 545 if (operationTypeMap[type] == OP::ADD) { 546 // for add operation, the index can equal totalCountOfOriginalDataset_ 547 isValid = index >= 0 && index <= totalCountOfOriginalDataset_; 548 } else { 549 isValid = index >= 0 && index < totalCountOfOriginalDataset_; 550 } 551 if (!isValid) { 552 TAG_LOGE( 553 AceLogTag::ACE_LAZY_FOREACH, "%{public}s(%{public}d) Operation is out of range", type.c_str(), index); 554 } 555 return isValid; 556 } 557 OperateAdd(V2::Operation & operation,int32_t & initialIndex)558 void LazyForEachBuilder::OperateAdd(V2::Operation& operation, int32_t& initialIndex) 559 { 560 OperationInfo itemInfo; 561 if (!ValidateIndex(operation.index, operation.type)) { 562 return; 563 } 564 auto indexExist = operationList_.find(operation.index); 565 if (indexExist == operationList_.end()) { 566 itemInfo.changeCount = operation.count; 567 if (!operation.key.empty()) { 568 itemInfo.extraKey.push_back(operation.key); 569 } else if (operation.keyList.size() >= static_cast<size_t>(1)) { 570 for (std::string key : operation.keyList) { 571 itemInfo.extraKey.push_back(key); 572 } 573 } 574 initialIndex = std::min(initialIndex, operation.index); 575 operationList_.try_emplace(operation.index, itemInfo); 576 } else { 577 ThrowRepeatOperationError(operation.index); 578 } 579 } 580 OperateDelete(V2::Operation & operation,int32_t & initialIndex)581 void LazyForEachBuilder::OperateDelete(V2::Operation& operation, int32_t& initialIndex) 582 { 583 OperationInfo itemInfo; 584 if (!ValidateIndex(operation.index, operation.type)) { 585 return; 586 } 587 auto indexExist = operationList_.find(operation.index); 588 if (indexExist == operationList_.end()) { 589 itemInfo.changeCount = -operation.count; 590 itemInfo.isDeleting = true; 591 initialIndex = std::min(initialIndex, operation.index); 592 operationList_.try_emplace(operation.index, itemInfo); 593 for (int32_t i = operation.index + 1; i < operation.index + operation.count; i++) { 594 OperationInfo extraInfo; 595 if (operationList_.find(i) == operationList_.end()) { 596 extraInfo.isDeleting = true; 597 operationList_.try_emplace(i, extraInfo); 598 } else { 599 ThrowRepeatOperationError(i); 600 } 601 } 602 } else { 603 ThrowRepeatOperationError(operation.index); 604 } 605 } 606 OperateChange(V2::Operation & operation,int32_t & initialIndex,std::map<int32_t,LazyForEachChild> & cachedTemp,std::map<int32_t,LazyForEachChild> & expiringTemp)607 void LazyForEachBuilder::OperateChange(V2::Operation& operation, int32_t& initialIndex, 608 std::map<int32_t, LazyForEachChild>& cachedTemp, std::map<int32_t, LazyForEachChild>& expiringTemp) 609 { 610 OperationInfo itemInfo; 611 if (!ValidateIndex(operation.index, operation.type)) { 612 return; 613 } 614 auto indexExist = operationList_.find(operation.index); 615 if (indexExist == operationList_.end()) { 616 itemInfo.isChanged = true; 617 auto iter = cachedTemp.find(operation.index); 618 if (iter == cachedTemp.end()) { 619 iter = expiringTemp.find(operation.index); 620 } 621 if (iter == expiringTemp.end()) { 622 return; 623 } 624 if (!operation.key.empty()) { 625 itemInfo.key = operation.key; 626 } else { 627 itemInfo.key = iter->second.first; 628 } 629 initialIndex = std::min(initialIndex, operation.index); 630 operationList_.try_emplace(operation.index, itemInfo); 631 } else { 632 ThrowRepeatOperationError(operation.index); 633 } 634 } 635 OperateMove(V2::Operation & operation,int32_t & initialIndex,std::map<int32_t,LazyForEachChild> & cachedTemp,std::map<int32_t,LazyForEachChild> & expiringTemp)636 void LazyForEachBuilder::OperateMove(V2::Operation& operation, int32_t& initialIndex, 637 std::map<int32_t, LazyForEachChild>& cachedTemp, std::map<int32_t, LazyForEachChild>& expiringTemp) 638 { 639 OperationInfo fromInfo; 640 OperationInfo toInfo; 641 if (!ValidateIndex(operation.coupleIndex.first, operation.type) || 642 !ValidateIndex(operation.coupleIndex.second, operation.type)) { 643 return; 644 } 645 auto fromIndexExist = operationList_.find(operation.coupleIndex.first); 646 auto toIndexExist = operationList_.find(operation.coupleIndex.second); 647 if (fromIndexExist == operationList_.end()) { 648 fromInfo.changeCount = -1; 649 fromInfo.isDeleting = true; 650 initialIndex = std::min(initialIndex, operation.coupleIndex.first); 651 operationList_.try_emplace(operation.coupleIndex.first, fromInfo); 652 } else { 653 ThrowRepeatOperationError(operation.coupleIndex.first); 654 } 655 if (toIndexExist == operationList_.end()) { 656 toInfo.changeCount = 1; 657 auto iter = cachedTemp.find(operation.coupleIndex.first); 658 if (iter == cachedTemp.end()) { 659 iter = expiringTemp.find(operation.coupleIndex.first); 660 } 661 if (iter == expiringTemp.end()) { 662 return; 663 } 664 toInfo.node = iter->second.second; 665 toInfo.moveIn = true; 666 toInfo.fromDiffTo = operation.coupleIndex.first - operation.coupleIndex.second; 667 if (!operation.key.empty()) { 668 toInfo.key = operation.key; 669 } else { 670 toInfo.key = iter->second.first; 671 } 672 initialIndex = std::min(initialIndex, operation.coupleIndex.second); 673 operationList_.try_emplace(operation.coupleIndex.second, toInfo); 674 } else { 675 ThrowRepeatOperationError(operation.coupleIndex.second); 676 } 677 } 678 OperateExchange(V2::Operation & operation,int32_t & initialIndex,std::map<int32_t,LazyForEachChild> & cachedTemp,std::map<int32_t,LazyForEachChild> & expiringTemp)679 void LazyForEachBuilder::OperateExchange(V2::Operation& operation, int32_t& initialIndex, 680 std::map<int32_t, LazyForEachChild>& cachedTemp, std::map<int32_t, LazyForEachChild>& expiringTemp) 681 { 682 OperationInfo startInfo; 683 OperationInfo endInfo; 684 if (!ValidateIndex(operation.coupleIndex.first, operation.type) || 685 !ValidateIndex(operation.coupleIndex.second, operation.type)) { 686 return; 687 } 688 auto startIndexExist = operationList_.find(operation.coupleIndex.first); 689 auto endIndexExist = operationList_.find(operation.coupleIndex.second); 690 if (startIndexExist == operationList_.end()) { 691 auto iter = FindItem(operation.coupleIndex.first, cachedTemp, expiringTemp); 692 // if item can't be find in cachedItems_ nor expiringItem_, set UI node to null 693 if (iter == expiringTemp.end()) { 694 startInfo.node = nullptr; 695 } else { 696 startInfo.node = iter->second.second; 697 if (!operation.coupleKey.first.empty()) { 698 startInfo.key = operation.coupleKey.first; 699 } else { 700 startInfo.key = iter->second.first; 701 } 702 } 703 startInfo.isExchange = true; 704 initialIndex = std::min(initialIndex, operation.coupleIndex.second); 705 operationList_.try_emplace(operation.coupleIndex.second, startInfo); 706 } else { 707 ThrowRepeatOperationError(operation.coupleIndex.first); 708 } 709 if (endIndexExist == operationList_.end()) { 710 auto iter = FindItem(operation.coupleIndex.second, cachedTemp, expiringTemp); 711 // if item can't be find in cachedItems_ nor expiringItem_, set UI node to null 712 if (iter == expiringTemp.end()) { 713 endInfo.node = nullptr; 714 } else { 715 endInfo.node = iter->second.second; 716 if (!operation.coupleKey.second.empty()) { 717 endInfo.key = operation.coupleKey.second; 718 } else { 719 endInfo.key = iter->second.first; 720 } 721 } 722 endInfo.isExchange = true; 723 initialIndex = std::min(initialIndex, operation.coupleIndex.first); 724 operationList_.try_emplace(operation.coupleIndex.first, endInfo); 725 } else { 726 ThrowRepeatOperationError(operation.coupleIndex.second); 727 } 728 } 729 FindItem(int32_t index,std::map<int32_t,LazyForEachChild> & cachedTemp,std::map<int32_t,LazyForEachChild> & expiringTemp)730 std::map<int32_t, LazyForEachChild>::iterator LazyForEachBuilder::FindItem(int32_t index, 731 std::map<int32_t, LazyForEachChild>& cachedTemp, std::map<int32_t, LazyForEachChild>& expiringTemp) 732 { 733 auto iterOfCached = cachedTemp.find(index); 734 auto iterOfExpiring = expiringTemp.find(index); 735 // if UI node can't be find in cachedTemp, find it in expiringTemp 736 if (iterOfCached == cachedTemp.end() || iterOfCached->second.second == nullptr) { 737 return iterOfExpiring; 738 } else { 739 return iterOfCached; 740 } 741 } 742 OperateReload(std::map<int32_t,LazyForEachChild> & expiringTemp)743 void LazyForEachBuilder::OperateReload(std::map<int32_t, LazyForEachChild>& expiringTemp) 744 { 745 for (auto& [index, node] : expiringTemp) { 746 expiringItem_.emplace(node.first, LazyForEachCacheChild(index, node.second)); 747 } 748 operationList_.clear(); 749 OnDataReloaded(); 750 } 751 ThrowRepeatOperationError(int32_t index)752 void LazyForEachBuilder::ThrowRepeatOperationError(int32_t index) 753 { 754 TAG_LOGE(AceLogTag::ACE_LAZY_FOREACH, "Repeat Operation for index: %{public}d", index); 755 } 756 RecycleChildByIndex(int32_t index)757 void LazyForEachBuilder::RecycleChildByIndex(int32_t index) 758 { 759 auto iter = cachedItems_.find(index); 760 if (iter != cachedItems_.end()) { 761 if (!iter->second.second) { 762 return; 763 } 764 auto dummyNode = AceType::DynamicCast<RecycleDummyNode>(iter->second.second); 765 if (!dummyNode) { 766 return; 767 } 768 auto keyIter = expiringItem_.find(iter->second.first); 769 if (keyIter != expiringItem_.end() && keyIter->second.second) { 770 expiringItem_.erase(keyIter); 771 } 772 cachedItems_.erase(index); 773 } 774 } 775 PreBuild(int64_t deadline,const std::optional<LayoutConstraintF> & itemConstraint,bool canRunLongPredictTask)776 bool LazyForEachBuilder::PreBuild(int64_t deadline, const std::optional<LayoutConstraintF>& itemConstraint, 777 bool canRunLongPredictTask) 778 { 779 ACE_SYNTAX_SCOPED_TRACE("expiringItem_ count:[%zu]", expiringItem_.size()); 780 outOfBoundaryNodes_.clear(); 781 if (itemConstraint && !canRunLongPredictTask) { 782 return false; 783 } 784 auto count = OnGetTotalCount(); 785 std::unordered_map<std::string, LazyForEachCacheChild> cache; 786 std::set<int32_t> idleIndexes; 787 // if List contains a mixture of ListItem, LazyForeach, and Foreach... 788 // Then both startIndex_ and endIndex_ can be -1 789 CheckCacheIndex(idleIndexes, count); 790 791 ProcessCachedIndex(cache, idleIndexes); 792 793 bool result = true; 794 result = ProcessPreBuildingIndex(cache, deadline, itemConstraint, canRunLongPredictTask, idleIndexes); 795 if (!result) { 796 expiringItem_.swap(cache); 797 return result; 798 } 799 800 for (auto index : idleIndexes) { 801 result = PreBuildByIndex(index, cache, deadline, itemConstraint, canRunLongPredictTask); 802 if (!result) { 803 break; 804 } 805 } 806 expiringItem_.swap(cache); 807 return result; 808 } 809 RecordOutOfBoundaryNodes(int32_t index)810 void LazyForEachBuilder::RecordOutOfBoundaryNodes(int32_t index) 811 { 812 outOfBoundaryNodes_.emplace_back(index); 813 } 814 RecycleItemsOutOfBoundary()815 void LazyForEachBuilder::RecycleItemsOutOfBoundary() 816 { 817 for (const auto& i: outOfBoundaryNodes_) { 818 RecycleChildByIndex(i); 819 } 820 outOfBoundaryNodes_.clear(); 821 } 822 UpdateMoveFromTo(int32_t from,int32_t to)823 void LazyForEachBuilder::UpdateMoveFromTo(int32_t from, int32_t to) 824 { 825 if (moveFromTo_) { 826 moveFromTo_.value().second = to; 827 } else { 828 moveFromTo_ = { from, to }; 829 } 830 } 831 ResetMoveFromTo()832 void LazyForEachBuilder::ResetMoveFromTo() 833 { 834 moveFromTo_.reset(); 835 } 836 ConvertFormToIndex(int32_t index)837 int32_t LazyForEachBuilder::ConvertFormToIndex(int32_t index) 838 { 839 if (!moveFromTo_) { 840 return index; 841 } 842 if (moveFromTo_.value().second == index) { 843 return moveFromTo_.value().first; 844 } 845 if (moveFromTo_.value().first <= index && index < moveFromTo_.value().second) { 846 return index + 1; 847 } 848 if (moveFromTo_.value().second < index && index <= moveFromTo_.value().first) { 849 return index - 1; 850 } 851 return index; 852 } 853 RemoveAllChild()854 void LazyForEachBuilder::RemoveAllChild() 855 { 856 ACE_SYNTAX_SCOPED_TRACE("LazyForEach RemoveAllChild"); 857 for (auto& [index, node] : cachedItems_) { 858 if (!node.second) { 859 continue; 860 } 861 auto frameNode = AceType::DynamicCast<FrameNode>(node.second->GetFrameChildByIndex(0, true)); 862 if (frameNode) { 863 frameNode->SetActive(false); 864 } 865 auto tempNode = node.second; 866 auto pair = expiringItem_.try_emplace(node.first, LazyForEachCacheChild(index, std::move(node.second))); 867 if (!pair.second) { 868 TAG_LOGW(AceLogTag::ACE_LAZY_FOREACH, "Use repeat key for index: %{public}d", index); 869 ProcessOffscreenNode(tempNode, true); 870 } 871 } 872 } 873 SetActiveChildRange(int32_t start,int32_t end)874 bool LazyForEachBuilder::SetActiveChildRange(int32_t start, int32_t end) 875 { 876 ACE_SYNTAX_SCOPED_TRACE("LazyForEach active range start[%d], end[%d]", start, end); 877 startIndex_ = start; 878 endIndex_ = end; 879 int32_t count = GetTotalCount(); 880 UpdateHistoricalTotalCount(count); 881 bool needBuild = false; 882 for (auto& [index, node] : cachedItems_) { 883 bool isInRange = (index < count) && ((start <= end && start <= index && end >= index) || 884 (start > end && (index <= end || index >= start))); 885 if (!isInRange) { 886 if (!node.second) { 887 continue; 888 } 889 auto frameNode = AceType::DynamicCast<FrameNode>(node.second->GetFrameChildByIndex(0, true)); 890 if (frameNode) { 891 frameNode->SetActive(false); 892 } 893 auto tempNode = node.second; 894 auto pair = expiringItem_.try_emplace(node.first, LazyForEachCacheChild(index, std::move(node.second))); 895 if (!pair.second) { 896 TAG_LOGW(AceLogTag::ACE_LAZY_FOREACH, "Use repeat key for index: %{public}d", index); 897 ProcessOffscreenNode(tempNode, true); 898 } 899 needBuild = true; 900 continue; 901 } 902 if (node.second) { 903 auto frameNode = AceType::DynamicCast<FrameNode>(node.second->GetFrameChildByIndex(0, true)); 904 if (frameNode) { 905 frameNode->SetActive(true); 906 } 907 continue; 908 } 909 auto keyIter = expiringItem_.find(node.first); 910 if (keyIter != expiringItem_.end() && keyIter->second.second) { 911 node.second = keyIter->second.second; 912 expiringItem_.erase(keyIter); 913 auto frameNode = AceType::DynamicCast<FrameNode>(node.second->GetFrameChildByIndex(0, true)); 914 if (frameNode) { 915 frameNode->SetActive(true); 916 } 917 } 918 needBuild = true; 919 } 920 return needBuild; 921 } 922 GetChildIndex(const RefPtr<FrameNode> & targetNode)923 int32_t LazyForEachBuilder::GetChildIndex(const RefPtr<FrameNode>& targetNode) 924 { 925 for (auto& [index, node] : cachedItems_) { 926 if (node.second) { 927 auto frameNode = AceType::DynamicCast<FrameNode>(node.second->GetFrameChildByIndex(0, true)); 928 if (frameNode == targetNode) { 929 return index; 930 } 931 } 932 } 933 for (auto& [key, node] : expiringItem_) { 934 if (!node.second) { 935 continue; 936 } 937 auto frameNode = AceType::DynamicCast<FrameNode>(node.second->GetFrameChildByIndex(0, true)); 938 if (frameNode && frameNode == targetNode) { 939 return node.first; 940 } 941 } 942 return -1; 943 } 944 CacheItem(int32_t index,std::unordered_map<std::string,LazyForEachCacheChild> & cache,const std::optional<LayoutConstraintF> & itemConstraint,int64_t deadline,bool & isTimeout)945 RefPtr<UINode> LazyForEachBuilder::CacheItem(int32_t index, 946 std::unordered_map<std::string, LazyForEachCacheChild>& cache, 947 const std::optional<LayoutConstraintF>& itemConstraint, int64_t deadline, bool& isTimeout) 948 { 949 ACE_SCOPED_TRACE("Builder:BuildLazyItem [%d]", index); 950 auto itemInfo = OnGetChildByIndex(ConvertFormToIndex(index), expiringItem_); 951 CHECK_NULL_RETURN(itemInfo.second, nullptr); 952 auto pair = cache.try_emplace(itemInfo.first, LazyForEachCacheChild(index, itemInfo.second)); 953 auto context = itemInfo.second->GetContext(); 954 CHECK_NULL_RETURN(context, itemInfo.second); 955 auto frameNode = AceType::DynamicCast<FrameNode>(itemInfo.second->GetFrameChildByIndex(0, false, true)); 956 context->SetPredictNode(frameNode); 957 if (!itemInfo.second->RenderCustomChild(deadline)) { 958 isTimeout = true; 959 context->ResetPredictNode(); 960 return itemInfo.second; 961 } 962 if (pair.second) { 963 ProcessOffscreenNode(itemInfo.second, false); 964 } else { 965 TAG_LOGW(AceLogTag::ACE_LAZY_FOREACH, "Use repeat key for index: %{public}d", index); 966 } 967 968 itemInfo.second->Build(nullptr); 969 context->ResetPredictNode(); 970 itemInfo.second->SetJSViewActive(false, true); 971 cachedItems_[index] = LazyForEachChild(itemInfo.first, nullptr); 972 973 return itemInfo.second; 974 } 975 CheckCacheIndex(std::set<int32_t> & idleIndexes,int32_t count)976 void LazyForEachBuilder::CheckCacheIndex(std::set<int32_t>& idleIndexes, int32_t count) 977 { 978 if (count == 0) { 979 return; 980 } 981 for (int32_t i = 1; i <= cacheCount_ - endShowCached_; i++) { 982 if (isLoop_) { 983 if ((startIndex_ <= endIndex_ && endIndex_ + i < count) || 984 startIndex_ > endIndex_ + i) { 985 idleIndexes.emplace(endIndex_ + i); 986 } else if ((endIndex_ + i) % count < startIndex_) { 987 idleIndexes.emplace((endIndex_ + i) % count); 988 } 989 } else { 990 if (endIndex_ + i >= 0 && endIndex_ + i < count) { 991 idleIndexes.emplace(endIndex_ + i); 992 } 993 } 994 } 995 for (int32_t i = 1; i <= cacheCount_ - startShowCached_; i++) { 996 if (isLoop_) { 997 if ((startIndex_ <= endIndex_ && startIndex_ >= i) || 998 startIndex_ > endIndex_ + i) { 999 idleIndexes.emplace(startIndex_ - i); 1000 } else if ((startIndex_ - i + count) % count > endIndex_) { 1001 idleIndexes.emplace((startIndex_ - i + count) % count); 1002 } 1003 } else { 1004 if (startIndex_ - i >= 0 && startIndex_ - i < count) { 1005 idleIndexes.emplace(startIndex_ - i); 1006 } 1007 } 1008 } 1009 } 1010 PreBuildByIndex(int32_t index,std::unordered_map<std::string,LazyForEachCacheChild> & cache,int64_t deadline,const std::optional<LayoutConstraintF> & itemConstraint,bool canRunLongPredictTask)1011 bool LazyForEachBuilder::PreBuildByIndex(int32_t index, 1012 std::unordered_map<std::string, LazyForEachCacheChild>& cache, int64_t deadline, 1013 const std::optional<LayoutConstraintF>& itemConstraint, bool canRunLongPredictTask) 1014 { 1015 if (GetSysTimestamp() > deadline) { 1016 if (DeleteExpiringItemImmediately()) { 1017 return false; 1018 } 1019 for (const auto& [key, node] : expiringItem_) { 1020 if (node.first == -1) { 1021 cache.try_emplace(key, node); 1022 } 1023 } 1024 return false; 1025 } 1026 bool isTimeout = false; 1027 preBuildingIndex_ = -1; 1028 auto uiNode = CacheItem(index, cache, itemConstraint, deadline, isTimeout); 1029 if (isTimeout) { 1030 preBuildingIndex_ = index; 1031 return false; 1032 } 1033 if (!canRunLongPredictTask && itemConstraint) { 1034 return false; 1035 } 1036 if (canRunLongPredictTask && uiNode && itemConstraint) { 1037 RefPtr<FrameNode> frameNode = DynamicCast<FrameNode>(uiNode); 1038 while (!frameNode) { 1039 auto tempNode = uiNode; 1040 uiNode = tempNode->GetFirstChild(); 1041 if (!uiNode) { 1042 break; 1043 } 1044 frameNode = DynamicCast<FrameNode>(uiNode); 1045 } 1046 if (frameNode) { 1047 frameNode->GetGeometryNode()->SetParentLayoutConstraint(itemConstraint.value()); 1048 FrameNode::ProcessOffscreenNode(frameNode); 1049 } 1050 } 1051 return true; 1052 } 1053 ProcessCachedIndex(std::unordered_map<std::string,LazyForEachCacheChild> & cache,std::set<int32_t> & idleIndexes)1054 void LazyForEachBuilder::ProcessCachedIndex(std::unordered_map<std::string, LazyForEachCacheChild>& cache, 1055 std::set<int32_t>& idleIndexes) 1056 { 1057 auto expiringIter = expiringItem_.begin(); 1058 while (expiringIter != expiringItem_.end()) { 1059 const auto& key = expiringIter->first; 1060 const auto& node = expiringIter->second; 1061 auto iter = idleIndexes.find(node.first); 1062 if (iter != idleIndexes.end() && node.second) { 1063 LoadCacheByIndex(cache, idleIndexes, node, key, iter, expiringIter); 1064 } else { 1065 LoadCacheByKey(cache, idleIndexes, node, key, expiringIter); 1066 } 1067 } 1068 } 1069 ProcessOffscreenNode(RefPtr<UINode> uiNode,bool remove)1070 void LazyForEachBuilder::ProcessOffscreenNode(RefPtr<UINode> uiNode, bool remove) 1071 { 1072 if (uiNode) { 1073 auto frameNode = DynamicCast<FrameNode>(uiNode); 1074 while (!frameNode) { 1075 auto tempNode = uiNode; 1076 uiNode = tempNode->GetFirstChild(); 1077 if (!uiNode) { 1078 break; 1079 } 1080 frameNode = DynamicCast<FrameNode>(uiNode); 1081 } 1082 if (frameNode) { 1083 if (!remove) { 1084 Inspector::AddOffscreenNode(frameNode); 1085 } else { 1086 Inspector::RemoveOffscreenNode(frameNode); 1087 } 1088 } 1089 } 1090 } 1091 GetAllChildren()1092 const std::map<int32_t, LazyForEachChild>& LazyForEachBuilder::GetAllChildren() 1093 { 1094 if (!cachedItems_.empty()) { 1095 startIndex_ = cachedItems_.begin()->first; 1096 endIndex_ = cachedItems_.rbegin()->first; 1097 } 1098 if (isLoop_ && !cachedItems_.empty()) { 1099 int32_t lastIndex = -1; 1100 for (auto& [index, node] : cachedItems_) { 1101 if (lastIndex > -1 && index - lastIndex > 1) { 1102 startIndex_ = index; 1103 endIndex_ = lastIndex; 1104 break; 1105 } 1106 } 1107 } 1108 return cachedItems_; 1109 } 1110 SetJSViewActive(bool active)1111 void LazyForEachBuilder::SetJSViewActive(bool active) 1112 { 1113 for (const auto& node : cachedItems_) { 1114 if (node.second.second == nullptr) { 1115 continue; 1116 } 1117 node.second.second->SetJSViewActive(active, true); 1118 } 1119 for (const auto& node : expiringItem_) { 1120 if (node.second.second == nullptr) { 1121 continue; 1122 } 1123 node.second.second->SetJSViewActive(active, true); 1124 } 1125 } 1126 PaintDebugBoundaryTreeAll(bool flag)1127 void LazyForEachBuilder::PaintDebugBoundaryTreeAll(bool flag) 1128 { 1129 for (const auto& node : cachedItems_) { 1130 if (node.second.second == nullptr) { 1131 continue; 1132 } 1133 node.second.second->PaintDebugBoundaryTreeAll(flag); 1134 } 1135 for (const auto& node : expiringItem_) { 1136 if (node.second.second == nullptr) { 1137 continue; 1138 } 1139 node.second.second->PaintDebugBoundaryTreeAll(flag); 1140 } 1141 } SetDestroying(bool isDestroying,bool cleanStatus)1142 void LazyForEachBuilder::SetDestroying(bool isDestroying, bool cleanStatus) 1143 { 1144 for (const auto& node : cachedItems_) { 1145 if (node.second.second == nullptr) { 1146 continue; 1147 } 1148 if (node.second.second->IsReusableNode()) { 1149 node.second.second->SetDestroying(isDestroying, false); 1150 } else { 1151 node.second.second->SetDestroying(isDestroying, cleanStatus); 1152 } 1153 } 1154 1155 for (const auto& node : expiringItem_) { 1156 if (node.second.second == nullptr) { 1157 continue; 1158 } 1159 if (node.second.second->IsReusableNode()) { 1160 node.second.second->SetDestroying(isDestroying, false); 1161 } else { 1162 node.second.second->SetDestroying(isDestroying, cleanStatus); 1163 } 1164 } 1165 } 1166 } 1167