From c5ea02b5f5003d3d5eb4bb3cda583efc8708b747 Mon Sep 17 00:00:00 2001 From: liubihao Date: Sat, 25 Jan 2025 10:08:07 +0800 Subject: [PATCH 1/8] PR 1&2. Add Repeat Virtual-Scroll v2 (for API 16) & Fix useKey. Signed-off-by: liubihao --- .../bridge/declarative_frontend/BUILD.gn | 2 + .../engine/jsi/jsi_view_register_impl.cpp | 2 + .../engine/jsi/jsi_view_register_impl_ng.cpp | 1 + .../jsview/js_repeat_virtual_scroll.cpp | 2 +- .../jsview/js_repeat_virtual_scroll_2.cpp | 231 ++++ .../jsview/js_repeat_virtual_scroll_2.h | 39 + .../state_mgmt/files_to_watch.gni | 1 + .../src/lib/partial_update/pu_foreach.d.ts | 30 + .../src/lib/partial_update/pu_repeat.ts | 75 +- .../src/lib/partial_update/pu_repeat_impl.ts | 8 +- .../pu_repeat_virtual_scroll_2_impl.ts | 1032 +++++++++++++++++ .../pu_repeat_virtual_scroll_impl.ts | 39 +- .../state_mgmt/src/lib/sdk/i_repeat.ts | 4 +- .../state_mgmt/tsconfig.base.json | 1 + .../state_mgmt/tsconfig.test.json | 1 + .../core/components_ng/base/frame_node.cpp | 4 +- .../base/view_partial_update_model_ng.cpp | 13 +- .../list/list_height_offset_calculator.h | 8 +- .../list/list_lanes_layout_algorithm.cpp | 3 + .../pattern/list/list_position_map.h | 8 +- .../pattern/swiper/swiper_pattern.cpp | 18 + frameworks/core/components_ng/syntax/BUILD.gn | 7 + .../syntax/repeat_virtual_scroll_2_caches.cpp | 398 +++++++ .../syntax/repeat_virtual_scroll_2_caches.h | 259 +++++ .../syntax/repeat_virtual_scroll_2_model.h | 57 + .../repeat_virtual_scroll_2_model_ng.cpp | 95 ++ .../syntax/repeat_virtual_scroll_2_model_ng.h | 48 + .../syntax/repeat_virtual_scroll_2_node.cpp | 626 ++++++++++ .../syntax/repeat_virtual_scroll_2_node.h | 282 +++++ .../syntax/repeat_virtual_scroll_caches.cpp | 7 + .../syntax/repeat_virtual_scroll_caches.h | 15 +- .../syntax/repeat_node_cache_syntax_test.cpp | 2 + 32 files changed, 3250 insertions(+), 68 deletions(-) create mode 100644 frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp create mode 100644 frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.h create mode 100644 frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts create mode 100644 frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.cpp create mode 100644 frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.h create mode 100644 frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h create mode 100644 frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp create mode 100644 frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h create mode 100644 frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp create mode 100644 frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h diff --git a/frameworks/bridge/declarative_frontend/BUILD.gn b/frameworks/bridge/declarative_frontend/BUILD.gn index a13f70703fa..70aa431dccb 100644 --- a/frameworks/bridge/declarative_frontend/BUILD.gn +++ b/frameworks/bridge/declarative_frontend/BUILD.gn @@ -359,6 +359,7 @@ template("declarative_js_engine") { "jsview/js_relative_container.cpp", "jsview/js_repeat.cpp", "jsview/js_repeat_virtual_scroll.cpp", + "jsview/js_repeat_virtual_scroll_2.cpp", "jsview/js_richeditor.cpp", "jsview/js_row.cpp", "jsview/js_save_button.cpp", @@ -919,6 +920,7 @@ template("declarative_js_engine_ng") { "jsview/js_refresh.cpp", "jsview/js_repeat.cpp", "jsview/js_repeat_virtual_scroll.cpp", + "jsview/js_repeat_virtual_scroll_2.cpp", "jsview/js_richeditor.cpp", "jsview/js_row.cpp", "jsview/js_save_button.cpp", diff --git a/frameworks/bridge/declarative_frontend/engine/jsi/jsi_view_register_impl.cpp b/frameworks/bridge/declarative_frontend/engine/jsi/jsi_view_register_impl.cpp index aea8d65bc77..60620a93f72 100644 --- a/frameworks/bridge/declarative_frontend/engine/jsi/jsi_view_register_impl.cpp +++ b/frameworks/bridge/declarative_frontend/engine/jsi/jsi_view_register_impl.cpp @@ -119,6 +119,7 @@ #include "bridge/declarative_frontend/jsview/js_relative_container.h" #include "bridge/declarative_frontend/jsview/js_repeat.h" #include "bridge/declarative_frontend/jsview/js_repeat_virtual_scroll.h" +#include "bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.h" #include "bridge/declarative_frontend/jsview/js_richeditor.h" #include "bridge/declarative_frontend/jsview/js_row.h" #include "bridge/declarative_frontend/jsview/js_row_split.h" @@ -566,6 +567,7 @@ static const std::unordered_map> { "Panel", JSSlidingPanel::JSBind }, { "RepeatNative", JSRepeat::JSBind }, { "RepeatVirtualScrollNative", JSRepeatVirtualScroll::JSBind }, + { "RepeatVirtualScroll2Native", JSRepeatVirtualScroll2::JSBind }, { "NavDestination", JSNavDestination::JSBind }, { "Navigation", JSNavigation::JSBind }, { "NativeNavPathStack", JSNavPathStack::JSBind }, diff --git a/frameworks/bridge/declarative_frontend/engine/jsi/jsi_view_register_impl_ng.cpp b/frameworks/bridge/declarative_frontend/engine/jsi/jsi_view_register_impl_ng.cpp index 9e0ca857b31..6da333f8119 100644 --- a/frameworks/bridge/declarative_frontend/engine/jsi/jsi_view_register_impl_ng.cpp +++ b/frameworks/bridge/declarative_frontend/engine/jsi/jsi_view_register_impl_ng.cpp @@ -102,6 +102,7 @@ #include "frameworks/bridge/declarative_frontend/jsview/js_refresh.h" #include "frameworks/bridge/declarative_frontend/jsview/js_repeat.h" #include "frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll.h" +#include "frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.h" #include "frameworks/bridge/declarative_frontend/jsview/js_row.h" #include "frameworks/bridge/declarative_frontend/jsview/js_row_split.h" #include "frameworks/bridge/declarative_frontend/jsview/js_scope_util.h" diff --git a/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll.cpp b/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll.cpp index ed7492a7841..8a7ddf48d4f 100644 --- a/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll.cpp +++ b/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll.cpp @@ -48,7 +48,7 @@ enum { MIN_PARAM_SIZE = 4, }; -bool ParseAndVerifyParams(const JSCallbackInfo& info) +static bool ParseAndVerifyParams(const JSCallbackInfo& info) { if (info.Length() < MIN_PARAM_SIZE) { return false; diff --git a/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp b/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp new file mode 100644 index 00000000000..93cc83a2638 --- /dev/null +++ b/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.h" + +#include + +#include "base/log/ace_trace.h" +#include "base/log/log_wrapper.h" +#include "bridge/declarative_frontend/jsview/js_view_common_def.h" +#include "core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h" + +namespace OHOS::Ace { + +std::unique_ptr RepeatVirtualScroll2Model::instance_ = nullptr; +#define UNUSED_CACHED_SIZE_PARAM 2 + +RepeatVirtualScroll2Model* RepeatVirtualScroll2Model::GetInstance() +{ + if (!instance_) { + instance_.reset(new NG::RepeatVirtualScroll2ModelNG()); + } + return instance_.get(); +} +} // namespace OHOS::Ace + +namespace OHOS::Ace::Framework { + +enum { + PARAM_TOTAL_COUNT = 0, + PARAM_HANDLERS = 1, + PARAM_SIZE = 2, +}; + +static JSRef GetJSFunc(JsiRef options, const char* propertyName) +{ + return JSRef::Cast(options->GetProperty(propertyName)); +} + +static bool ParseAndVerifyParams(const JSCallbackInfo& info) +{ + if (info.Length() != PARAM_SIZE || !info[PARAM_TOTAL_COUNT]->IsNumber() || !info[PARAM_HANDLERS]->IsObject()) { + return false; + } + + auto handlers = JSRef::Cast(info[PARAM_HANDLERS]); + return (handlers->GetProperty("onGetRid4Index")->IsFunction() && + handlers->GetProperty("onRecycleItems")->IsFunction() && + handlers->GetProperty("onActiveRange")->IsFunction() && handlers->GetProperty("onPurge")->IsFunction()); +} + +void JSRepeatVirtualScroll2::Create(const JSCallbackInfo& info) +{ + if (!ParseAndVerifyParams(info)) { + TAG_LOGW(AceLogTag::ACE_REPEAT, "Invalid arguments for RepeatVirtualScroll"); + return; + } + + // arg 0 totalCount : number + auto totalCount = info[PARAM_TOTAL_COUNT]->ToNumber(); + + // arg 2 onGetRid4Index(number int32_t) : number(uint32_t) + auto handlers = JSRef::Cast(info[PARAM_HANDLERS]); + auto onGetRid4Index = [execCtx = info.GetExecutionContext(), func = GetJSFunc(handlers, "onGetRid4Index")]( + int32_t forIndex) -> std::pair { + JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx, std::pair(0, 0)); + auto params = ConvertToJSValues(forIndex); + JSRef jsVal = func->Call(JSRef(), params.size(), params.data()); + // convert js-array to std::pair + if (!jsVal->IsArray() || JSRef::Cast(jsVal)->Length() != 2) { + TAG_LOGW(AceLogTag::ACE_REPEAT, "jsVal should be array."); + return std::pair(0, 0); + } + JSRef jsArr = JSRef::Cast(jsVal); + return std::pair( + jsArr->GetValueAt(0)->ToNumber(), jsArr->GetValueAt(1)->ToNumber()); + }; + + auto onRecycleItems = [execCtx = info.GetExecutionContext(), func = GetJSFunc(handlers, "onRecycleItems")]( + int32_t fromIndex, int32_t toIndex) -> void { + JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx); + auto params = ConvertToJSValues(fromIndex, toIndex); + func->Call(JSRef(), params.size(), params.data()); + }; + + auto onActiveRange = [execCtx = info.GetExecutionContext(), func = GetJSFunc(handlers, "onActiveRange")]( + int32_t fromIndex, int32_t toIndex, int32_t fromCache, int32_t toCache, bool isLoop) -> void { + JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx); + auto params = ConvertToJSValues(fromIndex, toIndex, fromCache, toCache, isLoop); + func->Call(JSRef(), params.size(), params.data()); + }; + + auto onPurge = [execCtx = info.GetExecutionContext(), func = GetJSFunc(handlers, "onPurge")]() { + JSRef jsVal = func->Call(JSRef(), 0, nullptr); + }; + + RepeatVirtualScroll2Model::GetInstance()->Create(totalCount, onGetRid4Index, onRecycleItems, onActiveRange, + onPurge); +} + +void JSRepeatVirtualScroll2::RemoveNode(const JSCallbackInfo& info) +{ + ACE_SCOPED_TRACE("RepeatVirtualScroll:RemoveNode"); + if (!info[0]->IsNumber()) { + TAG_LOGE(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::RemoveNode - invalid parameter ERROR."); + return; + } + TAG_LOGD(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::RemoveNode"); + auto rid = info[0]->ToNumber(); + RepeatVirtualScroll2Model::GetInstance()->RemoveNode(rid); +} + +// setInvalid(repeatElmtId : number, fromIndex : number) +void JSRepeatVirtualScroll2::SetInvalid(const JSCallbackInfo& info) +{ + ACE_SCOPED_TRACE("RepeatVirtualScroll:SetInvalid"); + if (!info[0]->IsNumber() || !info[1]->IsNumber()) { + TAG_LOGE(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::SetInvalid - invalid parameter ERROR"); + return; + } + TAG_LOGD(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::SetInvalid"); + auto repeatElmtId = info[0]->ToNumber(); + auto rid = info[1]->ToNumber(); + RepeatVirtualScroll2Model::GetInstance()->SetInvalid(repeatElmtId, rid); +} + +// requestContainerReLayout(repeatElmtId: number, totalCount: number, index: number): void; +void JSRepeatVirtualScroll2::RequestContainerReLayout(const JSCallbackInfo& info) +{ + ACE_SCOPED_TRACE("RepeatVirtualScroll:RequestContainerReLayout"); + enum { + PARAM_ELMTID = 0, + PARAM_TOTAL_COUNT = 1, + PARAM_CHILD_INDEX = 2, + }; + + if (!info[PARAM_ELMTID]->IsNumber() || !info[PARAM_TOTAL_COUNT]->IsNumber() || + !info[PARAM_CHILD_INDEX]->IsNumber()) { + TAG_LOGE(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::RequestContainerReLayout - invalid parameters ERROR"); + return; + } + + TAG_LOGD(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::RequestContainerReLayout"); + auto repeatElmtId = info[PARAM_ELMTID]->ToNumber(); + auto totalCount = info[PARAM_TOTAL_COUNT]->ToNumber(); + auto invalidateContainerLayoutFromChildIndex = info[PARAM_CHILD_INDEX]->ToNumber(); + RepeatVirtualScroll2Model::GetInstance()->RequestContainerReLayout( + repeatElmtId, totalCount, invalidateContainerLayoutFromChildIndex); +} + +// updateL1Rid4Index(repeatElmtId: number, +// totalCount: number, +// invalidateContainerLayoutFromChildIndex: number, +// l1rid4index: Array>): void; +void JSRepeatVirtualScroll2::UpdateL1Rid4Index(const JSCallbackInfo& info) +{ + ACE_SCOPED_TRACE("RepeatVirtualScroll:UpdateL1Rid4Index"); + enum { + PARAM_ELMTID = 0, + PARAM_TOTAL_COUNT = 1, + PARAM_CHILD_INDEX = 2, + PARAM_ARRAY_PAIRS = 3, + }; + + if (!info[PARAM_ELMTID]->IsNumber() || !info[PARAM_TOTAL_COUNT]->IsNumber() || + !info[PARAM_CHILD_INDEX]->IsNumber() || !info[PARAM_ARRAY_PAIRS]->IsArray()) { + TAG_LOGE(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::UpdateL1Rid4Index - invalid parameters ERROR"); + return; + } + + TAG_LOGD(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::UpdateL1Rid4Index"); + auto repeatElmtId = info[PARAM_ELMTID]->ToNumber(); + auto totalCount = info[PARAM_TOTAL_COUNT]->ToNumber(); + auto invalidateContainerLayoutFromChildIndex = info[PARAM_CHILD_INDEX]->ToNumber(); + + auto arrayOfPairs = JSRef::Cast(info[PARAM_ARRAY_PAIRS]); + std::map l1Rid4Index; + for (size_t i = 0; i < arrayOfPairs->Length(); i++) { + JSRef pair = arrayOfPairs->GetValueAt(i); + auto index = pair->GetValueAt(0)->ToNumber(); + auto rid = pair->GetValueAt(1)->ToNumber(); + TAG_LOGD(AceLogTag::ACE_REPEAT, " ... index: %{public}d rid: %{public}d", index, static_cast(rid)); + l1Rid4Index[index] = rid; + } + RepeatVirtualScroll2Model::GetInstance()->UpdateL1Rid4Index( + repeatElmtId, totalCount, invalidateContainerLayoutFromChildIndex, l1Rid4Index); +} + +void JSRepeatVirtualScroll2::OnMove(const JSCallbackInfo& info) +{ + if (!info[0]->IsFunction()) { + RepeatVirtualScroll2Model::GetInstance()->OnMove(nullptr); + return; + } + auto onMove = [execCtx = info.GetExecutionContext(), func = JSRef::Cast(info[0])]( + int32_t from, int32_t to) { + auto params = ConvertToJSValues(from, to); + func->Call(JSRef(), params.size(), params.data()); + }; + RepeatVirtualScroll2Model::GetInstance()->OnMove(std::move(onMove)); +} + +void JSRepeatVirtualScroll2::JSBind(BindingTarget globalObj) +{ + JSClass::Declare("RepeatVirtualScroll2Native"); + JSClass::StaticMethod("create", &JSRepeatVirtualScroll2::Create); + + JSClass::StaticMethod("removeNode", &JSRepeatVirtualScroll2::RemoveNode); + JSClass::StaticMethod("setInvalid", &JSRepeatVirtualScroll2::SetInvalid); + JSClass::StaticMethod( + "requestContainerReLayout", &JSRepeatVirtualScroll2::RequestContainerReLayout); + + JSClass::StaticMethod("updateL1Rid4Index", &JSRepeatVirtualScroll2::UpdateL1Rid4Index); + + JSClass::StaticMethod("onMove", &JSRepeatVirtualScroll2::OnMove); + JSClass::Bind<>(globalObj); +} + +} // namespace OHOS::Ace::Framework diff --git a/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.h b/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.h new file mode 100644 index 00000000000..7baa355b08d --- /dev/null +++ b/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FRAMEWORKS_BRIDGE_DECLARATIVE_FRONTEND_JS_VIEW_JS_REPEAT_VIRTUAL_SCROLL_2_H +#define FRAMEWORKS_BRIDGE_DECLARATIVE_FRONTEND_JS_VIEW_JS_REPEAT_VIRTUAL_SCROLL_2_H + +#include "bridge/declarative_frontend/engine/bindings.h" +#include "bridge/declarative_frontend/engine/js_ref_ptr.h" + +namespace OHOS::Ace::Framework { + +class JSRepeatVirtualScroll2 { +public: + static void JSBind(BindingTarget globalObj); + + static void Create(const JSCallbackInfo& info); + + static void RemoveNode(const JSCallbackInfo& info); + static void SetInvalid(const JSCallbackInfo& info); + static void RequestContainerReLayout(const JSCallbackInfo& info); + static void UpdateL1Rid4Index(const JSCallbackInfo& info); + + static void OnMove(const JSCallbackInfo& info); +}; + +} // namespace OHOS::Ace::Framework +#endif // FRAMEWORKS_BRIDGE_DECLARATIVE_FRONTEND_JS_VIEW_JS_REPEAT_VIRTUAL_SCROLL_2_H diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/files_to_watch.gni b/frameworks/bridge/declarative_frontend/state_mgmt/files_to_watch.gni index 194879f06a1..64fbe52d823 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/files_to_watch.gni +++ b/frameworks/bridge/declarative_frontend/state_mgmt/files_to_watch.gni @@ -89,6 +89,7 @@ state_mgmt_release_files_to_watch = [ "//foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat.ts", "//foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_impl.ts", "//foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts", + "//foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts", "//foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/sdk/v2_persistence.ts", "//foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/sdk/ui_utils.ts", "//foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/common/gesture_span.ts", diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_foreach.d.ts b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_foreach.d.ts index b8a463c5c19..ddb3d5c7506 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_foreach.d.ts +++ b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_foreach.d.ts @@ -64,3 +64,33 @@ declare class RepeatVirtualScrollNative { static onMove(handler: (from: number, to: number) => void); static setCreateByTemplate(isCreatedByTemplate: boolean): void; } + +// Repeat.virtualScroll (v2) maps to C++ RepeatVirtualScroll2Node +declare class RepeatVirtualScroll2Native { + static create( + totalCount: number, + handlers: { + onGetRid4Index: (forIndex: number) => [number, number], + onRecycleItems: (fromIndex: number, toIndex: number) => void, + onActiveRange: (fromIndex: number, toIndex: number, fromCache: number, toCache: number, isLoop: boolean) => void, + onPurge: () => void; + } + ): void; + + // purge node with given Repeat Item Id (Rid) + static removeNode(rid: number): void; + + // Drop node with given Repeat Item Id (Rid) from L1 cache + static setInvalid(repeatelmtId: number, rid: number): void; + + // invalidate owning Container layout starting from Repeat child index + static requestContainerReLayout(repeatElmtId: number, totalCount: number, index: number): void; + + static updateL1Rid4Index(repeatElmtId: number, + totalCount: number, + invalidateContainerLayoutFromChildIndex: number, + l1rid4index: Array>): void; + + // drag and drop + static onMove(handler: (from: number, to: number) => void); +} \ No newline at end of file diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat.ts b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat.ts index 0bc3a5275cf..7b3a37290ba 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat.ts +++ b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat.ts @@ -86,9 +86,7 @@ class __RepeatItemV2 implements RepeatItem, __IRepeatItemInternal { } public updateIndex(newIndex: number): void { - if (this.index !== undefined) { - this.index = newIndex; - } + this.index = newIndex; } } @@ -140,7 +138,8 @@ interface __RepeatConfig { arr?: Array; itemGenFuncs?: { [type: string]: RepeatItemGenFunc }; keyGenFunc?: RepeatKeyGenFunc; - typeGenFunc?: RepeatTypeGenFunc; + keyGenFuncSpecified?: boolean; + ttypeGenFunc?: RepeatTTypeGenFunc; totalCountSpecified?: boolean; totalCount?: number; templateOptions?: { [type: string]: RepeatTemplateImplOptions }; @@ -149,11 +148,14 @@ interface __RepeatConfig { reusable?: boolean; }; +// should be empty string, don't change it +const RepeatEachFuncTtype : string = ''; + // __Repeat implements ForEach with child re-use for both existing state observation // and deep observation , for non-virtual and virtual code paths (TODO) class __Repeat implements RepeatAPI { private config: __RepeatConfig = {}; - private impl: __RepeatImpl | __RepeatVirtualScrollImpl; + private impl: __RepeatImpl | __RepeatVirtualScrollImpl | __RepeatVirtualScroll2Impl; private isVirtualScroll = false; constructor(owningView: ViewV2 | ViewPU, arr: Array) { @@ -161,7 +163,9 @@ class __Repeat implements RepeatAPI { this.config.arr = arr ?? []; this.config.itemGenFuncs = {}; this.config.keyGenFunc = __RepeatDefaultKeyGen.funcWithIndex; - this.config.typeGenFunc = ((): string => ''); + this.config.keyGenFuncSpecified = false; + this.config.ttypeGenFunc = undefined; + this.config.totalCountSpecified = false; this.config.totalCount = this.config.arr.length; this.config.templateOptions = {}; @@ -180,21 +184,25 @@ class __Repeat implements RepeatAPI { } public each(itemGenFunc: RepeatItemGenFunc): RepeatAPI { - this.config.itemGenFuncs[''] = itemGenFunc; - this.config.templateOptions[''] = this.normTemplateOptions({}); + this.config.itemGenFuncs[RepeatEachFuncTtype] = itemGenFunc; + this.config.templateOptions[RepeatEachFuncTtype] = this.normTemplateOptions({}); return this; } public key(keyGenFunc: RepeatKeyGenFunc): RepeatAPI { this.config.keyGenFunc = keyGenFunc; + this.config.keyGenFuncSpecified = true; return this; } public virtualScroll(options? : { totalCount?: number, reusable?: boolean }): RepeatAPI { - if (Number.isInteger(options?.totalCount)) { + // totalCount must be integer and must be 0 or larger + if (Number.isInteger(options?.totalCount) && options.totalCount >=0) { this.config.totalCount = options.totalCount; this.config.totalCountSpecified = true; } else { + // use default totalCount value if given a non-integer or negative value + this.config.totalCount = this.config.arr.length; this.config.totalCountSpecified = false; } this.isVirtualScroll = true; @@ -206,36 +214,17 @@ class __Repeat implements RepeatAPI { } // function to decide which template to use, each template has an ttype - public templateId(typeGenFunc: RepeatTypeGenFunc): RepeatAPI { - const typeGenFuncImpl = (item: T, index: number): string => { - try { - return typeGenFunc(item, index); - } catch (e) { - stateMgmtConsole.applicationError(`Repeat with virtual scroll. Exception in templateId():`, e?.message); - return ''; - } - }; - // typeGenFunc wrapper with ttype validation - const typeGenFuncSafe = (item: T, index: number): string => { - const itemType = typeGenFuncImpl(item, index); - const itemFunc = this.config.itemGenFuncs[itemType]; - if (typeof itemFunc !== 'function') { - stateMgmtConsole.applicationError(`Repeat with virtual scroll. Missing Repeat.template for ttype '${itemType}'`); - return ''; - } - return itemType; - }; - - this.config.typeGenFunc = typeGenFuncSafe; + public templateId(ttypeGenFunc: RepeatTTypeGenFunc): RepeatAPI { + this.config.ttypeGenFunc = ttypeGenFunc; return this; } // template: ttype + builder function to render specific type of data item - public template(type: string, itemGenFunc: RepeatItemGenFunc, + public template(ttype: string, itemGenFunc: RepeatItemGenFunc, options?: RepeatTemplateOptions): RepeatAPI { - this.config.itemGenFuncs[type] = itemGenFunc; - this.config.templateOptions[type] = this.normTemplateOptions(options); + this.config.itemGenFuncs[ttype] = itemGenFunc; + this.config.templateOptions[ttype] = this.normTemplateOptions(options); return this; } @@ -245,17 +234,23 @@ class __Repeat implements RepeatAPI { } public render(isInitialRender: boolean): void { - if (!this.config.itemGenFuncs?.['']) { + if (!this.config.itemGenFuncs?.[RepeatEachFuncTtype]) { throw new Error(`__Repeat item builder function unspecified. Usage error`); } if (!this.isVirtualScroll) { - // Repeat - this.impl ??= new __RepeatImpl(); - this.impl.render(this.config, isInitialRender); + // Repeat + this.impl ??= new __RepeatImpl(); + this.impl.render(this.config, isInitialRender); + return; + } + if (Utils.getApiVersion() < 16) { + // RepeatVirtualScroll + this.impl ??= new __RepeatVirtualScrollImpl(); + this.impl.render(this.config, isInitialRender); } else { - // RepeatVirtualScroll - this.impl ??= new __RepeatVirtualScrollImpl(); - this.impl.render(this.config, isInitialRender); + // RepeatVirtualScroll v2 + this.impl ??= new __RepeatVirtualScroll2Impl(); + this.impl.render(this.config, isInitialRender); } } diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_impl.ts b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_impl.ts index c59b5027ff2..08bfce218bb 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_impl.ts +++ b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_impl.ts @@ -19,7 +19,7 @@ class __RepeatImpl { private arr_: Array; private itemGenFuncs_: { [type: string]: RepeatItemGenFunc }; private keyGenFunction_?: RepeatKeyGenFunc; - private typeGenFunc_: RepeatTypeGenFunc; + private ttypeGenFunc_: RepeatTTypeGenFunc; // private mkRepeatItem_: (item: T, index?: number) =>__RepeatItemFactoryReturn; private onMoveHandler_?: OnMoveHandler; @@ -34,7 +34,7 @@ class __RepeatImpl { public render(config: __RepeatConfig, isInitialRender: boolean): void { this.arr_ = config.arr; this.itemGenFuncs_ = config.itemGenFuncs; - this.typeGenFunc_ = config.typeGenFunc; + this.ttypeGenFunc_ = config.ttypeGenFunc; this.keyGenFunction_ = config.keyGenFunc; this.mkRepeatItem_ = config.mkRepeatItem; this.onMoveHandler_ = config.onMoveHandler; @@ -165,8 +165,8 @@ class __RepeatImpl { RepeatNative.createNewChildStart(key); // execute the itemGen function - const itemType = this.typeGenFunc_(repeatItem.item, repeatItem.index) ?? ''; - const itemFunc = this.itemGenFuncs_[itemType] ?? this.itemGenFuncs_['']; + const itemType = this.ttypeGenFunc_?.(repeatItem.item, repeatItem.index) ?? RepeatEachFuncTtype; + const itemFunc = this.itemGenFuncs_[itemType] ?? this.itemGenFuncs_[RepeatEachFuncTtype]; itemFunc(repeatItem); RepeatNative.createNewChildFinish(key); diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts new file mode 100644 index 00000000000..c2c4cc49290 --- /dev/null +++ b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts @@ -0,0 +1,1032 @@ +/* + * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * all definitions in this file are framework internal +*/ + +/** Repeat with .virtualScroll TS side implementation + * for C++ side part of the implementation read the comments at top of file + * core/components_ng/syntax/repeat_virtual_scroll_node.h + * + * See the factory solutions in pu_repeat.ts + * __RepeatVirtualScroll2Impl gets created before first render + * + * onGetRid4Index(index) called upon layout calling C++ GetFrameChildByIndex + * (whenever C++ does not find UINode sub-tree for index in L1) + * 1- gets the data item + * 2- calculates the templateId (called ttype internally) for this index, then + * 3- canUpdateTryMatch(index, ttype) tries to find spare UINode tree (identified by its RID) + * 4- if found calls updateChild(rid, item, index, ....) + * updateChild simply updates repeatItem.item, .index, and requests UI updates synchronously (ypdateDirty2(true) + * TS informs to C++ the RID , and that existing UINode sub-tree has been updated + * 5- if not fond calls createNewChild(index, ttype, ...) + * creates a new RepeatItem instance with a new RID + * calls the .each or .template item builder function to perform initial render + * TS informs C++ about the RID and that new UINode sub-tree root node should be taken from ViewStackProcessor + * added to C++ side RID -> UINode map / general cache + * + * + * onRecycleItems(fromIndex, upToBeforeIndex) - called from C++ RecycleItems + * - move L1 items with fromIndex <= index < upToBeforeIndex to L2. + * calls C++ RepeatVirtualScroll2Native.setInvalid so also C++ side moves the RID from L1 to L2 +* C++ does NOT remove the item from active tree, or change its Active status + * if so needed at the end of one processing cycle onActiveRange will do + * + * onActiveRange(....) - called from C++ + * - TS iterates over L1 items to check which which one is still in informed range, move to L2 + * does NOT call RepeatVirtualScrollCaches::SetInvalid + * - C++ does the same + * a) iterate over L1 to remove items no longer in active range, move to L2 + * b) iterate over L2, remove from render tree and set Active status + * c) order purge idle task + * d) calculate dynamicCachedCount for .each and for templateIds that do not have specified cachedCount option + * + * - Note: the rather convolute algorithm that uses parameters to decide if item is in active range or not + * needs to be exactly same in this function on TS side and on C++ side to ensure L1 info in TS and in C++ side + * reman in sync. + * + * onPurge called from C++ RepeatVirtualScrollNode::Purge in idle task + * ensure L1 side is within limits (cachedCount), deletes UINode subtrees that do not fit the permissible side + * cachedCount is defined for each templateId / ttype for for .each separately/ + * + * + * rerender called from ViewV2 UpdateElement(repeatElmtId) + * + * what triggers Repeat rerender? + * - new source array assignment; array add, delete, move item + * - input to templateId function changed + * - input to key function changed (if key used) + * - totalCount changed + * + * cached array item at index and new array item at index2 are considered the same if + * - the keys computed from them are the same (if key used) + * - if items compare equal with === operator + * the algorithm that compares items can handle duplicate array items. + * the algorithm will fail unnoticed if cached array item and new array item are different but their key is the same + * (this violates the requirement of stable keys) + * + * read the source code of rerender, each step has documentation + * + * the outcome of rerender is + * 1- array items in cached and new array (active range), aka retained array items, repeatItem.index updated if changed + * 2- delete array items -> RID / UINode sub-tree moved to L2 + * 3- added array items - try to find fitting RID / UINode subtree (same templateId / ttype) and update by updating repeatItem.item and .index + * newly added array items that can not be rendered by update are NOT rendered, will deal with new renders when GetFrameChildByIndex requests + * 4- synchronous partial update to update all bindings that need update from step 1 and 3. + * 5- inform new L1 (and thereby also L2) to C++ by calling updateL1Rid4Index(list of rid), same call also invalidates container layout starting from + * first changes item index, updates also totalCount on C++ side + * + * + * The most important data structures + * + * RID - Repeat Item ID - a unique number, uniquely identifies a RepeatItem - templateId - UINode triple + * these found data items remain together until deleted from the system (ie.. until purge or unload deletes the UINode subtree) + * + * meta4Rid_: Map> + * - for each RID: + * - constant: RepeatItem + * - constant: ttype + * - mutableL key + * - counterpart on C++ side RepeatVirtualScrollCaches.cacheItem4Rid_ maps RID -> UINode + * + * activeDataItems - Array> + * This is the central data structure for rerender, as allows to compare previous item value / keys + * Sparse array, only includes items for active range + * - array item value at last render/update, rid, ttype, key (if used), some state info + * + * spareRid_ : set RID currently not in active range, "L2" + * + * ttypeGenFunc_ templateId function + * itemGenFuncs_: map of item builder functions per templateId / ttype and .each + * + + */ + +class ActiveDataItem { + public item: T; + public rid?: number; + public ttype?: string; + public key?: string; + public state : number; + + public static readonly UINodeExists = 1; + public static readonly UINodeRenderFailed = 2; + public static readonly UINodeToBeRendered = 3; + public static readonly NoValue = 4; + + protected constructor(state: number, itemValue?: T, rid?: number, ttype?: string, key?: string) { + this.state = state; + this.item = itemValue; + this.rid = rid; + this.ttype = ttype; + this.key = key; + } + + // factory functions for permissible ActiveDataItems + public static createWithUINode(itemValue: T, rid: number, ttype: string, key?: string): ActiveDataItem { + return new ActiveDataItem(ActiveDataItem.UINodeExists, itemValue, rid, ttype, key); + } + + public static createFailedToCreateUINodeDataItem(itemValue: T): ActiveDataItem { + return new ActiveDataItem(ActiveDataItem.UINodeRenderFailed, itemValue); + } + + public static createMissingDataItem(): ActiveDataItem { + return new ActiveDataItem(ActiveDataItem.NoValue); + } + + public static createToBeRenderedDataItem(itemValue: T, ttype: string, key?: string): ActiveDataItem { + return new ActiveDataItem(ActiveDataItem.UINodeToBeRendered, itemValue, undefined, ttype, key); + } + + public toString(): string { + return this.state === ActiveDataItem.UINodeExists ? + `[rid: ${this.rid}, ttype: ${this.ttype}${this.key ? ", key: "+this.key : ""}]` : `[no item]`; + } + + public dump(): string { + const state = this.state === ActiveDataItem.UINodeExists + ? "UINode exists" + : this.state === ActiveDataItem.UINodeRenderFailed + ? "UINode failed to render" + : this.state === ActiveDataItem.UINodeToBeRendered + ? "UINode to be rendered" + : this.state === ActiveDataItem.NoValue + ? "No data value" + : "unknown state (error)"; + const rid = this.rid ?? "no RID/not rendered"; + const ttype = this.ttype ?? "ttype N/A"; + return (this.state === ActiveDataItem.UINodeExists) + ? `state: '${state}', RID: ${rid}, ttype: ${ttype}, key: ${this.key}` + : `state: '${state}'`; + } + + public shortDump() : string { + const state = this.state === ActiveDataItem.UINodeExists + ? "UINode exists" + : this.state === ActiveDataItem.UINodeRenderFailed + ? "UINode failed to render" + : this.state === ActiveDataItem.NoValue + ? "No data value" + : "unknown state (error)"; + const rid = this.rid ?? "no RID/not rendered"; + const ttype = this.ttype ?? "ttype N/A"; + return (this.state === ActiveDataItem.UINodeExists) + ? `state: '${state}', RID: ${rid}, ttype: ${ttype}` + : `state: '${state}'`; + } + +} + +// info about each created UINode / each RepeatItem / each RID +// only optional key is allowed to change +class RIDMeta { + public readonly repeatItem_ : __RepeatItemV2; + public readonly ttype_ : string; + public key_? : string; + + constructor(repeatItem : __RepeatItemV2, ttype : string, key? : string) { + this.repeatItem_ = repeatItem; + this.ttype_ = ttype; + this.key_ = key; + } +} + +class __RepeatVirtualScroll2Impl { + private arr_: Array; + + // key function + private keyGenFunc_? : RepeatKeyGenFunc; + + // is key function specified ? + private useKeys_: boolean = false; + + // index <-> key bidirectional mapping + private key4Index_: Map = new Map(); + private index4Key_: Map = new Map(); + + // map if .each and .template functions + private itemGenFuncs_: { [type: string]: RepeatItemGenFunc }; + + // templateId function + private ttypeGenFunc_? : RepeatTTypeGenFunc; + + // virtualScroll({ totalCount: non-function expression }), optional to set + private totalCount_: number = 0; + + // virtualScroll({ totalCount: () => number) }), optional to set + private totalCountFunc_?: () => number; + + // .template 3rd parameter, cachedCount + private templateOptions_: { [type: string]: RepeatTemplateImplOptions }; + + // factory for interface RepeatItem objects + private mkRepeatItem_: (item: T, index?: number) => __RepeatItemFactoryReturn; + private onMoveHandler_?: OnMoveHandler; + + // RepeatVirtualScrollNode elmtId + private repeatElmtId_: number = -1; + + private owningViewV2_: ViewV2; + + // used to generate unique RID + private nextRid: number = 1; + + // previously informed active range from - to + private activeRange_: [number, number] = [Number.NaN, Number.NaN]; + + // Map containing all rid: rid -> RepeatItem, ttype, key? + // entires never change + private meta4Rid_: Map> = new Map>(); + + // Map containing all rid: rid -> ttype, + // entires never change + // private ttype4Rid_: Map = new Map(); + + // sparse Array containing copy of data items and optionally keys in active range + private activeDataItems_: Array> = new Array>(); + + // rid not in L1 / not in active range belong to this set + // they are no longer associated with a data item + private spareRid_: Set = new Set(); + + // when access view model record dependency on 'this'. + private startRecordDependencies(clearBindings : boolean = false) : void { + // FIXME needed ? ViewStackProcessor.StartGetAccessRecordingFor(this.repeatElmtId_); + ObserveV2.getObserve().startRecordDependencies(this.owningViewV2_, this.repeatElmtId_, clearBindings); + } + + private stopRecordDependencies() : void { + // FIXME needed? ViewStackProcessor.StopGetAccessRecording(); + ObserveV2.getObserve().stopRecordDependencies(); + } + + /** + * return array item if it exists + * todo try to lazy load if it does nit exist + * @param index + * @returns tuple data item exists , data item + * (need to do like this to differentiate missing data item and undefined item value + * same as std::optional in C++) + */ + private getItemUnmonitored(index: number | string): [boolean, T] { + stateMgmtConsole.debug(`getItemUnmonitored ${index} data item exists: ${index in this.arr_}`) + return [(index in this.arr_), this.arr_[index]]; + } + + private getItemMonitored(index: number | string): [boolean, T] { + stateMgmtConsole.debug(`getItemMonitored ${index} data item exists: ${index in this.arr_}`) + + this.startRecordDependencies(/* do not clear bindings */ false); + + const result = this.arr_[index]; + + this.stopRecordDependencies(); + + return [(index in this.arr_), result]; + } + + // initial render + // called from __Repeat.render + public render(config: __RepeatConfig, isInitialRender: boolean): void { + + // params that can change + this.arr_ = config.arr; + + // if totalCountSpecified==false, then need to create dependency on array length + // so when array length changes, will update totalCount. use totalCountFunc_ for this + this.totalCountFunc_ = config.totalCountSpecified ? (typeof config.totalCount === 'function' ? config.totalCount : undefined) : () => this.arr_.length; + if (this.totalCountFunc_) { + this.totalCount_ = this.totalCountFunc_(); + // Check legal totalCount value + if (!Number.isInteger(this.totalCount_) || this.totalCount_ < 0) { + this.totalCount_ = this.arr_.length; + } + } else { + this.totalCount_ = config.totalCount as number; + } + + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) render: totalCountFunc_ ${this.totalCountFunc_ ? this.totalCountFunc_() : 'N/A'}, totalCount ${this.totalCount_} arr length ${this.arr_.length} .`); + + if (isInitialRender) { + this.owningViewV2_ = config.owningView_; + this.itemGenFuncs_ = config.itemGenFuncs; + this.ttypeGenFunc_ = config.ttypeGenFunc; + this.templateOptions_ = config.templateOptions; + + this.keyGenFunc_ = config.keyGenFunc; + this.useKeys_ = config.keyGenFuncSpecified; + + this.mkRepeatItem_ = config.mkRepeatItem; + this.onMoveHandler_ = config.onMoveHandler; + + if (!this.itemGenFuncs_[RepeatEachFuncTtype]) { + throw new Error(`${this.constructor.name}(${this.repeatElmtId_})) lacks mandatory '.each' attribute function, i.e. has no default item builder. Application error!`); + } + + this.initialRender(); + } else { + this.reRender(); + } + } + + /**/ + private initialRender(): void { + this.repeatElmtId_ = ObserveV2.getCurrentRecordedId(); + + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) initialRender() data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start`); + + // Create the RepeatVirtualScrollNode object + // pass the C++ to TS callback functions. + RepeatVirtualScroll2Native.create(this.totalCount_, { + onGetRid4Index: this.onGetRid4Index.bind(this), + onRecycleItems: this.onRecycleItems.bind(this), + onActiveRange: this.onActiveRange.bind(this), + onPurge: this.onPurge.bind(this) + }); + + // Why is this separate, can onMove be added to create? + RepeatVirtualScroll2Native.onMove(this.onMoveHandler_); + + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) initialRender() data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - done`); + } + + // given data item and the ttype it needs to be rendered with from updated array: + // find same data item in activeDataItems, it also still needs to use same ttype as given + // return its { index in activeDataItems, its rid } + private findDataItemInOldActivateDataItemsByValue(dataItem: T, ttype: string): { oldIndexStr: string, rid?: number } | undefined { + for (const oldIndex in this.activeDataItems_) { + const oldItem = this.activeDataItems_[oldIndex]; + if (oldItem.item === dataItem && oldItem.rid && oldItem.ttype === ttype) { + return { oldIndexStr: oldIndex, rid: oldItem.rid }; + } + } + return undefined; + } + + // find same data item in activeDataItems by key, it also still needs to use same ttype as given + private findDataItemInOldActivateDataItemsByKey(key: string, ttype: string): {oldIndexStr: string, rid?: number} | undefined { + for (const oldIndex in this.activeDataItems_) { + const oldItem = this.activeDataItems_[oldIndex]; + if (oldItem.key === key && oldItem.rid && oldItem.ttype === ttype) { + return { oldIndexStr: oldIndex, rid: oldItem.rid }; + } + } + return undefined; + } + + // update Repeat, see overview documentation at the top of this file. + private reRender(): void { + + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) reRender() data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start`); + + const activeRangeFrom = this.activeRange_[0]; + const activeRangeTo = this.activeRange_[1]; + + stateMgmtConsole.debug(` ... checking range ${activeRangeFrom} - ${activeRangeTo}`); + + + let firstIndexChanged = Math.min(activeRangeTo + 1, this.arr_.length); + + // replacement for this.activeDataItems_ + const newActiveDataItems: Array> = new Array>(); + + // replacement for l1Rid4Index_ index -> rid map on C++ side + // will send to C++ when done + const newL1Rid4Index = new Map(); + + // clear keys for new rerender + this.key4Index_.clear(); + this.index4Key_.clear(); + + // 1. move data items to newActiveDataItems that are unchanged (same item / same key, still at same index, same ttyp) + // create createMissingDataItem -type entries for all other new data items. + let hasChanges = false; + for (const indexS in this.activeDataItems_) { + const activeIndex = parseInt(indexS); + if (activeIndex < 0) { + // out of range to consider + continue; + } + if (activeIndex >= this.arr_.length || activeIndex >= this.totalCount_) { + // data item has been popped from arr_ array that are part of active range + hasChanges = true; + break; + } + + const [ dataItemExists, dataItemAtIndex ] = this.getItemUnmonitored(activeIndex); + if (!dataItemExists) { + stateMgmtConsole.debug(` ... index ${activeIndex} no data in new array, had data before ${this.activeDataItems_[indexS].state !== ActiveDataItem.NoValue}`); + hasChanges = hasChanges || (this.activeDataItems_[indexS].state !== ActiveDataItem.NoValue); + newActiveDataItems[activeIndex] = ActiveDataItem.createMissingDataItem(); + continue; + } + + const ttype = this.computeTtype(dataItemAtIndex, activeIndex, /* monitored access already enabled */ false); + const key = this.computeKey(dataItemAtIndex, activeIndex, /* monitor access already on-going */ false, newActiveDataItems); + + // compare with ttype and data item, or with ttype and key + if ((ttype === this.activeDataItems_[activeIndex].ttype) && + ((!this.useKeys_ && dataItemAtIndex === this.activeDataItems_[activeIndex].item) || + (this.useKeys_ && key === this.activeDataItems_[activeIndex].key))) { + stateMgmtConsole.debug(` ... index ${activeIndex} ttype ${ttype}${this.useKeys_ ? ", key " + key : ""} and dataItem unchanged.`); + newActiveDataItems[activeIndex] = this.activeDataItems_[activeIndex]; + + // add to index -> rid map to be sent to C++ + newL1Rid4Index.set(activeIndex, this.activeDataItems_[activeIndex].rid); + + // the data item is handled, remove it from old active data range + // so we do not use it again + delete this.activeDataItems_[activeIndex]; + } else { + stateMgmtConsole.debug(` ... index ${activeIndex} has changed ${dataItemAtIndex !== this.activeDataItems_[activeIndex].item}, ttype ${ttype} has changed ${ttype !== this.activeDataItems_[activeIndex].ttype}, key ${key} has changed ${key !== this.activeDataItems_[activeIndex].key}, using keys ${this.useKeys_}`); + newActiveDataItems[activeIndex] = ActiveDataItem.createToBeRenderedDataItem(dataItemAtIndex, ttype, key); + hasChanges = true; + } + } // for activeItems + + if (!hasChanges) { + // invalidate the layout only for items beyond active range + // this is for the case that there is space for more visible items in the container. + // triggers layout to request FrameCount() / totalCount and if increased newly added source array items + // FIXME TODO correct??? Math.min(this.totalCount_ - 1, activeRangeTo + 1) + this.activeDataItems_ = newActiveDataItems; + RepeatVirtualScroll2Native.requestContainerReLayout(this.repeatElmtId_, this.totalCount_, Math.min(this.totalCount_ - 1, activeRangeTo + 1)); + return; + } + + + // step 2. move retained data items + // these are items with same value / same key in new and old array: + // their index has changed, ttype is unchanged + for (const indexS in newActiveDataItems) { + const activeIndex = parseInt(indexS); + const newActiveDataItemAtActiveIndex = newActiveDataItems[activeIndex]; + + if (newActiveDataItemAtActiveIndex.state === ActiveDataItem.UINodeExists) { + // same index in new and old, processed in step 1 + continue; + } + + if (newActiveDataItemAtActiveIndex.state === ActiveDataItem.NoValue) { + stateMgmtConsole.debug(` ... new index ${activeIndex} missing in updated source array.`); + firstIndexChanged = Math.min(firstIndexChanged, activeIndex); + continue; + } + + // retainedItem must have same item value / same key, same ttype, but new index + let movedDataItem; + if (this.useKeys_) { + const key = this.computeKey(newActiveDataItemAtActiveIndex.item as T, activeIndex, /* monitor access already ongoing */ false, newActiveDataItems); + movedDataItem = this.findDataItemInOldActivateDataItemsByKey(key, newActiveDataItemAtActiveIndex.ttype); + } else { + movedDataItem = this.findDataItemInOldActivateDataItemsByValue(newActiveDataItemAtActiveIndex.item as T, newActiveDataItemAtActiveIndex.ttype); + } + + if (movedDataItem) { + // data item rendered before, and needed ttype to render has not changed + newActiveDataItemAtActiveIndex.rid = movedDataItem.rid; + newActiveDataItemAtActiveIndex.state = ActiveDataItem.UINodeExists; + + // add to index -> rid map to be sent to C++ + newL1Rid4Index.set(activeIndex, movedDataItem.rid); + + // index has changed, update it in RepeatItem + const ridMeta = this.meta4Rid_.get(movedDataItem.rid); + stateMgmtConsole.debug(` ... new index ${activeIndex} / old index ${ridMeta.repeatItem_.index}: keep in L1: rid ${movedDataItem.rid}, unchanged ttype '${newActiveDataItemAtActiveIndex.ttype}'`); + ridMeta.repeatItem_.updateIndex(activeIndex) + firstIndexChanged = Math.min(firstIndexChanged, activeIndex); + + // the data item is handled, remove it from old active data range + // so we do not use it again + delete this.activeDataItems_[movedDataItem.oldIndexStr]; + } else { + // update is needed for this data item + // either because dataItem is new, or new ttype needs to used + stateMgmtConsole.debug(` ... need update for index ${activeIndex}`); + firstIndexChanged = Math.min(firstIndexChanged, activeIndex); + } + } // for new data items in active range + + // step 3. remaining old data items, i.e. data item removed from source array + // add their rid to spare + for (let oldIndex in this.activeDataItems_) { + if (this.activeDataItems_[oldIndex].rid) { + this.spareRid_.add(this.activeDataItems_[oldIndex].rid); + const index = parseInt(oldIndex); + this.index4Key_.delete(this.key4Index_.get(index)); + this.key4Index_.delete(index); + } + } + + // Step 4: data items in new source array that are either new in the array + // or have been there before but need to be rendered with different ttype + // if canUpdate then do the update. + // if need new render, do not do the new render right away. Wait for layout to ask + // for the item to render. + for (const indexS in newActiveDataItems) { + const activeIndex = parseInt(indexS); + const newActiveDataItemAtActiveIndex = newActiveDataItems[activeIndex]; + + if (newActiveDataItemAtActiveIndex.state !== ActiveDataItem.UINodeToBeRendered) { + continue; + } + + const optRid = this.canUpdate(newActiveDataItemAtActiveIndex.ttype); + if (optRid > 0) { + const ridMeta = this.meta4Rid_.get(optRid); + if (ridMeta) { + // found rid / repeatItem to update + stateMgmtConsole.debug(` ... index ${activeIndex}: update rid ${optRid} / ttype '${newActiveDataItemAtActiveIndex.ttype}'`); + + newActiveDataItemAtActiveIndex.rid = optRid; + newActiveDataItemAtActiveIndex.state = ActiveDataItem.UINodeExists; + + if (this.useKeys_) { + // FIXME rerender is monitored, switch to unmonitored + const key = this.computeKey(newActiveDataItemAtActiveIndex.item as T, activeIndex, /* monitor access already ongoing */ false, newActiveDataItems); + newActiveDataItemAtActiveIndex.key = key; + ridMeta.key_ = key; + } + + // spare rid is used + this.spareRid_.delete(optRid); + + // add to index -> rid map to be sent to C++ + newL1Rid4Index.set(activeIndex, optRid); + + // don't need to call getItem here, the data item has been lazy loaded before + ridMeta.repeatItem_.updateItem(newActiveDataItemAtActiveIndex.item as T); + ridMeta.repeatItem_.updateIndex(activeIndex); + + firstIndexChanged = Math.min(firstIndexChanged, activeIndex); + } + } else { + stateMgmtConsole.debug(` ... active range index ${activeIndex}: no rid found to update`); + } + }; + + // render all data changes in one go + // FIXME we are in the middle of rerender, should this be deleted + ObserveV2.getObserve().updateDirty2(true); + + this.activeDataItems_ = newActiveDataItems; + + stateMgmtConsole.debug(`rerender result: `); + stateMgmtConsole.debug(`spareRid : ${this.dumpSpareRid()}`); + stateMgmtConsole.debug(`this.dumpDataItems: ${this.activeDataItems_}`); + stateMgmtConsole.debug(`newL1Rid4Index: ${JSON.stringify(Array.from(newL1Rid4Index))}`) + stateMgmtConsole.debug(` ... first item changed at index ${firstIndexChanged} .`); + //RepeatVirtualScroll2Native.requestContainerReLayout(this.repeatElmtId_, 0); + + RepeatVirtualScroll2Native.updateL1Rid4Index(this.repeatElmtId_, this.totalCount_, firstIndexChanged, Array.from(newL1Rid4Index)); + + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) reRender() data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - done`); + } + + private computeTtype(item: T, index: number, monitorAccess: boolean): string { + if (this.ttypeGenFunc_ === undefined) { + return RepeatEachFuncTtype; + } + // record dependencies if monitoring is enabled + monitorAccess && this.startRecordDependencies(false); + let ttype = RepeatEachFuncTtype; + try { + ttype = this.ttypeGenFunc_(item, index); + } catch(e) { + stateMgmtConsole.applicationError(`__RepeatVirtualScroll2Impl. Error generating ttype at index: ${index}`, + e?.message); + } + if (ttype in this.itemGenFuncs_ === false) { + stateMgmtConsole.applicationError(`__RepeatVirtualScroll2Impl. No template found for ttype '${ttype}'`); + ttype = RepeatEachFuncTtype; + } + monitorAccess && this.stopRecordDependencies(); + return ttype; + } + + private computeKey(item : T, index: number, monitorAccess: boolean = true, activateDataItems? : Array>) : string | undefined { + if (!this.useKeys_) { + return undefined; + } + + let key = this.key4Index_.get(index); + if (!key) { + monitorAccess && this.startRecordDependencies(false); + try { + key = this.keyGenFunc_(item, index); + } catch { + stateMgmtConsole.applicationError(`__RepeatVirtualScroll2Impl (${this.repeatElmtId_}): unstable key function. Fix the key gen function in your application!`); + key = this.mkRandomKey(index, "_key-gen-crashed_"); + } + monitorAccess && this.stopRecordDependencies(); + + const optIndex1 : number | undefined = this.index4Key_.get(key); + if (optIndex1 !== undefined) { + key = this.handleDuplicateKey(optIndex1, index, key, activateDataItems); + } else { + this.key4Index_.set(index, key); + this.index4Key_.set(key, index); + } + } + return key; + } + + private mkRandomKey(index : number, origKey : string) : string { + return `___${index}_+_${origKey}_+_${Math.random()}`; + } + + /* + when generating key for index2, detected that index1 has same key already + need to change the key for both index'es + returns random key for index 2 + */ + private handleDuplicateKey(index1 : number, index2 : number, origKey : string, + activateDataItems? : Array>) : string { + const key2 = this.mkRandomKey(index2, origKey); + this.key4Index_.set(index2, key2); + this.index4Key_.set(key2, index2); + + // also make a new key for index1 + const key1 = this.mkRandomKey(index1, origKey); + this.key4Index_.set(index1, key1); + this.index4Key_.set(key1, index1); + this.index4Key_.delete(origKey); + if (activateDataItems && activateDataItems[index1] !== undefined) { + stateMgmtConsole.debug(` ... correcting key of activeDataItem index ${index1} from '${activateDataItems[index1].key}' to '${key1}'.`); + activateDataItems[index1].key = key1; + } + stateMgmtConsole.applicationError(`__RepeatVirtualScroll2Impl (${this.repeatElmtId_}): Detected duplicate key ${origKey} for index ${index1} and ${index2}! Generated random key will decrease Repeat performance. Fix the key gen function in your application!`); + return key2; + } + + /** +- * called from C++ GetFrameChild whenever need to create new node and add to L1 +- * or update spare node and add back to L1 +- * @param forIndex +- * @returns +- */ + private onGetRid4Index(forIndex: number): [number, number] { + if (forIndex < 0 || forIndex >= this.totalCount_) { + throw new Error(`${this.constructor.name}(${this.repeatElmtId_}) onGetRid4Index index ${forIndex} \n + data array length: ${this.arr_.length}, totalCount: ${this.totalCount_}: Out of range, application error.`); + } + const [ dataItemExists, dataItem ] = this.getItemUnmonitored(forIndex); + if (!dataItemExists) { + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onGetRid4Index index ${forIndex} - missing data item.`); + this.activeDataItems_[forIndex] = ActiveDataItem.createMissingDataItem(); + return [0, /* failed to create or update */ 0]; + } + + const ttype = this.computeTtype(dataItem, forIndex, /* enable monitored access */ true); + const key = this.computeKey(dataItem, forIndex, /* monitor access*/ true, this.activeDataItems_); + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onGetRid4Index index ${forIndex}, ttype is '${ttype}' data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start start ++++++++`); + + // spare UINode / RID available to update? + const optRid = this.canUpdateTryMatch(ttype, dataItem, key); + + const result: [number, number] = (optRid > 0) + ? this.updateChild(optRid, ttype, forIndex, key) + : this.createNewChild(forIndex, ttype, key); + + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onGetRid4Index index ${forIndex} ttype is '${ttype}' data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - DONE`); + return result; + } + + // return RID of Node that can be updated (matching ttype), + // or -1 if none + private canUpdate(ttype: string): number { + for (const rid of this.spareRid_) { + if (this.meta4Rid_.get(rid).ttype_ === ttype) { + stateMgmtConsole.debug(` canUpdate: Found spare rid ${rid} for ttype '${ttype}'`); + return rid; + } + } + stateMgmtConsole.debug(` canUpdate: Found NO spare rid for ttype '${ttype}'`); + return -1; + } + + // return RID of Node that can be updated (matching ttype), + // or -1 if none + private canUpdateTryMatch(ttype: string, dataItem: T, key?: string): number { + // 1. round: find matching RID, also data item matches + for (const rid of this.spareRid_) { + const ridMeta = this.meta4Rid_.get(rid); + // compare ttype and data item, or ttype and key + if (ridMeta && ridMeta.ttype_ === ttype && + ((!this.useKeys_ && ridMeta.repeatItem_?.item === dataItem) || + (this.useKeys_ && ridMeta.key_ === key)) + ) { + // FIXME also return the repeatItem + stateMgmtConsole.debug(` canUpdateTryMatch: Found spare rid ${rid} for ttype '${ttype}' contentItem matches.`); + return rid; + } + } + + // just find a matching RID + for (const rid of this.spareRid_) { + if (this.meta4Rid_.get(rid).ttype_ === ttype) { + stateMgmtConsole.debug(` canUpdateTryMatch: Found spare rid ${rid} for ttype '${ttype}'`); + return rid; + } + } + stateMgmtConsole.debug(` canUpdateTryMatch: Found NO spare rid for ttype '${ttype}'`); + return -1; + } + + /** + * crete new Child node onto the ViewStackProcess + * @param forIndex + * @param ttype + * @returns [ success, 1 for new node created ] + */ + private createNewChild(forIndex: number, ttype: string, key?: string): [number, number] { + + let itemGenFunc = this.itemGenFuncs_[ttype]; + this.startRecordDependencies(); + + // item exists in arr_, has been checked before + const [ _, dataItem ] = this.getItemUnmonitored(forIndex); + const repeatItem = this.mkRepeatItem_(dataItem, forIndex); + const rid = this.nextRid++; + + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) createNewChild index ${forIndex} -> new rid ${rid} / ttype ${ttype}, key ${key} - data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start`); + + try { + // execute item builder function + itemGenFunc(repeatItem); + } catch (e) { + this.stopRecordDependencies(); + + stateMgmtConsole.applicationError(`${this.constructor.name}(${this.repeatElmtId_}) initialRenderChild(forIndex: ${forIndex}, templateId: '${ttype}') -> RID ${rid}: data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - item initial render failed!`); + this.activeDataItems_[forIndex] = ActiveDataItem.createFailedToCreateUINodeDataItem(this.arr_[forIndex]); + return [0, /* did not success creating new UINode */ 0]; + } + + // a new UINode subtree, create a new rid -> RepeatItem, ttype, key + this.meta4Rid_.set(rid, new RIDMeta(repeatItem, ttype, key)); + this.activeDataItems_[forIndex] = ActiveDataItem.createWithUINode(this.arr_[forIndex], rid, ttype, key); + this.stopRecordDependencies(); + + + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) createNewChild index ${forIndex} -> new rid ${rid} / ttype ${ttype}, key ${key} - data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - done`); + return [rid, /* created new UINode successfully */ 1]; + } + + + /** + * update given rid / RepeatItem to data item of given index + * @param rid + * @param ttype + * @param forIndex + * @returns [ success, 2 for updated existing node ] + */ + private updateChild(rid: number, ttype: string, forIndex: number, key?: string): [number, number] { + const ridMeta = this.meta4Rid_.get(rid); + if (!ridMeta || !ridMeta.repeatItem_) { + // error + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) updateChild(forIndex: ${forIndex}, templateId: ${ttype}) <- RID ${rid}: data array length: ${this.arr_.length}, totalCount: ${this.totalCount_}. Failed to find RepeatItem. Internal error!.`); + this.activeDataItems_[forIndex] = ActiveDataItem.createFailedToCreateUINodeDataItem(this.arr_[forIndex]); + return [0, /* failed to update */ 0]; + } + + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) updateChild(forIndex: ${forIndex}, templateId: ${ttype}) <- RID ${rid}, key: ${key}: data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start`); + + // item exists in arr_, has been checked before + const [_, dataItem] = this.getItemMonitored(forIndex); + + // rid is taken, move out of spare Set + this.spareRid_.delete(rid); + this.activeDataItems_[forIndex] = ActiveDataItem.createWithUINode(dataItem, rid, ttype, key); + if (this.useKeys_ && key !== undefined) { + // rid, repeatItem, ttype are constant, but key changes in ridMeta on child update + ridMeta.key_ = key; + } + + if (ridMeta.repeatItem_.item !== dataItem || ridMeta.repeatItem_.index !== forIndex) { + // repeatItem needs update, will trigger partial update to using UINodes: + ridMeta.repeatItem_.updateItem(dataItem); + ridMeta.repeatItem_.updateIndex(forIndex); + + ObserveV2.getObserve().updateDirty2(/* update synchronously */ true); + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) updateChild(forIndex: ${forIndex}, templateId: ${ttype}) <- RID ${rid}, key: ${key}: update has been done - done`); + } else { + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) updateChild(forIndex: ${forIndex}, templateId: ${ttype}) <- RID ${rid}, key: ${key}: item and index value as in spare, no update needed - done`); + } + + return [rid, /* update success */ 2]; + } + + + /** + * overloaded function from FrameNode + * called from layout, inform index range of active / L1 items that can be removed from L1 + * to spare nodes, allow to update them + * Note: Grid, List layout has a bug: Frequently calls GetFrameChildForIndex for the index 'fromIndex' + * which moves this item back to L1 + * @param fromIndex + * @param toIndex + */ + private onRecycleItems(fromIndex: number, toIndex: number): void { + // avoid negative fromIndex + // FIXME is this always correct? + fromIndex = Math.max(0, fromIndex); + for (let index = fromIndex; index < toIndex; index++) { + if (index >= this.totalCount_ || !(index in this.activeDataItems_)) { + continue; + } + if (this.activeDataItems_[index].state === ActiveDataItem.UINodeExists) { + this.dropFromL1ActiveNodes(index); + } + } + stateMgmtConsole.debug(`onRecycleItems(${fromIndex}...<${toIndex}) after applying changes: `) + stateMgmtConsole.debug(this.dumpSpareRid()); + stateMgmtConsole.debug(this.dumpDataItems()); + } + + private dropFromL1ActiveNodes(index: number, invalidate : boolean = true): boolean { + if (!(index in this.activeDataItems_)) { + return false; + } + + const rid: number | undefined = this.activeDataItems_[index].rid; + const ttype: string | undefined = this.activeDataItems_[index].ttype; + + // delete makes array item empty, does not re-index. + delete this.activeDataItems_[index]; + this.index4Key_.delete(this.key4Index_.get(index)); + this.key4Index_.delete(index); + + if (rid === undefined || ttype === undefined) { + // data item is not rendered, yet + stateMgmtConsole.debug(`dropFromL1ActiveNodes index: ${index} no rid ${rid} or no ttype '${ttype ? ttype : 'undefined'}'. Dropping un-rendered item silently.`); + return; + } + + // add to spare rid Set + this.spareRid_.add(rid); + + if (invalidate) { + stateMgmtConsole.debug(`dropFromL1ActiveNodes index: ${index} - rid: ${rid}/ttype: '${ttype}' - spareRid: ${this.dumpSpareRid()} - invalidate in RepeatVirtualScrollNode ...`); + // call RepeatVirtualScrollCaches::SetInvalid + RepeatVirtualScroll2Native.setInvalid(this.repeatElmtId_, rid); + } else { + stateMgmtConsole.debug(`dropFromL1ActiveNodes index: ${index} - rid: ${rid}/ttype: '${ttype}' - spareRid: ${this.dumpSpareRid()}`); + } + + return true; + } + + private onActiveRange(start: number, end: number, cacheStart: number, cacheEnd: number, isLoop: boolean): void { + + const fromIndex = Math.max(0, start - cacheStart); + const toIndex = end + cacheEnd; + + if (Number.isNaN(this.activeRange_[0])) { + // first call to onActiveRange + this.activeRange_ = [fromIndex, toIndex]; + } else if (this.activeRange_[0] === fromIndex && this.activeRange_[1] === toIndex) { + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onActiveRange(start: ${start}, end: ${end}, cacheStart: ${cacheStart}, cacheEnd: ${cacheEnd}, isLoop ${isLoop}) data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - unchanged, skipping.`); + return; + } + + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onActiveRange(start: ${start}, end: ${end}, cacheStart: ${cacheStart}, cacheEnd: ${cacheEnd}, isLoop ${isLoop}) data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start start ++++++++`); + + // check which of the activeDataItems needs to be removed from L1 & activeDataItems + let numberOfActiveItems = 0; + for (let index = 0; index < this.activeDataItems_.length; index++) { + if (!(index in this.activeDataItems_)) { + continue; + } + + // same condition as in C++ RepeatVirtualScrollNode::CheckNode4IndexInL + const remainInL1 = ((start - cacheStart <= index) && (index <= end + cacheEnd)) + || (isLoop && (((end < start) && (start - cacheStart <= index || index <= end + cacheEnd)) || + ((start - cacheStart < 0) && (index >= start - cacheStart + this.totalCount_)) || + ((end + cacheEnd >= this.totalCount_) && (index <= end + cacheEnd - this.totalCount_)))); + stateMgmtConsole.debug(` .... index: ${index}: ${remainInL1 ? 'keep in L1' : 'drop from L1'} dataItem: ${this.activeDataItems_[index].dump()}`); + if (remainInL1) { + if (this.activeDataItems_[index].state === ActiveDataItem.UINodeExists) { + numberOfActiveItems += 1; + } + } else { + if (this.activeDataItems_[index].state === ActiveDataItem.UINodeExists) { + this.dropFromL1ActiveNodes(index, /* call C++ RepeatVirtualScrollCaches::SetInvalid */ false); + } + } + }; + stateMgmtConsole.debug(` --> Result: number remaining activeItems ${numberOfActiveItems}.`); + stateMgmtConsole.debug(this.dumpDataItems()); + stateMgmtConsole.debug(this.dumpSpareRid()); + stateMgmtConsole.debug(this.dumpRepeatItem4Rid()); + + // memorize + this.activeRange_ = [fromIndex, toIndex]; + + // adjust dynamic cachedCount for each template type that is using dynamic cached count + stateMgmtConsole.debug(`templateOptions_ ${JSON.stringify(this.templateOptions_)}`); + Object.entries(this.templateOptions_).forEach((pair) => { + const options: RepeatTemplateImplOptions = pair[1]; + if (!options.cachedCountSpecified) { + options.cachedCount = Number.isInteger(options.cachedCount) + ? Math.max(numberOfActiveItems, options.cachedCount) + : numberOfActiveItems; + } + }); + stateMgmtConsole.debug(this.dumpCachedCount()); + } + + + private onPurge() { + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) purge(), totalCount: ${this.totalCount_} - start ++++++++`); + + // deep copy templateOptions_ + let availableCachedCount: { [ttype: string]: number } = {}; + Object.entries(this.templateOptions_).forEach((pair) => { + availableCachedCount[pair[0]] = pair[1].cachedCount as number; + }); + + /// stateMgmtConsole.debug('onPurge before applying changes: ') + //stateMgmtConsole.debug(this.dumpSpareRid()); + //stateMgmtConsole.debug(this.dumpDataItems()); + + // Improvement needed: + // this is a simplistic purge is more or less randomly purges + // extra nodes + // avoid delete on iterated Set, copy into Array + const spareRid1 : Array = Array.from(this.spareRid_); + for (const rid of spareRid1) { + const ttype: string = this.meta4Rid_.get(rid).ttype_; + if (availableCachedCount[ttype] === 0) { + // purge rid + this.purgeNode(rid); + } else { + availableCachedCount[ttype] -= 1; + } + } + stateMgmtConsole.debug('onPurge after applying changes: ') + stateMgmtConsole.debug(this.dumpSpareRid()); + stateMgmtConsole.debug(this.dumpDataItems()); + } + + private purgeNode(rid: number): void { + stateMgmtConsole.debug(` ... delete node rid: ${rid} .`); + this.meta4Rid_.delete(rid); + this.spareRid_.delete(rid); + RepeatVirtualScroll2Native.removeNode(rid); + } + + private dumpSpareRid(): string { + return `spareRid size: ${this.spareRid_.size} ${JSON.stringify(Array.from(this.spareRid_).map(rid => `rid: ${rid}`))}`; + } + + private dumpRepeatItem4Rid(): string { + return `meta4Rid_ size: ${this.meta4Rid_.size}: ${JSON.stringify(Array.from(this.meta4Rid_))} .`; + } + + private dumpDataItems(): string { + let result = ``; + let sepa = ""; + let count = 0; + for (const index in this.activeDataItems_) { + const dataItemDump = this.activeDataItems_[index].dump(); + const repeatItemIndex = this.activeDataItems_[index].rid ? this.meta4Rid_.get(this.activeDataItems_[index].rid)?.repeatItem_?.index : "N/A"; + result += `${sepa}index ${index}, ${dataItemDump} (repeatItemIndex ${repeatItemIndex})`; + sepa = ", \n"; + count += 1; + } + return `activeDataItems(array): length: ${this.activeDataItems_.length}, range: [${this.activeRange_[0]}-${this.activeRange_[1]}], entries count: ${count} =============\n${result}`; + } + + private dumpCachedCount() : string { + let result = ""; + let sepa = ""; + Object.entries(this.templateOptions_).forEach((pair) => { + const options: RepeatTemplateImplOptions = pair[1]; + result += `${sepa}'template ${pair[0]}': specified: ${options.cachedCountSpecified} cachedCount: ${options.cachedCount}: ` + sepa = ", "; + }); + return result; + } + + private dumpKeys() : string { + let result = ""; + let sepa = ""; + this.key4Index_.forEach((key, index) => { + result += `${sepa}${index}: ${key}`; + sepa = "\n"; + }) + return result; + } +}; \ No newline at end of file diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts index 6198a9acdc2..13da55a853b 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts +++ b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts @@ -22,7 +22,7 @@ class __RepeatVirtualScrollImpl { private arr_: Array; private itemGenFuncs_: { [type: string]: RepeatItemGenFunc }; private keyGenFunc_?: RepeatKeyGenFunc; - private typeGenFunc_: RepeatTypeGenFunc; + private ttypeGenFunc_?: RepeatTTypeGenFunc; private totalCount_: number; private totalCountSpecified : boolean = false; @@ -50,14 +50,14 @@ class __RepeatVirtualScrollImpl { this.arr_ = config.arr; this.itemGenFuncs_ = config.itemGenFuncs; this.keyGenFunc_ = config.keyGenFunc; - this.typeGenFunc_ = config.typeGenFunc; + this.ttypeGenFunc_ = config.ttypeGenFunc; // if totalCountSpecified==false, then need to create dependency on array length // so when array length changes, will update totalCount this.totalCountSpecified = config.totalCountSpecified; - this.totalCount_ = (!this.totalCountSpecified || config.totalCount < 0) + this.totalCount_ = (!this.totalCountSpecified || (config.totalCount as number) < 0) ? this.arr_.length - : config.totalCount; + : config.totalCount as number; this.templateOptions_ = config.templateOptions; @@ -77,6 +77,25 @@ class __RepeatVirtualScrollImpl { } } + // wraps a type gen function with validation logic + private ttypeGenFunc(item: T, index: number): string { + if (this.ttypeGenFunc_ === undefined) { + return RepeatEachFuncTtype; + } + let ttype = RepeatEachFuncTtype; + try { + ttype = this.ttypeGenFunc_(item, index); + } catch(e) { + stateMgmtConsole.applicationError(`__RepeatVirtualScrollImpl. Error generating ttype at index: ${index}`, + e?.message); + } + if (ttype in this.itemGenFuncs_ == false) { + stateMgmtConsole.applicationError(`__RepeatVirtualScrollImpl. No template found for ttype '${ttype}'`); + ttype = RepeatEachFuncTtype; + } + return ttype; + } + /**/ private initialRender( owningView: ViewV2, @@ -211,7 +230,7 @@ class __RepeatVirtualScrollImpl { ObserveV2.getObserve().startRecordDependencies(owningView, this.repeatElmtId_, false); for (let i = from; i <= to && i < this.arr_.length; i++) { - let ttype = this.typeGenFunc_(this.arr_[i], i); + let ttype = this.ttypeGenFunc(this.arr_[i], i); result.push(ttype); } // for ObserveV2.getObserve().stopRecordDependencies(); @@ -230,18 +249,18 @@ class __RepeatVirtualScrollImpl { if (from <= to) { for (let i = Math.max(0, from); i <= to && i < this.arr_.length; i++) { const item = this.arr_[i]; - const ttype = this.typeGenFunc_(this.arr_[i], i); + const ttype = this.ttypeGenFunc(this.arr_[i], i); this.lastActiveRangeData_[i] = { item, ttype }; } } else { for (let i = 0; i <= to && i < this.arr_.length; i++) { const item = this.arr_[i]; - const ttype = this.typeGenFunc_(this.arr_[i], i); + const ttype = this.ttypeGenFunc(this.arr_[i], i); this.lastActiveRangeData_[i] = { item, ttype }; } for (let i = Math.max(0, from); i < this.arr_.length; i++) { const item = this.arr_[i]; - const ttype = this.typeGenFunc_(this.arr_[i], i); + const ttype = this.ttypeGenFunc(this.arr_[i], i); this.lastActiveRangeData_[i] = { item, ttype }; } } @@ -281,7 +300,7 @@ class __RepeatVirtualScrollImpl { private initialRenderItem(repeatItem: __RepeatItemFactoryReturn): void { // execute the itemGen function - const itemType = this.typeGenFunc_(repeatItem.item, repeatItem.index); + const itemType = this.ttypeGenFunc(repeatItem.item, repeatItem.index); const isTemplate: boolean = (itemType !== ''); const itemFunc = this.itemGenFuncs_[itemType]; itemFunc(repeatItem); @@ -297,7 +316,7 @@ class __RepeatVirtualScrollImpl { const oldItem = this.lastActiveRangeData_[+i]?.item; const oldType = this.lastActiveRangeData_[+i]?.ttype; const newItem = this.arr_[+i]; - const newType = this.typeGenFunc_(this.arr_[+i], +i); + const newType = this.ttypeGenFunc(this.arr_[+i], +i); if (oldItem !== newItem) { stateMgmtConsole.debug(`__RepeatVirtualScrollImpl.hasVisibleItemsChanged() i:#${i} item changed => true`); diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/sdk/i_repeat.ts b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/sdk/i_repeat.ts index 73468b67343..f73da688f81 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/sdk/i_repeat.ts +++ b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/sdk/i_repeat.ts @@ -23,7 +23,7 @@ interface RepeatItem { } type RepeatItemGenFunc = (i: RepeatItem) => void; -type RepeatTypeGenFunc = (item: T, index: number) => string; +type RepeatTTypeGenFunc = (item: T, index: number) => string; type RepeatKeyGenFunc = (item: T, index?: number) => string; type RepeatTemplateOptions = { cachedCount?: number }; type RepeatTemplateImplOptions = { cachedCountSpecified: boolean, cachedCount?: number }; @@ -58,7 +58,7 @@ interface RepeatAPI { virtualScroll: (options?: { totalCount?: number, reusable?: boolean }) => RepeatAPI; // function to decide which template to use, each template has an id - templateId: (typeFunc: RepeatTypeGenFunc) => RepeatAPI; + templateId: (typeFunc: RepeatTTypeGenFunc) => RepeatAPI; // template: id + builder function to render specific type of data item template: (type: string, itemGenFunc: RepeatItemGenFunc, options?: RepeatTemplateOptions) => RepeatAPI; diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/tsconfig.base.json b/frameworks/bridge/declarative_frontend/state_mgmt/tsconfig.base.json index 7239965aed6..e859dbade45 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/tsconfig.base.json +++ b/frameworks/bridge/declarative_frontend/state_mgmt/tsconfig.base.json @@ -93,6 +93,7 @@ "src/lib/partial_update/pu_repeat.ts", "src/lib/partial_update/pu_repeat_impl.ts", "src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts", + "src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts", // sdk "src/lib/sdk/v2_persistence.ts", diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/tsconfig.test.json b/frameworks/bridge/declarative_frontend/state_mgmt/tsconfig.test.json index 59d97ce8c9e..814aad62445 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/tsconfig.test.json +++ b/frameworks/bridge/declarative_frontend/state_mgmt/tsconfig.test.json @@ -83,6 +83,7 @@ "src/lib/partial_update/pu_repeat.ts", "src/lib/partial_update/pu_repeat_impl.ts", "src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts", + "src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts", // sdk "src/lib/sdk/v2_persistence.ts", diff --git a/frameworks/core/components_ng/base/frame_node.cpp b/frameworks/core/components_ng/base/frame_node.cpp index f6e401e8f51..e60f1ca2c4b 100644 --- a/frameworks/core/components_ng/base/frame_node.cpp +++ b/frameworks/core/components_ng/base/frame_node.cpp @@ -51,6 +51,7 @@ #endif #include "core/components_ng/syntax/lazy_for_each_node.h" #include "core/components_ng/syntax/repeat_virtual_scroll_node.h" +#include "core/components_ng/syntax/repeat_virtual_scroll_2_node.h" namespace { constexpr double VISIBLE_RATIO_MIN = 0.0; @@ -167,9 +168,10 @@ public: } auto lazyForEachNode = AceType::DynamicCast(UiNode); auto repeatVirtualScrollNode = AceType::DynamicCast(UiNode); + auto repeatVirtualScroll2Node = AceType::DynamicCast(UiNode); if (lazyForEachNode) { lazyForEachNode->BuildAllChildren(); - } else if (repeatVirtualScrollNode) { + } else if (repeatVirtualScrollNode || repeatVirtualScroll2Node) { TAG_LOGE(AceLogTag::ACE_REPEAT, "repeatVirtualScroll not support in non scoll container!"); } else { auto customNode = AceType::DynamicCast(UiNode); diff --git a/frameworks/core/components_ng/base/view_partial_update_model_ng.cpp b/frameworks/core/components_ng/base/view_partial_update_model_ng.cpp index 98a6a35507e..a82619cb505 100644 --- a/frameworks/core/components_ng/base/view_partial_update_model_ng.cpp +++ b/frameworks/core/components_ng/base/view_partial_update_model_ng.cpp @@ -18,6 +18,7 @@ #include "core/components_ng/pattern/custom/custom_measure_layout_node.h" #include "core/components_ng/pattern/custom/custom_title_node.h" #include "core/components_ng/syntax/repeat_virtual_scroll_node.h" +#include "core/components_ng/syntax/repeat_virtual_scroll_2_node.h" #include "core/components_ng/pattern/custom/custom_app_bar_node.h" namespace OHOS::Ace::NG { @@ -110,10 +111,16 @@ bool ViewPartialUpdateModelNG::AllowReusableV2Descendant(const WeakPtr& RefPtr node = weak.Upgrade(); CHECK_NULL_RETURN(node, false); - while ((node->GetParent()) && (node->GetParent()->GetTag() != V2::JS_VIEW_ETS_TAG) && - (AceType::DynamicCast(node->GetParent()) == nullptr)) { - node = node->GetParent(); + while (node->GetParent() && (node->GetParent()->GetTag() != V2::JS_VIEW_ETS_TAG)) { + if (AceType::DynamicCast(node->GetParent()) != nullptr) { + break; + } + if (AceType::DynamicCast(node->GetParent()) != nullptr) { + break; + } + node = node->GetParent(); } + bool result = ((node->GetParent() == nullptr) || (node->GetParent()->GetTag() == V2::JS_VIEW_ETS_TAG) || (node->IsAllowReusableV2Descendant())); return result; diff --git a/frameworks/core/components_ng/pattern/list/list_height_offset_calculator.h b/frameworks/core/components_ng/pattern/list/list_height_offset_calculator.h index 00ec234b2b4..84bff5c5db6 100644 --- a/frameworks/core/components_ng/pattern/list/list_height_offset_calculator.h +++ b/frameworks/core/components_ng/pattern/list/list_height_offset_calculator.h @@ -19,6 +19,7 @@ #include "core/components_ng/base/ui_node.h" #include "core/components_ng/syntax/lazy_for_each_node.h" #include "core/components_ng/syntax/repeat_virtual_scroll_node.h" +#include "core/components_ng/syntax/repeat_virtual_scroll_2_node.h" #include "core/components_ng/pattern/list/list_item_group_pattern.h" #include "core/components_ng/pattern/list/list_item_pattern.h" #include "core/components_ng/pattern/list/list_layout_algorithm.h" @@ -107,8 +108,11 @@ public: if (AceType::InstanceOf(child)) { auto frameNode = AceType::DynamicCast(child); CalculateFrameNode(frameNode); - } else if (AceType::InstanceOf(child) || - AceType::InstanceOf(child)) { + } else if (AceType::InstanceOf(child)) { + CalculateLazyForEachNode(child); + } else if (AceType::InstanceOf(child)) { + CalculateLazyForEachNode(child); + } else if (AceType::InstanceOf(child)) { CalculateLazyForEachNode(child); } else { CalculateUINode(child); diff --git a/frameworks/core/components_ng/pattern/list/list_lanes_layout_algorithm.cpp b/frameworks/core/components_ng/pattern/list/list_lanes_layout_algorithm.cpp index 3ca380755c7..cd18cf503c4 100644 --- a/frameworks/core/components_ng/pattern/list/list_lanes_layout_algorithm.cpp +++ b/frameworks/core/components_ng/pattern/list/list_lanes_layout_algorithm.cpp @@ -358,6 +358,9 @@ int32_t ListLanesLayoutAlgorithm::GetLazyForEachIndex(const RefPtr& h if (AceType::InstanceOf(parent)) { return parent->GetFrameNodeIndex(host); } + if (AceType::InstanceOf(parent)) { + return parent->GetFrameNodeIndex(host); + } parent = parent->GetParent(); } return -1; diff --git a/frameworks/core/components_ng/pattern/list/list_position_map.h b/frameworks/core/components_ng/pattern/list/list_position_map.h index f787dba6ae9..aa1eca53cfd 100644 --- a/frameworks/core/components_ng/pattern/list/list_position_map.h +++ b/frameworks/core/components_ng/pattern/list/list_position_map.h @@ -30,6 +30,7 @@ #include "core/components_ng/base/ui_node.h" #include "core/components_ng/syntax/lazy_for_each_node.h" #include "core/components_ng/syntax/repeat_virtual_scroll_node.h" +#include "core/components_ng/syntax/repeat_virtual_scroll_2_node.h" #include "core/components_ng/pattern/list/list_children_main_size.h" #include "core/components_ng/pattern/list/list_item_group_pattern.h" #include "core/components_ng/property/measure_property.h" @@ -171,10 +172,13 @@ public: if (AceType::InstanceOf(child)) { auto frameNode = AceType::DynamicCast(child); CalculateFrameNode(frameNode); - } else if (AceType::InstanceOf(child) || - AceType::InstanceOf(child)) { + } else if (AceType::InstanceOf(child)) { // Rules: only one type node(ListItem or ListItemGroup) can exist in LazyForEach. CalculateLazyForEachNode(child); + } else if (AceType::InstanceOf(child)) { + CalculateLazyForEachNode(child); + } else if (AceType::InstanceOf(child)) { + CalculateLazyForEachNode(child); } else { CalculateUINode(child); } diff --git a/frameworks/core/components_ng/pattern/swiper/swiper_pattern.cpp b/frameworks/core/components_ng/pattern/swiper/swiper_pattern.cpp index e1c4eb503b4..4d3e5cec0b5 100644 --- a/frameworks/core/components_ng/pattern/swiper/swiper_pattern.cpp +++ b/frameworks/core/components_ng/pattern/swiper/swiper_pattern.cpp @@ -52,6 +52,7 @@ #include "core/components_ng/syntax/for_each_node.h" #include "core/components_ng/syntax/lazy_for_each_node.h" #include "core/components_ng/syntax/repeat_virtual_scroll_node.h" +#include "core/components_ng/syntax/repeat_virtual_scroll_2_node.h" namespace OHOS::Ace::NG { namespace { @@ -2186,6 +2187,7 @@ void SwiperPattern::BuildForEachChild(const std::set& indexSet, const R auto forEachNode = AceType::DynamicCast(childNode.value()); auto repeatNode = AceType::DynamicCast(childNode.value()); + auto repeatNode2 = AceType::DynamicCast(childNode.value()); for (auto index : indexSet) { if (forEachNode && forEachNode->GetChildAtIndex(index)) { TAG_LOGI(AceLogTag::ACE_SWIPER, "Swiper preload item index: %{public}d, id:%{public}d", index, swiperId_); @@ -2196,6 +2198,10 @@ void SwiperPattern::BuildForEachChild(const std::set& indexSet, const R if (repeatNode) { repeatNode->GetFrameChildByIndex(index, true); } + + if (repeatNode2) { + repeatNode2->GetFrameChildByIndex(index, true); + } } } @@ -5019,6 +5025,11 @@ void SwiperPattern::SetLazyLoadIsLoop() const if (repeatVirtualNode) { repeatVirtualNode->SetIsLoop(IsLoop()); } + + auto repeatVirtualNode2 = AceType::DynamicCast(targetNode.value()); + if (repeatVirtualNode2) { + repeatVirtualNode2->SetIsLoop(IsLoop()); + } } } @@ -6530,6 +6541,9 @@ std::optional> SwiperPattern::FindLazyForEachNode(RefPtr if (AceType::DynamicCast(baseNode)) { return baseNode; } + if (AceType::DynamicCast(baseNode)) { + return baseNode; + } if (!isSelfNode && AceType::DynamicCast(baseNode)) { return std::nullopt; } @@ -6552,6 +6566,10 @@ std::optional> SwiperPattern::FindForEachNode(const RefPtr(baseNode)) { + return baseNode; + } + if (!isSelfNode && AceType::DynamicCast(baseNode)) { return std::nullopt; } diff --git a/frameworks/core/components_ng/syntax/BUILD.gn b/frameworks/core/components_ng/syntax/BUILD.gn index ae9f3a0c745..b2070b2670e 100644 --- a/frameworks/core/components_ng/syntax/BUILD.gn +++ b/frameworks/core/components_ng/syntax/BUILD.gn @@ -25,6 +25,13 @@ build_component_ng("syntax_ng") { "lazy_layout_wrapper_builder.cpp", "repeat_model_ng.cpp", "repeat_node.cpp", + + # Repeat virtual scrollAPI 16+ + "repeat_virtual_scroll_2_caches.cpp", + "repeat_virtual_scroll_2_model_ng.cpp", + "repeat_virtual_scroll_2_node.cpp", + + # Repeat virtual scroll API -15 "repeat_virtual_scroll_caches.cpp", "repeat_virtual_scroll_model_ng.cpp", "repeat_virtual_scroll_node.cpp", diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.cpp b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.cpp new file mode 100644 index 00000000000..150378754be --- /dev/null +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.cpp @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "core/components_ng/syntax/repeat_virtual_scroll_2_caches.h" + +#include +#include +#include +#include + +#include "base/log/log_wrapper.h" +#include "base/memory/referenced.h" +#include "core/components_ng/base/frame_node.h" +#include "core/components_ng/base/view_stack_processor.h" + +namespace OHOS::Ace::NG { + +using CacheItem = RepeatVirtualScroll2Caches::CacheItem; +using OptCacheItem = RepeatVirtualScroll2Caches::OptCacheItem; +using GetFrameChildResult = RepeatVirtualScroll2Caches::GetFrameChildResult; + +RefPtr RepeatVirtualScroll2CacheItem::MakeCacheItem(RefPtr& node, bool isL1) +{ + return MakeRefPtr(node, isL1); +} + +RepeatVirtualScroll2Caches::RepeatVirtualScroll2Caches( + const std::function(IndexType)>& onGetRid4Index) + : onGetRid4Index_(onGetRid4Index) +{} + +/** + * Return a FrameNode child for give index + */ +GetFrameChildResult RepeatVirtualScroll2Caches::GetFrameChild(IndexType index, bool needBuild) +{ + TAG_LOGE(AceLogTag::ACE_REPEAT, " ... GetFrameChild(index %{public}d, needBuild: %{public}d", index, + static_cast(needBuild)); + OptCacheItem optCacheItem = GetL1CacheItem4Index(index); + if (optCacheItem.has_value()) { + return std::pair(ONGETRID4INDEX_RESULT_UNCHANGED_NODE, optCacheItem.value()); + } + + if (!needBuild) { + return std::pair(ONGETRID4INDEX_RESULT_NO_NODE, nullptr); + } + + optCacheItem = CallOnGetRid4Index(index); + return optCacheItem.has_value() + ? std::pair(ONGETRID4INDEX_RESULT_UPDATED_NODE, optCacheItem.value()) + : std::pair(ONGETRID4INDEX_RESULT_NO_NODE, nullptr); +} + +/** + * Function called from TS: + * purge UINode with given id + * i.e. remove map entry rid -> CacheItem + */ +void RepeatVirtualScroll2Caches::RemoveNode(RIDType rid) +{ + TAG_LOGE(AceLogTag::ACE_REPEAT, " ... RemoveNode(rid : %{public}d) ...", static_cast(rid)); + + DropFromL1ByRid(rid); + + const auto iter = cacheItem4Rid_.find(rid); + if (iter != cacheItem4Rid_.end()) { + cacheItem4Rid_.erase(iter); + } +} + +/** + * Function called from TS: + * drop RID from L1 + * mark CacheItem for rid as invalid + */ +void RepeatVirtualScroll2Caches::SetInvalid(RIDType rid) +{ + TAG_LOGE(AceLogTag::ACE_REPEAT, " ... SetInvalid(rid : %{public}d) ...", static_cast(rid)); + DropFromL1ByRid(rid); +} + +/** + * drop given rid from the L1 + * see DropFromL1ByIndex + */ +void RepeatVirtualScroll2Caches::DropFromL1ByRid(RIDType rid) +{ + for (const auto iter : l1Rid4Index_) { + if (iter.second == rid) { + DropFromL1ByIndex(iter.first); + return; + } + } +} + +/** + * drop given index from the L1 + * update CacheItem.isL1 + * do not modify IsActive or render tree status + * + */ +void RepeatVirtualScroll2Caches::DropFromL1ByIndex(IndexType index) +{ + const std::optional& ridOpt = GetRID4Index(index); + if (ridOpt.has_value()) { + const auto cacheIter = cacheItem4Rid_.find(ridOpt.value()); + if (cacheIter != cacheItem4Rid_.end()) { + cacheIter->second->isL1_ = false; + } + } + + const auto iter = l1Rid4Index_.find(index); + if (iter != l1Rid4Index_.end()) { + l1Rid4Index_.erase(iter); + TAG_LOGE(AceLogTag::ACE_REPEAT, " ... DropFromL1ByIndex: removed index %{public}d -> rid %{public}d from L1", + static_cast(index), static_cast(ridOpt.value())); + } +} + +/** + * returns index if given rid is in L1Rid4Index + */ +std::optional RepeatVirtualScroll2Caches::GetL1Index4Rid(RIDType rid) +{ + for (auto l1Iter : l1Rid4Index_) { + if (l1Iter.second == rid) { + return l1Iter.first; + } + } + return std::nullopt; +} + +void RepeatVirtualScroll2Caches::UpdateL1Rid4Index(std::map l1Rd4Index) +{ + TAG_LOGE(AceLogTag::ACE_REPEAT, "UpdateL1Rid4Index"); + + l1Rid4Index_ = l1Rd4Index; + TAG_LOGE(AceLogTag::ACE_REPEAT, " ... L1Rid4Index: %{public}s", DumpL1Rid4Index().c_str()); + + ForEachCacheItem([&](RIDType rid, const CacheItem& cacheItem) { + cacheItem->isL1_ = (GetL1Index4RID(rid) != std::nullopt); + TAG_LOGE(AceLogTag::ACE_REPEAT, " ... %{public}s", DumpCacheItem(cacheItem).c_str()); + }); +} + +std::optional RepeatVirtualScroll2Caches::GetL1Index4RID(RIDType rid) const +{ + for (const auto iter : l1Rid4Index_) { + if (iter.second == rid) { + return iter.first; + } + } + return std::nullopt; +} + +/** + * Get the Index of frameNode in L1 / active range + * if UINode found + */ +std::optional RepeatVirtualScroll2Caches::GetL1Index4Node(const RefPtr& frameNode) const +{ + if (frameNode == nullptr) { + return std::nullopt; + ; + } + + for (const auto iter : l1Rid4Index_) { + const RIDType rid = iter.second; + + OptCacheItem cacheItemOpt = GetCacheItem4RID(rid); + if (cacheItemOpt.has_value() && cacheItemOpt.value()->node_ == frameNode) { + return iter.first; // index in index -> RID + } + } + return std::nullopt; +} + +OptCacheItem RepeatVirtualScroll2Caches::CallOnGetRid4Index(IndexType index) +{ + TAG_LOGD(AceLogTag::ACE_REPEAT, " ... CallOnGetRid4Index(index %{public}d: calling TS ...", index); + + // swap the ViewStackProcessor instance for secondary while we run the item builder function + // so that its results can easily be obtained from it, does not disturb main ViewStackProcessor + NG::ScopedViewStackProcessor scopedViewStackProcessor; + auto* viewStack = NG::ViewStackProcessor::GetInstance(); + + const std::pair result = onGetRid4Index_(index); + if (result.second == ONGETRID4INDEX_RESULT_CREATED_NEW_NODE) { + // case: new node was created successfully + // get it from ViewStackProcessor + RefPtr node4Index = viewStack->Finish(); + if (node4Index == nullptr) { + TAG_LOGE(AceLogTag::ACE_REPEAT, + "CallOnGetRid4Index(index %{public}d: New Node creation failed. No node on ViewStackProcessor. " + "Internal error!", static_cast(index)); + return nullptr; + } + + const RIDType rid = result.first; + if (rid < 0) { + TAG_LOGE(AceLogTag::ACE_REPEAT, "CallOnGetRid4Index(index %{public}d: Node creation failed. Invalid rid. " + "Internal error!", static_cast(index)); + return std::nullopt; + } + + // add to L1 index -> rid + l1Rid4Index_[index] = rid; + + // add to cache + cacheItem4Rid_[rid] = RepeatVirtualScroll2CacheItem::MakeCacheItem(node4Index, true); + + TAG_LOGD(AceLogTag::ACE_REPEAT, "REPEAT TRACE ABNORMAL (after startup) ... CallOnGetRid4Index" + "(index %{public}d -> rid %{public}d) returns CacheItem with newly created node %{public}s .", + index, static_cast(rid), DumpCacheItem(cacheItem4Rid_[rid]).c_str()); + + return cacheItem4Rid_[rid]; + } // case ONGETRID4INDEX_RESULT_CREATED_NEW_NODE) + + if (result.second == ONGETRID4INDEX_RESULT_UPDATED_NODE) { + // case: TS updated existing node that's in the cache already + const RIDType rid = result.first; + if (rid < INVALID_RID) { + TAG_LOGE(AceLogTag::ACE_REPEAT, "Node update for index %{public}d failed. Invalid rid. " + "Internal error!", static_cast(index)); + return std::nullopt; + } + const auto& optCacheItem = GetCacheItem4RID(rid); + if (!optCacheItem.has_value() || optCacheItem.value()->node_ == nullptr) { + TAG_LOGE(AceLogTag::ACE_REPEAT, "Node update for index %{public}d failed. No node in CacheItem. " + "Internal error!", static_cast(index)); + return std::nullopt; + } + + // add to L1 but do not Set Active or add to render tree. + l1Rid4Index_[index] = rid; + optCacheItem.value()->isL1_ = true; + + TAG_LOGE(AceLogTag::ACE_REPEAT, + "... CallOnGetRid4Index(index %{public}d -> rid %{public}d): returns CacheItem with updated node " + "%{public}s .", index, static_cast(rid), DumpCacheItem(cacheItem4Rid_[rid]).c_str()); + + return optCacheItem; + } // case ONGETRID4INDEX_RESULT_UPDATED_NODE + + // TS was not able to deliver UINode + TAG_LOGE(AceLogTag::ACE_REPEAT,"New node creation failed for index %{public}d. TS unable to create/update node. " + "Application error!", static_cast(index)); + return std::nullopt; +} + +/** + * return CacheItem for RID, if it exists + * do not check and CacheItem flags + */ +OptCacheItem RepeatVirtualScroll2Caches::GetCacheItem4RID(RIDType rid) const +{ + auto cacheIter = cacheItem4Rid_.find(rid); + if (cacheIter != cacheItem4Rid_.end()) { + return cacheIter->second; + } + return std::nullopt; +} + +/** + * if L1 includes RID for index, return it + */ +std::optional RepeatVirtualScroll2Caches::GetRID4Index(IndexType index) const +{ + auto ridIter = l1Rid4Index_.find(index); + if (ridIter != l1Rid4Index_.end()) { + return ridIter->second; + } + return std::nullopt; +} + +OptCacheItem RepeatVirtualScroll2Caches::GetL1CacheItem4Index(IndexType index) +{ + const auto iter = l1Rid4Index_.find(index); + if (iter == l1Rid4Index_.end()) { + TAG_LOGE(AceLogTag::ACE_REPEAT, "GetL1CacheItem4Index(index: %{public}d) returns nullptr, new index", index); + return std::nullopt; + } + + const RIDType rid = iter->second; + const auto cacheItemIter = cacheItem4Rid_.find(rid); + if (cacheItemIter != cacheItem4Rid_.end() && cacheItemIter->second->node_ != nullptr && + cacheItemIter->second->isL1_) { + TAG_LOGE(AceLogTag::ACE_REPEAT, " ... GetL1CacheItem4Index(index: %{public}d) returns L1 %{public}s .", + index, DumpCacheItem(cacheItemIter->second).c_str()); + return cacheItemIter->second; + } + TAG_LOGE( + AceLogTag::ACE_REPEAT, "GetL1CacheItem4Index(index: %{public}d) returns nullptr. Invalid CacheItem.", index); + return std::nullopt; +} + +bool RepeatVirtualScroll2Caches::RebuildL1(const std::function& cbFunc) +{ + std::map l1Copy; + std::swap(l1Copy, l1Rid4Index_); + bool modified = false; + + for (const auto l1Iter : l1Copy) { + const IndexType index = l1Iter.first; + const RIDType rid = l1Iter.second; + + OptCacheItem optCacheItem = GetCacheItem4RID(rid); + if (optCacheItem.has_value()) { + if (optCacheItem.value()->node_ != nullptr && cbFunc(index, optCacheItem.value())) { + // keep in L1 + l1Rid4Index_[index] = rid; + optCacheItem.value()->isL1_ = true; + } else { + optCacheItem.value()->isL1_ = false; + optCacheItem.value()->isActive_ = false; + optCacheItem.value()->isOnRenderTree_ = false; + modified = true; + } + } + } + return modified; +} + +void RepeatVirtualScroll2Caches::ForEachL1Node( + const std::function& node)>& cbFunc) +{ + for (const auto l1Iter : l1Rid4Index_) { + const IndexType index = l1Iter.first; + const RIDType rid = l1Iter.second; + OptCacheItem optCacheItem = GetCacheItem4RID(rid); + if (optCacheItem.has_value()) { + cbFunc(index, rid, optCacheItem.value()->node_); + } + } +} + +void RepeatVirtualScroll2Caches::ForEachCacheItem( + const std::function& cbFunc) +{ + for (auto cacheItemIter : cacheItem4Rid_) { + cbFunc(cacheItemIter.first, cacheItemIter.second); + } +} + +std::string RepeatVirtualScroll2Caches::DumpUINode(const RefPtr& node) const +{ + return (node == nullptr) ? "UINode: nullptr" + : "UINode: " + node->GetTag() + "(" + std::to_string(node->GetId()) + ")"; +} + +std::string RepeatVirtualScroll2Caches::DumpCacheItem(const CacheItem& cacheItem) const +{ + return (cacheItem->node_ == nullptr) + ? "UINode nullptr" + : DumpUINode(cacheItem->node_) + ", isL1: " + std::to_string(cacheItem->isL1_) + + ", isActive: " + std::to_string(cacheItem->isActive_); +} + +std::string RepeatVirtualScroll2Caches::DumpUINodeCache() const +{ + std::string result = + "All UINodes in cache (cacheItem4Rid_): size=" + std::to_string(cacheItem4Rid_.size()) + "--------------\n"; + for (const auto& cacheItemIIter : cacheItem4Rid_) { + const auto optIndex4Rid = GetL1Index4RID(cacheItemIIter.first); + result += " rid: " + std::to_string(cacheItemIIter.first) + " -> " + + DumpCacheItem(cacheItemIIter.second).c_str() + " L1 active node index " + + (optIndex4Rid.has_value() ? std::to_string(optIndex4Rid.value()) : "N/A") + "\n"; + } + return result; +} + +std::string RepeatVirtualScroll2Caches::DumpL1Rid4Index() const +{ + std::string result = "l1Rid4Index size=" + std::to_string(l1Rid4Index_.size()) + "--------------\n"; + + for (const auto mapIter : l1Rid4Index_) { + const OptCacheItem optCacheItem = GetCacheItem4RID(mapIter.second); + result += + " index " + std::to_string(mapIter.first) + " -> cacheItem(rid: " + std::to_string(mapIter.second) + + "): " + (optCacheItem.has_value() ? DumpCacheItem(optCacheItem.value()).c_str() : "CacheItem N/A ERROR") + + "\n"; + } + return result; +} + +} // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.h new file mode 100644 index 00000000000..e390334513f --- /dev/null +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.h @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_2_CACHES_H +#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_2_CACHES_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "base/memory/referenced.h" +#include "base/utils/macros.h" +#include "core/components_ng/base/frame_node.h" + +namespace OHOS::Ace::NG { + +/** + * scenario 1: GetFrameChild(index) need to render new UINode + * 1. GetFrameChild(index) + * 2. GetL1UINode4Index(index) return nothing + * 3. CallOnGetRid4Index(index) + * 4. calls onGetRid4Index_(index) to TS side + * a) get key for index, get ttype for index + * b) RepeatItem exists for this index/data item and this ttype? - no + * c) itemGenFunction(item, index) -> RepeatItem with new rid + * d) bracket execution of itemGenFunction with calls to these C++ functions + * BeforeChildNodeCreation() - creases 2nd VSP + * AddNodeFromVSP(rid) - gets the newly create FrameNode from the VSP + * adds it to rid -> CacheItem { valid: true } + * e) TS function completed, returns new rid + * 5. CallOnGetRid4Index continues + * 6. GetCacheItem4RID(rid) returns cached item with UINode added in step 4d + * 7. CallOnGetRid4Index returns UINode + * 8. GetFrameChild returns UINode + * + * + * scenario 2: GetFrameChild(index) need to update existing UINode + * 1. GetFrameChild(index) + * 2. GetL1UINode4Index(index) return nothing + * 3. CallOnGetRid4Index(index) + * 4. onGetRid4Index_(index) to TS side + * a) get key for index, get ttype for index + * b) RepeatItem exists for this ttype? - yes, it has rid + * c) RepeatItem.updateItem(item), RepeatItem.updateIndex(index) + * d) apply updates synchronously + * e) TS function completed, returns existing rid + * 5. CallOnGetRid4Index continues + * 6. GetCacheItem4RID(rid) returns cached item with updated UINode + * 7. CallOnGetRid4Index returns UINode + * 8. GetFrameChild returns UINode + * + * + * scenario 3: GetFrameChild(index) can return value L1 node + * 1. GetFrameChild(index) + * 2. GetL1UINode4Index(index) return UINode + * 3. GetFrameChild completes, returns the UINode + * + * + * scenario 4: Repeat Rerender + * 1. array items in visible range have changed ? - yes + * 2. visible range.forEach((index => + * 3. has ttype changes ? and has data item changed? + * 4. if yes to either, + * get RID for old data item + * SetInvalid(rid) on C++ side + * request re-layout from container + * 5. if no, do nothing + * + * TS Side has + * cache: data item -> RID + * + * scenario 5: layout calls RecycleItems(from, to) + * 1. [for..to].forEach((index) => + * 2. if index in L1, get rid + * 3. setInvalid(rid) + * + */ + +class RepeatVirtualScroll2CacheItem : public virtual AceType { + DECLARE_ACE_TYPE(RepeatVirtualScroll2CacheItem, AceType); + +public: + static RefPtr MakeCacheItem(RefPtr& node, bool isL1); + + RepeatVirtualScroll2CacheItem(RefPtr& node, bool isL1) + : isL1_(isL1), isActive_(false), isOnRenderTree_(false), node_(node) + {} + ~RepeatVirtualScroll2CacheItem() override = default; + + bool isL1_; + bool isActive_; + bool isOnRenderTree_; + RefPtr node_; + + ACE_DISALLOW_COPY_AND_MOVE(RepeatVirtualScroll2CacheItem); +}; + +using IndexType = int32_t; +using RIDType = uint32_t; + + +// result codes of onGetRid4Index result (2nd 2nd in the pair) +// Note: using #define instead of enum class due to bridging limitations +// between TS and UINode classes: not allowed to import the class definition +// from js_repeat_virtual_scroll.cpp +#define ONGETRID4INDEX_RESULT_FAILED 0 +#define ONGETRID4INDEX_RESULT_NO_NODE 0 +#define ONGETRID4INDEX_RESULT_CREATED_NEW_NODE 1 +#define ONGETRID4INDEX_RESULT_UPDATED_NODE 2 +#define ONGETRID4INDEX_RESULT_UNCHANGED_NODE 3 + +#define NO_L1_INDEX (-1) +#define INVALID_RID 0 + +class RepeatVirtualScroll2Caches { +public: + using CacheItem = RefPtr; + using OptCacheItem = std::optional; + using GetFrameChildResult = std::pair; + +public: + RepeatVirtualScroll2Caches(const std::function(IndexType)>& onGetRid4Index); + + /** + * Return a FrameNode child for give index + */ + GetFrameChildResult GetFrameChild(IndexType index, bool needBuild); + + /** + * Function called from TS: + * purge UINode with given id + * i.e. remove map entry rid -> CacheItem + */ + void RemoveNode(RIDType rid); + + /** + * Function called from TS: + * drop RID from L1 + * mark CacheItem for rid as invalid + */ + void SetInvalid(RIDType rid); + + /** + * rebuild the L1 + * call cbFunc for each item + * keep in L1 if function returns true + * otherwise remove silently + * + * return true if any items have been dropped + * return false only if no changes made + */ + bool RebuildL1(const std::function& cbFunc); + + /** + * iterate over L1 items and call cbFunc for each + * cbFunction is NOT allowed to add to or remove items from L1 + */ + void ForEachL1Node(const std::function& node)>& cbFunc); + + /** + * iterate over all rid -> CacheItems 's + * cbFunction is NOT allowed to add to or remove CacheItems from cacheItem4Rid_ + */ + void ForEachCacheItem(const std::function& cbFunc); + + std::optional GetL1Index4Node(const RefPtr& frameNode) const; + + /** + * Drop rid / index from L1, mark its CacheItem as no in L1 + * do not modify IsActive state, do not remove from renderTree + * either caller does it, or SetActiveRange will do a little later. + */ + void DropFromL1ByRid(RIDType rid); + void DropFromL1ByIndex(IndexType index); + + /** + * returns index if given rid is in L1Rid4Index + */ + std::optional GetL1Index4Rid(RIDType rid); + + // TS call JS to update l1Rid4Index_ following a + // Repeat.rerender + void UpdateL1Rid4Index(std::map l1Rd4Index); + + /** + * for debug purposes, use wisely, performance is slow! + */ + std::string DumpCacheItem(const CacheItem& cacheItem) const; + std::string DumpUINode(const RefPtr& node) const; + std::string DumpUINodeCache() const; + std::string DumpL1Rid4Index() const; + + /** + * return the index of given RID in L1 + * or NO_L1_INDEX + */ + std::optional GetL1Index4RID(RIDType rid) const; + +private: + /** + * TS make new node or update from L1 + * add to L1 but do not SetActive or add to render tree + * CacheItem has node, isL1=True + */ + OptCacheItem CallOnGetRid4Index(IndexType index); + + /** + * return CacheItem for RID, if it exists + * do not check any CacheItem flags + */ + OptCacheItem GetCacheItem4RID(RIDType rid) const; + + /** + * if L1 includes RID for index, return it + */ + std::optional GetRID4Index(IndexType index) const; + + /** + * get valid UINode from L1 for given index + */ + OptCacheItem GetL1CacheItem4Index(IndexType index); + // std::optional>& GetL1UINode4Index(IndexType index); + + // L1 items index -> RID + std::map l1Rid4Index_; + + // all nodes RID -> CacheItem[UINode, isValid] + std::map cacheItem4Rid_; + + // TS callback function for given index, provides RID + std::function(IndexType)> onGetRid4Index_; + + // TS function to inform new active range from ... to + std::function onSetActiveRange_; + + // TS function to call to purge + std::function onPurge_; +}; // class NodeCache + +} // namespace OHOS::Ace::NG + +#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_2_CACHES_H diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h new file mode 100644 index 00000000000..8c687368cfc --- /dev/null +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_SYNTAX_REPEAT_VIRTUAL_SCROLL_2_MODEL_H +#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_SYNTAX_REPEAT_VIRTUAL_SCROLL_2_MODEL_H + +#include +#include +#include +#include +#include +#include + +#include "base/utils/macros.h" + +namespace OHOS::Ace { + +enum class CreateFrameChildResult; + +class ACE_EXPORT RepeatVirtualScroll2Model { +public: + RepeatVirtualScroll2Model() = default; + virtual ~RepeatVirtualScroll2Model() = default; + + static RepeatVirtualScroll2Model* GetInstance(); + virtual void Create(uint32_t totalCount, + const std::function(int32_t)>& onGetRid4Index, + const std::function onRecycleItems, + const std::function onActiveRange, + const std::function onPurge) = 0; + + virtual void RemoveNode(uint32_t rid) = 0; + virtual void SetInvalid(int32_t repeatElmtId, uint32_t rid) = 0; + virtual void RequestContainerReLayout( + int32_t repeatElmtId, uint32_t totalCount, int32_t invalidateContainerLayoutFromChildIndex) = 0; + virtual void UpdateL1Rid4Index(int32_t repeatElmtId, uint32_t totalCount, + uint32_t invalidateContainerLayoutFromChildIndex, std::map& l1Rd4Index) = 0; + virtual void OnMove(std::function&& onMove) = 0; + +private: + static std::unique_ptr instance_; +}; +} // namespace OHOS::Ace + +#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_2_H diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp new file mode 100644 index 00000000000..534b036ca1b --- /dev/null +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h" + +#include "base/utils/utils.h" +#include "core/common/container.h" +#include "core/components_ng/base/view_stack_processor.h" +#include "core/components_ng/syntax/repeat_virtual_scroll_2_node.h" +#include "core/components_ng/syntax/syntax_item.h" + +namespace OHOS::Ace::NG { + +void RepeatVirtualScroll2ModelNG::Create(uint32_t totalCount, + const std::function(int32_t)>& onGetRid4Index, + const std::function onRecycleItems, + const std::function onActiveRange, + const std::function onPurge) +{ + ACE_SCOPED_TRACE("RepeatVirtualScroll2ModelNG::Create"); + auto* stack = ViewStackProcessor::GetInstance(); + auto nodeId = stack->ClaimNodeId(); + auto repeatNode = RepeatVirtualScroll2Node::GetOrCreateRepeatNode( + nodeId, totalCount, onGetRid4Index, onRecycleItems, onActiveRange, onPurge); + + stack->Push(repeatNode); + stack->PopContainer(); +} + +void RepeatVirtualScroll2ModelNG::RemoveNode(uint32_t rid) +{ + auto* stack = ViewStackProcessor::GetInstance(); + auto repeatNode = AceType::DynamicCast(stack->GetMainElementNode()); + CHECK_NULL_VOID(repeatNode); + repeatNode->RemoveNode(rid); +} + +void RepeatVirtualScroll2ModelNG::SetInvalid(int32_t repeatElmtId, uint32_t rid) +{ + auto* stack = ViewStackProcessor::GetInstance(); + auto repeatNode = AceType::DynamicCast(stack->GetMainElementNode()); + if (repeatNode == nullptr) { + repeatNode = ElementRegister::GetInstance()->GetSpecificItemById(repeatElmtId); + } + CHECK_NULL_VOID(repeatNode); + repeatNode->SetInvalid(rid); +} + +void RepeatVirtualScroll2ModelNG::RequestContainerReLayout( + int32_t repeatElmtId, uint32_t totalCount, int32_t invalidateContainerLayoutFromChildIndex) +{ + // called as part of Repeat re-render, call chain starts on TS side + // therefore, can not put RepeatVirtualScroll2Node to the ViewStackProcessor + // instead, lookup the node from ElementRegister + auto repeatNode = ElementRegister::GetInstance()->GetSpecificItemById(repeatElmtId); + CHECK_NULL_VOID(repeatNode); + repeatNode->UpdateTotalCount(totalCount); + repeatNode->RequestContainerReLayout(invalidateContainerLayoutFromChildIndex); +} + +void RepeatVirtualScroll2ModelNG::UpdateL1Rid4Index(int32_t repeatElmtId, uint32_t totalCount, + uint32_t invalidateContainerLayoutFromChildIndex, std::map& l1Rd4Index) +{ + auto* stack = ViewStackProcessor::GetInstance(); + auto repeatNode = AceType::DynamicCast(stack->GetMainElementNode()); + if (repeatNode == nullptr) { + repeatNode = ElementRegister::GetInstance()->GetSpecificItemById(repeatElmtId); + } + CHECK_NULL_VOID(repeatNode); + repeatNode->UpdateTotalCount(totalCount); + repeatNode->UpdateL1Rid4Index(l1Rd4Index); + repeatNode->RequestContainerReLayout(invalidateContainerLayoutFromChildIndex); +} + +void RepeatVirtualScroll2ModelNG::OnMove(std::function&& onMove) +{ + auto* stack = ViewStackProcessor::GetInstance(); + auto node = AceType::DynamicCast(stack->GetMainElementNode()); + CHECK_NULL_VOID(node); + node->SetOnMove(std::move(onMove)); +} + +} // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h new file mode 100644 index 00000000000..927abcaea6a --- /dev/null +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_2_MODEL_NG_H +#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_2_MODEL_NG_H + +#include +#include +#include +#include +#include + +#include "base/utils/macros.h" +#include "core/components_ng/syntax/repeat_virtual_scroll_2_model.h" + +namespace OHOS::Ace::NG { + +class ACE_EXPORT RepeatVirtualScroll2ModelNG : public RepeatVirtualScroll2Model { +public: + void Create(uint32_t totalCount, const std::function(int32_t)>& onGetRid4Index, + const std::function onRecycleItems, + const std::function onActiveRange, + const std::function onPurge) override; + + void RemoveNode(uint32_t rid) override; + void SetInvalid(int32_t repeatElmtId, uint32_t rid) override; + void RequestContainerReLayout( + int32_t repeatElmtId, uint32_t totalCount, int32_t invalidateContainerLayoutFromChildIndex) override; + void UpdateL1Rid4Index(int32_t repeatElmtId, uint32_t totalCount, uint32_t invalidateContainerLayoutFromChildIndex, + std::map& l1Rd4Index) override; + void OnMove(std::function&& onMove) override; +}; + +} // namespace OHOS::Ace::NG + +#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_2_MODEL_NG_H diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp new file mode 100644 index 00000000000..32dec117e3c --- /dev/null +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp @@ -0,0 +1,626 @@ +/* + * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "core/components_ng/syntax/repeat_virtual_scroll_2_node.h" + +#include +#include +#include + +#include "base/log/ace_trace.h" +#include "base/log/log_wrapper.h" +#include "core/components_ng/base/frame_node.h" +#include "core/pipeline/base/element_register.h" +#include "core/pipeline_ng/pipeline_context.h" + +namespace OHOS::Ace::NG { + +using CacheItem = RepeatVirtualScroll2Caches::CacheItem; + +// REPEAT +RefPtr RepeatVirtualScroll2Node::GetOrCreateRepeatNode(int32_t nodeId, uint32_t totalCount, + const std::function(IndexType)>& onGetRid4Index, + const std::function onRecycleItems, + const std::function onActiveRange, + const std::function onPurge) +{ + auto node = ElementRegister::GetInstance()->GetSpecificItemById(nodeId); + if (node) { + TAG_LOGD(AceLogTag::ACE_REPEAT, "Repeat(%{public}d).GetOrCreateRepeatNode Found RepeatVirtualScroll2Node", + static_cast(node->GetId())); + node->UpdateTotalCount(totalCount); + return node; + } + node = + MakeRefPtr(nodeId, totalCount, onGetRid4Index, onRecycleItems, onActiveRange, + onPurge); + + ElementRegister::GetInstance()->AddUINode(node); + return node; +} + +RepeatVirtualScroll2Node::RepeatVirtualScroll2Node(int32_t nodeId, int32_t totalCount, + const std::function(IndexType)>& onGetRid4Index, + const std::function onRecycleItems, + const std::function onActiveRange, + const std::function onPurge) + : ForEachBaseNode(V2::JS_REPEAT_ETS_TAG, nodeId), totalCount_(totalCount), caches_(onGetRid4Index), + onRecycleItems_(onRecycleItems), onActiveRange_(onActiveRange), onPurge_(onPurge), + postUpdateTaskHasBeenScheduled_(false) +{} + +bool RepeatVirtualScroll2Node::CheckNode4IndexInL1(int32_t index, int32_t start, int32_t end, int32_t cacheStart, + int32_t cacheEnd, RefPtr& frameNode, CacheItem& cacheItem) +{ + if (((start <= index) && (index <= end)) || ((end < start) && (index <= end || start <= index))) { + TAG_LOGD(AceLogTag::ACE_REPEAT, "in range: index %{public}d -> nodeId %{public}d: SetActive(true)", index, + static_cast(frameNode->GetId())); + frameNode->SetActive(true); + cacheItem->isActive_ = true; + } else { + TAG_LOGD(AceLogTag::ACE_REPEAT, "out of range: index %{public}d -> nodeId %{public}d: SetActive(false)", index, + frameNode->GetId()); + frameNode->SetActive(false); + cacheItem->isActive_ = false; + } + + auto totalCount = static_cast(totalCount_); + if ((start - cacheStart <= index) && (index <= end + cacheEnd)) { + cacheItem->isL1_ = true; + cacheItem->isOnRenderTree_ = true; + return true; + } + if (isLoop_) { + if (((end < start) && (start - cacheStart <= index || index <= end + cacheEnd)) || + ((start - cacheStart < 0) && (index >= start - cacheStart + totalCount)) || + ((end + cacheEnd >= totalCount) && (index <= end + cacheEnd - totalCount))) { + cacheItem->isL1_ = true; + cacheItem->isOnRenderTree_ = true; + return true; + } + } + cacheItem->isL1_ = false; + cacheItem->isOnRenderTree_ = false; + return false; +} + + +// added by jianjing +void RepeatVirtualScroll2Node::DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd, + bool showCache) +{ + // get normalized active range (with positive indices only) + int32_t nStart = start - cacheStart; + int32_t nEnd = end + cacheEnd; + if (totalCount_ == 0) { + nStart = 0; + nEnd = 0; + } else if (isLoop_) { + nStart = (nStart + totalCount_) % totalCount_; + nEnd = (nEnd + totalCount_) % totalCount_; + } else { + nStart = std::max(nStart, 0); + // start <= end <= totalCount - 1 + nEnd = std::min(std::max(nEnd, nStart), int32_t(totalCount_-1)); + } + + // step 1: if range unchanged skip full run unless forced (rerender will force run once) + if (!forceRunDoSetActiveRange_ && (prevActiveRangeStart_ == nStart && prevActiveRangeEnd_ == nEnd)) { + // skip executing if range has no changed + TAG_LOGD(AceLogTag::ACE_REPEAT, + "DoSetActiveChildRange: Repeat(nodeId): %{public}d: start: %{public}d - end: %{public}d; cacheStart: " + "%{public}d, cacheEnd: %{public}d: ==> normalized range to keep in L1: %{public}d - %{public}d is " + "unchanged. Skipping.", + GetId(), start, end, cacheStart, cacheEnd, nStart, nEnd); + return; + } + + TAG_LOGD(AceLogTag::ACE_REPEAT, + "REPEAT TRACE DoSetActiveChildRange: Repeat(nodeId): %{public}d: start: %{public}d - end: %{public}d; " + "cacheStart: %{public}d, cacheEnd: %{public}d: ==> normalized range to keep in L1: %{public}d - %{public}d,", + GetId(), start, end, cacheStart, cacheEnd, nStart, nEnd); + + // swap the ViewStackProcessor for secondary and push this node + // call sequence is C++ -> TS -> JS + // for TS -> JS to find 'this' node, we need to put 'this' to the 2nd + // ViewStackProcessor + NG::ScopedViewStackProcessor scopedViewStackProcessor; + auto* viewStack = NG::ViewStackProcessor::GetInstance(); + viewStack->Push(Referenced::Claim(this)); + + ACE_SCOPED_TRACE("Repeat.DoSetActiveChildRange start [%d] - end [%d; cacheStart: [%d], cacheEnd: [%d]", start, end, + cacheStart, cacheEnd); + + // step 2. call TS side + onActiveRange_(start, end, cacheStart, cacheEnd, isLoop_); + + // step 3: iterate over L1, for each entry check of it is still in active range + TAG_LOGD(AceLogTag::ACE_REPEAT, "Rebuild L1 on C++ side ..."); + bool needSync = RebuildL1(start, end, cacheStart, cacheEnd); + + // step 4: iterate over all UINode sub-trees, only interested in L2 ones + // for items moved from L1 to L2 but sitl active and on render tree, correct this. + TAG_LOGD(AceLogTag::ACE_REPEAT, "Checking spare nodes on C++ side ...."); + needSync = processActiveL2Nodes() || needSync; + + // memorize range + prevActiveRangeStart_ = nStart; + prevActiveRangeEnd_ = nEnd; + forceRunDoSetActiveRange_ = false; + + if (needSync) { + // step 5: order a resync from layout + // what these calls exactly do has never been defined for us. + RequestSyncTree(); + } + + TAG_LOGD(AceLogTag::ACE_REPEAT, + "DoSetActiveChildRange: Repeat(%{public}d): start: %{public}d - end: %{public}d; cacheStart: " + "%{public}d, cacheEnd: %{public}d: ==> normalized range to keep in L1: %{public}d - %{public}d \n UINode " + "cache:\n%{public}s \n %{public}s", + GetId(), start, end, cacheStart, cacheEnd, nStart, nEnd, caches_.DumpUINodeCache().c_str(), + caches_.DumpL1Rid4Index().c_str()); +} + +bool RepeatVirtualScroll2Node::RebuildL1(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd) +{ + return caches_.RebuildL1( + [&, start, end, cacheStart, cacheEnd, this](int32_t index, CacheItem& cacheItem) -> bool { + const auto node = cacheItem->node_; + if (!node) { + return false; + } + + auto frameNode = AceType::DynamicCast(node->GetFrameChildByIndex(0, true)); + if (!frameNode) { + return false; + } + + if (CheckNode4IndexInL1(index, start, end, cacheStart, cacheEnd, frameNode, cacheItem)) { + TAG_LOGD(AceLogTag::ACE_REPEAT, + " ... in L1: index %{public}d, node %{public}s with child id %{public}d: SetActive(True)", + index, caches_.DumpUINode(cacheItem->node_).c_str(), static_cast(frameNode->GetId())); + return true; + } + + TAG_LOGD(AceLogTag::ACE_REPEAT, + " ... out of L1: index %{public}d, node %{public}s with child id %{public}d: ", index, + caches_.DumpUINode(cacheItem->node_).c_str(), frameNode->GetId()); + + // move active node into L2 cached. check transition flag. + // Animations support need to modify here + if (!cacheItem->isOnRenderTree_) { + return false; + } + if (node->OnRemoveFromParent(true)) { + // OnRemoveFromParent returns true means the child can be removed from tree immediately. + RemoveDisappearingChild(node); + } else { + AddDisappearingChild(node); + } + return false; + } + ); +} + +bool RepeatVirtualScroll2Node::processActiveL2Nodes() +{ + bool needSync = false; + caches_.ForEachCacheItem([&](RIDType rid, const CacheItem& cacheItem) { + if (caches_.GetL1Index4RID(rid) != std::nullopt) { + // in L1 + return; + } + if (!cacheItem->isActive_ && !cacheItem->isOnRenderTree_) { + return; + } + // spare item is still active or on render tree + // two cases when this can happen: + // 1. remove from L1 by RecycleItems + // 2. Repeat.rerender + auto frameNode = AceType::DynamicCast(cacheItem->node_->GetFrameChildByIndex(0, true)); + if (frameNode && cacheItem->isActive_) { + frameNode->SetActive(false); + cacheItem->isActive_ = false; + needSync = true; + TAG_LOGD(AceLogTag::ACE_REPEAT, " ... spare node %{public}s: apply SetActive(false)", + caches_.DumpCacheItem(cacheItem).c_str()); + } + if (!cacheItem->isOnRenderTree_) { + return; + } + if (cacheItem->node_->OnRemoveFromParent(true)) { + // OnRemoveFromParent returns true means the child can be removed from tree immediately. + RemoveDisappearingChild(cacheItem->node_); + } else { + // else move child into disappearing children, skip syncing render tree + AddDisappearingChild(cacheItem->node_); + } + cacheItem->isOnRenderTree_ = false; + needSync = true; + TAG_LOGD(AceLogTag::ACE_REPEAT, " ... spare nodes %{public}s: removed node from render tree", + caches_.DumpCacheItem(cacheItem).c_str()); + }); + + return needSync; +} + +void RepeatVirtualScroll2Node::RequestSyncTree() +{ + TAG_LOGD(AceLogTag::ACE_REPEAT, "requesting sync of UI tree"); + UINode::MarkNeedSyncRenderTree(false); + children_.clear(); + // re-assemble children_ + PostIdleTask(); +} + +/** + * Currently NOT IN USE + */ +void RepeatVirtualScroll2Node::DoSetActiveChildRange( + const std::set& activeItems, const std::set& cachedItems, int32_t baseIndex) +{ + // Function currently unused + // when taking to use, need to update and align to 1st version of DoSetActiveChildRange above + // the call to TS is missing! + + bool needSync = + caches_.RebuildL1([&activeItems, &cachedItems, baseIndex, this](int32_t index, CacheItem& cacheItem) -> bool { + auto frameNode = AceType::DynamicCast(cacheItem->node_->GetFrameChildByIndex(0, true)); + if (!frameNode) { + return false; + } + if (activeItems.find(index + baseIndex) != activeItems.end()) { + frameNode->SetActive(true); + cacheItem->isL1_ = true; + cacheItem->isActive_ = true; + cacheItem->isOnRenderTree_ = true; + return true; + } else { + frameNode->SetActive(false); + cacheItem->isActive_ = false; + } + if (cachedItems.find(index + baseIndex) != cachedItems.end()) { + cacheItem->isL1_ = true; + return true; + } + + cacheItem->isOnRenderTree_ = false; + cacheItem->isL1_ = false; + if (cacheItem->node_->OnRemoveFromParent(true)) { + RemoveDisappearingChild(cacheItem->node_); + } else { + AddDisappearingChild(cacheItem->node_); + } + return false; + }); + if (needSync) { + UINode::MarkNeedSyncRenderTree(false); + children_.clear(); + // re-assemble children_ + PostIdleTask(); + } +} + +// TS calls this function at the end of rerender to update caches.l1Rid4Index_ +// forceRunDoSetActiveRange_ indicates that following DoSetActiveChildRange +// must do a full run even if range is unchanged (which is typically the case). +void RepeatVirtualScroll2Node::UpdateL1Rid4Index(std::map& l1Rd4Index) +{ + caches_.UpdateL1Rid4Index(l1Rd4Index); + + // run next DoSetActiveChild range even if range unchanged + forceRunDoSetActiveRange_ = true; +} + +void RepeatVirtualScroll2Node::RequestContainerReLayout(IndexType fromRepeatItemIndex) +{ + TAG_LOGD(AceLogTag::ACE_REPEAT, + "RequestContainerReLayout triggered by Repeat rerender: nodeId: %{public}d . Start re-layout from child index " + "%{public}d + startIndex_%{public}d /", + static_cast(GetId()), static_cast(fromRepeatItemIndex), startIndex_); + + children_.clear(); + + auto frameNode = GetParentFrameNode(); + if (frameNode) { + // container children starting from index 0 need to be updated + frameNode->ChildrenUpdatedFrom(fromRepeatItemIndex + startIndex_); + } + + // do not call when visible items have not changed + MarkNeedSyncRenderTree(true); + + // do not call when visible items have not changed + MarkNeedFrameFlushDirty(PROPERTY_UPDATE_MEASURE_SELF_AND_PARENT | PROPERTY_UPDATE_BY_CHILD_REQUEST); +} + +// called from container layout +// index N-th item +// needBuild: true - if found in cache, then return, if not in cache then return newly build +// false: - if found in cache, then return, if not found in cache then return nullptr +// isCache: true indicates prebuild item (only used by List/Grid/Waterflow, this item should go to L2 cache, +// do not add to the tree, +// isCache==false this item is for display or near display area +// addToRenderTree: true - set it to active state, call SetActive +RefPtr RepeatVirtualScroll2Node::GetFrameChildByIndex( + uint32_t index, bool needBuild, bool isCache, bool addToRenderTree) +{ + TAG_LOGD(AceLogTag::ACE_REPEAT, + "nodeId: %{public}d: GetFrameChildByIndex(index: %{public}d, " + "needBuild: %{public}d, isCache: %{public}d, " + "addToRenderTree: %{public}d) ...", + static_cast(GetId()), static_cast(index), static_cast(needBuild), + static_cast(isCache), static_cast(addToRenderTree)); + + ACE_SCOPED_TRACE("Repeat.GetFrameChildByIndex index[%d], needBuild[%d] isCache[%d] " + "addToRenderTree[%d]", + index, static_cast(needBuild), static_cast(isCache), static_cast(addToRenderTree)); + + if (prevRecycleFrom > 0 && prevRecycleFrom <= index && index < prevRecycleTo) { + TAG_LOGD(AceLogTag::ACE_REPEAT, "REPEAT TRACE ABNORMAL... layout requesting index %{public}d) that was just " + "informed to be recycled.", static_cast(index)); + } + + return GetFrameChildByIndexImpl(index, needBuild, isCache, addToRenderTree); +} + +RefPtr RepeatVirtualScroll2Node::GetFrameChildByIndexImpl( + uint32_t index, bool needBuild, bool isCache, bool addToRenderTree) +{ + std::pair resultPair = caches_.GetFrameChild(index, needBuild); + const bool hasNoNode = ((resultPair.first == ONGETRID4INDEX_RESULT_NO_NODE) + || (resultPair.second == nullptr) + || (resultPair.second->node_ == nullptr)); + + if (hasNoNode && !needBuild) { + TAG_LOGD(AceLogTag::ACE_REPEAT, + "... GetFrameChild(%{public}d) not in caches && needBuild==false, GetFrameChildByIndex returns nullptr .", + static_cast(index)); + return nullptr; + } + + // node for index needs to be created or updated on JS side + if (hasNoNode) { + TAG_LOGE(AceLogTag::ACE_REPEAT, + " ... GetFrameChild(%{public}d) failed to create new or update existing node. Non-recoverable error.", + static_cast(index)); + return nullptr; + } + + CacheItem& cacheItem4Index = resultPair.second; + + TAG_LOGD(AceLogTag::ACE_REPEAT, " ... GetFrameChild(%{public}d) returns node %{public}s .", + static_cast(index), caches_.DumpUINode(cacheItem4Index->node_).c_str()); + + if (isActive_) { + cacheItem4Index->node_->SetJSViewActive(true); + } + + if (addToRenderTree && !isCache) { + cacheItem4Index->node_->SetActive(true); + cacheItem4Index->isActive_ = true; + } + + if (cacheItem4Index->isOnRenderTree_ && (resultPair.first == ONGETRID4INDEX_RESULT_UNCHANGED_NODE)) { + return cacheItem4Index->node_->GetFrameChildByIndex(0, needBuild); + } + + // put to render tree + cacheItem4Index->isOnRenderTree_ = true; + + if (cacheItem4Index->node_->GetDepth() != GetDepth() + 1) { + cacheItem4Index->node_->SetDepth(GetDepth() + 1); + } + // attach to repeat node and pass context to it. + cacheItem4Index->node_->SetParent(WeakClaim(this)); + if (IsOnMainTree()) { + cacheItem4Index->node_->AttachToMainTree(false, GetContext()); + } + + MarkNeedSyncRenderTree(); + children_.clear(); + // re-assemble children_ + PostIdleTask(); + + auto childNode = cacheItem4Index->node_->GetFrameChildByIndex(0, needBuild); + if (onMoveEvent_) { + InitDragManager(AceType::DynamicCast(childNode)); + } + + return childNode; +} + +int32_t RepeatVirtualScroll2Node::GetFrameNodeIndex(const RefPtr& node, bool /*isExpanded*/) +{ + TAG_LOGD(AceLogTag::ACE_REPEAT, "GetFrameNodeIndex "); + const std::optional& indexOpt = caches_.GetL1Index4Node(node); + return indexOpt.has_value() ? indexOpt.value() : -1; +} + +const std::list>& RepeatVirtualScroll2Node::GetChildren(bool /*notDetach*/) const +{ + if (!children_.empty()) { + TAG_LOGD(AceLogTag::ACE_REPEAT, "Repeat(%{public}d).GetChildren just returns non-empty children_", + static_cast(GetId())); + return children_; + } + + TAG_LOGD(AceLogTag::ACE_REPEAT, "GetChildren rebuild starting ...."); + + // can not modify l1_cache while iterating + // GetChildren is overloaded, can not change it to non-const + // need to order the child. + caches_.ForEachL1Node( + [&](IndexType index, RIDType rid, const RefPtr& node) -> void { children_.emplace_back(node); }); + return children_; +} + +// called by container layout +// instructs range of L1 items to move to L2, make available for update +// unfortunately, layout gets it occasionally wrong during fast scroll +// makes subsequent GetFrameChildByIndex on an index inside recycle range. +// call forwards to TS onRecycleItems_ +void RepeatVirtualScroll2Node::RecycleItems(int32_t from, int32_t to) +{ + if (from+1>=to) { + return; + } + if (from == prevRecycleFrom && to == prevRecycleTo) { + return; + } + + TAG_LOGD(AceLogTag::ACE_REPEAT, + "REPEAT TRACE Repeat(%{public}d: RecycleItems(from: %{public}d to: %{public}d), startIndex: " + "%{public}d: repeatItem " + "%{public}d up to smaller than " + "%{public}d to the reusable items cache", + static_cast(GetId()), from, to, startIndex_, from - startIndex_, to - startIndex_); + + // swap the ViewStackProcessor instance for secondary and push this node + // call sequence is C++ -> TS -> JS + // for TS -> JS to find 'this' node, we need to put 'this' to the 2nd + // ViewStackProcessor + NG::ScopedViewStackProcessor scopedViewStackProcessor; + auto* viewStack = NG::ViewStackProcessor::GetInstance(); + viewStack->Push(Referenced::Claim(this)); + + onRecycleItems_(from - startIndex_, to - startIndex_); + + prevRecycleFrom = from; + prevRecycleTo = to; + + TAG_LOGD(AceLogTag::ACE_REPEAT, "%{public}s \n %{public}s", caches_.DumpUINodeCache().c_str(), + caches_.DumpL1Rid4Index().c_str()); +} + +// Purge scheduled as idle task by DoSetActiveChildRange +// enforces L2 cache size limits at the end of processing 'cycle' +// means: L2 cache limits are allowed to be exceeded during one execution 'cycle' +// RecycleItems >>> GetFrameChildByIndex >>> DoSetActiveChildRange >> Purge +// justification: must allow items are put from L1 to L2 but updated and put back to L1 during same cycle +void RepeatVirtualScroll2Node::Purge() +{ + // swap the ViewStackProcessor instance for secondary and push this node + // call sequence is C++ -> TS -> JS + // for TS -> JS to find 'this' node, we need to put 'this' to the 2nd + // ViewStackProcessor + NG::ScopedViewStackProcessor scopedViewStackProcessor; + auto* viewStack = NG::ViewStackProcessor::GetInstance(); + viewStack->Push(Referenced::Claim(this)); + + onPurge_(); +} + +void RepeatVirtualScroll2Node::SetNodeIndexOffset(int32_t start, int32_t /*count*/) +{ + startIndex_ = start; +} + +// called by components, returns app-defined totalCount +// rerender updated app-defined totalCount to totalCount_ +int32_t RepeatVirtualScroll2Node::FrameCount() const +{ + TAG_LOGD(AceLogTag::ACE_REPEAT, "FrameCount returns %{public}d", static_cast(totalCount_)); + return totalCount_; +} + +void RepeatVirtualScroll2Node::PostIdleTask() +{ + if (postUpdateTaskHasBeenScheduled_) { + return; + } + TAG_LOGD(AceLogTag::ACE_REPEAT, "Repeat(%{public}d).PostIdleTask Posting idle task", + static_cast(GetId())); + postUpdateTaskHasBeenScheduled_ = true; + auto* context = GetContext(); + CHECK_NULL_VOID(context); + + context->AddPredictTask([weak = AceType::WeakClaim(this)](int64_t /*deadline*/, bool /*canUseLongPredictTask*/) { + ACE_SCOPED_TRACE("Repeat.IdleTask"); + auto node = weak.Upgrade(); + CHECK_NULL_VOID(node); + node->postUpdateTaskHasBeenScheduled_ = false; + TAG_LOGD(AceLogTag::ACE_REPEAT, "Repeat(%{public}d).PostIdleTask idle task calls GetChildren", + static_cast(node->GetId())); + node->GetChildren(); + + TAG_LOGD(AceLogTag::ACE_REPEAT, "idle task calls Purge"); + node->Purge(); + + TAG_LOGD(AceLogTag::ACE_REPEAT, " ============ after caches.purge ============= "); + TAG_LOGD(AceLogTag::ACE_REPEAT, "%{public}s", node->caches_.DumpUINodeCache().c_str()); + TAG_LOGD(AceLogTag::ACE_REPEAT, "%{public}s", node->caches_.DumpL1Rid4Index().c_str()); + TAG_LOGD(AceLogTag::ACE_REPEAT, " ============ done caches.purge ============= "); + }); +} + +void RepeatVirtualScroll2Node::OnConfigurationUpdate(const ConfigurationChange& configurationChange) +{ + if ((configurationChange.colorModeUpdate || configurationChange.fontUpdate)) { + caches_.ForEachCacheItem([&configurationChange](RIDType rid, const CacheItem& cacheItem) { + if (cacheItem->node_ != nullptr) { + cacheItem->node_->UpdateConfigurationUpdate(configurationChange); + } + }); + } +} + +void RepeatVirtualScroll2Node::SetJSViewActive(bool active, bool isLazyForEachNode, bool isReuse) +{ + TAG_LOGD(AceLogTag::ACE_REPEAT, "SetJSViewActive ..."); + caches_.ForEachCacheItem([active](RIDType rid, const CacheItem& cacheItem) { + if (cacheItem->node_ != nullptr) { + cacheItem->node_->SetJSViewActive(active); + } + }); + isActive_ = active; +} + +void RepeatVirtualScroll2Node::PaintDebugBoundaryTreeAll(bool flag) +{ + caches_.ForEachCacheItem([flag](RIDType rid, const CacheItem& cacheItem) { + if (cacheItem->node_ != nullptr) { + cacheItem->node_->PaintDebugBoundaryTreeAll(flag); + } + }); +} + +void RepeatVirtualScroll2Node::SetOnMove(std::function&& onMove) +{ + // To do +} + +// FOREAch +void RepeatVirtualScroll2Node::MoveData(int32_t from, int32_t to) { + // to do +} + +RefPtr RepeatVirtualScroll2Node::GetFrameNode(int32_t index) +{ + return AceType::DynamicCast(GetFrameChildByIndex(index, false, false)); +} + +void RepeatVirtualScroll2Node::InitDragManager(const RefPtr& child) +{ + // To do +} + +void RepeatVirtualScroll2Node::InitAllChildrenDragManager(bool init) +{ + // To do +} + +} // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h new file mode 100644 index 00000000000..9c85beab2ff --- /dev/null +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Repeat virtual scroll framework implementation design + * outline of RepeatVirtualScroll2Node and RepeatVirtualScroll2Caches C++ helper class + * for documentation of TS see state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts + * + * 1. scenario: scroll + * =================== + * + * Container Layout calls RepeatVirtualScroll2Node::RecycleItems(int32_t from, int32_t to) + * 1.1 forwards the call to TS onRecycleItems_(from, to) + * + * Layout calls RepeatVirtualScroll2Node::GetFrameChildByIndex(index, ....) several times + * 2.1 forward call to RepeatVirtualScroll2Caches::GetFrameChild(index) + * 2.2 if UINode for this index in L1, use it + * 2.3 if no UINode for this index in L1, call TS by calling onGetRid4Index_(index) lambda + * three result cases for onGetRid4Index_ function: + * - TS has newly rendered UINode sub-tree, add the cache and the L1, return UINode + * - TS has update a UINode subtree, add to L1, return UINode + * - TS failed to JS render, return nullptr + * + * Layout calls RepeatVirtualScroll2Node::DoSetActiveChildRange(....) + * 3.1 if range unchanged and not force run full activeRange after rerender, skip. + * 3.2 forward call to TS by calling onActiveRange_ lambda + * 3.3 iterate over L1 to check or each UINode if still in active range. + * 3.4 iterate over entire (L1 + L2) cache to set active status and remove from render tree + * of items moved to L2 by rerender. + * + * 4. purge (idle task) triggered by DoSetActiveChildRange + * 4.1 iterate the cache and enforce cache size limits (forwards to TS) + * + * 2. scenario Repeat rerender + * ============================ + * + * owning ViewV2.Updateelement calls __RepeatVirtualScrollImpl.rerender() + * all processing on TS side, see TS documentation + * rerender finishes with updateL1Rid4Index call C++ as shown below. + * + * C++ lambdas to call these TS functions + * onGetRid4Index: (forIndex: number) => [number, number], + * onRecycleItems: (fromIndex: number, toIndex: number) => void, + * onActiveRange: (fromIndex: number, toIndex: number, fromCache: number, toCache: number, isLoop:boolean) => void, + * onPurge: () => void; + * onMoveHandler: (from: number, to: number) => void; + * + * C++ functions exposed to JS + * (compare RepeatVirtualScrollNative class in pu_foreach.d.ts, JS interface in js_virtual_scroll.cpp/h, + * model implementation in virtual_scroll_model_ng.cpp/h) + * + * TS RemoveNode(rid: number): void --> C++ RepeatVirtualScroll2Node::RemoveNode(rid) used by onPurge + * TS setInvalid(repeatElmtId: number, rid: number): void; --> RepeatVirtualScroll2Node::SetInvalid(rid) + * TS requestContainerReLayout(repeatElmtId: number, totalCount: number, + * invalidateContainerLayoutFromChildIndex: number): void --> + * C++ RepeatVirtualScroll2Node::SetTotalCount(totalCount) + * C++ RepeatVirtualScroll2Node::RequestContainerReLayout(invalidateContainerLayoutFromChildIndex) + * TS updateL1Rid4Index(repeatElmtId: number, totalCount: number, invalidateContainerLayoutFromChildIndex: number, + * l1rid4index: Array>): void --> + * C++ RepeatVirtualScroll2Node::SetTotalCount(totalCount) + * C++ RepeatVirtualScroll2Node::UpdateL1Rid4Index(std::map& l1Rd4Index); + * C++ RepeatVirtualScroll2Node::RequestContainerReLayout(invalidateContainerLayoutFromChildIndex) + */ + +#ifndef FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_2_NODE_H +#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_2_NODE_H + +#include +#include +#include + +#include "base/memory/referenced.h" +#include "base/utils/macros.h" +#include "core/components_ng/base/ui_node.h" +#include "core/components_ng/syntax/for_each_base_node.h" +#include "core/components_ng/syntax/repeat_virtual_scroll_2_caches.h" + +namespace OHOS::Ace::NG { +class ACE_EXPORT RepeatVirtualScroll2Node : public ForEachBaseNode { + DECLARE_ACE_TYPE(RepeatVirtualScroll2Node, ForEachBaseNode); + +public: + static RefPtr GetOrCreateRepeatNode(int32_t nodeId, uint32_t totalCount, + const std::function(IndexType)>& onGetRid4Index, + const std::function onRecycleItems, + const std::function onActiveRange, + const std::function onPurge); + + RepeatVirtualScroll2Node(int32_t nodeId, int32_t totalCount, + const std::function(IndexType)>& onGetRid4Index, + const std::function onRecycleItems, + const std::function onActiveRange, + const std::function onPurge); + + ~RepeatVirtualScroll2Node() override = default; + + void UpdateTotalCount(uint32_t totalCount) + { + totalCount_ = totalCount; + } + + // Number of children that Repeat can product + // returns TotalCount + int32_t FrameCount() const override; + + // called from TS upon Repeat rerender + // tell the Container to invalid its layout + // incl re-layout of children start from startIndex + void RequestContainerReLayout(IndexType startIndex); + + /** + * GetChildren re-assembles children_ and cleanup the L1 cache + * active items remain in L1 cache and are added to RepeatVirtualScroll.children_ + * inactive items are moved from L1 to L2 cache, not added to children_ + * function returns children_ + * function runs as part of idle task + */ + const std::list>& GetChildren(bool notDetach = false) const override; + + /** + * scenario: called by layout informs: + * - start: the first visible index + * - end: the last visible index + * - cacheStart: number of items cached before start + * - cacheEnd: number of items cached after end + * + * Total L1 cache includes items from start-cacheStart to end+cacheEnd, + * but the active items are only start...end. + * + * those items with index in range [ start ... end ] are marked active + * those out of range marked inactive. + * + * those items out of cached range are removed from L1 + * requests idle task + */ + void DoSetActiveChildRange( + int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd, bool showCache = false) override; + + /** + * those items with index in cachedItems are marked active + * those items with index in cachedItems are marked inactive + * baseIndex is repeat first node index + */ + void DoSetActiveChildRange( + const std::set& activeItems, const std::set& cachedItems, int32_t baseIndex) override; + + // largely unknown when it is expected to be called + // meant to inform which items with index [ from .. to ] can be recycled / updated + void RecycleItems(int32_t from, int32_t to) override; + + // Called by parent generate frame child. + void SetNodeIndexOffset(int32_t start, int32_t count) override; + + /** Called by Layout to request ListItem and child subtree + for given index + either returns existing item for index from L1 or L2 cache, or gets a new item created + update of L2 cache item to new index) + result is in L1 cache if isCache is false, and L2 cache if isCache is true. + + meaning of parameters + needBuild: true - if found in cache, then return, if not in cache then return newly build + false: - if found in cache, then return, if not found in cache then return nullptr + isCache: true indicates prebuild item (only used by List/Grid/Waterflow, this item should go to L2 cache, + do not add to the tree, + isCaxche==false this item is for display or near display area + addToRenderTree: true - set it to active state, call SetActive + */ + RefPtr GetFrameChildByIndex( + uint32_t index, bool needBuild, bool isCache = false, bool addToRenderTree = false) override; + + bool IsAtomicNode() const override + { + return false; + } + + // used for drag move operation. + void SetOnMove(std::function&& onMove); + void MoveData(int32_t from, int32_t to) override; + RefPtr GetFrameNode(int32_t index) override; + int32_t GetFrameNodeIndex(const RefPtr& node, bool isExpanded = true) override; + void InitDragManager(const RefPtr& childNode); + void InitAllChildrenDragManager(bool init); + + void OnConfigurationUpdate(const ConfigurationChange& configurationChange) override; + + void SetJSViewActive(bool active = true, bool isLazyForEachNode = false, bool isReuse = false) override; + void PaintDebugBoundaryTreeAll(bool flag) override; + + void RemoveNode(RIDType rid) + { + caches_.RemoveNode(rid); + } + + void SetInvalid(RIDType rid) + { + caches_.SetInvalid(rid); + } + + // TS call JS to update caches.l1Rid4Index_ + // and invalidate container layout following a + // Repeat.rerender + void UpdateL1Rid4Index(std::map& l1Rd4Index); + + + void SetIsLoop(bool isLoop) + { + isLoop_ = isLoop; + } + +private: + RefPtr GetFrameChildByIndexImpl( + uint32_t index, bool needBuild, bool isCache, bool addToRenderTree); + + bool CheckNode4IndexInL1(int32_t index, int32_t start, int32_t end, int32_t cacheStart, + int32_t cacheEnd, RefPtr& frameNode, RepeatVirtualScroll2Caches::CacheItem& cacheItem); + + bool RebuildL1(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd); + bool processActiveL2Nodes(); + + void RequestSyncTree(); + void PostIdleTask(); + + // tell TS to purge nodes exceeding cachedCount + void Purge(); + + // RepeatVirtualScroll2Node is not instance of FrameNode + // needs to propagate active state to all items inside + bool isActive_ = true; + + // size of data source when all data items loaded + uint32_t totalCount_ = 0; + + // loop property of the parent container + bool isLoop_ = false; + + // caches: + mutable RepeatVirtualScroll2Caches caches_; + + // used by one of the unknown functions + std::list ids_; + + // re-assembled by GetChildren called from idle task + mutable std::list> children_; + + int32_t startIndex_ = 0; + + // memorize parameters of previous DoSetActiveRange class + // to skip processing if params unchanged + int32_t prevActiveRangeStart_ = 0; + int32_t prevActiveRangeEnd_ = -1; + + // remove from final version + int32_t prevRecycleFrom = -1; + int32_t prevRecycleTo = -1; + + // run next DoSetActiveChild range even if range unchanged + bool forceRunDoSetActiveRange_ = false; + + std::function onRecycleItems_; + std::function onActiveRange_; + std::function onPurge_; + + // true in the time from requesting idle / predict task until exec predict tsk. + bool postUpdateTaskHasBeenScheduled_; + + ACE_DISALLOW_COPY_AND_MOVE(RepeatVirtualScroll2Node); +}; +} // namespace OHOS::Ace::NG + +#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_2_NODE_H diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_caches.cpp b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_caches.cpp index f14926eb74b..cffc121eea9 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_caches.cpp +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_caches.cpp @@ -19,6 +19,13 @@ namespace OHOS::Ace::NG { +using CacheItem = RepeatVirtualScrollCaches::CacheItem; + +bool KeySorterClass::operator()(const std::string& left, const std::string& right) const +{ + return virtualScroll_->CompareKeyByIndexDistance(left, right); +} + RepeatVirtualScrollCaches::RepeatVirtualScrollCaches( const std::map>& cacheCountL24ttype, const std::function& onCreateNode, diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_caches.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_caches.h index 74a0774ea55..a0153649e52 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_caches.h +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_caches.h @@ -33,12 +33,21 @@ namespace OHOS::Ace::NG { // custom sorting for std::set only works with struct // with operator() inside class RepeatVirtualScrollCaches; -struct CacheItem { - bool isValid = false; - RefPtr item; +struct KeySorterClass { + const RepeatVirtualScrollCaches* virtualScroll_; + + explicit KeySorterClass(const RepeatVirtualScrollCaches* virtualScroll) : virtualScroll_(virtualScroll) {} + bool operator()(const std::string& left, const std::string& right) const; }; class RepeatVirtualScrollCaches { + friend struct KeySorterClass; +public: + struct CacheItem { + bool isValid = false; + RefPtr item; + }; + public: RepeatVirtualScrollCaches(const std::map>& cacheCountL24ttype, const std::function& onCreateNode, diff --git a/test/unittest/core/syntax/repeat_node_cache_syntax_test.cpp b/test/unittest/core/syntax/repeat_node_cache_syntax_test.cpp index c1cc4ae9238..b86c3bcf3d0 100644 --- a/test/unittest/core/syntax/repeat_node_cache_syntax_test.cpp +++ b/test/unittest/core/syntax/repeat_node_cache_syntax_test.cpp @@ -53,6 +53,8 @@ constexpr int32_t COUNT_1 = 1; constexpr int32_t COUNT_3 = 3; } // namespace +using CacheItem = RepeatVirtualScrollCaches::CacheItem; + class RepeatNodeCacheSyntaxTest : public testing::Test { public: -- Gitee From bc4b6adfc58de806171c4806dd207a0a96f6a97f Mon Sep 17 00:00:00 2001 From: liubihao Date: Sat, 25 Jan 2025 10:09:30 +0800 Subject: [PATCH 2/8] Repeat: optimize ActiveRange & fix code check. Signed-off-by: liubihao --- .../jsview/js_repeat_virtual_scroll_2.cpp | 7 +- .../src/lib/partial_update/pu_foreach.d.ts | 2 +- .../src/lib/partial_update/pu_repeat.ts | 2 +- .../pu_repeat_virtual_scroll_2_impl.ts | 137 ++++++----- .../syntax/repeat_virtual_scroll_2_caches.cpp | 3 +- .../syntax/repeat_virtual_scroll_2_caches.h | 1 + .../syntax/repeat_virtual_scroll_2_model.h | 2 +- .../repeat_virtual_scroll_2_model_ng.cpp | 2 +- .../syntax/repeat_virtual_scroll_2_model_ng.h | 2 +- .../syntax/repeat_virtual_scroll_2_node.cpp | 227 ++++++++++-------- .../syntax/repeat_virtual_scroll_2_node.h | 22 +- 11 files changed, 221 insertions(+), 186 deletions(-) diff --git a/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp b/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp index 93cc83a2638..275821501d5 100644 --- a/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp +++ b/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp @@ -58,7 +58,8 @@ static bool ParseAndVerifyParams(const JSCallbackInfo& info) auto handlers = JSRef::Cast(info[PARAM_HANDLERS]); return (handlers->GetProperty("onGetRid4Index")->IsFunction() && handlers->GetProperty("onRecycleItems")->IsFunction() && - handlers->GetProperty("onActiveRange")->IsFunction() && handlers->GetProperty("onPurge")->IsFunction()); + handlers->GetProperty("onActiveRange")->IsFunction() && + handlers->GetProperty("onPurge")->IsFunction()); } void JSRepeatVirtualScroll2::Create(const JSCallbackInfo& info) @@ -96,9 +97,9 @@ void JSRepeatVirtualScroll2::Create(const JSCallbackInfo& info) }; auto onActiveRange = [execCtx = info.GetExecutionContext(), func = GetJSFunc(handlers, "onActiveRange")]( - int32_t fromIndex, int32_t toIndex, int32_t fromCache, int32_t toCache, bool isLoop) -> void { + int32_t fromIndex, int32_t toIndex) -> void { JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx); - auto params = ConvertToJSValues(fromIndex, toIndex, fromCache, toCache, isLoop); + auto params = ConvertToJSValues(fromIndex, toIndex); func->Call(JSRef(), params.size(), params.data()); }; diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_foreach.d.ts b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_foreach.d.ts index ddb3d5c7506..219ccd01e24 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_foreach.d.ts +++ b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_foreach.d.ts @@ -72,7 +72,7 @@ declare class RepeatVirtualScroll2Native { handlers: { onGetRid4Index: (forIndex: number) => [number, number], onRecycleItems: (fromIndex: number, toIndex: number) => void, - onActiveRange: (fromIndex: number, toIndex: number, fromCache: number, toCache: number, isLoop: boolean) => void, + onActiveRange: (fromIndex: number, toIndex: number) => void, onPurge: () => void; } ): void; diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat.ts b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat.ts index 7b3a37290ba..2fb1d146670 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat.ts +++ b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat.ts @@ -197,7 +197,7 @@ class __Repeat implements RepeatAPI { public virtualScroll(options? : { totalCount?: number, reusable?: boolean }): RepeatAPI { // totalCount must be integer and must be 0 or larger - if (Number.isInteger(options?.totalCount) && options.totalCount >=0) { + if (Number.isInteger(options?.totalCount) && options.totalCount >= 0) { this.config.totalCount = options.totalCount; this.config.totalCountSpecified = true; } else { diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts index c2c4cc49290..812ad890a3e 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts +++ b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts @@ -28,7 +28,7 @@ * 2- calculates the templateId (called ttype internally) for this index, then * 3- canUpdateTryMatch(index, ttype) tries to find spare UINode tree (identified by its RID) * 4- if found calls updateChild(rid, item, index, ....) - * updateChild simply updates repeatItem.item, .index, and requests UI updates synchronously (ypdateDirty2(true) + * updateChild simply updates repeatItem.item, .index, and requests UI updates synchronously (updateDirty2(true) * TS informs to C++ the RID , and that existing UINode sub-tree has been updated * 5- if not fond calls createNewChild(index, ttype, ...) * creates a new RepeatItem instance with a new RID @@ -40,7 +40,7 @@ * onRecycleItems(fromIndex, upToBeforeIndex) - called from C++ RecycleItems * - move L1 items with fromIndex <= index < upToBeforeIndex to L2. * calls C++ RepeatVirtualScroll2Native.setInvalid so also C++ side moves the RID from L1 to L2 -* C++ does NOT remove the item from active tree, or change its Active status + * C++ does NOT remove the item from active tree, or change its Active status * if so needed at the end of one processing cycle onActiveRange will do * * onActiveRange(....) - called from C++ @@ -53,12 +53,12 @@ * d) calculate dynamicCachedCount for .each and for templateIds that do not have specified cachedCount option * * - Note: the rather convolute algorithm that uses parameters to decide if item is in active range or not - * needs to be exactly same in this function on TS side and on C++ side to ensure L1 info in TS and in C++ side + * needs to be exactly same in this function on TS side and on C++ side to ensure L1 info in TS and in C++ side * reman in sync. * * onPurge called from C++ RepeatVirtualScrollNode::Purge in idle task * ensure L1 side is within limits (cachedCount), deletes UINode subtrees that do not fit the permissible side - * cachedCount is defined for each templateId / ttype for for .each separately/ + * cachedCount is defined for each templateId / ttype for for .each separately * * * rerender called from ViewV2 UpdateElement(repeatElmtId) @@ -97,7 +97,7 @@ * - for each RID: * - constant: RepeatItem * - constant: ttype - * - mutableL key + * - mutable: key * - counterpart on C++ side RepeatVirtualScrollCaches.cacheItem4Rid_ maps RID -> UINode * * activeDataItems - Array> @@ -152,7 +152,7 @@ class ActiveDataItem { public toString(): string { return this.state === ActiveDataItem.UINodeExists ? - `[rid: ${this.rid}, ttype: ${this.ttype}${this.key ? ", key: "+this.key : ""}]` : `[no item]`; + `[rid: ${this.rid}, ttype: ${this.ttype}${this.key ? ", key: " + this.key : ""}]` : `[no item]`; } public dump(): string { @@ -207,7 +207,7 @@ class __RepeatVirtualScroll2Impl { private arr_: Array; // key function - private keyGenFunc_? : RepeatKeyGenFunc; + private keyGenFunc_?: RepeatKeyGenFunc; // is key function specified ? private useKeys_: boolean = false; @@ -220,7 +220,7 @@ class __RepeatVirtualScroll2Impl { private itemGenFuncs_: { [type: string]: RepeatItemGenFunc }; // templateId function - private ttypeGenFunc_? : RepeatTTypeGenFunc; + private ttypeGenFunc_?: RepeatTTypeGenFunc; // virtualScroll({ totalCount: non-function expression }), optional to set private totalCount_: number = 0; @@ -252,7 +252,7 @@ class __RepeatVirtualScroll2Impl { // Map containing all rid: rid -> ttype, // entires never change - // private ttype4Rid_: Map = new Map(); + // private ttype4Rid_: Map = new Map(); // sparse Array containing copy of data items and optionally keys in active range private activeDataItems_: Array> = new Array>(); @@ -274,19 +274,19 @@ class __RepeatVirtualScroll2Impl { /** * return array item if it exists - * todo try to lazy load if it does nit exist + * todo try to lazy load if it does not exist * @param index * @returns tuple data item exists , data item * (need to do like this to differentiate missing data item and undefined item value * same as std::optional in C++) */ private getItemUnmonitored(index: number | string): [boolean, T] { - stateMgmtConsole.debug(`getItemUnmonitored ${index} data item exists: ${index in this.arr_}`) + stateMgmtConsole.debug(`getItemUnmonitored ${index} data item exists: ${index in this.arr_}`); return [(index in this.arr_), this.arr_[index]]; } private getItemMonitored(index: number | string): [boolean, T] { - stateMgmtConsole.debug(`getItemMonitored ${index} data item exists: ${index in this.arr_}`) + stateMgmtConsole.debug(`getItemMonitored ${index} data item exists: ${index in this.arr_}`); this.startRecordDependencies(/* do not clear bindings */ false); @@ -306,7 +306,9 @@ class __RepeatVirtualScroll2Impl { // if totalCountSpecified==false, then need to create dependency on array length // so when array length changes, will update totalCount. use totalCountFunc_ for this - this.totalCountFunc_ = config.totalCountSpecified ? (typeof config.totalCount === 'function' ? config.totalCount : undefined) : () => this.arr_.length; + this.totalCountFunc_ = config.totalCountSpecified ? + (typeof config.totalCount === 'function' ? config.totalCount : undefined) : + () => this.arr_.length; if (this.totalCountFunc_) { this.totalCount_ = this.totalCountFunc_(); // Check legal totalCount value @@ -317,7 +319,9 @@ class __RepeatVirtualScroll2Impl { this.totalCount_ = config.totalCount as number; } - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) render: totalCountFunc_ ${this.totalCountFunc_ ? this.totalCountFunc_() : 'N/A'}, totalCount ${this.totalCount_} arr length ${this.arr_.length} .`); + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_})`, + `render: totalCountFunc_ ${this.totalCountFunc_ ? this.totalCountFunc_() : 'N/A'},`, + `totalCount ${this.totalCount_} arr length ${this.arr_.length} .`); if (isInitialRender) { this.owningViewV2_ = config.owningView_; @@ -332,7 +336,8 @@ class __RepeatVirtualScroll2Impl { this.onMoveHandler_ = config.onMoveHandler; if (!this.itemGenFuncs_[RepeatEachFuncTtype]) { - throw new Error(`${this.constructor.name}(${this.repeatElmtId_})) lacks mandatory '.each' attribute function, i.e. has no default item builder. Application error!`); + throw new Error(`${this.constructor.name}(${this.repeatElmtId_}))` + + `lacks mandatory '.each' attribute function, i.e. has no default item builder. Application error!`); } this.initialRender(); @@ -397,7 +402,7 @@ class __RepeatVirtualScroll2Impl { stateMgmtConsole.debug(` ... checking range ${activeRangeFrom} - ${activeRangeTo}`); - let firstIndexChanged = Math.min(activeRangeTo + 1, this.arr_.length); + let firstIndexChanged = Math.min(activeRangeTo + 1, this.arr_.length); // replacement for this.activeDataItems_ const newActiveDataItems: Array> = new Array>(); @@ -405,11 +410,11 @@ class __RepeatVirtualScroll2Impl { // replacement for l1Rid4Index_ index -> rid map on C++ side // will send to C++ when done const newL1Rid4Index = new Map(); - + // clear keys for new rerender this.key4Index_.clear(); this.index4Key_.clear(); - + // 1. move data items to newActiveDataItems that are unchanged (same item / same key, still at same index, same ttyp) // create createMissingDataItem -type entries for all other new data items. let hasChanges = false; @@ -440,7 +445,7 @@ class __RepeatVirtualScroll2Impl { if ((ttype === this.activeDataItems_[activeIndex].ttype) && ((!this.useKeys_ && dataItemAtIndex === this.activeDataItems_[activeIndex].item) || (this.useKeys_ && key === this.activeDataItems_[activeIndex].key))) { - stateMgmtConsole.debug(` ... index ${activeIndex} ttype ${ttype}${this.useKeys_ ? ", key " + key : ""} and dataItem unchanged.`); + stateMgmtConsole.debug(` ... index ${activeIndex} ttype '${ttype}'${this.useKeys_ ? ", key " + key : ""} and dataItem unchanged.`); newActiveDataItems[activeIndex] = this.activeDataItems_[activeIndex]; // add to index -> rid map to be sent to C++ @@ -462,7 +467,8 @@ class __RepeatVirtualScroll2Impl { // triggers layout to request FrameCount() / totalCount and if increased newly added source array items // FIXME TODO correct??? Math.min(this.totalCount_ - 1, activeRangeTo + 1) this.activeDataItems_ = newActiveDataItems; - RepeatVirtualScroll2Native.requestContainerReLayout(this.repeatElmtId_, this.totalCount_, Math.min(this.totalCount_ - 1, activeRangeTo + 1)); + RepeatVirtualScroll2Native.requestContainerReLayout( + this.repeatElmtId_, this.totalCount_, Math.min(this.totalCount_ - 1, activeRangeTo + 1)); return; } @@ -583,12 +589,12 @@ class __RepeatVirtualScroll2Impl { this.activeDataItems_ = newActiveDataItems; - stateMgmtConsole.debug(`rerender result: `); - stateMgmtConsole.debug(`spareRid : ${this.dumpSpareRid()}`); - stateMgmtConsole.debug(`this.dumpDataItems: ${this.activeDataItems_}`); - stateMgmtConsole.debug(`newL1Rid4Index: ${JSON.stringify(Array.from(newL1Rid4Index))}`) - stateMgmtConsole.debug(` ... first item changed at index ${firstIndexChanged} .`); - //RepeatVirtualScroll2Native.requestContainerReLayout(this.repeatElmtId_, 0); + stateMgmtConsole.debug(`rerender result: `, + `\nspareRid : ${this.dumpSpareRid()}`, + `\nthis.dumpDataItems: ${this.activeDataItems_}`, + `\nnewL1Rid4Index: ${JSON.stringify(Array.from(newL1Rid4Index))}`, + `\n ... first item changed at index ${firstIndexChanged} .`); + // RepeatVirtualScroll2Native.requestContainerReLayout(this.repeatElmtId_, 0); RepeatVirtualScroll2Native.updateL1Rid4Index(this.repeatElmtId_, this.totalCount_, firstIndexChanged, Array.from(newL1Rid4Index)); @@ -616,7 +622,8 @@ class __RepeatVirtualScroll2Impl { return ttype; } - private computeKey(item : T, index: number, monitorAccess: boolean = true, activateDataItems? : Array>) : string | undefined { + private computeKey(item : T, index: number, monitorAccess: boolean = true, + activateDataItems? : Array>) : string | undefined { if (!this.useKeys_) { return undefined; } @@ -652,31 +659,33 @@ class __RepeatVirtualScroll2Impl { need to change the key for both index'es returns random key for index 2 */ - private handleDuplicateKey(index1 : number, index2 : number, origKey : string, + private handleDuplicateKey(prevIndex : number, curIndex : number, origKey : string, activateDataItems? : Array>) : string { - const key2 = this.mkRandomKey(index2, origKey); - this.key4Index_.set(index2, key2); - this.index4Key_.set(key2, index2); + const curKey = this.mkRandomKey(curIndex, origKey); + this.key4Index_.set(curIndex, curKey); + this.index4Key_.set(curKey, curIndex); // also make a new key for index1 - const key1 = this.mkRandomKey(index1, origKey); - this.key4Index_.set(index1, key1); - this.index4Key_.set(key1, index1); + const prevKey = this.mkRandomKey(prevIndex, origKey); + this.key4Index_.set(prevIndex, prevKey); + this.index4Key_.set(prevKey, prevIndex); this.index4Key_.delete(origKey); - if (activateDataItems && activateDataItems[index1] !== undefined) { - stateMgmtConsole.debug(` ... correcting key of activeDataItem index ${index1} from '${activateDataItems[index1].key}' to '${key1}'.`); - activateDataItems[index1].key = key1; + if (activateDataItems && activateDataItems[prevIndex] !== undefined) { + stateMgmtConsole.debug(` ... correcting key of activeDataItem index ${prevIndex} from '${activateDataItems[prevIndex].key}' to '${prevKey}'.`); + activateDataItems[prevIndex].key = prevKey; } - stateMgmtConsole.applicationError(`__RepeatVirtualScroll2Impl (${this.repeatElmtId_}): Detected duplicate key ${origKey} for index ${index1} and ${index2}! Generated random key will decrease Repeat performance. Fix the key gen function in your application!`); - return key2; + stateMgmtConsole.applicationError(`__RepeatVirtualScroll2Impl (${this.repeatElmtId_}): `, + `Detected duplicate key ${origKey} for index ${prevIndex} and ${curIndex}! `, + `Generated random key will decrease Repeat performance. Fix the key gen function in your application!`); + return curKey; } /** -- * called from C++ GetFrameChild whenever need to create new node and add to L1 -- * or update spare node and add back to L1 -- * @param forIndex -- * @returns -- */ + * called from C++ GetFrameChild whenever need to create new node and add to L1 + * or update spare node and add back to L1 + * @param forIndex + * @returns + */ private onGetRid4Index(forIndex: number): [number, number] { if (forIndex < 0 || forIndex >= this.totalCount_) { throw new Error(`${this.constructor.name}(${this.repeatElmtId_}) onGetRid4Index index ${forIndex} \n @@ -777,8 +786,8 @@ class __RepeatVirtualScroll2Impl { // a new UINode subtree, create a new rid -> RepeatItem, ttype, key this.meta4Rid_.set(rid, new RIDMeta(repeatItem, ttype, key)); this.activeDataItems_[forIndex] = ActiveDataItem.createWithUINode(this.arr_[forIndex], rid, ttype, key); - this.stopRecordDependencies(); + this.stopRecordDependencies(); stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) createNewChild index ${forIndex} -> new rid ${rid} / ttype ${ttype}, key ${key} - data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - done`); return [rid, /* created new UINode successfully */ 1]; @@ -888,34 +897,34 @@ class __RepeatVirtualScroll2Impl { return true; } - private onActiveRange(start: number, end: number, cacheStart: number, cacheEnd: number, isLoop: boolean): void { - - const fromIndex = Math.max(0, start - cacheStart); - const toIndex = end + cacheEnd; + private onActiveRange(nStart: number, nEnd: number): void { if (Number.isNaN(this.activeRange_[0])) { // first call to onActiveRange - this.activeRange_ = [fromIndex, toIndex]; - } else if (this.activeRange_[0] === fromIndex && this.activeRange_[1] === toIndex) { - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onActiveRange(start: ${start}, end: ${end}, cacheStart: ${cacheStart}, cacheEnd: ${cacheEnd}, isLoop ${isLoop}) data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - unchanged, skipping.`); + this.activeRange_ = [nStart, nEnd]; + } else if (this.activeRange_[0] === nStart && this.activeRange_[1] === nEnd) { + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onActiveRange`, + `(nStart: ${nStart}, nEnd: ${nEnd})`, + `data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - unchanged, skipping.`); return; } - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onActiveRange(start: ${start}, end: ${end}, cacheStart: ${cacheStart}, cacheEnd: ${cacheEnd}, isLoop ${isLoop}) data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start start ++++++++`); + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onActiveRange`, + `(nStart: ${nStart}, nEnd: ${nEnd})`, + `data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start start ++++++++`); - // check which of the activeDataItems needs to be removed from L1 & activeDataItems + // check which of the activeDataItems needs to be removed from L1 & activeDataItems let numberOfActiveItems = 0; for (let index = 0; index < this.activeDataItems_.length; index++) { if (!(index in this.activeDataItems_)) { continue; } - // same condition as in C++ RepeatVirtualScrollNode::CheckNode4IndexInL - const remainInL1 = ((start - cacheStart <= index) && (index <= end + cacheEnd)) - || (isLoop && (((end < start) && (start - cacheStart <= index || index <= end + cacheEnd)) || - ((start - cacheStart < 0) && (index >= start - cacheStart + this.totalCount_)) || - ((end + cacheEnd >= this.totalCount_) && (index <= end + cacheEnd - this.totalCount_)))); - stateMgmtConsole.debug(` .... index: ${index}: ${remainInL1 ? 'keep in L1' : 'drop from L1'} dataItem: ${this.activeDataItems_[index].dump()}`); + // same condition as in C++ RepeatVirtualScroll2Node::CheckNode4IndexInL1 + const remainInL1 = (nStart <= index && index <= nEnd) || + (nStart > nEnd && ((index >= nStart && index < this.totalCount_) || (index >= 0 && index <= nEnd))); + stateMgmtConsole.debug(` .... index: ${index}: ${remainInL1 ? 'keep in L1' : 'drop from L1'}`, + `dataItem: ${this.activeDataItems_[index].dump()}`); if (remainInL1) { if (this.activeDataItems_[index].state === ActiveDataItem.UINodeExists) { numberOfActiveItems += 1; @@ -926,13 +935,11 @@ class __RepeatVirtualScroll2Impl { } } }; - stateMgmtConsole.debug(` --> Result: number remaining activeItems ${numberOfActiveItems}.`); - stateMgmtConsole.debug(this.dumpDataItems()); - stateMgmtConsole.debug(this.dumpSpareRid()); - stateMgmtConsole.debug(this.dumpRepeatItem4Rid()); + stateMgmtConsole.debug(` --> onActiveRange Result: number remaining activeItems ${numberOfActiveItems}.`, + `\n${this.dumpDataItems()}\n${this.dumpSpareRid()}\n${this.dumpRepeatItem4Rid()}`); // memorize - this.activeRange_ = [fromIndex, toIndex]; + this.activeRange_ = [nStart, nEnd]; // adjust dynamic cachedCount for each template type that is using dynamic cached count stateMgmtConsole.debug(`templateOptions_ ${JSON.stringify(this.templateOptions_)}`); diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.cpp b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.cpp index 150378754be..0ba64d85975 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.cpp +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.cpp @@ -46,7 +46,7 @@ RepeatVirtualScroll2Caches::RepeatVirtualScroll2Caches( */ GetFrameChildResult RepeatVirtualScroll2Caches::GetFrameChild(IndexType index, bool needBuild) { - TAG_LOGE(AceLogTag::ACE_REPEAT, " ... GetFrameChild(index %{public}d, needBuild: %{public}d", index, + TAG_LOGE(AceLogTag::ACE_REPEAT, " ... GetFrameChild(index %{public}d, needBuild: %{public}d)", index, static_cast(needBuild)); OptCacheItem optCacheItem = GetL1CacheItem4Index(index); if (optCacheItem.has_value()) { @@ -173,7 +173,6 @@ std::optional RepeatVirtualScroll2Caches::GetL1Index4Node(const RefPt { if (frameNode == nullptr) { return std::nullopt; - ; } for (const auto iter : l1Rid4Index_) { diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.h index e390334513f..168f37e7403 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.h +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.h @@ -114,6 +114,7 @@ public: using IndexType = int32_t; using RIDType = uint32_t; +using ActiveRangeType = std::pair; // result codes of onGetRid4Index result (2nd 2nd in the pair) diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h index 8c687368cfc..665c67cc1b2 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h @@ -38,7 +38,7 @@ public: virtual void Create(uint32_t totalCount, const std::function(int32_t)>& onGetRid4Index, const std::function onRecycleItems, - const std::function onActiveRange, + const std::function onActiveRange, const std::function onPurge) = 0; virtual void RemoveNode(uint32_t rid) = 0; diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp index 534b036ca1b..03698646d8e 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp @@ -26,7 +26,7 @@ namespace OHOS::Ace::NG { void RepeatVirtualScroll2ModelNG::Create(uint32_t totalCount, const std::function(int32_t)>& onGetRid4Index, const std::function onRecycleItems, - const std::function onActiveRange, + const std::function onActiveRange, const std::function onPurge) { ACE_SCOPED_TRACE("RepeatVirtualScroll2ModelNG::Create"); diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h index 927abcaea6a..c7c9b347df4 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h @@ -31,7 +31,7 @@ class ACE_EXPORT RepeatVirtualScroll2ModelNG : public RepeatVirtualScroll2Model public: void Create(uint32_t totalCount, const std::function(int32_t)>& onGetRid4Index, const std::function onRecycleItems, - const std::function onActiveRange, + const std::function onActiveRange, const std::function onPurge) override; void RemoveNode(uint32_t rid) override; diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp index 32dec117e3c..0bab5f2c49c 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp @@ -33,7 +33,7 @@ using CacheItem = RepeatVirtualScroll2Caches::CacheItem; RefPtr RepeatVirtualScroll2Node::GetOrCreateRepeatNode(int32_t nodeId, uint32_t totalCount, const std::function(IndexType)>& onGetRid4Index, const std::function onRecycleItems, - const std::function onActiveRange, + const std::function onActiveRange, const std::function onPurge) { auto node = ElementRegister::GetInstance()->GetSpecificItemById(nodeId); @@ -43,9 +43,8 @@ RefPtr RepeatVirtualScroll2Node::GetOrCreateRepeatNode node->UpdateTotalCount(totalCount); return node; } - node = - MakeRefPtr(nodeId, totalCount, onGetRid4Index, onRecycleItems, onActiveRange, - onPurge); + node = MakeRefPtr(nodeId, totalCount, + onGetRid4Index, onRecycleItems, onActiveRange, onPurge); ElementRegister::GetInstance()->AddUINode(node); return node; @@ -54,66 +53,91 @@ RefPtr RepeatVirtualScroll2Node::GetOrCreateRepeatNode RepeatVirtualScroll2Node::RepeatVirtualScroll2Node(int32_t nodeId, int32_t totalCount, const std::function(IndexType)>& onGetRid4Index, const std::function onRecycleItems, - const std::function onActiveRange, + const std::function onActiveRange, const std::function onPurge) : ForEachBaseNode(V2::JS_REPEAT_ETS_TAG, nodeId), totalCount_(totalCount), caches_(onGetRid4Index), onRecycleItems_(onRecycleItems), onActiveRange_(onActiveRange), onPurge_(onPurge), postUpdateTaskHasBeenScheduled_(false) {} -bool RepeatVirtualScroll2Node::CheckNode4IndexInL1(int32_t index, int32_t start, int32_t end, int32_t cacheStart, - int32_t cacheEnd, RefPtr& frameNode, CacheItem& cacheItem) +void RepeatVirtualScroll2Node::DoSetActiveChildRange( + int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd, bool showCache) { - if (((start <= index) && (index <= end)) || ((end < start) && (index <= end || start <= index))) { - TAG_LOGD(AceLogTag::ACE_REPEAT, "in range: index %{public}d -> nodeId %{public}d: SetActive(true)", index, - static_cast(frameNode->GetId())); - frameNode->SetActive(true); - cacheItem->isActive_ = true; - } else { - TAG_LOGD(AceLogTag::ACE_REPEAT, "out of range: index %{public}d -> nodeId %{public}d: SetActive(false)", index, - frameNode->GetId()); - frameNode->SetActive(false); - cacheItem->isActive_ = false; + // showCache==true when container needs activate pre-render nodes + if (showCache) { + start -= cacheStart; + end += cacheEnd; + cacheStart = 0; + cacheEnd = 0; } + // get normalized active range (with positive indices only) + // step 1, step 2 + const auto activeRange = CheckActiveRange(start, end, cacheStart, cacheEnd); + const int32_t nStart = activeRange.first; + const int32_t nEnd = activeRange.second; - auto totalCount = static_cast(totalCount_); - if ((start - cacheStart <= index) && (index <= end + cacheEnd)) { - cacheItem->isL1_ = true; - cacheItem->isOnRenderTree_ = true; - return true; - } - if (isLoop_) { - if (((end < start) && (start - cacheStart <= index || index <= end + cacheEnd)) || - ((start - cacheStart < 0) && (index >= start - cacheStart + totalCount)) || - ((end + cacheEnd >= totalCount) && (index <= end + cacheEnd - totalCount))) { - cacheItem->isL1_ = true; - cacheItem->isOnRenderTree_ = true; - return true; - } + // step 3: iterate over L1, for each entry check of it is still in active range + TAG_LOGD(AceLogTag::ACE_REPEAT, "Rebuild L1 on C++ side ..."); + bool needSync = RebuildL1(start, end, nStart, nEnd); + + // step 4: iterate over all UINode sub-trees, only interested in L2 ones + // for items moved from L1 to L2 but sitl active and on render tree, correct this. + TAG_LOGD(AceLogTag::ACE_REPEAT, "Checking spare nodes on C++ side ...."); + needSync = ProcessActiveL2Nodes() || needSync; + + // memorize range + prevActiveRangeStart_ = nStart; + prevActiveRangeEnd_ = nEnd; + forceRunDoSetActiveRange_ = false; + + if (needSync) { + // step 5: order a resync from layout + // what these calls exactly do has never been defined for us. + RequestSyncTree(); } - cacheItem->isL1_ = false; - cacheItem->isOnRenderTree_ = false; - return false; + + TAG_LOGD(AceLogTag::ACE_REPEAT, + "DoSetActiveChildRange: Repeat(%{public}d): start: %{public}d - end: %{public}d; cacheStart: " + "%{public}d, cacheEnd: %{public}d: ==> normalized range to keep in L1: %{public}d - %{public}d \n" + "UINode cache:\n%{public}s\n%{public}s", + GetId(), start, end, cacheStart, cacheEnd, nStart, nEnd, + caches_.DumpUINodeCache().c_str(), caches_.DumpL1Rid4Index().c_str()); } +bool RepeatVirtualScroll2Node::CheckNode4IndexInL1(int32_t index, int32_t nStart, int32_t nEnd, CacheItem& cacheItem) +{ + auto totalCount = static_cast(totalCount_); + const bool remainInL1 = (nStart <= index && index <= nEnd) || + (nStart > nEnd && ((index >= nStart && index < totalCount) || (index >= 0 && index <= nEnd))); + cacheItem->isL1_ = remainInL1; + cacheItem->isOnRenderTree_ = remainInL1; + return remainInL1; +} -// added by jianjing -void RepeatVirtualScroll2Node::DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd, - bool showCache) +ActiveRangeType RepeatVirtualScroll2Node::CheckActiveRange( + int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd) { - // get normalized active range (with positive indices only) + const int32_t signed_totalCount = static_cast(totalCount_); int32_t nStart = start - cacheStart; int32_t nEnd = end + cacheEnd; - if (totalCount_ == 0) { - nStart = 0; - nEnd = 0; - } else if (isLoop_) { - nStart = (nStart + totalCount_) % totalCount_; - nEnd = (nEnd + totalCount_) % totalCount_; + const int32_t divider = 2; + + if (start > end) { // swiper-loop scenario + nStart = std::min(nStart, signed_totalCount); + nEnd = std::max(nEnd, 0); + if (nStart <= nEnd) { // overlapped + nStart = signed_totalCount / divider + 1; + nEnd = signed_totalCount / divider; + } } else { - nStart = std::max(nStart, 0); - // start <= end <= totalCount - 1 - nEnd = std::min(std::max(nEnd, nStart), int32_t(totalCount_-1)); + if (nStart >= signed_totalCount || nEnd < 0) { + nStart = 0; + nEnd = 0; + } else { + nStart = std::max(nStart, 0); + // start <= end <= totalCount - 1 + nEnd = std::min(std::max(nEnd, nStart), signed_totalCount - 1); + } } // step 1: if range unchanged skip full run unless forced (rerender will force run once) @@ -124,7 +148,7 @@ void RepeatVirtualScroll2Node::DoSetActiveChildRange(int32_t start, int32_t end, "%{public}d, cacheEnd: %{public}d: ==> normalized range to keep in L1: %{public}d - %{public}d is " "unchanged. Skipping.", GetId(), start, end, cacheStart, cacheEnd, nStart, nEnd); - return; + return {nStart, nEnd}; } TAG_LOGD(AceLogTag::ACE_REPEAT, @@ -140,55 +164,46 @@ void RepeatVirtualScroll2Node::DoSetActiveChildRange(int32_t start, int32_t end, auto* viewStack = NG::ViewStackProcessor::GetInstance(); viewStack->Push(Referenced::Claim(this)); - ACE_SCOPED_TRACE("Repeat.DoSetActiveChildRange start [%d] - end [%d; cacheStart: [%d], cacheEnd: [%d]", start, end, - cacheStart, cacheEnd); + ACE_SCOPED_TRACE("Repeat.DoSetActiveChildRange start[%d]-end[%d], cacheStart[%d], cacheEnd[%d]. keep in [%d]-[%d]", + start, end, cacheStart, cacheEnd, nStart, nEnd); // step 2. call TS side - onActiveRange_(start, end, cacheStart, cacheEnd, isLoop_); - - // step 3: iterate over L1, for each entry check of it is still in active range - TAG_LOGD(AceLogTag::ACE_REPEAT, "Rebuild L1 on C++ side ..."); - bool needSync = RebuildL1(start, end, cacheStart, cacheEnd); - - // step 4: iterate over all UINode sub-trees, only interested in L2 ones - // for items moved from L1 to L2 but sitl active and on render tree, correct this. - TAG_LOGD(AceLogTag::ACE_REPEAT, "Checking spare nodes on C++ side ...."); - needSync = processActiveL2Nodes() || needSync; - - // memorize range - prevActiveRangeStart_ = nStart; - prevActiveRangeEnd_ = nEnd; - forceRunDoSetActiveRange_ = false; - - if (needSync) { - // step 5: order a resync from layout - // what these calls exactly do has never been defined for us. - RequestSyncTree(); - } + onActiveRange_(nStart, nEnd); - TAG_LOGD(AceLogTag::ACE_REPEAT, - "DoSetActiveChildRange: Repeat(%{public}d): start: %{public}d - end: %{public}d; cacheStart: " - "%{public}d, cacheEnd: %{public}d: ==> normalized range to keep in L1: %{public}d - %{public}d \n UINode " - "cache:\n%{public}s \n %{public}s", - GetId(), start, end, cacheStart, cacheEnd, nStart, nEnd, caches_.DumpUINodeCache().c_str(), - caches_.DumpL1Rid4Index().c_str()); + return {nStart, nEnd}; } -bool RepeatVirtualScroll2Node::RebuildL1(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd) +bool RepeatVirtualScroll2Node::RebuildL1(int32_t start, int32_t end, int32_t nStart, int32_t nEnd) { return caches_.RebuildL1( - [&, start, end, cacheStart, cacheEnd, this](int32_t index, CacheItem& cacheItem) -> bool { + [&, start, end, nStart, nEnd, weak = WeakClaim(this)](int32_t index, CacheItem& cacheItem) -> bool { + auto repeatNode = weak.Upgrade(); + CHECK_NULL_RETURN(repeatNode, false); const auto node = cacheItem->node_; if (!node) { return false; } - auto frameNode = AceType::DynamicCast(node->GetFrameChildByIndex(0, true)); if (!frameNode) { return false; } + if (((start <= index) && (index <= end)) || + ((end < start) && (index <= end || start <= index))) { + TAG_LOGD(AceLogTag::ACE_REPEAT, + "in range: index %{public}d -> child nodeId %{public}d: SetActive(true)", + index, frameNode->GetId()); + frameNode->SetActive(true); + cacheItem->isActive_ = true; + } else { + TAG_LOGD(AceLogTag::ACE_REPEAT, + "out of range: index %{public}d -> child nodeId %{public}d: SetActive(false)", + index, frameNode->GetId()); + frameNode->SetActive(false); + cacheItem->isActive_ = false; + } - if (CheckNode4IndexInL1(index, start, end, cacheStart, cacheEnd, frameNode, cacheItem)) { + if (repeatNode->CheckNode4IndexInL1(index, nStart, nEnd, cacheItem)) { + // keep in Repeat L1 TAG_LOGD(AceLogTag::ACE_REPEAT, " ... in L1: index %{public}d, node %{public}s with child id %{public}d: SetActive(True)", index, caches_.DumpUINode(cacheItem->node_).c_str(), static_cast(frameNode->GetId())); @@ -196,8 +211,8 @@ bool RepeatVirtualScroll2Node::RebuildL1(int32_t start, int32_t end, int32_t cac } TAG_LOGD(AceLogTag::ACE_REPEAT, - " ... out of L1: index %{public}d, node %{public}s with child id %{public}d: ", index, - caches_.DumpUINode(cacheItem->node_).c_str(), frameNode->GetId()); + " ... out of L1: index %{public}d, node %{public}s with child id %{public}d", + index, caches_.DumpUINode(cacheItem->node_).c_str(), frameNode->GetId()); // move active node into L2 cached. check transition flag. // Animations support need to modify here @@ -206,16 +221,16 @@ bool RepeatVirtualScroll2Node::RebuildL1(int32_t start, int32_t end, int32_t cac } if (node->OnRemoveFromParent(true)) { // OnRemoveFromParent returns true means the child can be removed from tree immediately. - RemoveDisappearingChild(node); + repeatNode->RemoveDisappearingChild(node); } else { - AddDisappearingChild(node); + repeatNode->AddDisappearingChild(node); } return false; } ); } -bool RepeatVirtualScroll2Node::processActiveL2Nodes() +bool RepeatVirtualScroll2Node::ProcessActiveL2Nodes() { bool needSync = false; caches_.ForEachCacheItem([&](RIDType rid, const CacheItem& cacheItem) { @@ -234,8 +249,10 @@ bool RepeatVirtualScroll2Node::processActiveL2Nodes() if (frameNode && cacheItem->isActive_) { frameNode->SetActive(false); cacheItem->isActive_ = false; + cacheItem->node_->SetJSViewActive(false); needSync = true; - TAG_LOGD(AceLogTag::ACE_REPEAT, " ... spare node %{public}s: apply SetActive(false)", + TAG_LOGD(AceLogTag::ACE_REPEAT, + " ... spare node %{public}s: apply SetActive(false) & SetJSViewActive(false)", caches_.DumpCacheItem(cacheItem).c_str()); } if (!cacheItem->isOnRenderTree_) { @@ -272,12 +289,14 @@ void RepeatVirtualScroll2Node::RequestSyncTree() void RepeatVirtualScroll2Node::DoSetActiveChildRange( const std::set& activeItems, const std::set& cachedItems, int32_t baseIndex) { - // Function currently unused + // Function currently unused // when taking to use, need to update and align to 1st version of DoSetActiveChildRange above // the call to TS is missing! - bool needSync = - caches_.RebuildL1([&activeItems, &cachedItems, baseIndex, this](int32_t index, CacheItem& cacheItem) -> bool { + bool needSync = caches_.RebuildL1([&activeItems, &cachedItems, baseIndex, weak = WeakClaim(this)]( + int32_t index, CacheItem& cacheItem) -> bool { + auto repeatNode = weak.Upgrade(); + CHECK_NULL_RETURN(repeatNode, false); auto frameNode = AceType::DynamicCast(cacheItem->node_->GetFrameChildByIndex(0, true)); if (!frameNode) { return false; @@ -300,9 +319,9 @@ void RepeatVirtualScroll2Node::DoSetActiveChildRange( cacheItem->isOnRenderTree_ = false; cacheItem->isL1_ = false; if (cacheItem->node_->OnRemoveFromParent(true)) { - RemoveDisappearingChild(cacheItem->node_); + repeatNode->RemoveDisappearingChild(cacheItem->node_); } else { - AddDisappearingChild(cacheItem->node_); + repeatNode->AddDisappearingChild(cacheItem->node_); } return false; }); @@ -328,9 +347,9 @@ void RepeatVirtualScroll2Node::UpdateL1Rid4Index(std::map& l1 void RepeatVirtualScroll2Node::RequestContainerReLayout(IndexType fromRepeatItemIndex) { TAG_LOGD(AceLogTag::ACE_REPEAT, - "RequestContainerReLayout triggered by Repeat rerender: nodeId: %{public}d . Start re-layout from child index " - "%{public}d + startIndex_%{public}d /", - static_cast(GetId()), static_cast(fromRepeatItemIndex), startIndex_); + "RequestContainerReLayout triggered by Repeat rerender: nodeId: %{public}d." + "Start re-layout from child index(%{public}d) + startIndex(%{public}d)", + GetId(), static_cast(fromRepeatItemIndex), startIndex_); children_.clear(); @@ -365,13 +384,15 @@ RefPtr RepeatVirtualScroll2Node::GetFrameChildByIndex( static_cast(GetId()), static_cast(index), static_cast(needBuild), static_cast(isCache), static_cast(addToRenderTree)); - ACE_SCOPED_TRACE("Repeat.GetFrameChildByIndex index[%d], needBuild[%d] isCache[%d] " - "addToRenderTree[%d]", - index, static_cast(needBuild), static_cast(isCache), static_cast(addToRenderTree)); + ACE_SCOPED_TRACE("Repeat.GetFrameChildByIndex index[%d], needBuild[%d] isCache[%d] addToRenderTree[%d]", + static_cast(index), static_cast(needBuild), + static_cast(isCache), static_cast(addToRenderTree)); - if (prevRecycleFrom > 0 && prevRecycleFrom <= index && index < prevRecycleTo) { - TAG_LOGD(AceLogTag::ACE_REPEAT, "REPEAT TRACE ABNORMAL... layout requesting index %{public}d) that was just " - "informed to be recycled.", static_cast(index)); + if (prevRecycleFrom > 0 && prevRecycleFrom <= static_cast(index) && + static_cast(index) < prevRecycleTo) { + TAG_LOGD(AceLogTag::ACE_REPEAT, + "REPEAT TRACE ABNORMAL... layout requesting index %{public}d) that was just informed to be recycled.", + static_cast(index)); } return GetFrameChildByIndexImpl(index, needBuild, isCache, addToRenderTree); @@ -405,6 +426,8 @@ RefPtr RepeatVirtualScroll2Node::GetFrameChildByIndexImpl( TAG_LOGD(AceLogTag::ACE_REPEAT, " ... GetFrameChild(%{public}d) returns node %{public}s .", static_cast(index), caches_.DumpUINode(cacheItem4Index->node_).c_str()); + cacheItem4Index->node_->UpdateThemeScopeId(GetThemeScopeId()); + if (isActive_) { cacheItem4Index->node_->SetJSViewActive(true); } @@ -475,7 +498,7 @@ const std::list>& RepeatVirtualScroll2Node::GetChildren(bool /*no // call forwards to TS onRecycleItems_ void RepeatVirtualScroll2Node::RecycleItems(int32_t from, int32_t to) { - if (from+1>=to) { + if (from + 1 >= to) { return; } if (from == prevRecycleFrom && to == prevRecycleTo) { diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h index 9c85beab2ff..0f576d1a223 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h @@ -53,7 +53,7 @@ * C++ lambdas to call these TS functions * onGetRid4Index: (forIndex: number) => [number, number], * onRecycleItems: (fromIndex: number, toIndex: number) => void, - * onActiveRange: (fromIndex: number, toIndex: number, fromCache: number, toCache: number, isLoop:boolean) => void, + * onActiveRange: (fromIndex: number, toIndex: number) => void, * onPurge: () => void; * onMoveHandler: (from: number, to: number) => void; * @@ -95,13 +95,13 @@ public: static RefPtr GetOrCreateRepeatNode(int32_t nodeId, uint32_t totalCount, const std::function(IndexType)>& onGetRid4Index, const std::function onRecycleItems, - const std::function onActiveRange, + const std::function onActiveRange, const std::function onPurge); RepeatVirtualScroll2Node(int32_t nodeId, int32_t totalCount, const std::function(IndexType)>& onGetRid4Index, const std::function onRecycleItems, - const std::function onActiveRange, + const std::function onActiveRange, const std::function onPurge); ~RepeatVirtualScroll2Node() override = default; @@ -223,11 +223,8 @@ private: RefPtr GetFrameChildByIndexImpl( uint32_t index, bool needBuild, bool isCache, bool addToRenderTree); - bool CheckNode4IndexInL1(int32_t index, int32_t start, int32_t end, int32_t cacheStart, - int32_t cacheEnd, RefPtr& frameNode, RepeatVirtualScroll2Caches::CacheItem& cacheItem); - - bool RebuildL1(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd); - bool processActiveL2Nodes(); + bool RebuildL1(int32_t start, int32_t end, int32_t nStart, int32_t nEnd); + bool ProcessActiveL2Nodes(); void RequestSyncTree(); void PostIdleTask(); @@ -235,6 +232,13 @@ private: // tell TS to purge nodes exceeding cachedCount void Purge(); + // check whether index is in the L1 cache range + bool CheckNode4IndexInL1( + int32_t index, int32_t nStart, int32_t nEnd, RepeatVirtualScroll2Caches::CacheItem& cacheItem); + + // process params sent by parent + ActiveRangeType CheckActiveRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd); + // RepeatVirtualScroll2Node is not instance of FrameNode // needs to propagate active state to all items inside bool isActive_ = true; @@ -269,7 +273,7 @@ private: bool forceRunDoSetActiveRange_ = false; std::function onRecycleItems_; - std::function onActiveRange_; + std::function onActiveRange_; std::function onPurge_; // true in the time from requesting idle / predict task until exec predict tsk. -- Gitee From d6e4fe2ea9e419f8c1a80c4b200c86f5f3b3f9c8 Mon Sep 17 00:00:00 2001 From: liubihao Date: Sat, 25 Jan 2025 10:09:59 +0800 Subject: [PATCH 3/8] Repeat: adapt reusable. Signed-off-by: liubihao --- .../pu_repeat_virtual_scroll_2_impl.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts index 812ad890a3e..b84a19763df 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts +++ b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts @@ -231,6 +231,9 @@ class __RepeatVirtualScroll2Impl { // .template 3rd parameter, cachedCount private templateOptions_: { [type: string]: RepeatTemplateImplOptions }; + // reuse node in L2 cache or not + private reusable_?: boolean = true; + // factory for interface RepeatItem objects private mkRepeatItem_: (item: T, index?: number) => __RepeatItemFactoryReturn; private onMoveHandler_?: OnMoveHandler; @@ -332,6 +335,11 @@ class __RepeatVirtualScroll2Impl { this.keyGenFunc_ = config.keyGenFunc; this.useKeys_ = config.keyGenFuncSpecified; + if (config.reusable !== undefined) { + this.reusable_ = config.reusable; + } + stateMgmtConsole.debug(` ...reusable:${this.reusable_}`); + this.mkRepeatItem_ = config.mkRepeatItem; this.onMoveHandler_ = config.onMoveHandler; @@ -350,7 +358,8 @@ class __RepeatVirtualScroll2Impl { private initialRender(): void { this.repeatElmtId_ = ObserveV2.getCurrentRecordedId(); - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) initialRender() data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start`); + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) initialRender()`, + `data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start`); // Create the RepeatVirtualScrollNode object // pass the C++ to TS callback functions. @@ -549,7 +558,7 @@ class __RepeatVirtualScroll2Impl { continue; } - const optRid = this.canUpdate(newActiveDataItemAtActiveIndex.ttype); + const optRid = this.reusable_ ? this.canUpdate(newActiveDataItemAtActiveIndex.ttype) : -1; if (optRid > 0) { const ridMeta = this.meta4Rid_.get(optRid); if (ridMeta) { @@ -703,7 +712,7 @@ class __RepeatVirtualScroll2Impl { stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onGetRid4Index index ${forIndex}, ttype is '${ttype}' data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start start ++++++++`); // spare UINode / RID available to update? - const optRid = this.canUpdateTryMatch(ttype, dataItem, key); + const optRid = this.reusable_ ? this.canUpdateTryMatch(ttype, dataItem, key) : -1; const result: [number, number] = (optRid > 0) ? this.updateChild(optRid, ttype, forIndex, key) -- Gitee From 4f534fdb38181d0e16cc80bea00b71888582fb10 Mon Sep 17 00:00:00 2001 From: liubihao Date: Thu, 6 Feb 2025 11:04:07 +0800 Subject: [PATCH 4/8] Repeat: optimize ActiveRange 2 & fix code format. Signed-off-by: liubihao --- .../jsview/js_repeat_virtual_scroll_2.cpp | 4 +- .../src/lib/partial_update/pu_foreach.d.ts | 2 +- .../pu_repeat_virtual_scroll_2_impl.ts | 402 ++++++++++-------- .../pu_repeat_virtual_scroll_impl.ts | 2 +- .../syntax/repeat_virtual_scroll_2_model.h | 2 +- .../repeat_virtual_scroll_2_model_ng.cpp | 2 +- .../syntax/repeat_virtual_scroll_2_model_ng.h | 2 +- .../syntax/repeat_virtual_scroll_2_node.cpp | 85 +++- .../syntax/repeat_virtual_scroll_2_node.h | 8 +- 9 files changed, 301 insertions(+), 208 deletions(-) diff --git a/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp b/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp index 275821501d5..fb0f0884621 100644 --- a/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp +++ b/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp @@ -97,9 +97,9 @@ void JSRepeatVirtualScroll2::Create(const JSCallbackInfo& info) }; auto onActiveRange = [execCtx = info.GetExecutionContext(), func = GetJSFunc(handlers, "onActiveRange")]( - int32_t fromIndex, int32_t toIndex) -> void { + int32_t fromIndex, int32_t toIndex, bool isLoop) -> void { JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx); - auto params = ConvertToJSValues(fromIndex, toIndex); + auto params = ConvertToJSValues(fromIndex, toIndex, isLoop); func->Call(JSRef(), params.size(), params.data()); }; diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_foreach.d.ts b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_foreach.d.ts index 219ccd01e24..2b249bd4de8 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_foreach.d.ts +++ b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_foreach.d.ts @@ -72,7 +72,7 @@ declare class RepeatVirtualScroll2Native { handlers: { onGetRid4Index: (forIndex: number) => [number, number], onRecycleItems: (fromIndex: number, toIndex: number) => void, - onActiveRange: (fromIndex: number, toIndex: number) => void, + onActiveRange: (fromIndex: number, toIndex: number, isLoop: boolean) => void, onPurge: () => void; } ): void; diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts index b84a19763df..df500cf133c 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts +++ b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts @@ -15,17 +15,18 @@ * all definitions in this file are framework internal */ -/** Repeat with .virtualScroll TS side implementation - * for C++ side part of the implementation read the comments at top of file - * core/components_ng/syntax/repeat_virtual_scroll_node.h - * +/** + * Repeat with .virtualScroll TS side implementation + * for C++ side part of the implementation read the comments at top of file + * core/components_ng/syntax/repeat_virtual_scroll_node.h + * * See the factory solutions in pu_repeat.ts * __RepeatVirtualScroll2Impl gets created before first render - * - * onGetRid4Index(index) called upon layout calling C++ GetFrameChildByIndex + * + * onGetRid4Index(index) called upon layout calling C++ GetFrameChildByIndex * (whenever C++ does not find UINode sub-tree for index in L1) * 1- gets the data item - * 2- calculates the templateId (called ttype internally) for this index, then + * 2- calculates the templateId (called ttype internally) for this index, then * 3- canUpdateTryMatch(index, ttype) tries to find spare UINode tree (identified by its RID) * 4- if found calls updateChild(rid, item, index, ....) * updateChild simply updates repeatItem.item, .index, and requests UI updates synchronously (updateDirty2(true) @@ -36,14 +37,13 @@ * TS informs C++ about the RID and that new UINode sub-tree root node should be taken from ViewStackProcessor * added to C++ side RID -> UINode map / general cache * - * * onRecycleItems(fromIndex, upToBeforeIndex) - called from C++ RecycleItems - * - move L1 items with fromIndex <= index < upToBeforeIndex to L2. + * - move L1 items with fromIndex <= index < upToBeforeIndex to L2. * calls C++ RepeatVirtualScroll2Native.setInvalid so also C++ side moves the RID from L1 to L2 * C++ does NOT remove the item from active tree, or change its Active status * if so needed at the end of one processing cycle onActiveRange will do - * - * onActiveRange(....) - called from C++ + * + * onActiveRange(....) - called from C++ * - TS iterates over L1 items to check which which one is still in informed range, move to L2 * does NOT call RepeatVirtualScrollCaches::SetInvalid * - C++ does the same @@ -51,66 +51,66 @@ * b) iterate over L2, remove from render tree and set Active status * c) order purge idle task * d) calculate dynamicCachedCount for .each and for templateIds that do not have specified cachedCount option - * - * - Note: the rather convolute algorithm that uses parameters to decide if item is in active range or not + * + * - Note: the rather convolute algorithm that uses parameters to decide if item is in active range or not * needs to be exactly same in this function on TS side and on C++ side to ensure L1 info in TS and in C++ side * reman in sync. - * + * * onPurge called from C++ RepeatVirtualScrollNode::Purge in idle task * ensure L1 side is within limits (cachedCount), deletes UINode subtrees that do not fit the permissible side * cachedCount is defined for each templateId / ttype for for .each separately - * - * + * + * * rerender called from ViewV2 UpdateElement(repeatElmtId) - * + * * what triggers Repeat rerender? * - new source array assignment; array add, delete, move item * - input to templateId function changed * - input to key function changed (if key used) * - totalCount changed - * + * * cached array item at index and new array item at index2 are considered the same if * - the keys computed from them are the same (if key used) * - if items compare equal with === operator * the algorithm that compares items can handle duplicate array items. - * the algorithm will fail unnoticed if cached array item and new array item are different but their key is the same + * the algorithm will fail unnoticed if cached array item and new array item are different but their key is the same * (this violates the requirement of stable keys) - * + * * read the source code of rerender, each step has documentation - * + * * the outcome of rerender is * 1- array items in cached and new array (active range), aka retained array items, repeatItem.index updated if changed * 2- delete array items -> RID / UINode sub-tree moved to L2 - * 3- added array items - try to find fitting RID / UINode subtree (same templateId / ttype) and update by updating repeatItem.item and .index - * newly added array items that can not be rendered by update are NOT rendered, will deal with new renders when GetFrameChildByIndex requests + * 3- added array items - try to find fitting RID / UINode subtree (same templateId / ttype) and + * update by updating repeatItem.item and .index newly added array items that can not be rendered + * by update are NOT rendered, will deal with new renders when GetFrameChildByIndex requests * 4- synchronous partial update to update all bindings that need update from step 1 and 3. - * 5- inform new L1 (and thereby also L2) to C++ by calling updateL1Rid4Index(list of rid), same call also invalidates container layout starting from - * first changes item index, updates also totalCount on C++ side - * - * + * 5- inform new L1 (and thereby also L2) to C++ by calling updateL1Rid4Index(list of rid), same call also + * invalidates container layout starting from first changes item index, updates also totalCount on C++ side + * + * * The most important data structures - * + * * RID - Repeat Item ID - a unique number, uniquely identifies a RepeatItem - templateId - UINode triple - * these found data items remain together until deleted from the system (ie.. until purge or unload deletes the UINode subtree) - * + * these found data items remain together until deleted from the system + * (ie.. until purge or unload deletes the UINode subtree) + * * meta4Rid_: Map> - * - for each RID: - * - constant: RepeatItem + * - for each RID: + * - constant: RepeatItem * - constant: ttype * - mutable: key - * - counterpart on C++ side RepeatVirtualScrollCaches.cacheItem4Rid_ maps RID -> UINode - * + * - counterpart on C++ side RepeatVirtualScrollCaches.cacheItem4Rid_ maps RID -> UINode + * * activeDataItems - Array> * This is the central data structure for rerender, as allows to compare previous item value / keys * Sparse array, only includes items for active range * - array item value at last render/update, rid, ttype, key (if used), some state info - * + * * spareRid_ : set RID currently not in active range, "L2" - * - * ttypeGenFunc_ templateId function + * + * ttypeGenFunc_: templateId function * itemGenFuncs_: map of item builder functions per templateId / ttype and .each - * - */ class ActiveDataItem { @@ -118,7 +118,7 @@ class ActiveDataItem { public rid?: number; public ttype?: string; public key?: string; - public state : number; + public state: number; public static readonly UINodeExists = 1; public static readonly UINodeRenderFailed = 2; @@ -151,8 +151,9 @@ class ActiveDataItem { } public toString(): string { - return this.state === ActiveDataItem.UINodeExists ? - `[rid: ${this.rid}, ttype: ${this.ttype}${this.key ? ", key: " + this.key : ""}]` : `[no item]`; + return this.state === ActiveDataItem.UINodeExists + ? `[rid: ${this.rid}, ttype: ${this.ttype}${this.key ? ", key: " + this.key : ""}]` + : `[no item]`; } public dump(): string { @@ -192,11 +193,11 @@ class ActiveDataItem { // info about each created UINode / each RepeatItem / each RID // only optional key is allowed to change class RIDMeta { - public readonly repeatItem_ : __RepeatItemV2; - public readonly ttype_ : string; - public key_? : string; + public readonly repeatItem_: __RepeatItemV2; + public readonly ttype_: string; + public key_?: string; - constructor(repeatItem : __RepeatItemV2, ttype : string, key? : string) { + constructor(repeatItem: __RepeatItemV2, ttype: string, key?: string) { this.repeatItem_ = repeatItem; this.ttype_ = ttype; this.key_ = key; @@ -232,7 +233,7 @@ class __RepeatVirtualScroll2Impl { private templateOptions_: { [type: string]: RepeatTemplateImplOptions }; // reuse node in L2 cache or not - private reusable_?: boolean = true; + private allowUpdate_?: boolean = true; // factory for interface RepeatItem objects private mkRepeatItem_: (item: T, index?: number) => __RepeatItemFactoryReturn; @@ -265,19 +266,17 @@ class __RepeatVirtualScroll2Impl { private spareRid_: Set = new Set(); // when access view model record dependency on 'this'. - private startRecordDependencies(clearBindings : boolean = false) : void { - // FIXME needed ? ViewStackProcessor.StartGetAccessRecordingFor(this.repeatElmtId_); + private startRecordDependencies(clearBindings: boolean = false): void { ObserveV2.getObserve().startRecordDependencies(this.owningViewV2_, this.repeatElmtId_, clearBindings); } - private stopRecordDependencies() : void { - // FIXME needed? ViewStackProcessor.StopGetAccessRecording(); + private stopRecordDependencies(): void { ObserveV2.getObserve().stopRecordDependencies(); } /** * return array item if it exists - * todo try to lazy load if it does not exist + * * @param index * @returns tuple data item exists , data item * (need to do like this to differentiate missing data item and undefined item value @@ -303,7 +302,6 @@ class __RepeatVirtualScroll2Impl { // initial render // called from __Repeat.render public render(config: __RepeatConfig, isInitialRender: boolean): void { - // params that can change this.arr_ = config.arr; @@ -336,9 +334,9 @@ class __RepeatVirtualScroll2Impl { this.useKeys_ = config.keyGenFuncSpecified; if (config.reusable !== undefined) { - this.reusable_ = config.reusable; + this.allowUpdate_ = config.reusable; } - stateMgmtConsole.debug(` ...reusable:${this.reusable_}`); + stateMgmtConsole.debug(`allowUpdate: ${this.allowUpdate_}`); this.mkRepeatItem_ = config.mkRepeatItem; this.onMoveHandler_ = config.onMoveHandler; @@ -354,7 +352,6 @@ class __RepeatVirtualScroll2Impl { } } - /**/ private initialRender(): void { this.repeatElmtId_ = ObserveV2.getCurrentRecordedId(); @@ -373,13 +370,15 @@ class __RepeatVirtualScroll2Impl { // Why is this separate, can onMove be added to create? RepeatVirtualScroll2Native.onMove(this.onMoveHandler_); - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) initialRender() data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - done`); + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) initialRender() data array length: `, + `${this.arr_.length}, totalCount: ${this.totalCount_} - done`); } // given data item and the ttype it needs to be rendered with from updated array: // find same data item in activeDataItems, it also still needs to use same ttype as given // return its { index in activeDataItems, its rid } - private findDataItemInOldActivateDataItemsByValue(dataItem: T, ttype: string): { oldIndexStr: string, rid?: number } | undefined { + private findDataItemInOldActivateDataItemsByValue( + dataItem: T, ttype: string): { oldIndexStr: string, rid?: number } | undefined { for (const oldIndex in this.activeDataItems_) { const oldItem = this.activeDataItems_[oldIndex]; if (oldItem.item === dataItem && oldItem.rid && oldItem.ttype === ttype) { @@ -390,7 +389,8 @@ class __RepeatVirtualScroll2Impl { } // find same data item in activeDataItems by key, it also still needs to use same ttype as given - private findDataItemInOldActivateDataItemsByKey(key: string, ttype: string): {oldIndexStr: string, rid?: number} | undefined { + private findDataItemInOldActivateDataItemsByKey( + key: string, ttype: string): {oldIndexStr: string, rid?: number} | undefined { for (const oldIndex in this.activeDataItems_) { const oldItem = this.activeDataItems_[oldIndex]; if (oldItem.key === key && oldItem.rid && oldItem.ttype === ttype) { @@ -402,14 +402,13 @@ class __RepeatVirtualScroll2Impl { // update Repeat, see overview documentation at the top of this file. private reRender(): void { - - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) reRender() data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start`); + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) reRender() data array length: `, + `${this.arr_.length}, totalCount: ${this.totalCount_} - start`); const activeRangeFrom = this.activeRange_[0]; const activeRangeTo = this.activeRange_[1]; - stateMgmtConsole.debug(` ... checking range ${activeRangeFrom} - ${activeRangeTo}`); - + stateMgmtConsole.debug(`checking range ${activeRangeFrom} - ${activeRangeTo}`); let firstIndexChanged = Math.min(activeRangeTo + 1, this.arr_.length); @@ -424,12 +423,13 @@ class __RepeatVirtualScroll2Impl { this.key4Index_.clear(); this.index4Key_.clear(); - // 1. move data items to newActiveDataItems that are unchanged (same item / same key, still at same index, same ttyp) + // step 1. move data items to newActiveDataItems that are unchanged + // (same item / same key, still at same index, same ttype) // create createMissingDataItem -type entries for all other new data items. let hasChanges = false; for (const indexS in this.activeDataItems_) { const activeIndex = parseInt(indexS); - if (activeIndex < 0) { + if (activeIndex < 0) { // out of range to consider continue; } @@ -439,33 +439,42 @@ class __RepeatVirtualScroll2Impl { break; } - const [ dataItemExists, dataItemAtIndex ] = this.getItemUnmonitored(activeIndex); + const [dataItemExists, dataItemAtIndex] = this.getItemUnmonitored(activeIndex); if (!dataItemExists) { - stateMgmtConsole.debug(` ... index ${activeIndex} no data in new array, had data before ${this.activeDataItems_[indexS].state !== ActiveDataItem.NoValue}`); + stateMgmtConsole.debug(`index ${activeIndex} no data in new array, had data before `, + `${this.activeDataItems_[indexS].state !== ActiveDataItem.NoValue}`); hasChanges = hasChanges || (this.activeDataItems_[indexS].state !== ActiveDataItem.NoValue); newActiveDataItems[activeIndex] = ActiveDataItem.createMissingDataItem(); continue; } - - const ttype = this.computeTtype(dataItemAtIndex, activeIndex, /* monitored access already enabled */ false); - const key = this.computeKey(dataItemAtIndex, activeIndex, /* monitor access already on-going */ false, newActiveDataItems); + + const ttype = this.computeTtype(dataItemAtIndex, activeIndex, + /* monitored access already enabled */ false); + const key = this.computeKey(dataItemAtIndex, activeIndex, + /* monitor access already on-going */ false, newActiveDataItems); // compare with ttype and data item, or with ttype and key if ((ttype === this.activeDataItems_[activeIndex].ttype) && ((!this.useKeys_ && dataItemAtIndex === this.activeDataItems_[activeIndex].item) || - (this.useKeys_ && key === this.activeDataItems_[activeIndex].key))) { - stateMgmtConsole.debug(` ... index ${activeIndex} ttype '${ttype}'${this.useKeys_ ? ", key " + key : ""} and dataItem unchanged.`); + (this.useKeys_ && key === this.activeDataItems_[activeIndex].key))) { + stateMgmtConsole.debug( + `index ${activeIndex} ttype '${ttype}'${this.useKeys_ ? ", key " + key : ""} `, + `and dataItem unchanged.`); newActiveDataItems[activeIndex] = this.activeDataItems_[activeIndex]; // add to index -> rid map to be sent to C++ newL1Rid4Index.set(activeIndex, this.activeDataItems_[activeIndex].rid); - + // the data item is handled, remove it from old active data range // so we do not use it again delete this.activeDataItems_[activeIndex]; } else { - stateMgmtConsole.debug(` ... index ${activeIndex} has changed ${dataItemAtIndex !== this.activeDataItems_[activeIndex].item}, ttype ${ttype} has changed ${ttype !== this.activeDataItems_[activeIndex].ttype}, key ${key} has changed ${key !== this.activeDataItems_[activeIndex].key}, using keys ${this.useKeys_}`); - newActiveDataItems[activeIndex] = ActiveDataItem.createToBeRenderedDataItem(dataItemAtIndex, ttype, key); + stateMgmtConsole.debug(`index ${activeIndex} has changed `, + `${dataItemAtIndex !== this.activeDataItems_[activeIndex].item}, ttype ${ttype} has changed `, + `${ttype !== this.activeDataItems_[activeIndex].ttype}, key ${key} has changed `, + `${key !== this.activeDataItems_[activeIndex].key}, using keys ${this.useKeys_}`); + newActiveDataItems[activeIndex] = + ActiveDataItem.createToBeRenderedDataItem(dataItemAtIndex, ttype, key); hasChanges = true; } } // for activeItems @@ -474,14 +483,12 @@ class __RepeatVirtualScroll2Impl { // invalidate the layout only for items beyond active range // this is for the case that there is space for more visible items in the container. // triggers layout to request FrameCount() / totalCount and if increased newly added source array items - // FIXME TODO correct??? Math.min(this.totalCount_ - 1, activeRangeTo + 1) this.activeDataItems_ = newActiveDataItems; RepeatVirtualScroll2Native.requestContainerReLayout( this.repeatElmtId_, this.totalCount_, Math.min(this.totalCount_ - 1, activeRangeTo + 1)); return; } - // step 2. move retained data items // these are items with same value / same key in new and old array: // their index has changed, ttype is unchanged @@ -495,18 +502,21 @@ class __RepeatVirtualScroll2Impl { } if (newActiveDataItemAtActiveIndex.state === ActiveDataItem.NoValue) { - stateMgmtConsole.debug(` ... new index ${activeIndex} missing in updated source array.`); + stateMgmtConsole.debug(`new index ${activeIndex} missing in updated source array.`); firstIndexChanged = Math.min(firstIndexChanged, activeIndex); - continue; + continue; } // retainedItem must have same item value / same key, same ttype, but new index let movedDataItem; if (this.useKeys_) { - const key = this.computeKey(newActiveDataItemAtActiveIndex.item as T, activeIndex, /* monitor access already ongoing */ false, newActiveDataItems); - movedDataItem = this.findDataItemInOldActivateDataItemsByKey(key, newActiveDataItemAtActiveIndex.ttype); + const key = this.computeKey(newActiveDataItemAtActiveIndex.item as T, activeIndex, + /* monitor access already ongoing */ false, newActiveDataItems); + movedDataItem = + this.findDataItemInOldActivateDataItemsByKey(key, newActiveDataItemAtActiveIndex.ttype); } else { - movedDataItem = this.findDataItemInOldActivateDataItemsByValue(newActiveDataItemAtActiveIndex.item as T, newActiveDataItemAtActiveIndex.ttype); + movedDataItem = this.findDataItemInOldActivateDataItemsByValue( + newActiveDataItemAtActiveIndex.item as T, newActiveDataItemAtActiveIndex.ttype); } if (movedDataItem) { @@ -519,8 +529,9 @@ class __RepeatVirtualScroll2Impl { // index has changed, update it in RepeatItem const ridMeta = this.meta4Rid_.get(movedDataItem.rid); - stateMgmtConsole.debug(` ... new index ${activeIndex} / old index ${ridMeta.repeatItem_.index}: keep in L1: rid ${movedDataItem.rid}, unchanged ttype '${newActiveDataItemAtActiveIndex.ttype}'`); - ridMeta.repeatItem_.updateIndex(activeIndex) + stateMgmtConsole.debug(`new index ${activeIndex} / old index ${ridMeta.repeatItem_.index}: `, + `keep in L1: rid ${movedDataItem.rid}, unchanged ttype '${newActiveDataItemAtActiveIndex.ttype}'`); + ridMeta.repeatItem_.updateIndex(activeIndex); firstIndexChanged = Math.min(firstIndexChanged, activeIndex); // the data item is handled, remove it from old active data range @@ -529,7 +540,7 @@ class __RepeatVirtualScroll2Impl { } else { // update is needed for this data item // either because dataItem is new, or new ttype needs to used - stateMgmtConsole.debug(` ... need update for index ${activeIndex}`); + stateMgmtConsole.debug(`need update for index ${activeIndex}`); firstIndexChanged = Math.min(firstIndexChanged, activeIndex); } } // for new data items in active range @@ -545,10 +556,10 @@ class __RepeatVirtualScroll2Impl { } } - // Step 4: data items in new source array that are either new in the array + // step 4: data items in new source array that are either new in the array // or have been there before but need to be rendered with different ttype // if canUpdate then do the update. - // if need new render, do not do the new render right away. Wait for layout to ask + // if need new render, do not do the new render right away. Wait for layout to ask // for the item to render. for (const indexS in newActiveDataItems) { const activeIndex = parseInt(indexS); @@ -558,19 +569,20 @@ class __RepeatVirtualScroll2Impl { continue; } - const optRid = this.reusable_ ? this.canUpdate(newActiveDataItemAtActiveIndex.ttype) : -1; + const optRid = this.canUpdate(newActiveDataItemAtActiveIndex.ttype); if (optRid > 0) { const ridMeta = this.meta4Rid_.get(optRid); if (ridMeta) { // found rid / repeatItem to update - stateMgmtConsole.debug(` ... index ${activeIndex}: update rid ${optRid} / ttype '${newActiveDataItemAtActiveIndex.ttype}'`); + stateMgmtConsole.debug(`index ${activeIndex}: update rid ${optRid} / ttype `, + `'${newActiveDataItemAtActiveIndex.ttype}'`); newActiveDataItemAtActiveIndex.rid = optRid; newActiveDataItemAtActiveIndex.state = ActiveDataItem.UINodeExists; if (this.useKeys_) { - // FIXME rerender is monitored, switch to unmonitored - const key = this.computeKey(newActiveDataItemAtActiveIndex.item as T, activeIndex, /* monitor access already ongoing */ false, newActiveDataItems); + const key = this.computeKey(newActiveDataItemAtActiveIndex.item as T, activeIndex, + /* monitor access already ongoing */ false, newActiveDataItems); newActiveDataItemAtActiveIndex.key = key; ridMeta.key_ = key; } @@ -588,12 +600,11 @@ class __RepeatVirtualScroll2Impl { firstIndexChanged = Math.min(firstIndexChanged, activeIndex); } } else { - stateMgmtConsole.debug(` ... active range index ${activeIndex}: no rid found to update`); + stateMgmtConsole.debug(`active range index ${activeIndex}: no rid found to update`); } }; // render all data changes in one go - // FIXME we are in the middle of rerender, should this be deleted ObserveV2.getObserve().updateDirty2(true); this.activeDataItems_ = newActiveDataItems; @@ -602,12 +613,13 @@ class __RepeatVirtualScroll2Impl { `\nspareRid : ${this.dumpSpareRid()}`, `\nthis.dumpDataItems: ${this.activeDataItems_}`, `\nnewL1Rid4Index: ${JSON.stringify(Array.from(newL1Rid4Index))}`, - `\n ... first item changed at index ${firstIndexChanged} .`); - // RepeatVirtualScroll2Native.requestContainerReLayout(this.repeatElmtId_, 0); + `\nfirst item changed at index ${firstIndexChanged} .`); - RepeatVirtualScroll2Native.updateL1Rid4Index(this.repeatElmtId_, this.totalCount_, firstIndexChanged, Array.from(newL1Rid4Index)); + RepeatVirtualScroll2Native.updateL1Rid4Index( + this.repeatElmtId_, this.totalCount_, firstIndexChanged, Array.from(newL1Rid4Index)); - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) reRender() data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - done`); + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) reRender() data array length: `, + `${this.arr_.length}, totalCount: ${this.totalCount_} - done`); } private computeTtype(item: T, index: number, monitorAccess: boolean): string { @@ -620,8 +632,8 @@ class __RepeatVirtualScroll2Impl { try { ttype = this.ttypeGenFunc_(item, index); } catch(e) { - stateMgmtConsole.applicationError(`__RepeatVirtualScroll2Impl. Error generating ttype at index: ${index}`, - e?.message); + stateMgmtConsole.applicationError( + `__RepeatVirtualScroll2Impl. Error generating ttype at index: ${index}`, e?.message); } if (ttype in this.itemGenFuncs_ === false) { stateMgmtConsole.applicationError(`__RepeatVirtualScroll2Impl. No template found for ttype '${ttype}'`); @@ -631,8 +643,8 @@ class __RepeatVirtualScroll2Impl { return ttype; } - private computeKey(item : T, index: number, monitorAccess: boolean = true, - activateDataItems? : Array>) : string | undefined { + private computeKey(item: T, index: number, monitorAccess: boolean = true, + activateDataItems?: Array>): string | undefined { if (!this.useKeys_) { return undefined; } @@ -643,12 +655,13 @@ class __RepeatVirtualScroll2Impl { try { key = this.keyGenFunc_(item, index); } catch { - stateMgmtConsole.applicationError(`__RepeatVirtualScroll2Impl (${this.repeatElmtId_}): unstable key function. Fix the key gen function in your application!`); + stateMgmtConsole.applicationError(`__RepeatVirtualScroll2Impl (${this.repeatElmtId_}):`, + `unstable key function. Fix the key gen function in your application!`); key = this.mkRandomKey(index, "_key-gen-crashed_"); } monitorAccess && this.stopRecordDependencies(); - const optIndex1 : number | undefined = this.index4Key_.get(key); + const optIndex1: number | undefined = this.index4Key_.get(key); if (optIndex1 !== undefined) { key = this.handleDuplicateKey(optIndex1, index, key, activateDataItems); } else { @@ -659,17 +672,15 @@ class __RepeatVirtualScroll2Impl { return key; } - private mkRandomKey(index : number, origKey : string) : string { + private mkRandomKey(index: number, origKey: string): string { return `___${index}_+_${origKey}_+_${Math.random()}`; } - /* - when generating key for index2, detected that index1 has same key already - need to change the key for both index'es - returns random key for index 2 - */ - private handleDuplicateKey(prevIndex : number, curIndex : number, origKey : string, - activateDataItems? : Array>) : string { + // when generating key for index2, detected that index1 has same key already + // need to change the key for both index'es + // returns random key for index 2 + private handleDuplicateKey(prevIndex: number, curIndex: number, origKey: string, + activateDataItems?: Array>): string { const curKey = this.mkRandomKey(curIndex, origKey); this.key4Index_.set(curIndex, curKey); this.index4Key_.set(curKey, curIndex); @@ -680,7 +691,8 @@ class __RepeatVirtualScroll2Impl { this.index4Key_.set(prevKey, prevIndex); this.index4Key_.delete(origKey); if (activateDataItems && activateDataItems[prevIndex] !== undefined) { - stateMgmtConsole.debug(` ... correcting key of activeDataItem index ${prevIndex} from '${activateDataItems[prevIndex].key}' to '${prevKey}'.`); + stateMgmtConsole.debug(`correcting key of activeDataItem index ${prevIndex} from `, + `'${activateDataItems[prevIndex].key}' to '${prevKey}'.`); activateDataItems[prevIndex].key = prevKey; } stateMgmtConsole.applicationError(`__RepeatVirtualScroll2Impl (${this.repeatElmtId_}): `, @@ -692,62 +704,72 @@ class __RepeatVirtualScroll2Impl { /** * called from C++ GetFrameChild whenever need to create new node and add to L1 * or update spare node and add back to L1 + * * @param forIndex * @returns */ private onGetRid4Index(forIndex: number): [number, number] { if (forIndex < 0 || forIndex >= this.totalCount_) { - throw new Error(`${this.constructor.name}(${this.repeatElmtId_}) onGetRid4Index index ${forIndex} \n - data array length: ${this.arr_.length}, totalCount: ${this.totalCount_}: Out of range, application error.`); + throw new Error(`${this.constructor.name}(${this.repeatElmtId_}) onGetRid4Index index ${forIndex}` + + `\ndata array length: ${this.arr_.length}, totalCount: ${this.totalCount_}: ` + + `Out of range, application error.`); } - const [ dataItemExists, dataItem ] = this.getItemUnmonitored(forIndex); + const [dataItemExists, dataItem] = this.getItemUnmonitored(forIndex); if (!dataItemExists) { - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onGetRid4Index index ${forIndex} - missing data item.`); + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) `, + `onGetRid4Index index ${forIndex} - missing data item.`); this.activeDataItems_[forIndex] = ActiveDataItem.createMissingDataItem(); return [0, /* failed to create or update */ 0]; } const ttype = this.computeTtype(dataItem, forIndex, /* enable monitored access */ true); - const key = this.computeKey(dataItem, forIndex, /* monitor access*/ true, this.activeDataItems_); - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onGetRid4Index index ${forIndex}, ttype is '${ttype}' data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start start ++++++++`); + const key = this.computeKey(dataItem, forIndex, /* monitor access*/ true, this.activeDataItems_); + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onGetRid4Index index ${forIndex}, `, + `ttype is '${ttype}' data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start`); // spare UINode / RID available to update? - const optRid = this.reusable_ ? this.canUpdateTryMatch(ttype, dataItem, key) : -1; + const optRid = this.canUpdateTryMatch(ttype, dataItem, key); const result: [number, number] = (optRid > 0) ? this.updateChild(optRid, ttype, forIndex, key) : this.createNewChild(forIndex, ttype, key); - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onGetRid4Index index ${forIndex} ttype is '${ttype}' data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - DONE`); + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onGetRid4Index index ${forIndex} `, + `ttype is '${ttype}' data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - DONE`); return result; } // return RID of Node that can be updated (matching ttype), // or -1 if none private canUpdate(ttype: string): number { + if (!this.allowUpdate_) { + return -1; + } for (const rid of this.spareRid_) { if (this.meta4Rid_.get(rid).ttype_ === ttype) { - stateMgmtConsole.debug(` canUpdate: Found spare rid ${rid} for ttype '${ttype}'`); + stateMgmtConsole.debug(`canUpdate: Found spare rid ${rid} for ttype '${ttype}'`); return rid; } } - stateMgmtConsole.debug(` canUpdate: Found NO spare rid for ttype '${ttype}'`); + stateMgmtConsole.debug(`canUpdate: Found NO spare rid for ttype '${ttype}'`); return -1; } // return RID of Node that can be updated (matching ttype), // or -1 if none private canUpdateTryMatch(ttype: string, dataItem: T, key?: string): number { + if (!this.allowUpdate_) { + return -1; + } // 1. round: find matching RID, also data item matches for (const rid of this.spareRid_) { const ridMeta = this.meta4Rid_.get(rid); // compare ttype and data item, or ttype and key - if (ridMeta && ridMeta.ttype_ === ttype && + if (ridMeta && ridMeta.ttype_ === ttype && ((!this.useKeys_ && ridMeta.repeatItem_?.item === dataItem) || - (this.useKeys_ && ridMeta.key_ === key)) - ) { - // FIXME also return the repeatItem - stateMgmtConsole.debug(` canUpdateTryMatch: Found spare rid ${rid} for ttype '${ttype}' contentItem matches.`); + (this.useKeys_ && ridMeta.key_ === key))) { + stateMgmtConsole.debug( + `canUpdateTryMatch: Found spare rid ${rid} for ttype '${ttype}' contentItem matches.`); return rid; } } @@ -755,31 +777,33 @@ class __RepeatVirtualScroll2Impl { // just find a matching RID for (const rid of this.spareRid_) { if (this.meta4Rid_.get(rid).ttype_ === ttype) { - stateMgmtConsole.debug(` canUpdateTryMatch: Found spare rid ${rid} for ttype '${ttype}'`); + stateMgmtConsole.debug(`canUpdateTryMatch: Found spare rid ${rid} for ttype '${ttype}'`); return rid; } } - stateMgmtConsole.debug(` canUpdateTryMatch: Found NO spare rid for ttype '${ttype}'`); + stateMgmtConsole.debug(`canUpdateTryMatch: Found NO spare rid for ttype '${ttype}'`); return -1; } /** * crete new Child node onto the ViewStackProcess + * * @param forIndex * @param ttype * @returns [ success, 1 for new node created ] */ private createNewChild(forIndex: number, ttype: string, key?: string): [number, number] { - let itemGenFunc = this.itemGenFuncs_[ttype]; this.startRecordDependencies(); // item exists in arr_, has been checked before - const [ _, dataItem ] = this.getItemUnmonitored(forIndex); + const [_, dataItem] = this.getItemUnmonitored(forIndex); const repeatItem = this.mkRepeatItem_(dataItem, forIndex); const rid = this.nextRid++; - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) createNewChild index ${forIndex} -> new rid ${rid} / ttype ${ttype}, key ${key} - data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start`); + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) createNewChild index ${forIndex} -> `, + `new rid ${rid} / ttype ${ttype}, key ${key} - data array length: ${this.arr_.length}, `, + `totalCount: ${this.totalCount_} - start`); try { // execute item builder function @@ -787,7 +811,10 @@ class __RepeatVirtualScroll2Impl { } catch (e) { this.stopRecordDependencies(); - stateMgmtConsole.applicationError(`${this.constructor.name}(${this.repeatElmtId_}) initialRenderChild(forIndex: ${forIndex}, templateId: '${ttype}') -> RID ${rid}: data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - item initial render failed!`); + stateMgmtConsole.applicationError(`${this.constructor.name}(${this.repeatElmtId_}) `, + `initialRenderChild(forIndex: ${forIndex}, templateId: '${ttype}') -> RID ${rid}: `, + `data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - `, + `item initial render failed!`); this.activeDataItems_[forIndex] = ActiveDataItem.createFailedToCreateUINodeDataItem(this.arr_[forIndex]); return [0, /* did not success creating new UINode */ 0]; } @@ -798,13 +825,15 @@ class __RepeatVirtualScroll2Impl { this.stopRecordDependencies(); - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) createNewChild index ${forIndex} -> new rid ${rid} / ttype ${ttype}, key ${key} - data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - done`); + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) createNewChild index ${forIndex} -> `, + `new rid ${rid} / ttype ${ttype}, key ${key} - data array length: ${this.arr_.length}, `, + `totalCount: ${this.totalCount_} - done`); return [rid, /* created new UINode successfully */ 1]; } - /** * update given rid / RepeatItem to data item of given index + * * @param rid * @param ttype * @param forIndex @@ -814,12 +843,17 @@ class __RepeatVirtualScroll2Impl { const ridMeta = this.meta4Rid_.get(rid); if (!ridMeta || !ridMeta.repeatItem_) { // error - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) updateChild(forIndex: ${forIndex}, templateId: ${ttype}) <- RID ${rid}: data array length: ${this.arr_.length}, totalCount: ${this.totalCount_}. Failed to find RepeatItem. Internal error!.`); + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) `, + `updateChild(forIndex: ${forIndex}, templateId: ${ttype}) <- RID ${rid}: `, + `data array length: ${this.arr_.length}, totalCount: ${this.totalCount_}. `, + `Failed to find RepeatItem. Internal error!.`); this.activeDataItems_[forIndex] = ActiveDataItem.createFailedToCreateUINodeDataItem(this.arr_[forIndex]); return [0, /* failed to update */ 0]; } - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) updateChild(forIndex: ${forIndex}, templateId: ${ttype}) <- RID ${rid}, key: ${key}: data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start`); + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) updateChild`, + `(forIndex: ${forIndex}, templateId: ${ttype}) <- RID ${rid}, key: ${key}: `, + `data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start`); // item exists in arr_, has been checked before const [_, dataItem] = this.getItemMonitored(forIndex); @@ -838,27 +872,30 @@ class __RepeatVirtualScroll2Impl { ridMeta.repeatItem_.updateIndex(forIndex); ObserveV2.getObserve().updateDirty2(/* update synchronously */ true); - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) updateChild(forIndex: ${forIndex}, templateId: ${ttype}) <- RID ${rid}, key: ${key}: update has been done - done`); + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) updateChild`, + `(forIndex: ${forIndex}, templateId: ${ttype}) <- RID ${rid}, key: ${key}: `, + `update has been done - done`); } else { - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) updateChild(forIndex: ${forIndex}, templateId: ${ttype}) <- RID ${rid}, key: ${key}: item and index value as in spare, no update needed - done`); + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) updateChild`, + `(forIndex: ${forIndex}, templateId: ${ttype}) <- RID ${rid}, key: ${key}: `, + `item and index value as in spare, no update needed - done`); } return [rid, /* update success */ 2]; } - /** * overloaded function from FrameNode * called from layout, inform index range of active / L1 items that can be removed from L1 * to spare nodes, allow to update them * Note: Grid, List layout has a bug: Frequently calls GetFrameChildForIndex for the index 'fromIndex' * which moves this item back to L1 + * * @param fromIndex * @param toIndex */ private onRecycleItems(fromIndex: number, toIndex: number): void { // avoid negative fromIndex - // FIXME is this always correct? fromIndex = Math.max(0, fromIndex); for (let index = fromIndex; index < toIndex; index++) { if (index >= this.totalCount_ || !(index in this.activeDataItems_)) { @@ -868,9 +905,8 @@ class __RepeatVirtualScroll2Impl { this.dropFromL1ActiveNodes(index); } } - stateMgmtConsole.debug(`onRecycleItems(${fromIndex}...<${toIndex}) after applying changes: `) - stateMgmtConsole.debug(this.dumpSpareRid()); - stateMgmtConsole.debug(this.dumpDataItems()); + stateMgmtConsole.debug(`onRecycleItems(${fromIndex}...<${toIndex}) after applying changes: `, + `\n${this.dumpSpareRid()}\n${this.dumpDataItems()}`); } private dropFromL1ActiveNodes(index: number, invalidate : boolean = true): boolean { @@ -889,7 +925,7 @@ class __RepeatVirtualScroll2Impl { if (rid === undefined || ttype === undefined) { // data item is not rendered, yet stateMgmtConsole.debug(`dropFromL1ActiveNodes index: ${index} no rid ${rid} or no ttype '${ttype ? ttype : 'undefined'}'. Dropping un-rendered item silently.`); - return; + return false; } // add to spare rid Set @@ -906,21 +942,28 @@ class __RepeatVirtualScroll2Impl { return true; } - private onActiveRange(nStart: number, nEnd: number): void { - + private onActiveRange(nStart: number, nEnd: number, isLoop: boolean): void { if (Number.isNaN(this.activeRange_[0])) { - // first call to onActiveRange + // first call to onActiveRange / no active node this.activeRange_ = [nStart, nEnd]; } else if (this.activeRange_[0] === nStart && this.activeRange_[1] === nEnd) { - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onActiveRange`, - `(nStart: ${nStart}, nEnd: ${nEnd})`, - `data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - unchanged, skipping.`); - return; + if (!isLoop) { + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onActiveRange`, + `(nStart: ${nStart}, nEnd: ${nEnd})`, + `data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - unchanged, skipping.`); + return; + } + } + + const maxInt: number = 2147483647; + if (nStart === maxInt && nEnd === maxInt) { // there is no active node on screen + nStart = NaN; + nEnd = NaN; } stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onActiveRange`, `(nStart: ${nStart}, nEnd: ${nEnd})`, - `data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start start ++++++++`); + `data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start`); // check which of the activeDataItems needs to be removed from L1 & activeDataItems let numberOfActiveItems = 0; @@ -930,9 +973,14 @@ class __RepeatVirtualScroll2Impl { } // same condition as in C++ RepeatVirtualScroll2Node::CheckNode4IndexInL1 - const remainInL1 = (nStart <= index && index <= nEnd) || - (nStart > nEnd && ((index >= nStart && index < this.totalCount_) || (index >= 0 && index <= nEnd))); - stateMgmtConsole.debug(` .... index: ${index}: ${remainInL1 ? 'keep in L1' : 'drop from L1'}`, + let remainInL1 = (nStart <= index && index <= nEnd); + if (isLoop) { + remainInL1 = remainInL1 || + (nStart > nEnd && (nStart <= index || index <= nEnd)) || + (nStart < 0 && index >= nStart + this.totalCount_) || + (nEnd >= this.totalCount_ && index <= nEnd - this.totalCount_); + } + stateMgmtConsole.debug(`index: ${index}: ${remainInL1 ? 'keep in L1' : 'drop from L1'}`, `dataItem: ${this.activeDataItems_[index].dump()}`); if (remainInL1) { if (this.activeDataItems_[index].state === ActiveDataItem.UINodeExists) { @@ -943,12 +991,12 @@ class __RepeatVirtualScroll2Impl { this.dropFromL1ActiveNodes(index, /* call C++ RepeatVirtualScrollCaches::SetInvalid */ false); } } - }; - stateMgmtConsole.debug(` --> onActiveRange Result: number remaining activeItems ${numberOfActiveItems}.`, - `\n${this.dumpDataItems()}\n${this.dumpSpareRid()}\n${this.dumpRepeatItem4Rid()}`); + } // memorize this.activeRange_ = [nStart, nEnd]; + stateMgmtConsole.debug(`onActiveRange Result: number remaining activeItems ${numberOfActiveItems}.`, + `\n${this.dumpDataItems()}\n${this.dumpSpareRid()}\n${this.dumpRepeatItem4Rid()}`); // adjust dynamic cachedCount for each template type that is using dynamic cached count stateMgmtConsole.debug(`templateOptions_ ${JSON.stringify(this.templateOptions_)}`); @@ -963,9 +1011,9 @@ class __RepeatVirtualScroll2Impl { stateMgmtConsole.debug(this.dumpCachedCount()); } - private onPurge() { - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) purge(), totalCount: ${this.totalCount_} - start ++++++++`); + stateMgmtConsole.debug( + `${this.constructor.name}(${this.repeatElmtId_}) purge(), totalCount: ${this.totalCount_} - start`); // deep copy templateOptions_ let availableCachedCount: { [ttype: string]: number } = {}; @@ -973,10 +1021,6 @@ class __RepeatVirtualScroll2Impl { availableCachedCount[pair[0]] = pair[1].cachedCount as number; }); - /// stateMgmtConsole.debug('onPurge before applying changes: ') - //stateMgmtConsole.debug(this.dumpSpareRid()); - //stateMgmtConsole.debug(this.dumpDataItems()); - // Improvement needed: // this is a simplistic purge is more or less randomly purges // extra nodes @@ -991,24 +1035,23 @@ class __RepeatVirtualScroll2Impl { availableCachedCount[ttype] -= 1; } } - stateMgmtConsole.debug('onPurge after applying changes: ') - stateMgmtConsole.debug(this.dumpSpareRid()); - stateMgmtConsole.debug(this.dumpDataItems()); + stateMgmtConsole.debug(`onPurge after applying changes: \n${this.dumpSpareRid()}\n${this.dumpDataItems()}`); } private purgeNode(rid: number): void { - stateMgmtConsole.debug(` ... delete node rid: ${rid} .`); + stateMgmtConsole.debug(`delete node rid: ${rid}.`); this.meta4Rid_.delete(rid); this.spareRid_.delete(rid); RepeatVirtualScroll2Native.removeNode(rid); } private dumpSpareRid(): string { - return `spareRid size: ${this.spareRid_.size} ${JSON.stringify(Array.from(this.spareRid_).map(rid => `rid: ${rid}`))}`; + return `spareRid size: ${this.spareRid_.size} ` + + `${JSON.stringify(Array.from(this.spareRid_).map(rid => `rid: ${rid}`))}.`; } private dumpRepeatItem4Rid(): string { - return `meta4Rid_ size: ${this.meta4Rid_.size}: ${JSON.stringify(Array.from(this.meta4Rid_))} .`; + return `meta4Rid_ size: ${this.meta4Rid_.size}: ${JSON.stringify(Array.from(this.meta4Rid_))}.`; } private dumpDataItems(): string { @@ -1017,32 +1060,37 @@ class __RepeatVirtualScroll2Impl { let count = 0; for (const index in this.activeDataItems_) { const dataItemDump = this.activeDataItems_[index].dump(); - const repeatItemIndex = this.activeDataItems_[index].rid ? this.meta4Rid_.get(this.activeDataItems_[index].rid)?.repeatItem_?.index : "N/A"; + const repeatItemIndex = this.activeDataItems_[index].rid + ? this.meta4Rid_.get(this.activeDataItems_[index].rid)?.repeatItem_?.index + : "N/A"; result += `${sepa}index ${index}, ${dataItemDump} (repeatItemIndex ${repeatItemIndex})`; sepa = ", \n"; count += 1; } - return `activeDataItems(array): length: ${this.activeDataItems_.length}, range: [${this.activeRange_[0]}-${this.activeRange_[1]}], entries count: ${count} =============\n${result}`; + return `activeDataItems(array): length: ${this.activeDataItems_.length}, ` + + `range: [${this.activeRange_[0]}-${this.activeRange_[1]}], ` + + `entries count: ${count} =============\n${result}`; } - private dumpCachedCount() : string { + private dumpCachedCount(): string { let result = ""; let sepa = ""; Object.entries(this.templateOptions_).forEach((pair) => { const options: RepeatTemplateImplOptions = pair[1]; - result += `${sepa}'template ${pair[0]}': specified: ${options.cachedCountSpecified} cachedCount: ${options.cachedCount}: ` + result += `${sepa}'template ${pair[0]}': specified: ${options.cachedCountSpecified} ` + + `cachedCount: ${options.cachedCount}: `; sepa = ", "; }); return result; } - private dumpKeys() : string { + private dumpKeys(): string { let result = ""; let sepa = ""; this.key4Index_.forEach((key, index) => { result += `${sepa}${index}: ${key}`; sepa = "\n"; - }) + }); return result; } }; \ No newline at end of file diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts index 13da55a853b..2d7ad3824b1 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts +++ b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts @@ -89,7 +89,7 @@ class __RepeatVirtualScrollImpl { stateMgmtConsole.applicationError(`__RepeatVirtualScrollImpl. Error generating ttype at index: ${index}`, e?.message); } - if (ttype in this.itemGenFuncs_ == false) { + if (ttype in this.itemGenFuncs_ === false) { stateMgmtConsole.applicationError(`__RepeatVirtualScrollImpl. No template found for ttype '${ttype}'`); ttype = RepeatEachFuncTtype; } diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h index 665c67cc1b2..91eb045f3a3 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h @@ -38,7 +38,7 @@ public: virtual void Create(uint32_t totalCount, const std::function(int32_t)>& onGetRid4Index, const std::function onRecycleItems, - const std::function onActiveRange, + const std::function onActiveRange, const std::function onPurge) = 0; virtual void RemoveNode(uint32_t rid) = 0; diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp index 03698646d8e..ac6e27053f3 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp @@ -26,7 +26,7 @@ namespace OHOS::Ace::NG { void RepeatVirtualScroll2ModelNG::Create(uint32_t totalCount, const std::function(int32_t)>& onGetRid4Index, const std::function onRecycleItems, - const std::function onActiveRange, + const std::function onActiveRange, const std::function onPurge) { ACE_SCOPED_TRACE("RepeatVirtualScroll2ModelNG::Create"); diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h index c7c9b347df4..beb58e3c274 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h @@ -31,7 +31,7 @@ class ACE_EXPORT RepeatVirtualScroll2ModelNG : public RepeatVirtualScroll2Model public: void Create(uint32_t totalCount, const std::function(int32_t)>& onGetRid4Index, const std::function onRecycleItems, - const std::function onActiveRange, + const std::function onActiveRange, const std::function onPurge) override; void RemoveNode(uint32_t rid) override; diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp index 0bab5f2c49c..080ae6c1a11 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp @@ -33,7 +33,7 @@ using CacheItem = RepeatVirtualScroll2Caches::CacheItem; RefPtr RepeatVirtualScroll2Node::GetOrCreateRepeatNode(int32_t nodeId, uint32_t totalCount, const std::function(IndexType)>& onGetRid4Index, const std::function onRecycleItems, - const std::function onActiveRange, + const std::function onActiveRange, const std::function onPurge) { auto node = ElementRegister::GetInstance()->GetSpecificItemById(nodeId); @@ -53,7 +53,7 @@ RefPtr RepeatVirtualScroll2Node::GetOrCreateRepeatNode RepeatVirtualScroll2Node::RepeatVirtualScroll2Node(int32_t nodeId, int32_t totalCount, const std::function(IndexType)>& onGetRid4Index, const std::function onRecycleItems, - const std::function onActiveRange, + const std::function onActiveRange, const std::function onPurge) : ForEachBaseNode(V2::JS_REPEAT_ETS_TAG, nodeId), totalCount_(totalCount), caches_(onGetRid4Index), onRecycleItems_(onRecycleItems), onActiveRange_(onActiveRange), onPurge_(onPurge), @@ -104,38 +104,83 @@ void RepeatVirtualScroll2Node::DoSetActiveChildRange( caches_.DumpUINodeCache().c_str(), caches_.DumpL1Rid4Index().c_str()); } +/** + * check whether the node is in L1 cache. called from ReBuildL1 + * + * index: node index + * nStart: first return value of CheckActiveRange + * nEnd: second return value of CheckActiveRange + * cacheItem: repeat cache item of index + */ bool RepeatVirtualScroll2Node::CheckNode4IndexInL1(int32_t index, int32_t nStart, int32_t nEnd, CacheItem& cacheItem) { auto totalCount = static_cast(totalCount_); - const bool remainInL1 = (nStart <= index && index <= nEnd) || - (nStart > nEnd && ((index >= nStart && index < totalCount) || (index >= 0 && index <= nEnd))); + bool remainInL1 = (nStart <= index && index <= nEnd); // cover scenario 1 & 2 & 4 + if (isLoop_) { + remainInL1 = remainInL1 || + (nStart > nEnd && (nStart <= index || index <= nEnd)) || // cover scenario 3.2 & 3.4 + (nStart < 0 && index >= nStart + totalCount) || // cover scenario 3.1 + (nEnd >= totalCount && index <= nEnd - totalCount); // cover scenario 3.3 + } cacheItem->isL1_ = remainInL1; cacheItem->isOnRenderTree_ = remainInL1; return remainInL1; } +/** + * pre-process active range. called from DoSetActiveChildRange + * + * start: start index on the screen, may be negative or greater than totalCount + * end: end index on the screen, may be negative or greater than totalCount + * cacheStart: cachedCount above - depends on container + * cacheEnd: cachedCount below - depends on container + * + * possible scenarios (only one repeat in scroll container): + * - scenario 1: List/Swiper-no-Loop + * - 1.1 screen: [0 1 2] 3 4 5.., DoSetActiveChildRange params: (0,2,0,2), nStart&nEnd: (0,4) + * - 1.2 screen: ..1 2 [3 4 5] 6 7, DoSetActiveChildRange params: (3,5,2,2), nStart&nEnd: (1,7) + * - 1.3 screen: ..5 6 [7 8 9], DoSetActiveChildRange params: (7,9,2,0), nStart&nEnd: (5,9) + * - scenario 2: Grid/WaterFlow + * - 2.1 screen: [0 1 2] 3 4 5.., DoSetActiveChildRange params: (0,2,2,2), nStart&nEnd: (0,4) + * - 2.2 screen: ..1 2 [3 4 5] 6 7, DoSetActiveChildRange params: (3,5,2,2), nStart&nEnd: (1,7) + * - 2.3 screen: ..5 6 [7 8 9], DoSetActiveChildRange params: (7,9,2,2), nStart&nEnd: (5,9) + * - scenario 3: Swiper-Loop + * - 3.1 screen: ..8 9 [0 1 2] 3 4.., DoSetActiveChildRange params: (0,2,2,2), nStart&nEnd: (-2,4) + * - 3.2 screen: ..7 8 [9 0 1] 2 3.., DoSetActiveChildRange params: (9,1,2,2), nStart&nEnd: (7,3) + * - 3.3 screen: ..5 6 [7 8 9] 0 1.., DoSetActiveChildRange params: (8,2,2,2), nStart&nEnd: (5,11) + * - 3.4 screen(overlapped): 2 [3 0] 1, DoSetActiveChildRange params: (3,0,2,2), nStart&nEnd: (2,1) + * + * possible scenarios (multiple components in scroll container, X indicates a non-repeat child): + * - scenario 4: List/Grid/WaterFlow/Swiper-no-Loop + * - 4.1 screen: ..[X X 0 1 2] 3 4.., DoSetActiveChildRange params: (-2,3,2,2), nStart&nEnd: (0,4) + * - 4.2 screen: ..[X X X] 0 1 2.., DoSetActiveChildRange params: (-5,-1,2,2), nStart&nEnd: (0,1) + * - 4.3 screen: ..[X X X] X X 0 1.., DoSetActiveChildRange params: (-5,-3,2,2), nStart&nEnd: (NaN) + * - 4.4 screen: ..0 1 [2 3 4 X X].., DoSetActiveChildRange params: (2,6,2,2), nStart&nEnd: (0,4) + * - 4.5 screen: ..0 1 [X X X].., DoSetActiveChildRange params: (2,5,2,2), nStart&nEnd: (0,1) + * - 4.6 screen: ..0 1 X X [X X X].., DoSetActiveChildRange params: (4,6,2,2), nStart&nEnd: (NaN) + * - scenario 5: Swiper-Loop. Currently it isn't allowed to be used. + */ ActiveRangeType RepeatVirtualScroll2Node::CheckActiveRange( int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd) { const int32_t signed_totalCount = static_cast(totalCount_); int32_t nStart = start - cacheStart; int32_t nEnd = end + cacheEnd; - const int32_t divider = 2; - - if (start > end) { // swiper-loop scenario - nStart = std::min(nStart, signed_totalCount); - nEnd = std::max(nEnd, 0); - if (nStart <= nEnd) { // overlapped - nStart = signed_totalCount / divider + 1; - nEnd = signed_totalCount / divider; - } - } else { - if (nStart >= signed_totalCount || nEnd < 0) { - nStart = 0; - nEnd = 0; - } else { + + if (start > end && nStart <= nEnd) { // cover scenario 3.4 + // nStart & nEnd overlapped. must keep nStart > nEnd, otherwise CheckNode4IndexInL1 + // cannot determine swiper-loop scenario + const int32_t divider = 2; + nStart = signed_totalCount / divider + 1; + nEnd = signed_totalCount / divider; + } + if (!isLoop_) { + if (nStart >= signed_totalCount || nEnd < 0) { // no active node on screen + nStart = INT32_MAX; + nEnd = INT32_MAX; + } else { // cover scenario 1 & 2 & 4 + // keep 0 <= nStart <= nEnd <= totalCount - 1 nStart = std::max(nStart, 0); - // start <= end <= totalCount - 1 nEnd = std::min(std::max(nEnd, nStart), signed_totalCount - 1); } } @@ -168,7 +213,7 @@ ActiveRangeType RepeatVirtualScroll2Node::CheckActiveRange( start, end, cacheStart, cacheEnd, nStart, nEnd); // step 2. call TS side - onActiveRange_(nStart, nEnd); + onActiveRange_(nStart, nEnd, isLoop_); return {nStart, nEnd}; } diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h index 0f576d1a223..ffefe507ad8 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h @@ -53,7 +53,7 @@ * C++ lambdas to call these TS functions * onGetRid4Index: (forIndex: number) => [number, number], * onRecycleItems: (fromIndex: number, toIndex: number) => void, - * onActiveRange: (fromIndex: number, toIndex: number) => void, + * onActiveRange: (fromIndex: number, toIndex: number, isLoop: boolean) => void, * onPurge: () => void; * onMoveHandler: (from: number, to: number) => void; * @@ -95,13 +95,13 @@ public: static RefPtr GetOrCreateRepeatNode(int32_t nodeId, uint32_t totalCount, const std::function(IndexType)>& onGetRid4Index, const std::function onRecycleItems, - const std::function onActiveRange, + const std::function onActiveRange, const std::function onPurge); RepeatVirtualScroll2Node(int32_t nodeId, int32_t totalCount, const std::function(IndexType)>& onGetRid4Index, const std::function onRecycleItems, - const std::function onActiveRange, + const std::function onActiveRange, const std::function onPurge); ~RepeatVirtualScroll2Node() override = default; @@ -273,7 +273,7 @@ private: bool forceRunDoSetActiveRange_ = false; std::function onRecycleItems_; - std::function onActiveRange_; + std::function onActiveRange_; std::function onPurge_; // true in the time from requesting idle / predict task until exec predict tsk. -- Gitee From abf3a9f58d9e68e4f95cd87197077883be003494 Mon Sep 17 00:00:00 2001 From: liubihao Date: Mon, 10 Feb 2025 15:51:37 +0800 Subject: [PATCH 5/8] Repeat: remove the extra KeySorterClass Signed-off-by: liubihao --- .../syntax/repeat_virtual_scroll_caches.cpp | 5 ----- .../syntax/repeat_virtual_scroll_caches.h | 12 ------------ 2 files changed, 17 deletions(-) diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_caches.cpp b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_caches.cpp index cffc121eea9..018e6201fa4 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_caches.cpp +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_caches.cpp @@ -21,11 +21,6 @@ namespace OHOS::Ace::NG { using CacheItem = RepeatVirtualScrollCaches::CacheItem; -bool KeySorterClass::operator()(const std::string& left, const std::string& right) const -{ - return virtualScroll_->CompareKeyByIndexDistance(left, right); -} - RepeatVirtualScrollCaches::RepeatVirtualScrollCaches( const std::map>& cacheCountL24ttype, const std::function& onCreateNode, diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_caches.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_caches.h index a0153649e52..5bab3bcd0bf 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_caches.h +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_caches.h @@ -30,25 +30,13 @@ namespace OHOS::Ace::NG { -// custom sorting for std::set only works with struct -// with operator() inside -class RepeatVirtualScrollCaches; -struct KeySorterClass { - const RepeatVirtualScrollCaches* virtualScroll_; - - explicit KeySorterClass(const RepeatVirtualScrollCaches* virtualScroll) : virtualScroll_(virtualScroll) {} - bool operator()(const std::string& left, const std::string& right) const; -}; - class RepeatVirtualScrollCaches { - friend struct KeySorterClass; public: struct CacheItem { bool isValid = false; RefPtr item; }; -public: RepeatVirtualScrollCaches(const std::map>& cacheCountL24ttype, const std::function& onCreateNode, const std::function& onUpdateNode, -- Gitee From d70162417cf44957faf9d68a378b842ea897115b Mon Sep 17 00:00:00 2001 From: liubihao Date: Mon, 10 Feb 2025 16:06:36 +0800 Subject: [PATCH 6/8] clean code format check. Signed-off-by: liubihao --- .../pu_repeat_virtual_scroll_2_impl.ts | 202 ++++++++++-------- .../pu_repeat_virtual_scroll_impl.ts | 2 +- .../syntax/repeat_virtual_scroll_2_model.h | 6 +- .../repeat_virtual_scroll_2_model_ng.cpp | 6 +- .../syntax/repeat_virtual_scroll_2_model_ng.h | 6 +- .../syntax/repeat_virtual_scroll_2_node.cpp | 12 +- .../syntax/repeat_virtual_scroll_2_node.h | 12 +- 7 files changed, 136 insertions(+), 110 deletions(-) diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts index df500cf133c..050decdd4c1 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts +++ b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts @@ -152,22 +152,22 @@ class ActiveDataItem { public toString(): string { return this.state === ActiveDataItem.UINodeExists - ? `[rid: ${this.rid}, ttype: ${this.ttype}${this.key ? ", key: " + this.key : ""}]` + ? `[rid: ${this.rid}, ttype: ${this.ttype}${this.key ? ', key: ' + this.key : ''}]` : `[no item]`; } public dump(): string { const state = this.state === ActiveDataItem.UINodeExists - ? "UINode exists" + ? 'UINode exists' : this.state === ActiveDataItem.UINodeRenderFailed - ? "UINode failed to render" + ? 'UINode failed to render' : this.state === ActiveDataItem.UINodeToBeRendered - ? "UINode to be rendered" + ? 'UINode to be rendered' : this.state === ActiveDataItem.NoValue - ? "No data value" - : "unknown state (error)"; - const rid = this.rid ?? "no RID/not rendered"; - const ttype = this.ttype ?? "ttype N/A"; + ? 'No data value' + : 'unknown state (error)'; + const rid = this.rid ?? 'no RID/not rendered'; + const ttype = this.ttype ?? 'ttype N/A'; return (this.state === ActiveDataItem.UINodeExists) ? `state: '${state}', RID: ${rid}, ttype: ${ttype}, key: ${this.key}` : `state: '${state}'`; @@ -175,14 +175,14 @@ class ActiveDataItem { public shortDump() : string { const state = this.state === ActiveDataItem.UINodeExists - ? "UINode exists" + ? 'UINode exists' : this.state === ActiveDataItem.UINodeRenderFailed - ? "UINode failed to render" + ? 'UINode failed to render' : this.state === ActiveDataItem.NoValue - ? "No data value" - : "unknown state (error)"; - const rid = this.rid ?? "no RID/not rendered"; - const ttype = this.ttype ?? "ttype N/A"; + ? 'No data value' + : 'unknown state (error)'; + const rid = this.rid ?? 'no RID/not rendered'; + const ttype = this.ttype ?? 'ttype N/A'; return (this.state === ActiveDataItem.UINodeExists) ? `state: '${state}', RID: ${rid}, ttype: ${ttype}` : `state: '${state}'`; @@ -265,6 +265,9 @@ class __RepeatVirtualScroll2Impl { // they are no longer associated with a data item private spareRid_: Set = new Set(); + // request container re-layout + private firstIndexChanged_: number = 0; + // when access view model record dependency on 'this'. private startRecordDependencies(clearBindings: boolean = false): void { ObserveV2.getObserve().startRecordDependencies(this.owningViewV2_, this.repeatElmtId_, clearBindings); @@ -309,7 +312,7 @@ class __RepeatVirtualScroll2Impl { // so when array length changes, will update totalCount. use totalCountFunc_ for this this.totalCountFunc_ = config.totalCountSpecified ? (typeof config.totalCount === 'function' ? config.totalCount : undefined) : - () => this.arr_.length; + (): number => this.arr_.length; if (this.totalCountFunc_) { this.totalCount_ = this.totalCountFunc_(); // Check legal totalCount value @@ -410,14 +413,14 @@ class __RepeatVirtualScroll2Impl { stateMgmtConsole.debug(`checking range ${activeRangeFrom} - ${activeRangeTo}`); - let firstIndexChanged = Math.min(activeRangeTo + 1, this.arr_.length); + this.firstIndexChanged_ = Math.min(activeRangeTo + 1, this.arr_.length); // replacement for this.activeDataItems_ const newActiveDataItems: Array> = new Array>(); // replacement for l1Rid4Index_ index -> rid map on C++ side // will send to C++ when done - const newL1Rid4Index = new Map(); + const newL1Rid4Index: Map = new Map(); // clear keys for new rerender this.key4Index_.clear(); @@ -426,6 +429,46 @@ class __RepeatVirtualScroll2Impl { // step 1. move data items to newActiveDataItems that are unchanged // (same item / same key, still at same index, same ttype) // create createMissingDataItem -type entries for all other new data items. + if (!this.moveItemsUnchanged(newActiveDataItems, newL1Rid4Index)) { + return; + } + + // step 2. move retained data items + // these are items with same value / same key in new and old array: + // their index has changed, ttype is unchanged + this.moveRetainedItems(newActiveDataItems, newL1Rid4Index); + + // step 3. remaining old data items, i.e. data item removed from source array + // add their rid to spare + this.addRemovedItemsToSpare(); + + // step 4: data items in new source array that are either new in the array + // or have been there before but need to be rendered with different ttype + // if canUpdate then do the update. + // if need new render, do not do the new render right away. Wait for layout to ask + // for the item to render. + this.newItemsNeedToRender(newActiveDataItems, newL1Rid4Index); + + // render all data changes in one go + ObserveV2.getObserve().updateDirty2(true); + + this.activeDataItems_ = newActiveDataItems; + + stateMgmtConsole.debug(`rerender result: `, + `\nspareRid : ${this.dumpSpareRid()}`, + `\nthis.dumpDataItems: ${this.activeDataItems_}`, + `\nnewL1Rid4Index: ${JSON.stringify(Array.from(newL1Rid4Index))}`, + `\nfirst item changed at index ${this.firstIndexChanged_} .`); + + RepeatVirtualScroll2Native.updateL1Rid4Index( + this.repeatElmtId_, this.totalCount_, this.firstIndexChanged_, Array.from(newL1Rid4Index)); + + stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) reRender() data array length: `, + `${this.arr_.length}, totalCount: ${this.totalCount_} - done`); + } + + private moveItemsUnchanged( + newActiveDataItems: Array>, newL1Rid4Index: Map): boolean { let hasChanges = false; for (const indexS in this.activeDataItems_) { const activeIndex = parseInt(indexS); @@ -458,7 +501,7 @@ class __RepeatVirtualScroll2Impl { ((!this.useKeys_ && dataItemAtIndex === this.activeDataItems_[activeIndex].item) || (this.useKeys_ && key === this.activeDataItems_[activeIndex].key))) { stateMgmtConsole.debug( - `index ${activeIndex} ttype '${ttype}'${this.useKeys_ ? ", key " + key : ""} `, + `index ${activeIndex} ttype '${ttype}'${this.useKeys_ ? ', key ' + key : ''} `, `and dataItem unchanged.`); newActiveDataItems[activeIndex] = this.activeDataItems_[activeIndex]; @@ -485,13 +528,15 @@ class __RepeatVirtualScroll2Impl { // triggers layout to request FrameCount() / totalCount and if increased newly added source array items this.activeDataItems_ = newActiveDataItems; RepeatVirtualScroll2Native.requestContainerReLayout( - this.repeatElmtId_, this.totalCount_, Math.min(this.totalCount_ - 1, activeRangeTo + 1)); - return; + this.repeatElmtId_, this.totalCount_, Math.min(this.totalCount_ - 1, this.activeRange_[1] + 1)); + return false; } - // step 2. move retained data items - // these are items with same value / same key in new and old array: - // their index has changed, ttype is unchanged + return true; + } + + private moveRetainedItems( + newActiveDataItems: Array>, newL1Rid4Index: Map): void { for (const indexS in newActiveDataItems) { const activeIndex = parseInt(indexS); const newActiveDataItemAtActiveIndex = newActiveDataItems[activeIndex]; @@ -503,7 +548,7 @@ class __RepeatVirtualScroll2Impl { if (newActiveDataItemAtActiveIndex.state === ActiveDataItem.NoValue) { stateMgmtConsole.debug(`new index ${activeIndex} missing in updated source array.`); - firstIndexChanged = Math.min(firstIndexChanged, activeIndex); + this.firstIndexChanged_ = Math.min(this.firstIndexChanged_, activeIndex); continue; } @@ -532,7 +577,7 @@ class __RepeatVirtualScroll2Impl { stateMgmtConsole.debug(`new index ${activeIndex} / old index ${ridMeta.repeatItem_.index}: `, `keep in L1: rid ${movedDataItem.rid}, unchanged ttype '${newActiveDataItemAtActiveIndex.ttype}'`); ridMeta.repeatItem_.updateIndex(activeIndex); - firstIndexChanged = Math.min(firstIndexChanged, activeIndex); + this.firstIndexChanged_ = Math.min(this.firstIndexChanged_, activeIndex); // the data item is handled, remove it from old active data range // so we do not use it again @@ -541,12 +586,12 @@ class __RepeatVirtualScroll2Impl { // update is needed for this data item // either because dataItem is new, or new ttype needs to used stateMgmtConsole.debug(`need update for index ${activeIndex}`); - firstIndexChanged = Math.min(firstIndexChanged, activeIndex); + this.firstIndexChanged_ = Math.min(this.firstIndexChanged_, activeIndex); } } // for new data items in active range + } - // step 3. remaining old data items, i.e. data item removed from source array - // add their rid to spare + private addRemovedItemsToSpare(): void { for (let oldIndex in this.activeDataItems_) { if (this.activeDataItems_[oldIndex].rid) { this.spareRid_.add(this.activeDataItems_[oldIndex].rid); @@ -555,12 +600,10 @@ class __RepeatVirtualScroll2Impl { this.key4Index_.delete(index); } } + } - // step 4: data items in new source array that are either new in the array - // or have been there before but need to be rendered with different ttype - // if canUpdate then do the update. - // if need new render, do not do the new render right away. Wait for layout to ask - // for the item to render. + private newItemsNeedToRender( + newActiveDataItems: Array>, newL1Rid4Index: Map): void { for (const indexS in newActiveDataItems) { const activeIndex = parseInt(indexS); const newActiveDataItemAtActiveIndex = newActiveDataItems[activeIndex]; @@ -570,56 +613,39 @@ class __RepeatVirtualScroll2Impl { } const optRid = this.canUpdate(newActiveDataItemAtActiveIndex.ttype); - if (optRid > 0) { - const ridMeta = this.meta4Rid_.get(optRid); - if (ridMeta) { - // found rid / repeatItem to update - stateMgmtConsole.debug(`index ${activeIndex}: update rid ${optRid} / ttype `, - `'${newActiveDataItemAtActiveIndex.ttype}'`); - - newActiveDataItemAtActiveIndex.rid = optRid; - newActiveDataItemAtActiveIndex.state = ActiveDataItem.UINodeExists; - - if (this.useKeys_) { - const key = this.computeKey(newActiveDataItemAtActiveIndex.item as T, activeIndex, - /* monitor access already ongoing */ false, newActiveDataItems); - newActiveDataItemAtActiveIndex.key = key; - ridMeta.key_ = key; - } - - // spare rid is used - this.spareRid_.delete(optRid); - - // add to index -> rid map to be sent to C++ - newL1Rid4Index.set(activeIndex, optRid); - - // don't need to call getItem here, the data item has been lazy loaded before - ridMeta.repeatItem_.updateItem(newActiveDataItemAtActiveIndex.item as T); - ridMeta.repeatItem_.updateIndex(activeIndex); - - firstIndexChanged = Math.min(firstIndexChanged, activeIndex); - } - } else { + if (optRid <= 0) { stateMgmtConsole.debug(`active range index ${activeIndex}: no rid found to update`); + continue; } - }; + const ridMeta = this.meta4Rid_.get(optRid); + if (ridMeta) { + // found rid / repeatItem to update + stateMgmtConsole.debug(`index ${activeIndex}: update rid ${optRid} / ttype `, + `'${newActiveDataItemAtActiveIndex.ttype}'`); - // render all data changes in one go - ObserveV2.getObserve().updateDirty2(true); + newActiveDataItemAtActiveIndex.rid = optRid; + newActiveDataItemAtActiveIndex.state = ActiveDataItem.UINodeExists; - this.activeDataItems_ = newActiveDataItems; + if (this.useKeys_) { + const key = this.computeKey(newActiveDataItemAtActiveIndex.item as T, activeIndex, + /* monitor access already ongoing */ false, newActiveDataItems); + newActiveDataItemAtActiveIndex.key = key; + ridMeta.key_ = key; + } - stateMgmtConsole.debug(`rerender result: `, - `\nspareRid : ${this.dumpSpareRid()}`, - `\nthis.dumpDataItems: ${this.activeDataItems_}`, - `\nnewL1Rid4Index: ${JSON.stringify(Array.from(newL1Rid4Index))}`, - `\nfirst item changed at index ${firstIndexChanged} .`); + // spare rid is used + this.spareRid_.delete(optRid); - RepeatVirtualScroll2Native.updateL1Rid4Index( - this.repeatElmtId_, this.totalCount_, firstIndexChanged, Array.from(newL1Rid4Index)); + // add to index -> rid map to be sent to C++ + newL1Rid4Index.set(activeIndex, optRid); - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) reRender() data array length: `, - `${this.arr_.length}, totalCount: ${this.totalCount_} - done`); + // don't need to call getItem here, the data item has been lazy loaded before + ridMeta.repeatItem_.updateItem(newActiveDataItemAtActiveIndex.item as T); + ridMeta.repeatItem_.updateIndex(activeIndex); + + this.firstIndexChanged_ = Math.min(this.firstIndexChanged_, activeIndex); + } + }; } private computeTtype(item: T, index: number, monitorAccess: boolean): string { @@ -631,7 +657,7 @@ class __RepeatVirtualScroll2Impl { let ttype = RepeatEachFuncTtype; try { ttype = this.ttypeGenFunc_(item, index); - } catch(e) { + } catch (e) { stateMgmtConsole.applicationError( `__RepeatVirtualScroll2Impl. Error generating ttype at index: ${index}`, e?.message); } @@ -657,7 +683,7 @@ class __RepeatVirtualScroll2Impl { } catch { stateMgmtConsole.applicationError(`__RepeatVirtualScroll2Impl (${this.repeatElmtId_}):`, `unstable key function. Fix the key gen function in your application!`); - key = this.mkRandomKey(index, "_key-gen-crashed_"); + key = this.mkRandomKey(index, '_key-gen-crashed_'); } monitorAccess && this.stopRecordDependencies(); @@ -1011,7 +1037,7 @@ class __RepeatVirtualScroll2Impl { stateMgmtConsole.debug(this.dumpCachedCount()); } - private onPurge() { + private onPurge(): void { stateMgmtConsole.debug( `${this.constructor.name}(${this.repeatElmtId_}) purge(), totalCount: ${this.totalCount_} - start`); @@ -1056,15 +1082,15 @@ class __RepeatVirtualScroll2Impl { private dumpDataItems(): string { let result = ``; - let sepa = ""; + let sepa = ''; let count = 0; for (const index in this.activeDataItems_) { const dataItemDump = this.activeDataItems_[index].dump(); const repeatItemIndex = this.activeDataItems_[index].rid ? this.meta4Rid_.get(this.activeDataItems_[index].rid)?.repeatItem_?.index - : "N/A"; + : 'N/A'; result += `${sepa}index ${index}, ${dataItemDump} (repeatItemIndex ${repeatItemIndex})`; - sepa = ", \n"; + sepa = ', \n'; count += 1; } return `activeDataItems(array): length: ${this.activeDataItems_.length}, ` + @@ -1073,23 +1099,23 @@ class __RepeatVirtualScroll2Impl { } private dumpCachedCount(): string { - let result = ""; - let sepa = ""; + let result = ''; + let sepa = ''; Object.entries(this.templateOptions_).forEach((pair) => { const options: RepeatTemplateImplOptions = pair[1]; result += `${sepa}'template ${pair[0]}': specified: ${options.cachedCountSpecified} ` + `cachedCount: ${options.cachedCount}: `; - sepa = ", "; + sepa = ', '; }); return result; } private dumpKeys(): string { - let result = ""; - let sepa = ""; + let result = ''; + let sepa = ''; this.key4Index_.forEach((key, index) => { result += `${sepa}${index}: ${key}`; - sepa = "\n"; + sepa = '\n'; }); return result; } diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts index 2d7ad3824b1..8206ea20134 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts +++ b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts @@ -85,7 +85,7 @@ class __RepeatVirtualScrollImpl { let ttype = RepeatEachFuncTtype; try { ttype = this.ttypeGenFunc_(item, index); - } catch(e) { + } catch (e) { stateMgmtConsole.applicationError(`__RepeatVirtualScrollImpl. Error generating ttype at index: ${index}`, e?.message); } diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h index 91eb045f3a3..bc19178bef5 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h @@ -37,9 +37,9 @@ public: static RepeatVirtualScroll2Model* GetInstance(); virtual void Create(uint32_t totalCount, const std::function(int32_t)>& onGetRid4Index, - const std::function onRecycleItems, - const std::function onActiveRange, - const std::function onPurge) = 0; + const std::function& onRecycleItems, + const std::function& onActiveRange, + const std::function& onPurge) = 0; virtual void RemoveNode(uint32_t rid) = 0; virtual void SetInvalid(int32_t repeatElmtId, uint32_t rid) = 0; diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp index ac6e27053f3..72625a8e506 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp @@ -25,9 +25,9 @@ namespace OHOS::Ace::NG { void RepeatVirtualScroll2ModelNG::Create(uint32_t totalCount, const std::function(int32_t)>& onGetRid4Index, - const std::function onRecycleItems, - const std::function onActiveRange, - const std::function onPurge) + const std::function& onRecycleItems, + const std::function& onActiveRange, + const std::function& onPurge) { ACE_SCOPED_TRACE("RepeatVirtualScroll2ModelNG::Create"); auto* stack = ViewStackProcessor::GetInstance(); diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h index beb58e3c274..f50e3a076b6 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h @@ -30,9 +30,9 @@ namespace OHOS::Ace::NG { class ACE_EXPORT RepeatVirtualScroll2ModelNG : public RepeatVirtualScroll2Model { public: void Create(uint32_t totalCount, const std::function(int32_t)>& onGetRid4Index, - const std::function onRecycleItems, - const std::function onActiveRange, - const std::function onPurge) override; + const std::function& onRecycleItems, + const std::function& onActiveRange, + const std::function& onPurge) override; void RemoveNode(uint32_t rid) override; void SetInvalid(int32_t repeatElmtId, uint32_t rid) override; diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp index 080ae6c1a11..530f90c6c9e 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp @@ -32,9 +32,9 @@ using CacheItem = RepeatVirtualScroll2Caches::CacheItem; // REPEAT RefPtr RepeatVirtualScroll2Node::GetOrCreateRepeatNode(int32_t nodeId, uint32_t totalCount, const std::function(IndexType)>& onGetRid4Index, - const std::function onRecycleItems, - const std::function onActiveRange, - const std::function onPurge) + const std::function& onRecycleItems, + const std::function& onActiveRange, + const std::function& onPurge) { auto node = ElementRegister::GetInstance()->GetSpecificItemById(nodeId); if (node) { @@ -52,9 +52,9 @@ RefPtr RepeatVirtualScroll2Node::GetOrCreateRepeatNode RepeatVirtualScroll2Node::RepeatVirtualScroll2Node(int32_t nodeId, int32_t totalCount, const std::function(IndexType)>& onGetRid4Index, - const std::function onRecycleItems, - const std::function onActiveRange, - const std::function onPurge) + const std::function& onRecycleItems, + const std::function& onActiveRange, + const std::function& onPurge) : ForEachBaseNode(V2::JS_REPEAT_ETS_TAG, nodeId), totalCount_(totalCount), caches_(onGetRid4Index), onRecycleItems_(onRecycleItems), onActiveRange_(onActiveRange), onPurge_(onPurge), postUpdateTaskHasBeenScheduled_(false) diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h index ffefe507ad8..4130d447613 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h @@ -94,15 +94,15 @@ class ACE_EXPORT RepeatVirtualScroll2Node : public ForEachBaseNode { public: static RefPtr GetOrCreateRepeatNode(int32_t nodeId, uint32_t totalCount, const std::function(IndexType)>& onGetRid4Index, - const std::function onRecycleItems, - const std::function onActiveRange, - const std::function onPurge); + const std::function& onRecycleItems, + const std::function& onActiveRange, + const std::function& onPurge); RepeatVirtualScroll2Node(int32_t nodeId, int32_t totalCount, const std::function(IndexType)>& onGetRid4Index, - const std::function onRecycleItems, - const std::function onActiveRange, - const std::function onPurge); + const std::function& onRecycleItems, + const std::function& onActiveRange, + const std::function& onPurge); ~RepeatVirtualScroll2Node() override = default; -- Gitee From 8118687dc98df81c5d5da5415282fbe7960e04e5 Mon Sep 17 00:00:00 2001 From: liubihao Date: Thu, 13 Feb 2025 15:33:30 +0800 Subject: [PATCH 7/8] fix dupliate keys issue & code format. Signed-off-by: liubihao --- .../jsview/js_repeat_virtual_scroll_2.cpp | 67 +++++++++++-------- .../pu_repeat_virtual_scroll_2_impl.ts | 45 +++++++------ .../base/view_partial_update_model_ng.cpp | 7 +- .../syntax/repeat_virtual_scroll_2_caches.cpp | 44 ++++++------ .../syntax/repeat_virtual_scroll_2_caches.h | 17 +++-- .../syntax/repeat_virtual_scroll_2_node.cpp | 26 +++---- 6 files changed, 108 insertions(+), 98 deletions(-) diff --git a/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp b/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp index fb0f0884621..cc5212f7fb7 100644 --- a/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp +++ b/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp @@ -38,10 +38,10 @@ RepeatVirtualScroll2Model* RepeatVirtualScroll2Model::GetInstance() namespace OHOS::Ace::Framework { -enum { - PARAM_TOTAL_COUNT = 0, - PARAM_HANDLERS = 1, - PARAM_SIZE = 2, +enum CreateParam { + TOTAL_COUNT = 0, + HANDLERS = 1, + SIZE = 2, }; static JSRef GetJSFunc(JsiRef options, const char* propertyName) @@ -51,11 +51,13 @@ static JSRef GetJSFunc(JsiRef options, const char* propertyNam static bool ParseAndVerifyParams(const JSCallbackInfo& info) { - if (info.Length() != PARAM_SIZE || !info[PARAM_TOTAL_COUNT]->IsNumber() || !info[PARAM_HANDLERS]->IsObject()) { + if (info.Length() != CreateParam::SIZE || + !info[CreateParam::TOTAL_COUNT]->IsNumber() || + !info[CreateParam::HANDLERS]->IsObject()) { return false; } - auto handlers = JSRef::Cast(info[PARAM_HANDLERS]); + auto handlers = JSRef::Cast(info[CreateParam::HANDLERS]); return (handlers->GetProperty("onGetRid4Index")->IsFunction() && handlers->GetProperty("onRecycleItems")->IsFunction() && handlers->GetProperty("onActiveRange")->IsFunction() && @@ -70,10 +72,10 @@ void JSRepeatVirtualScroll2::Create(const JSCallbackInfo& info) } // arg 0 totalCount : number - auto totalCount = info[PARAM_TOTAL_COUNT]->ToNumber(); + auto totalCount = info[CreateParam::TOTAL_COUNT]->ToNumber(); // arg 2 onGetRid4Index(number int32_t) : number(uint32_t) - auto handlers = JSRef::Cast(info[PARAM_HANDLERS]); + auto handlers = JSRef::Cast(info[CreateParam::HANDLERS]); auto onGetRid4Index = [execCtx = info.GetExecutionContext(), func = GetJSFunc(handlers, "onGetRid4Index")]( int32_t forIndex) -> std::pair { JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx, std::pair(0, 0)); @@ -99,7 +101,10 @@ void JSRepeatVirtualScroll2::Create(const JSCallbackInfo& info) auto onActiveRange = [execCtx = info.GetExecutionContext(), func = GetJSFunc(handlers, "onActiveRange")]( int32_t fromIndex, int32_t toIndex, bool isLoop) -> void { JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx); - auto params = ConvertToJSValues(fromIndex, toIndex, isLoop); + auto params = ConvertToJSValues( + fromIndex != INT32_MAX ? fromIndex : std::numeric_limits::quiet_NaN(), + toIndex != INT32_MAX ? toIndex : std::numeric_limits::quiet_NaN(), + isLoop); func->Call(JSRef(), params.size(), params.data()); }; @@ -141,22 +146,24 @@ void JSRepeatVirtualScroll2::SetInvalid(const JSCallbackInfo& info) void JSRepeatVirtualScroll2::RequestContainerReLayout(const JSCallbackInfo& info) { ACE_SCOPED_TRACE("RepeatVirtualScroll:RequestContainerReLayout"); - enum { - PARAM_ELMTID = 0, - PARAM_TOTAL_COUNT = 1, - PARAM_CHILD_INDEX = 2, + enum RequestContainerReLayoutParam { + ELMTID = 0, + TOTAL_COUNT = 1, + CHILD_INDEX = 2, }; - if (!info[PARAM_ELMTID]->IsNumber() || !info[PARAM_TOTAL_COUNT]->IsNumber() || - !info[PARAM_CHILD_INDEX]->IsNumber()) { + if (!info[RequestContainerReLayoutParam::ELMTID]->IsNumber() || + !info[RequestContainerReLayoutParam::TOTAL_COUNT]->IsNumber() || + !info[RequestContainerReLayoutParam::CHILD_INDEX]->IsNumber()) { TAG_LOGE(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::RequestContainerReLayout - invalid parameters ERROR"); return; } TAG_LOGD(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::RequestContainerReLayout"); - auto repeatElmtId = info[PARAM_ELMTID]->ToNumber(); - auto totalCount = info[PARAM_TOTAL_COUNT]->ToNumber(); - auto invalidateContainerLayoutFromChildIndex = info[PARAM_CHILD_INDEX]->ToNumber(); + auto repeatElmtId = info[RequestContainerReLayoutParam::ELMTID]->ToNumber(); + auto totalCount = info[RequestContainerReLayoutParam::TOTAL_COUNT]->ToNumber(); + auto invalidateContainerLayoutFromChildIndex = + info[RequestContainerReLayoutParam::CHILD_INDEX]->ToNumber(); RepeatVirtualScroll2Model::GetInstance()->RequestContainerReLayout( repeatElmtId, totalCount, invalidateContainerLayoutFromChildIndex); } @@ -168,25 +175,27 @@ void JSRepeatVirtualScroll2::RequestContainerReLayout(const JSCallbackInfo& info void JSRepeatVirtualScroll2::UpdateL1Rid4Index(const JSCallbackInfo& info) { ACE_SCOPED_TRACE("RepeatVirtualScroll:UpdateL1Rid4Index"); - enum { - PARAM_ELMTID = 0, - PARAM_TOTAL_COUNT = 1, - PARAM_CHILD_INDEX = 2, - PARAM_ARRAY_PAIRS = 3, + enum UpdateL1Rid4IndexParam { + ELMTID = 0, + TOTAL_COUNT = 1, + CHILD_INDEX = 2, + ARRAY_PAIRS = 3, }; - if (!info[PARAM_ELMTID]->IsNumber() || !info[PARAM_TOTAL_COUNT]->IsNumber() || - !info[PARAM_CHILD_INDEX]->IsNumber() || !info[PARAM_ARRAY_PAIRS]->IsArray()) { + if (!info[UpdateL1Rid4IndexParam::ELMTID]->IsNumber() || + !info[UpdateL1Rid4IndexParam::TOTAL_COUNT]->IsNumber() || + !info[UpdateL1Rid4IndexParam::CHILD_INDEX]->IsNumber() || + !info[UpdateL1Rid4IndexParam::ARRAY_PAIRS]->IsArray()) { TAG_LOGE(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::UpdateL1Rid4Index - invalid parameters ERROR"); return; } TAG_LOGD(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll2::UpdateL1Rid4Index"); - auto repeatElmtId = info[PARAM_ELMTID]->ToNumber(); - auto totalCount = info[PARAM_TOTAL_COUNT]->ToNumber(); - auto invalidateContainerLayoutFromChildIndex = info[PARAM_CHILD_INDEX]->ToNumber(); + auto repeatElmtId = info[UpdateL1Rid4IndexParam::ELMTID]->ToNumber(); + auto totalCount = info[UpdateL1Rid4IndexParam::TOTAL_COUNT]->ToNumber(); + auto invalidateContainerLayoutFromChildIndex = info[UpdateL1Rid4IndexParam::CHILD_INDEX]->ToNumber(); - auto arrayOfPairs = JSRef::Cast(info[PARAM_ARRAY_PAIRS]); + auto arrayOfPairs = JSRef::Cast(info[UpdateL1Rid4IndexParam::ARRAY_PAIRS]); std::map l1Rid4Index; for (size_t i = 0; i < arrayOfPairs->Length(); i++) { JSRef pair = arrayOfPairs->GetValueAt(i); diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts index 050decdd4c1..221f6ce9b12 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts +++ b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023-2024 Huawei Device Co., Ltd. + * Copyright (c) 2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -216,7 +216,9 @@ class __RepeatVirtualScroll2Impl { // index <-> key bidirectional mapping private key4Index_: Map = new Map(); private index4Key_: Map = new Map(); - + // duplicate keys + private oldDuplicateKeys_: Set = new Set(); + // map if .each and .template functions private itemGenFuncs_: { [type: string]: RepeatItemGenFunc }; @@ -425,6 +427,7 @@ class __RepeatVirtualScroll2Impl { // clear keys for new rerender this.key4Index_.clear(); this.index4Key_.clear(); + this.oldDuplicateKeys_.clear(); // step 1. move data items to newActiveDataItems that are unchanged // (same item / same key, still at same index, same ttype) @@ -688,8 +691,8 @@ class __RepeatVirtualScroll2Impl { monitorAccess && this.stopRecordDependencies(); const optIndex1: number | undefined = this.index4Key_.get(key); - if (optIndex1 !== undefined) { - key = this.handleDuplicateKey(optIndex1, index, key, activateDataItems); + if (optIndex1 !== undefined || this.oldDuplicateKeys_.has(key)) { + key = this.handleDuplicateKey(index, key, optIndex1, activateDataItems); } else { this.key4Index_.set(index, key); this.index4Key_.set(key, index); @@ -705,24 +708,28 @@ class __RepeatVirtualScroll2Impl { // when generating key for index2, detected that index1 has same key already // need to change the key for both index'es // returns random key for index 2 - private handleDuplicateKey(prevIndex: number, curIndex: number, origKey: string, + private handleDuplicateKey(curIndex: number, origKey: string, prevIndex?: number, activateDataItems?: Array>): string { + this.oldDuplicateKeys_.add(origKey); + const curKey = this.mkRandomKey(curIndex, origKey); this.key4Index_.set(curIndex, curKey); this.index4Key_.set(curKey, curIndex); - // also make a new key for index1 - const prevKey = this.mkRandomKey(prevIndex, origKey); - this.key4Index_.set(prevIndex, prevKey); - this.index4Key_.set(prevKey, prevIndex); - this.index4Key_.delete(origKey); - if (activateDataItems && activateDataItems[prevIndex] !== undefined) { - stateMgmtConsole.debug(`correcting key of activeDataItem index ${prevIndex} from `, - `'${activateDataItems[prevIndex].key}' to '${prevKey}'.`); - activateDataItems[prevIndex].key = prevKey; + if (prevIndex !== undefined) { + // also make a new key for prevIndex + const prevKey = this.mkRandomKey(prevIndex, origKey); + this.key4Index_.set(prevIndex, prevKey); + this.index4Key_.set(prevKey, prevIndex); + this.index4Key_.delete(origKey); + if (activateDataItems && activateDataItems[prevIndex] !== undefined) { + stateMgmtConsole.debug(`correcting key of activeDataItem index ${prevIndex} from `, + `'${activateDataItems[prevIndex].key}' to '${prevKey}'.`); + activateDataItems[prevIndex].key = prevKey; + } } - stateMgmtConsole.applicationError(`__RepeatVirtualScroll2Impl (${this.repeatElmtId_}): `, - `Detected duplicate key ${origKey} for index ${prevIndex} and ${curIndex}! `, + stateMgmtConsole.applicationError(`${this.constructor.name}(${this.repeatElmtId_}): `, + `Detected duplicate key ${origKey} for index ${curIndex}! `, `Generated random key will decrease Repeat performance. Fix the key gen function in your application!`); return curKey; } @@ -981,12 +988,6 @@ class __RepeatVirtualScroll2Impl { } } - const maxInt: number = 2147483647; - if (nStart === maxInt && nEnd === maxInt) { // there is no active node on screen - nStart = NaN; - nEnd = NaN; - } - stateMgmtConsole.debug(`${this.constructor.name}(${this.repeatElmtId_}) onActiveRange`, `(nStart: ${nStart}, nEnd: ${nEnd})`, `data array length: ${this.arr_.length}, totalCount: ${this.totalCount_} - start`); diff --git a/frameworks/core/components_ng/base/view_partial_update_model_ng.cpp b/frameworks/core/components_ng/base/view_partial_update_model_ng.cpp index a82619cb505..ca198083d54 100644 --- a/frameworks/core/components_ng/base/view_partial_update_model_ng.cpp +++ b/frameworks/core/components_ng/base/view_partial_update_model_ng.cpp @@ -111,11 +111,12 @@ bool ViewPartialUpdateModelNG::AllowReusableV2Descendant(const WeakPtr& RefPtr node = weak.Upgrade(); CHECK_NULL_RETURN(node, false); - while (node->GetParent() && (node->GetParent()->GetTag() != V2::JS_VIEW_ETS_TAG)) { - if (AceType::DynamicCast(node->GetParent()) != nullptr) { + node = node->GetParent(); + while (node && (node->GetTag() != V2::JS_VIEW_ETS_TAG)) { + if (AceType::DynamicCast(node) != nullptr) { break; } - if (AceType::DynamicCast(node->GetParent()) != nullptr) { + if (AceType::DynamicCast(node) != nullptr) { break; } node = node->GetParent(); diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.cpp b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.cpp index 0ba64d85975..9a7e2384743 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.cpp +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.cpp @@ -46,21 +46,21 @@ RepeatVirtualScroll2Caches::RepeatVirtualScroll2Caches( */ GetFrameChildResult RepeatVirtualScroll2Caches::GetFrameChild(IndexType index, bool needBuild) { - TAG_LOGE(AceLogTag::ACE_REPEAT, " ... GetFrameChild(index %{public}d, needBuild: %{public}d)", index, + TAG_LOGD(AceLogTag::ACE_REPEAT, "GetFrameChild(index %{public}d, needBuild: %{public}d)", index, static_cast(needBuild)); OptCacheItem optCacheItem = GetL1CacheItem4Index(index); if (optCacheItem.has_value()) { - return std::pair(ONGETRID4INDEX_RESULT_UNCHANGED_NODE, optCacheItem.value()); + return std::pair(OnGetRid4IndexResult::UNCHANGED_NODE, optCacheItem.value()); } if (!needBuild) { - return std::pair(ONGETRID4INDEX_RESULT_NO_NODE, nullptr); + return std::pair(OnGetRid4IndexResult::NO_NODE, nullptr); } optCacheItem = CallOnGetRid4Index(index); return optCacheItem.has_value() - ? std::pair(ONGETRID4INDEX_RESULT_UPDATED_NODE, optCacheItem.value()) - : std::pair(ONGETRID4INDEX_RESULT_NO_NODE, nullptr); + ? std::pair(OnGetRid4IndexResult::UPDATED_NODE, optCacheItem.value()) + : std::pair(OnGetRid4IndexResult::NO_NODE, nullptr); } /** @@ -70,7 +70,7 @@ GetFrameChildResult RepeatVirtualScroll2Caches::GetFrameChild(IndexType index, b */ void RepeatVirtualScroll2Caches::RemoveNode(RIDType rid) { - TAG_LOGE(AceLogTag::ACE_REPEAT, " ... RemoveNode(rid : %{public}d) ...", static_cast(rid)); + TAG_LOGD(AceLogTag::ACE_REPEAT, "RemoveNode(rid : %{public}d)", static_cast(rid)); DropFromL1ByRid(rid); @@ -87,7 +87,7 @@ void RepeatVirtualScroll2Caches::RemoveNode(RIDType rid) */ void RepeatVirtualScroll2Caches::SetInvalid(RIDType rid) { - TAG_LOGE(AceLogTag::ACE_REPEAT, " ... SetInvalid(rid : %{public}d) ...", static_cast(rid)); + TAG_LOGD(AceLogTag::ACE_REPEAT, "SetInvalid(rid : %{public}d)", static_cast(rid)); DropFromL1ByRid(rid); } @@ -124,7 +124,7 @@ void RepeatVirtualScroll2Caches::DropFromL1ByIndex(IndexType index) const auto iter = l1Rid4Index_.find(index); if (iter != l1Rid4Index_.end()) { l1Rid4Index_.erase(iter); - TAG_LOGE(AceLogTag::ACE_REPEAT, " ... DropFromL1ByIndex: removed index %{public}d -> rid %{public}d from L1", + TAG_LOGD(AceLogTag::ACE_REPEAT, "DropFromL1ByIndex: removed index %{public}d -> rid %{public}d from L1", static_cast(index), static_cast(ridOpt.value())); } } @@ -144,14 +144,14 @@ std::optional RepeatVirtualScroll2Caches::GetL1Index4Rid(RIDType rid) void RepeatVirtualScroll2Caches::UpdateL1Rid4Index(std::map l1Rd4Index) { - TAG_LOGE(AceLogTag::ACE_REPEAT, "UpdateL1Rid4Index"); + TAG_LOGD(AceLogTag::ACE_REPEAT, "UpdateL1Rid4Index"); l1Rid4Index_ = l1Rd4Index; - TAG_LOGE(AceLogTag::ACE_REPEAT, " ... L1Rid4Index: %{public}s", DumpL1Rid4Index().c_str()); + TAG_LOGD(AceLogTag::ACE_REPEAT, "L1Rid4Index: %{public}s", DumpL1Rid4Index().c_str()); ForEachCacheItem([&](RIDType rid, const CacheItem& cacheItem) { cacheItem->isL1_ = (GetL1Index4RID(rid) != std::nullopt); - TAG_LOGE(AceLogTag::ACE_REPEAT, " ... %{public}s", DumpCacheItem(cacheItem).c_str()); + TAG_LOGD(AceLogTag::ACE_REPEAT, "DumpCacheItem: %{public}s", DumpCacheItem(cacheItem).c_str()); }); } @@ -188,7 +188,7 @@ std::optional RepeatVirtualScroll2Caches::GetL1Index4Node(const RefPt OptCacheItem RepeatVirtualScroll2Caches::CallOnGetRid4Index(IndexType index) { - TAG_LOGD(AceLogTag::ACE_REPEAT, " ... CallOnGetRid4Index(index %{public}d: calling TS ...", index); + TAG_LOGD(AceLogTag::ACE_REPEAT, "CallOnGetRid4Index(index %{public}d: calling TS", index); // swap the ViewStackProcessor instance for secondary while we run the item builder function // so that its results can easily be obtained from it, does not disturb main ViewStackProcessor @@ -196,7 +196,7 @@ OptCacheItem RepeatVirtualScroll2Caches::CallOnGetRid4Index(IndexType index) auto* viewStack = NG::ViewStackProcessor::GetInstance(); const std::pair result = onGetRid4Index_(index); - if (result.second == ONGETRID4INDEX_RESULT_CREATED_NEW_NODE) { + if (result.second == OnGetRid4IndexResult::CREATED_NEW_NODE) { // case: new node was created successfully // get it from ViewStackProcessor RefPtr node4Index = viewStack->Finish(); @@ -220,17 +220,17 @@ OptCacheItem RepeatVirtualScroll2Caches::CallOnGetRid4Index(IndexType index) // add to cache cacheItem4Rid_[rid] = RepeatVirtualScroll2CacheItem::MakeCacheItem(node4Index, true); - TAG_LOGD(AceLogTag::ACE_REPEAT, "REPEAT TRACE ABNORMAL (after startup) ... CallOnGetRid4Index" + TAG_LOGD(AceLogTag::ACE_REPEAT, "REPEAT TRACE ABNORMAL (after startup) ...CallOnGetRid4Index" "(index %{public}d -> rid %{public}d) returns CacheItem with newly created node %{public}s .", index, static_cast(rid), DumpCacheItem(cacheItem4Rid_[rid]).c_str()); return cacheItem4Rid_[rid]; - } // case ONGETRID4INDEX_RESULT_CREATED_NEW_NODE) + } // case OnGetRid4IndexResult::CREATED_NEW_NODE) - if (result.second == ONGETRID4INDEX_RESULT_UPDATED_NODE) { + if (result.second == OnGetRid4IndexResult::UPDATED_NODE) { // case: TS updated existing node that's in the cache already const RIDType rid = result.first; - if (rid < INVALID_RID) { + if (rid < 0) { TAG_LOGE(AceLogTag::ACE_REPEAT, "Node update for index %{public}d failed. Invalid rid. " "Internal error!", static_cast(index)); return std::nullopt; @@ -246,15 +246,15 @@ OptCacheItem RepeatVirtualScroll2Caches::CallOnGetRid4Index(IndexType index) l1Rid4Index_[index] = rid; optCacheItem.value()->isL1_ = true; - TAG_LOGE(AceLogTag::ACE_REPEAT, - "... CallOnGetRid4Index(index %{public}d -> rid %{public}d): returns CacheItem with updated node " + TAG_LOGD(AceLogTag::ACE_REPEAT, + "CallOnGetRid4Index(index %{public}d -> rid %{public}d): returns CacheItem with updated node " "%{public}s .", index, static_cast(rid), DumpCacheItem(cacheItem4Rid_[rid]).c_str()); return optCacheItem; - } // case ONGETRID4INDEX_RESULT_UPDATED_NODE + } // case OnGetRid4IndexResult::UPDATED_NODE // TS was not able to deliver UINode - TAG_LOGE(AceLogTag::ACE_REPEAT,"New node creation failed for index %{public}d. TS unable to create/update node. " + TAG_LOGE(AceLogTag::ACE_REPEAT, "New node creation failed for index %{public}d. TS unable to create/update node. " "Application error!", static_cast(index)); return std::nullopt; } @@ -296,7 +296,7 @@ OptCacheItem RepeatVirtualScroll2Caches::GetL1CacheItem4Index(IndexType index) const auto cacheItemIter = cacheItem4Rid_.find(rid); if (cacheItemIter != cacheItem4Rid_.end() && cacheItemIter->second->node_ != nullptr && cacheItemIter->second->isL1_) { - TAG_LOGE(AceLogTag::ACE_REPEAT, " ... GetL1CacheItem4Index(index: %{public}d) returns L1 %{public}s .", + TAG_LOGD(AceLogTag::ACE_REPEAT, "GetL1CacheItem4Index(index: %{public}d) returns L1 %{public}s .", index, DumpCacheItem(cacheItemIter->second).c_str()); return cacheItemIter->second; } diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.h index 168f37e7403..0edafd819db 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.h +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.h @@ -121,14 +121,13 @@ using ActiveRangeType = std::pair; // Note: using #define instead of enum class due to bridging limitations // between TS and UINode classes: not allowed to import the class definition // from js_repeat_virtual_scroll.cpp -#define ONGETRID4INDEX_RESULT_FAILED 0 -#define ONGETRID4INDEX_RESULT_NO_NODE 0 -#define ONGETRID4INDEX_RESULT_CREATED_NEW_NODE 1 -#define ONGETRID4INDEX_RESULT_UPDATED_NODE 2 -#define ONGETRID4INDEX_RESULT_UNCHANGED_NODE 3 - -#define NO_L1_INDEX (-1) -#define INVALID_RID 0 +enum OnGetRid4IndexResult { + FAILED = 0, + NO_NODE = 0, + CREATED_NEW_NODE = 1, + UPDATED_NODE = 2, + UNCHANGED_NODE = 3 +}; class RepeatVirtualScroll2Caches { public: @@ -210,7 +209,7 @@ public: /** * return the index of given RID in L1 - * or NO_L1_INDEX + * or NO_L1_INDEX(-1) */ std::optional GetL1Index4RID(RIDType rid) const; diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp index 530f90c6c9e..5970f09a59a 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024 Huawei Device Co., Ltd. + * Copyright (c) 2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -82,7 +82,7 @@ void RepeatVirtualScroll2Node::DoSetActiveChildRange( // step 4: iterate over all UINode sub-trees, only interested in L2 ones // for items moved from L1 to L2 but sitl active and on render tree, correct this. - TAG_LOGD(AceLogTag::ACE_REPEAT, "Checking spare nodes on C++ side ...."); + TAG_LOGD(AceLogTag::ACE_REPEAT, "Checking spare nodes on C++ side ..."); needSync = ProcessActiveL2Nodes() || needSync; // memorize range @@ -250,13 +250,13 @@ bool RepeatVirtualScroll2Node::RebuildL1(int32_t start, int32_t end, int32_t nSt if (repeatNode->CheckNode4IndexInL1(index, nStart, nEnd, cacheItem)) { // keep in Repeat L1 TAG_LOGD(AceLogTag::ACE_REPEAT, - " ... in L1: index %{public}d, node %{public}s with child id %{public}d: SetActive(True)", + "in L1: index %{public}d, node %{public}s with child id %{public}d: SetActive(True)", index, caches_.DumpUINode(cacheItem->node_).c_str(), static_cast(frameNode->GetId())); return true; } TAG_LOGD(AceLogTag::ACE_REPEAT, - " ... out of L1: index %{public}d, node %{public}s with child id %{public}d", + "out of L1: index %{public}d, node %{public}s with child id %{public}d", index, caches_.DumpUINode(cacheItem->node_).c_str(), frameNode->GetId()); // move active node into L2 cached. check transition flag. @@ -297,7 +297,7 @@ bool RepeatVirtualScroll2Node::ProcessActiveL2Nodes() cacheItem->node_->SetJSViewActive(false); needSync = true; TAG_LOGD(AceLogTag::ACE_REPEAT, - " ... spare node %{public}s: apply SetActive(false) & SetJSViewActive(false)", + "spare node %{public}s: apply SetActive(false) & SetJSViewActive(false)", caches_.DumpCacheItem(cacheItem).c_str()); } if (!cacheItem->isOnRenderTree_) { @@ -312,7 +312,7 @@ bool RepeatVirtualScroll2Node::ProcessActiveL2Nodes() } cacheItem->isOnRenderTree_ = false; needSync = true; - TAG_LOGD(AceLogTag::ACE_REPEAT, " ... spare nodes %{public}s: removed node from render tree", + TAG_LOGD(AceLogTag::ACE_REPEAT, "spare nodes %{public}s: removed node from render tree", caches_.DumpCacheItem(cacheItem).c_str()); }); @@ -425,7 +425,7 @@ RefPtr RepeatVirtualScroll2Node::GetFrameChildByIndex( TAG_LOGD(AceLogTag::ACE_REPEAT, "nodeId: %{public}d: GetFrameChildByIndex(index: %{public}d, " "needBuild: %{public}d, isCache: %{public}d, " - "addToRenderTree: %{public}d) ...", + "addToRenderTree: %{public}d).", static_cast(GetId()), static_cast(index), static_cast(needBuild), static_cast(isCache), static_cast(addToRenderTree)); @@ -447,13 +447,13 @@ RefPtr RepeatVirtualScroll2Node::GetFrameChildByIndexImpl( uint32_t index, bool needBuild, bool isCache, bool addToRenderTree) { std::pair resultPair = caches_.GetFrameChild(index, needBuild); - const bool hasNoNode = ((resultPair.first == ONGETRID4INDEX_RESULT_NO_NODE) + const bool hasNoNode = ((resultPair.first == OnGetRid4IndexResult::NO_NODE) || (resultPair.second == nullptr) || (resultPair.second->node_ == nullptr)); if (hasNoNode && !needBuild) { TAG_LOGD(AceLogTag::ACE_REPEAT, - "... GetFrameChild(%{public}d) not in caches && needBuild==false, GetFrameChildByIndex returns nullptr .", + "GetFrameChild(%{public}d) not in caches && needBuild==false, GetFrameChildByIndex returns nullptr .", static_cast(index)); return nullptr; } @@ -461,14 +461,14 @@ RefPtr RepeatVirtualScroll2Node::GetFrameChildByIndexImpl( // node for index needs to be created or updated on JS side if (hasNoNode) { TAG_LOGE(AceLogTag::ACE_REPEAT, - " ... GetFrameChild(%{public}d) failed to create new or update existing node. Non-recoverable error.", + "GetFrameChild(%{public}d) failed to create new or update existing node. Non-recoverable error.", static_cast(index)); return nullptr; } CacheItem& cacheItem4Index = resultPair.second; - TAG_LOGD(AceLogTag::ACE_REPEAT, " ... GetFrameChild(%{public}d) returns node %{public}s .", + TAG_LOGD(AceLogTag::ACE_REPEAT, "GetFrameChild(%{public}d) returns node %{public}s.", static_cast(index), caches_.DumpUINode(cacheItem4Index->node_).c_str()); cacheItem4Index->node_->UpdateThemeScopeId(GetThemeScopeId()); @@ -482,7 +482,7 @@ RefPtr RepeatVirtualScroll2Node::GetFrameChildByIndexImpl( cacheItem4Index->isActive_ = true; } - if (cacheItem4Index->isOnRenderTree_ && (resultPair.first == ONGETRID4INDEX_RESULT_UNCHANGED_NODE)) { + if (cacheItem4Index->isOnRenderTree_ && (resultPair.first == OnGetRid4IndexResult::UNCHANGED_NODE)) { return cacheItem4Index->node_->GetFrameChildByIndex(0, needBuild); } @@ -526,7 +526,7 @@ const std::list>& RepeatVirtualScroll2Node::GetChildren(bool /*no return children_; } - TAG_LOGD(AceLogTag::ACE_REPEAT, "GetChildren rebuild starting ...."); + TAG_LOGD(AceLogTag::ACE_REPEAT, "GetChildren rebuild starting ..."); // can not modify l1_cache while iterating // GetChildren is overloaded, can not change it to non-const -- Gitee From 5f62c60f664d03424f38c4053938ff8ed60f7374 Mon Sep 17 00:00:00 2001 From: liubihao Date: Fri, 14 Feb 2025 09:50:53 +0800 Subject: [PATCH 8/8] add codeowners. Signed-off-by: liubihao --- .gitee/CODEOWNERS | 2 ++ .../declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp | 2 +- .../declarative_frontend/jsview/js_repeat_virtual_scroll_2.h | 2 +- .../src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts | 2 +- .../components_ng/syntax/repeat_virtual_scroll_2_caches.cpp | 2 +- .../core/components_ng/syntax/repeat_virtual_scroll_2_caches.h | 2 +- .../core/components_ng/syntax/repeat_virtual_scroll_2_model.h | 2 +- .../components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp | 2 +- .../components_ng/syntax/repeat_virtual_scroll_2_model_ng.h | 2 +- .../core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp | 2 +- .../core/components_ng/syntax/repeat_virtual_scroll_2_node.h | 2 +- 11 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.gitee/CODEOWNERS b/.gitee/CODEOWNERS index 67de42e7ec3..e5bca4871ba 100644 --- a/.gitee/CODEOWNERS +++ b/.gitee/CODEOWNERS @@ -1224,6 +1224,8 @@ frameworks/bridge/declarative_frontend/jsview/js_repeat.cpp @arkuistatemgmt frameworks/bridge/declarative_frontend/jsview/js_repeat.h @arkuistatemgmt frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll.cpp @arkuistatemgmt frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll.h @arkuistatemgmt +frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp @arkuistatemgmt +frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.h @arkuistatemgmt frameworks/bridge/declarative_frontend/jsview/js_richeditor.cpp @zhuweifeng94 frameworks/bridge/declarative_frontend/jsview/js_richeditor.h @zhuweifeng94 frameworks/bridge/declarative_frontend/jsview/js_richtext.cpp @arkwebinarkuireview diff --git a/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp b/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp index cc5212f7fb7..c46ae6bd8dc 100644 --- a/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp +++ b/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at diff --git a/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.h b/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.h index 7baa355b08d..47a73014e78 100644 --- a/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.h +++ b/frameworks/bridge/declarative_frontend/jsview/js_repeat_virtual_scroll_2.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at diff --git a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts index 221f6ce9b12..e27c9022d7f 100644 --- a/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts +++ b/frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_repeat_virtual_scroll_2_impl.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.cpp b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.cpp index 9a7e2384743..30515a32a23 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.cpp +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.h index 0edafd819db..9bbadbe2873 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.h +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_caches.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h index bc19178bef5..ad9b8793983 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp index 72625a8e506..3433ee774e3 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h index f50e3a076b6..b41d457a41f 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_model_ng.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp index 5970f09a59a..59a6479fcf7 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at diff --git a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h index 4130d447613..f9424b27eee 100644 --- a/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h +++ b/frameworks/core/components_ng/syntax/repeat_virtual_scroll_2_node.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Huawei Device Co., Ltd. + * Copyright (c) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at -- Gitee