repeat feture

Signed-off-by: chenbenzhi <chenbenzhi@huawei.com>
Change-Id: Ie59b5b8d18ac833b2f44edb70be06e038c78211f
This commit is contained in:
chenbenzhi 2024-06-16 23:14:38 +08:00
parent 0b360b6bcc
commit 00c2b882cc
49 changed files with 3039 additions and 449 deletions

View File

@ -45,7 +45,7 @@ BreakAfterJavaFieldAnnotations: true
ColumnLimit: 120
CommentPragmas: '^ IWYU pragma:'
QualifierAlignment: Leave
ReflowComments: false
ReflowComments: true
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4

View File

@ -299,6 +299,7 @@ template("declarative_js_engine") {
"jsview/js_rendering_context.cpp",
"jsview/js_rendering_context_settings.cpp",
"jsview/js_repeat.cpp",
"jsview/js_repeat_virtual_scroll.cpp",
"jsview/js_richeditor.cpp",
"jsview/js_row.cpp",
"jsview/js_row_split.cpp",
@ -819,6 +820,7 @@ template("declarative_js_engine_ng") {
"jsview/js_rendering_context.cpp",
"jsview/js_rendering_context_settings.cpp",
"jsview/js_repeat.cpp",
"jsview/js_repeat_virtual_scroll.cpp",
"jsview/js_richeditor.cpp",
"jsview/js_row.cpp",
"jsview/js_row_split.cpp",

View File

@ -136,6 +136,7 @@
#include "bridge/declarative_frontend/jsview/js_rendering_context.h"
#include "bridge/declarative_frontend/jsview/js_rendering_context_settings.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_richeditor.h"
#include "bridge/declarative_frontend/jsview/js_row.h"
#include "bridge/declarative_frontend/jsview/js_row_split.h"
@ -634,6 +635,7 @@ static const std::unordered_map<std::string, std::function<void(BindingTarget)>>
{ "Swiper", JSSwiper::JSBind },
{ "Panel", JSSlidingPanel::JSBind },
{ "RepeatNative", JSRepeat::JSBind },
{ "RepeatVirtualScrollNative", JSRepeatVirtualScroll::JSBind },
{ "NavDestination", JSNavDestination::JSBind },
{ "Navigation", JSNavigation::JSBind },
{ "NativeNavPathStack", JSNavPathStack::JSBind },

View File

@ -123,6 +123,7 @@
#include "frameworks/bridge/declarative_frontend/jsview/js_rendering_context.h"
#include "frameworks/bridge/declarative_frontend/jsview/js_rendering_context_settings.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_row.h"
#include "frameworks/bridge/declarative_frontend/jsview/js_row_split.h"
#include "frameworks/bridge/declarative_frontend/jsview/js_scope_util.h"
@ -451,6 +452,7 @@ void JsBindViews(BindingTarget globalObj, void* nativeEngine)
JSTabsController::JSBind(globalObj);
JSForEach::JSBind(globalObj);
JSRepeat::JSBind(globalObj);
JSRepeatVirtualScroll::JSBind(globalObj);
JSIfElse::JSBind(globalObj);
JSDivider::JSBind(globalObj);
JSScroll::JSBind(globalObj);

View File

@ -4113,21 +4113,15 @@ class PUV2ViewBase extends NativeViewPartialUpdate {
this.purgeDeletedElmtIds();
Array.from(this.updateFuncByElmtId.keys()).sort(ViewPU.compareNumber).forEach(elmtId => this.UpdateElement(elmtId));
if (deep) {
this.childrenWeakrefMap_.forEach((weakRefChild) => {
const child = weakRefChild.deref();
if (child) {
if (child instanceof ViewPU) {
if (!child.hasBeenRecycled_) {
child.forceCompleteRerender(true);
} else {
child.delayCompleteRerender(deep);
}
}
} else {
throw new Error('forceCompleteRender not implemented for ViewV2, yet');
for (const child of this.childrenWeakrefMap_.values()) {
const childView = child.deref();
if (childView) {
childView.forceCompleteRerender(true);
}
});
}
}
}
/**
* force a complete rerender / update on specific node by executing update function.
@ -7315,6 +7309,10 @@ class ObserveV2 {
static IsObservedObjectV2(value) {
return (value && typeof (value) === 'object' && value[ObserveV2.V2_DECO_META]);
}
static getCurrentRecordedId() {
const bound = ObserveV2.getObserve().stackOfRenderedComponents_.top();
return bound ? bound[0] : -1;
}
// At the start of observeComponentCreation or
// MonitorV2 observeObjectAccess
startRecordDependencies(cmp, id) {
@ -7464,8 +7462,6 @@ class ObserveV2 {
// add dependency view model object 'target' property 'attrName'
// to current this.bindId
addRef(target, attrName) {
var _a, _b, _c, _d;
var _e, _f;
const bound = this.stackOfRenderedComponents_.top();
if (!bound) {
return;
@ -7476,7 +7472,15 @@ class ObserveV2 {
throw new TypeError(error);
}
const id = bound[0];
this.addRef4IdInternal(bound[0], target, attrName);
}
addRef4Id(id, target, attrName) {
this.addRef4IdInternal(id, target, attrName);
}
addRef4IdInternal(id, target, attrName) {
var _a, _b, _c, _d;
var _e, _f;
// Map: attribute/symbol -> dependent id
const symRefs = (_a = target[_e = ObserveV2.SYMBOL_REFS]) !== null && _a !== void 0 ? _a : (target[_e] = {});
(_b = symRefs[attrName]) !== null && _b !== void 0 ? _b : (symRefs[attrName] = new Set());
@ -7588,10 +7592,23 @@ class ObserveV2 {
}
updateDirty() {
this.startDirty_ = true;
this.updateDirty2();
this.updateDirty2(false);
this.startDirty_ = false;
}
updateDirty2() {
/**
* execute /update in this order
* - @Computed variables
* - @Monitor functions
* - UINode re-render
* three nested loops, means:
* process @Computed until no more @Computed need update
* process @Monitor until no more @Computed and @Monitor
* process UINode update until no more @Computed and @Monitor and UINode rerender
*
* @param updateUISynchronously should be set to true if called during VSYNC only
*
*/
updateDirty2(updateUISynchronously = false) {
aceTrace.begin('updateDirty2');
// obtain and unregister the removed elmtIds
@ -7626,9 +7643,10 @@ class ObserveV2 {
if (this.elmtIdsChanged_.size) {
const elmtIds = Array.from(this.elmtIdsChanged_).sort((elmtId1, elmtId2) => elmtId1 - elmtId2);
this.elmtIdsChanged_ = new Set();
this.updateUINodes(elmtIds);
updateUISynchronously ? this.updateUINodesSynchronously(elmtIds) : this.updateUINodes(elmtIds);
}
} while (this.elmtIdsChanged_.size + this.monitorIdsChanged_.size + this.computedPropIdsChanged_.size > 0);
aceTrace.end();
}
updateDirtyComputedProps(computed) {
@ -7676,10 +7694,11 @@ class ObserveV2 {
* FlushDirtyNodesUpdate to CustomNode to ViewV2.updateDirtyElements to UpdateElement
* Code left here to reproduce benchmark measurements, compare with future optimisation
* @param elmtIds
*
*/
updateUINodesWithoutVSync(elmtIds) {
updateUINodesSynchronously(elmtIds) {
aceTrace.begin(`ObserveV2.updateUINodes: ${elmtIds.length} elmtId`);
aceTrace.begin(`ObserveV2.updateUINodesSynchronously: ${elmtIds.length} elmtId`);
let view;
let weak;
elmtIds.forEach((elmtId) => {
@ -7703,7 +7722,7 @@ class ObserveV2 {
// much slower
updateUINodes(elmtIds) {
aceTrace.begin(`ObserveV2.updateUINodesSlow: ${elmtIds.length} elmtId`);
aceTrace.begin(`ObserveV2.updateUINodes: ${elmtIds.length} elmtId`);
let viewWeak;
let view;
elmtIds.forEach((elmtId) => {
@ -9530,9 +9549,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
// implementation for existing state observation system
class __RepeatItemPU {
constructor(owningView, initialItem, initialIndex) {
this._observedItem = new ObservedPropertyPU(initialItem, owningView, "Repeat item");
this._observedItem = new ObservedPropertyPU(initialItem, owningView, 'Repeat item');
if (initialIndex !== undefined) {
this._observedIndex = new ObservedPropertyPU(initialIndex, owningView, "Repeat index");
this._observedIndex = new ObservedPropertyPU(initialIndex, owningView, 'Repeat index');
}
}
get item() {
@ -9597,7 +9616,7 @@ class __RepeatDefaultKeyGen {
}
static funcImpl(item) {
// fast keygen logic can be used with objects/symbols only
if (typeof item !== 'object' && typeof item !== 'symbol') {
if (typeof item != 'object' && typeof item !== 'symbol') {
return JSON.stringify(item);
}
// generate a numeric key, store mappings in WeakMap
@ -9610,35 +9629,130 @@ class __RepeatDefaultKeyGen {
}
__RepeatDefaultKeyGen.weakMap_ = new WeakMap();
__RepeatDefaultKeyGen.lastKey_ = 0;
;
;
// __Repeat implements ForEach with child re-use for both existing state observation
// and deep observation , for non-virtual and virtual code paths (TODO)
class __RepeatV2 {
constructor(arr) {
this.config = {};
this.isVirtualScroll = false;
this.key2Item_ = new Map();
this.arr_ = arr !== null && arr !== void 0 ? arr : [];
this.keyGenFunction_ = __RepeatDefaultKeyGen.func;
}
updateArr(arr) {
this.arr_ = arr !== null && arr !== void 0 ? arr : [];
return this;
//console.log('__RepeatV2 ctor')
this.config.arr = arr !== null && arr !== void 0 ? arr : [];
this.config.itemGenFuncs = {};
this.config.keyGenFunc = __RepeatDefaultKeyGen.func;
this.config.typeGenFunc = (() => '');
this.config.totalCount = this.config.arr.length;
this.config.templateOptions = {};
this.config.mkRepeatItem = this.mkRepeatItem;
}
each(itemGenFunc) {
this.itemGenFunc_ = itemGenFunc;
//console.log('__RepeatV2.each()')
this.config.itemGenFuncs[''] = itemGenFunc;
this.config.templateOptions[''] = Object.assign({}, this.defaultTemplateOptions());
return this;
}
key(idGenFunc) {
this.keyGenFunction_ = idGenFunc !== null && idGenFunc !== void 0 ? idGenFunc : __RepeatDefaultKeyGen.func;
key(keyGenFunc) {
//console.log('__RepeatV2.key()')
this.config.keyGenFunc = keyGenFunc !== null && keyGenFunc !== void 0 ? keyGenFunc : __RepeatDefaultKeyGen.func;
return this;
}
virtualScroll() {
virtualScroll(options) {
var _a;
//console.log('__RepeatV2.virtualScroll()')
this.config.totalCount = (_a = options === null || options === void 0 ? void 0 : options.totalCount) !== null && _a !== void 0 ? _a : this.config.arr.length;
this.isVirtualScroll = true;
return this;
}
onMove(handler) {
this.onMoveHandler_ = handler;
// function to decide which template to use, each template has an id
templateId(typeFunc) {
//console.log('__RepeatV2.templateId()')
this.config.typeGenFunc = typeFunc;
return this;
}
// template: id + builder function to render specific type of data item
template(type, itemGenFunc, options) {
//console.log('__RepeatV2.template()')
this.config.itemGenFuncs[type] = itemGenFunc;
this.config.templateOptions[type] = Object.assign(Object.assign({}, this.defaultTemplateOptions()), options);
return this;
}
updateArr(arr) {
//console.log('__RepeatV2.updateArr()')
this.config.arr = arr !== null && arr !== void 0 ? arr : [];
return this;
}
render(isInitialRender) {
var _a, _b, _c;
//console.log('__RepeatV2.render()')
if (!((_a = this.config.itemGenFuncs) === null || _a === void 0 ? void 0 : _a[''])) {
throw new Error(`__RepeatV2 item builder function unspecified. Usage error`);
}
if (!this.isVirtualScroll) {
// Repeat
(_b = this.impl) !== null && _b !== void 0 ? _b : (this.impl = new __RepeatImpl());
this.impl.render(this.config, isInitialRender);
}
else {
// RepeatVirtualScroll
(_c = this.impl) !== null && _c !== void 0 ? _c : (this.impl = new __RepeatVirtualScrollImpl());
this.impl.render(this.config, isInitialRender);
}
}
onMove(handler) {
this.config.onMoveHandler = handler;
return this;
}
defaultTemplateOptions() {
return { cachedCount: 1 };
}
mkRepeatItem(item, index) {
return new __RepeatItemV2(item, index);
}
}
; // __RepeatV2<T>
// __Repeat implements ForEach with child re-use for both existing state observation
// and deep observation , for non-virtual and virtual code paths (TODO)
class __RepeatPU extends __RepeatV2 {
constructor(owningView, arr) {
super(arr);
this.owningView_ = owningView;
}
mkRepeatItem(item, index) {
return new __RepeatItemPU(this.owningView_, item, index);
}
}
/*
* 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
*/
class __RepeatImpl {
/**/
constructor() {
this.key2Item_ = new Map();
}
/**/
render(config, isInitialRender) {
this.arr_ = config.arr;
this.itemGenFuncs_ = config.itemGenFuncs;
this.typeGenFunc_ = config.typeGenFunc;
this.keyGenFunction_ = config.keyGenFunc;
this.mkRepeatItem_ = config.mkRepeatItem;
this.onMoveHandler_ = config.onMoveHandler;
isInitialRender ? this.initialRender() : this.reRender();
}
genKeys() {
const key2Item = new Map();
this.arr_.forEach((item, index) => {
@ -9646,34 +9760,20 @@ class __RepeatV2 {
key2Item.set(key, { key, index });
});
if (key2Item.size < this.arr_.length) {
stateMgmtConsole.warn("Duplicates detected, fallback to index-based keyGen.");
stateMgmtConsole.warn("__RepeatImpl: Duplicates detected, fallback to index-based keyGen.");
// Causes all items to be re-rendered
this.keyGenFunction_ = __RepeatDefaultKeyGen.funcWithIndex;
return this.genKeys();
}
return key2Item;
}
mkRepeatItem(item, index) {
return new __RepeatItemV2(item, index);
}
render(isInitialRender) {
if (!this.itemGenFunc_) {
throw new Error(`itemGen function undefined. Usage error`);
}
if (this.isVirtualScroll) {
// TODO: Add render for LazyforEach with child update.
throw new Error("TODO virtual code path");
}
else {
isInitialRender ? this.initialRenderNoneVirtual() : this.rerenderNoneVirtual();
}
}
initialRenderNoneVirtual() {
initialRender() {
//console.log('__RepeatImpl initialRender() 0')
this.key2Item_ = this.genKeys();
RepeatNative.startRender();
let index = 0;
this.key2Item_.forEach((itemInfo, key) => {
itemInfo.repeatItem = this.mkRepeatItem(this.arr_[index], index);
itemInfo.repeatItem = this.mkRepeatItem_(this.arr_[index], index);
this.initialRenderItem(key, itemInfo.repeatItem);
index++;
});
@ -9684,7 +9784,7 @@ class __RepeatV2 {
UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds);
}
rerenderNoneVirtual() {
reRender() {
const oldKey2Item = this.key2Item_;
this.key2Item_ = this.genKeys();
// identify array items that have been deleted
@ -9710,6 +9810,15 @@ class __RepeatV2 {
itemInfo.repeatItem.updateIndex(index);
// C++ mv from tempChildren[oldIndex] to end of children_
RepeatNative.moveChild(oldIndex);
// TBD moveChild() only when item types are same
//const type0 = this.typeGenFunc_(oldItemInfo.repeatItem.item, oldIndex);
//const type1 = this.typeGenFunc_(itemInfo.repeatItem.item, index);
//if (type0 == type1) {
// // C++ mv from tempChildren[oldIndex] to end of children_
// RepeatNative.moveChild(oldIndex);
//} else {
// this.initialRenderItem(key, itemInfo.repeatItem);
//}
}
else if (deletedKeysAndIndex.length) {
// case #2:
@ -9725,6 +9834,7 @@ class __RepeatV2 {
itemInfo.repeatItem.updateIndex(index);
// update key2item_ Map
this.key2Item_.set(key, itemInfo);
// TBD moveChild() only when item types are same
// C++ mv from tempChildren[oldIndex] to end of children_
RepeatNative.moveChild(oldKeyIndex);
}
@ -9732,7 +9842,7 @@ class __RepeatV2 {
// case #3:
// new array item, there are no deleted array items
// render new UINode children
itemInfo.repeatItem = this.mkRepeatItem(item, index);
itemInfo.repeatItem = this.mkRepeatItem_(item, index);
this.initialRenderItem(key, itemInfo.repeatItem);
}
index++;
@ -9752,26 +9862,155 @@ class __RepeatV2 {
}
initialRenderItem(key, repeatItem) {
var _a, _b;
//console.log('__RepeatImpl initialRenderItem()')
// render new UINode children
// C++: initial render will render to the end of children_
RepeatNative.createNewChildStart(key);
// execute the ItemGen function
this.itemGenFunc_(repeatItem);
// execute the itemGen function
const itemType = (_a = this.typeGenFunc_(repeatItem.item, repeatItem.index)) !== null && _a !== void 0 ? _a : '';
const itemFunc = (_b = this.itemGenFuncs_[itemType]) !== null && _b !== void 0 ? _b : this.itemGenFuncs_[''];
itemFunc(repeatItem);
RepeatNative.createNewChildFinish(key);
}
}
// __Repeat implements ForEach with child re-use for both existing state observation
// and deep observation , for non-virtual and virtual code paths (TODO)
class __RepeatPU extends __RepeatV2 {
constructor(owningView, arr) {
super(arr);
this.owningView_ = owningView;
;
/*
* 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
*/
// Implements ForEach with child re-use for both existing state observation and
// deep observation. For virtual-scroll code paths
class __RepeatVirtualScrollImpl {
/**/
constructor() {
}
mkRepeatItem(item, index) {
return new __RepeatItemPU(this.owningView_, item, index);
render(config, isInitialRender) {
this.arr_ = config.arr;
this.itemGenFuncs_ = config.itemGenFuncs;
this.keyGenFunc_ = config.keyGenFunc;
this.typeGenFunc_ = config.typeGenFunc;
this.totalCount_ = config.totalCount;
this.templateOptions_ = config.templateOptions;
this.mkRepeatItem_ = config.mkRepeatItem;
this.onMoveHandler_ = config.onMoveHandler;
if (isInitialRender) {
this.initialRender(ObserveV2.getCurrentRecordedId());
}
else {
this.reRender();
}
}
/**/
initialRender(repeatElmtId) {
// Map key -> RepeatItem
// added to closure of following lambdas
const _repeatItem4Key = new Map();
const repeatElmtId1 = repeatElmtId;
const onCreateNode = (forIndex) => {
if (forIndex < 0) {
// FIXME check also index < totalCount
throw new Error(`__RepeatVirtualScrollImpl onCreateNode: for index=${forIndex} out of range error.`);
}
// create dependency array item [forIndex] -> Repeat
// so Repeat updates when the array item changes
// FIXME observe dependencies, adding the array is insurgent for Array of objects
ObserveV2.getObserve().addRef4Id(repeatElmtId1, this.arr_, forIndex.toString());
const repeatItem = this.mkRepeatItem_(this.arr_[forIndex], forIndex);
const forKey = this.keyGenFunc_(this.arr_[forIndex], forIndex);
_repeatItem4Key.set(forKey, repeatItem);
// execute the itemGen function
this.initialRenderItem(repeatItem);
};
const onUpdateNode = (fromKey, forIndex) => {
if (!fromKey || fromKey == "" || forIndex < 0) {
// FIXME check also index < totalCount
throw new Error(`__RepeatVirtualScrollImpl onUpdateNode: fromKey "${fromKey}", forIndex=${forIndex} invalid function input error.`);
}
// create dependency array item [forIndex] -> Repeat
// so Repeat updates when the array item changes
// FIXME observe dependencies, adding the array is insurgent for Array of objects
ObserveV2.getObserve().addRef4Id(repeatElmtId1, this.arr_, forIndex.toString());
const repeatItem = _repeatItem4Key.get(fromKey);
if (!repeatItem) {
stateMgmtConsole.error(`__RepeatVirtualScrollImpl onUpdateNode: fromKey "${fromKey}", forIndex=${forIndex}, can not find RepeatItem for key. Unrecoverable error`);
return;
}
const forKey = this.keyGenFunc_(this.arr_[forIndex], forIndex);
repeatItem.updateItem(this.arr_[forIndex]);
repeatItem.updateIndex(forIndex);
// update Map according to made update:
// del fromKey entry and add forKey
_repeatItem4Key.delete(fromKey);
_repeatItem4Key.set(forKey, repeatItem);
// FIXME request re-render right away!
ObserveV2.getObserve().updateDirty2(true);
};
const onGetKeys4Range = (from, to) => {
const result = new Array();
for (let i = from; i <= to && i < this.arr_.length; i++) {
// create dependency array item [i] -> Repeat
// so Repeat updates when the array item changes
// FIXME observe dependencies, adding the array is insurgent for Array of objects
ObserveV2.getObserve().addRef4Id(repeatElmtId1, this.arr_, i.toString());
result.push(this.keyGenFunc_(this.arr_[i], i));
}
return result;
};
const onGetTypes4Range = (from, to) => {
var _a;
const result = new Array();
// FIXME observe dependencies, adding the array is insurgent for Array of objects
for (let i = from; i <= to && i < this.arr_.length; i++) {
// ObserveV2.getObserve().addRef4Id(repeatElmtId1, this.arr_, i.toString());
result.push((_a = this.typeGenFunc_(this.arr_[i], i)) !== null && _a !== void 0 ? _a : '');
}
return result;
};
RepeatVirtualScrollNative.create(this.totalCount_, Object.entries(this.templateOptions_), {
onCreateNode,
onUpdateNode,
onGetKeys4Range,
onGetTypes4Range
});
}
reRender() {
RepeatVirtualScrollNative.invalidateKeyCache(this.totalCount_);
}
initialRenderItem(repeatItem) {
var _a, _b;
// execute the itemGen function
const itemType = (_a = this.typeGenFunc_(repeatItem.item, repeatItem.index)) !== null && _a !== void 0 ? _a : '';
const itemFunc = (_b = this.itemGenFuncs_[itemType]) !== null && _b !== void 0 ? _b : this.itemGenFuncs_[''];
itemFunc(repeatItem);
}
}
;
/*
* Copyright (c) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");

View File

@ -106,7 +106,7 @@ void JSListItem::CreateForPartialUpdate(const JSCallbackInfo& args)
}
if (!isLazy) {
ListItemModel::GetInstance()->Create();
ListItemModel::GetInstance()->Create(nullptr, listItemStyle);
} else {
RefPtr<JsFunction> jsDeepRender = AceType::MakeRefPtr<JsFunction>(args.This(), JSRef<JSFunc>::Cast(arg0));
auto listItemDeepRenderFunc = [execCtx = args.GetExecutionContext(),

View File

@ -0,0 +1,128 @@
/*
* 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.h"
#include <string>
#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_model_ng.h"
#define JSFUNC(opts, propName) (JSRef<JSFunc>::Cast((opts)->GetProperty(propName)))
namespace OHOS::Ace {
std::unique_ptr<RepeatVirtualScrollModel> RepeatVirtualScrollModel::instance_ = nullptr;
RepeatVirtualScrollModel* RepeatVirtualScrollModel::GetInstance()
{
if (!instance_) {
instance_.reset(new NG::RepeatVirtualScrollModelNG());
}
return instance_.get();
}
} // namespace OHOS::Ace
namespace OHOS::Ace::Framework {
void JSRepeatVirtualScroll::Create(const JSCallbackInfo& info)
{
// arg 0
auto totalCount = info[0]->ToNumber<uint32_t>();
// arg 1
auto templateOptsArray = JSRef<JSArray>::Cast(info[1]);
std::map<std::string, uint32_t> templateCachedCountMap;
for (size_t i = 0; i < templateOptsArray->Length(); i++) {
JSRef<JSArray> pair = templateOptsArray->GetValueAt(i);
auto type = pair->GetValueAt(0)->ToString();
auto opts = JSRef<JSObject>::Cast(pair->GetValueAt(1));
templateCachedCountMap[type] = opts->GetProperty("cachedCount")->ToNumber<uint32_t>();
}
// arg 2
auto handlers = JSRef<JSObject>::Cast(info[2]);
auto onCreateNode = [execCtx = info.GetExecutionContext(), func = JSFUNC(handlers, "onCreateNode")](
uint32_t forIndex) -> void {
JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
auto params = ConvertToJSValues(forIndex);
func->Call(JSRef<JSObject>(), params.size(), params.data());
};
auto onUpdateNode = [execCtx = info.GetExecutionContext(), func = JSFUNC(handlers, "onUpdateNode")](
const std::string& fromKey, uint32_t forIndex) -> void {
JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
auto params = ConvertToJSValues(fromKey, forIndex);
func->Call(JSRef<JSObject>(), params.size(), params.data());
};
auto onGetKeys4Range = [execCtx = info.GetExecutionContext(), func = JSFUNC(handlers, "onGetKeys4Range")](
uint32_t from, uint32_t to) -> std::list<std::string> {
std::list<std::string> list;
JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx, list);
auto params = ConvertToJSValues(from, to);
JSRef<JSVal> jsVal = func->Call(JSRef<JSObject>(), params.size(), params.data());
// convert js-array to std::list
JSRef<JSArray> jsArr = JSRef<JSArray>::Cast(jsVal);
for (size_t i = 0; i < jsArr->Length(); i++) {
list.emplace_back(jsArr->GetValueAt(i)->ToString());
}
return list;
};
auto onGetTypes4Range = [execCtx = info.GetExecutionContext(), func = JSFUNC(handlers, "onGetTypes4Range")](
uint32_t from, uint32_t to) -> std::list<std::string> {
std::list<std::string> list;
JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx, list);
auto params = ConvertToJSValues(from, to);
JSRef<JSVal> jsVal = func->Call(JSRef<JSObject>(), params.size(), params.data());
// convert js-array to std::list
JSRef<JSArray> jsArr = JSRef<JSArray>::Cast(jsVal);
for (size_t i = 0; i < jsArr->Length(); i++) {
list.emplace_back(jsArr->GetValueAt(i)->ToString());
}
return list;
};
RepeatVirtualScrollModel::GetInstance()->Create(
totalCount,
templateCachedCountMap,
onCreateNode,
onUpdateNode,
onGetKeys4Range,
onGetTypes4Range
);
}
void JSRepeatVirtualScroll::InvalidateKeyCache(const JSCallbackInfo& info)
{
ACE_SCOPED_TRACE("RepeatVirtualScroll:InvalidateKeyCache");
TAG_LOGD(AceLogTag::ACE_REPEAT, "JSRepeatVirtualScroll::InvalidateKeyCache");
auto totalCount = info[0]->ToNumber<uint32_t>();
RepeatVirtualScrollModel::GetInstance()->InvalidateKeyCache(totalCount);
}
void JSRepeatVirtualScroll::JSBind(BindingTarget globalObj)
{
JSClass<JSRepeatVirtualScroll>::Declare("RepeatVirtualScrollNative");
JSClass<JSRepeatVirtualScroll>::StaticMethod("create", &JSRepeatVirtualScroll::Create);
JSClass<JSRepeatVirtualScroll>::StaticMethod("invalidateKeyCache", &JSRepeatVirtualScroll::InvalidateKeyCache);
JSClass<JSRepeatVirtualScroll>::Bind<>(globalObj);
}
} // namespace OHOS::Ace::Framework

View File

@ -0,0 +1,33 @@
/*
* 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_H
#define FRAMEWORKS_BRIDGE_DECLARATIVE_FRONTEND_JS_VIEW_JS_REPEAT_VIRTUAL_SCROLL_H
#include "bridge/declarative_frontend/engine/bindings.h"
#include "bridge/declarative_frontend/engine/js_ref_ptr.h"
namespace OHOS::Ace::Framework {
class JSRepeatVirtualScroll {
public:
static void JSBind(BindingTarget globalObj);
static void Create(const JSCallbackInfo& info);
static void InvalidateKeyCache(const JSCallbackInfo& info);
};
} // namespace OHOS::Ace::Framework
#endif // FRAMEWORKS_BRIDGE_DECLARATIVE_FRONTEND_JS_VIEW_JS_REPEAT_VIRTUAL_SCROLL_H

View File

@ -33,6 +33,7 @@ declare class ForEach {
static createNewChildFinish(id: string, parentView: NativeViewPartialUpdate): void;
}
// Repeat maps to C++ RepeatNode
declare class RepeatNative {
static startRender(): void;
static finishRender(removedChildElmtIds: Array<number>): void;
@ -40,4 +41,20 @@ declare class RepeatNative {
static createNewChildStart(id: string): void;
static createNewChildFinish(id: string): void;
static onMove(handler: (from: number, to: number) => void);
}
}
// Repeat.virtualScroll maps to C++ RepeatVirtualScrollNode
declare class RepeatVirtualScrollNative {
static create(
totalCount: number,
templateOptions: [string, RepeatTemplateOptions][],
handlers: {
onCreateNode: (forIndex: number) => void;
onUpdateNode: (fromKey: string, forIndex: number) => void;
onGetKeys4Range: (from: number, toNumber: number) => Array<string>;
onGetTypes4Range: (from: number, toNumber: number) => Array<string>;
}
): void;
// invalidate C++ side map index -> key
static invalidateKeyCache(totalCount : number): void;
}

View File

@ -40,9 +40,9 @@ class __RepeatItemPU<T> implements RepeatItem<T>, __IRepeatItemInternal<T> {
private _observedIndex?: ObservedPropertyPU<number>;
constructor(owningView: ViewPU, initialItem: T, initialIndex?: number) {
this._observedItem = new ObservedPropertyPU<T>(initialItem, owningView, "Repeat item");
this._observedItem = new ObservedPropertyPU<T>(initialItem, owningView, 'Repeat item');
if (initialIndex !== undefined) {
this._observedIndex = new ObservedPropertyPU<number>(initialIndex, owningView, "Repeat index");
this._observedIndex = new ObservedPropertyPU<number>(initialIndex, owningView, 'Repeat index');
}
}
@ -116,13 +116,13 @@ class __RepeatDefaultKeyGen {
}
// Return the same IDs for the same pairs <item, index>
public static funcWithIndex<T>(item: T, index:number) {
public static funcWithIndex<T>(item: T, index: number) {
return `${index}__` + __RepeatDefaultKeyGen.func(item);
}
private static funcImpl<T>(item: T) {
// fast keygen logic can be used with objects/symbols only
if (typeof item !== 'object' && typeof item !== 'symbol') {
if (typeof item != 'object' && typeof item !== 'symbol') {
return JSON.stringify(item);
}
// generate a numeric key, store mappings in WeakMap
@ -132,196 +132,118 @@ class __RepeatDefaultKeyGen {
// use cached key
return `${this.weakMap_.get(item)}`;
}
}
};
// TBD comments
interface __RepeatAPIConfig<T> {
arr?: Array<T>;
itemGenFuncs?: { [type: string]: RepeatItemGenFunc<T> };
keyGenFunc?: RepeatKeyGenFunc<T>;
typeGenFunc?: RepeatTypeGenFunc<T>;
//
totalCount?: number;
templateOptions?: { [type: string]: RepeatTemplateOptions };
//
mkRepeatItem?: (item: T, index?: number) => __RepeatItemFactoryReturn<T>;
onMoveHandler?: OnMoveHandler;
};
// __Repeat implements ForEach with child re-use for both existing state observation
// and deep observation , for non-virtual and virtual code paths (TODO)
class __RepeatV2<T> implements RepeatAPI<T> {
private arr_: Array<T>;
private itemGenFunc_?: RepeatItemGenFunc<T>;
private keyGenFunction_?: RepeatKeyGenFunc<T>;
private onMoveHandler_?: OnMoveHandler;
private isVirtualScroll: boolean = false;
private key2Item_: Map<string, __RepeatItemInfo<T>> = new Map<string, __RepeatItemInfo<T>>();
private config: __RepeatAPIConfig<T> = {};
private impl: __RepeatImpl<T> | __RepeatVirtualScrollImpl<T>;
private isVirtualScroll = false;
constructor(arr: Array<T>) {
this.arr_ = arr ?? [];
this.keyGenFunction_ = __RepeatDefaultKeyGen.func;
}
public updateArr(arr: Array<T>): RepeatAPI<T> {
this.arr_ = arr ?? [];
return this;
//console.log('__RepeatV2 ctor')
this.config.arr = arr ?? [];
this.config.itemGenFuncs = {};
this.config.keyGenFunc = __RepeatDefaultKeyGen.func;
this.config.typeGenFunc= (() => '');
this.config.totalCount = this.config.arr.length;
this.config.templateOptions = {};
this.config.mkRepeatItem = this.mkRepeatItem;
}
public each(itemGenFunc: RepeatItemGenFunc<T>): RepeatAPI<T> {
this.itemGenFunc_ = itemGenFunc;
//console.log('__RepeatV2.each()')
this.config.itemGenFuncs[''] = itemGenFunc;
this.config.templateOptions[''] = { ...this.defaultTemplateOptions() };
return this;
}
public key(idGenFunc: RepeatKeyGenFunc<T>): RepeatAPI<T> {
this.keyGenFunction_ = idGenFunc ?? __RepeatDefaultKeyGen.func;
public key(keyGenFunc: RepeatKeyGenFunc<T>): RepeatAPI<T> {
//console.log('__RepeatV2.key()')
this.config.keyGenFunc = keyGenFunc ?? __RepeatDefaultKeyGen.func;
return this;
}
public virtualScroll(): RepeatAPI<T> {
public virtualScroll(options? : { totalCount?: number }): RepeatAPI<T> {
//console.log('__RepeatV2.virtualScroll()')
this.config.totalCount = options?.totalCount ?? this.config.arr.length;
this.isVirtualScroll = true;
return this;
}
public onMove(handler: OnMoveHandler): RepeatAPI<T> {
this.onMoveHandler_ = handler;
// function to decide which template to use, each template has an id
public templateId(typeFunc: RepeatTypeGenFunc<T>): RepeatAPI<T> {
//console.log('__RepeatV2.templateId()')
this.config.typeGenFunc = typeFunc;
return this;
}
private genKeys(): Map<string, __RepeatItemInfo<T>> {
const key2Item = new Map<string, __RepeatItemInfo<T>>();
this.arr_.forEach((item, index) => {
const key = this.keyGenFunction_(item, index);
key2Item.set(key, { key, index });
});
if (key2Item.size < this.arr_.length) {
stateMgmtConsole.warn("Duplicates detected, fallback to index-based keyGen.");
// Causes all items to be re-rendered
this.keyGenFunction_ = __RepeatDefaultKeyGen.funcWithIndex;
return this.genKeys();
// template: id + builder function to render specific type of data item
public template(type: string, itemGenFunc: RepeatItemGenFunc<T>,
options?: RepeatTemplateOptions): RepeatAPI<T>
{
//console.log('__RepeatV2.template()')
this.config.itemGenFuncs[type] = itemGenFunc;
this.config.templateOptions[type] = { ...this.defaultTemplateOptions(), ...options };
return this;
}
public updateArr(arr: Array<T>): RepeatAPI<T> {
//console.log('__RepeatV2.updateArr()')
this.config.arr = arr ?? [];
return this;
}
public render(isInitialRender: boolean): void {
//console.log('__RepeatV2.render()')
if (!this.config.itemGenFuncs?.['']) {
throw new Error(`__RepeatV2 item builder function unspecified. Usage error`);
}
return key2Item;
if (!this.isVirtualScroll) {
// Repeat
this.impl ??= new __RepeatImpl<T>();
this.impl.render(this.config, isInitialRender);
} else {
// RepeatVirtualScroll
this.impl ??= new __RepeatVirtualScrollImpl<T>();
this.impl.render(this.config, isInitialRender);
}
}
public onMove(handler: OnMoveHandler): RepeatAPI<T> {
this.config.onMoveHandler = handler;
return this;
}
private defaultTemplateOptions (): RepeatTemplateOptions {
return { cachedCount: 1 };
}
protected mkRepeatItem<T>(item: T, index?: number): __RepeatItemFactoryReturn<T> {
return new __RepeatItemV2(item as T, index);
}
public render(isInitialRender: boolean): void {
if (!this.itemGenFunc_) {
throw new Error(`itemGen function undefined. Usage error`);
}
if (this.isVirtualScroll) {
// TODO: Add render for LazyforEach with child update.
throw new Error("TODO virtual code path");
} else {
isInitialRender ? this.initialRenderNoneVirtual() : this.rerenderNoneVirtual();
}
}
private initialRenderNoneVirtual(): void {
this.key2Item_ = this.genKeys();
RepeatNative.startRender();
let index = 0;
this.key2Item_.forEach((itemInfo, key) => {
itemInfo.repeatItem = this.mkRepeatItem(this.arr_[index], index);
this.initialRenderItem(key, itemInfo.repeatItem);
index++;
});
let removedChildElmtIds = new Array<number>();
// Fetch the removedChildElmtIds from C++ to unregister those elmtIds with UINodeRegisterProxy
RepeatNative.onMove(this.onMoveHandler_);
RepeatNative.finishRender(removedChildElmtIds);
UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds);
stateMgmtConsole.debug(`RepeatPU: initialRenderNoneVirtual elmtIds need unregister after repeat render: ${JSON.stringify(removedChildElmtIds)}`);
}
private rerenderNoneVirtual(): void {
const oldKey2Item: Map<string, __RepeatItemInfo<T>> = this.key2Item_;
this.key2Item_ = this.genKeys();
// identify array items that have been deleted
// these are candidates for re-use
const deletedKeysAndIndex = new Array<__RepeatItemInfo<T>>();
for (const [key, feInfo] of oldKey2Item) {
if (!this.key2Item_.has(key)) {
deletedKeysAndIndex.push(feInfo);
}
}
// C++: mv children_ aside to tempchildren_
RepeatNative.startRender();
let index = 0;
this.key2Item_.forEach((itemInfo, key) => {
const item = this.arr_[index];
let oldItemInfo = oldKey2Item.get(key);
if (oldItemInfo) {
// case #1 retained array item
// moved from oldIndex to index
const oldIndex = oldItemInfo.index;
itemInfo.repeatItem = oldItemInfo!.repeatItem!;
stateMgmtConsole.debug(`retained: key ${key} ${oldIndex}->${index}`);
itemInfo.repeatItem.updateIndex(index);
// C++ mv from tempChildren[oldIndex] to end of children_
RepeatNative.moveChild(oldIndex);
} else if (deletedKeysAndIndex.length) {
// case #2:
// new array item, there is an deleted array items whose
// UINode children cab re-used
const oldItemInfo = deletedKeysAndIndex.pop();
const reuseKey = oldItemInfo!.key;
const oldKeyIndex = oldItemInfo!.index;
const oldRepeatItem = oldItemInfo!.repeatItem!;
itemInfo.repeatItem = oldRepeatItem;
stateMgmtConsole.debug(`new: key ${key} reuse key ${reuseKey} ${oldKeyIndex}->${index}`);
itemInfo.repeatItem.updateItem(item);
itemInfo.repeatItem.updateIndex(index);
// update key2item_ Map
this.key2Item_.set(key, itemInfo);
// C++ mv from tempChildren[oldIndex] to end of children_
RepeatNative.moveChild(oldKeyIndex);
} else {
// case #3:
// new array item, there are no deleted array items
// render new UINode children
itemInfo.repeatItem = this.mkRepeatItem(item, index);
this.initialRenderItem(key, itemInfo.repeatItem);
}
index++;
})
// keep this.id2item_. by removing all entries for remaining
// deleted items
deletedKeysAndIndex.forEach(delItem => {
this.key2Item_.delete(delItem!.key);
});
// Finish up for.each update
// C++ tempChildren.clear() , trigger re-layout
let removedChildElmtIds = new Array<number>();
// Fetch the removedChildElmtIds from C++ to unregister those elmtIds with UINodeRegisterProxy
RepeatNative.onMove(this.onMoveHandler_);
RepeatNative.finishRender(removedChildElmtIds);
UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds);
stateMgmtConsole.debug(`RepeatPU: rerenderNoneVirtual elmtIds need unregister after repeat render: ${JSON.stringify(removedChildElmtIds)}`);
}
private initialRenderItem(key: string, repeatItem: __RepeatItemFactoryReturn<T>): void {
// render new UINode children
stateMgmtConsole.debug(`new: key ${key} n/a->${repeatItem.index}`);
// C++: initial render will render to the end of children_
RepeatNative.createNewChildStart(key);
// execute the ItemGen function
this.itemGenFunc_!(repeatItem);
RepeatNative.createNewChildFinish(key);
}
}
}; // __RepeatV2<T>
// __Repeat implements ForEach with child re-use for both existing state observation
// and deep observation , for non-virtual and virtual code paths (TODO)
class __RepeatPU<T> extends __RepeatV2<T> implements RepeatAPI<T> {
private owningView_ : ViewPU;
private owningView_: ViewPU;
constructor(owningView: ViewPU, arr: Array<T>) {
super(arr);

View File

@ -0,0 +1,184 @@
/*
* 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
*/
class __RepeatImpl<T> {
private arr_: Array<T>;
private itemGenFuncs_: { [type: string]: RepeatItemGenFunc<T> };
private keyGenFunction_?: RepeatKeyGenFunc<T>;
private typeGenFunc_: RepeatTypeGenFunc<T>;
//
private mkRepeatItem_: (item: T, index?: number) =>__RepeatItemFactoryReturn<T>;
private onMoveHandler_?: OnMoveHandler;
private key2Item_ = new Map<string, __RepeatItemInfo<T>>();
/**/
constructor() {
}
/**/
public render(config: __RepeatAPIConfig<T>, isInitialRender: boolean): void {
this.arr_ = config.arr;
this.itemGenFuncs_ = config.itemGenFuncs;
this.typeGenFunc_ = config.typeGenFunc;
this.keyGenFunction_ = config.keyGenFunc;
this.mkRepeatItem_ = config.mkRepeatItem;
this.onMoveHandler_ = config.onMoveHandler;
isInitialRender ? this.initialRender() : this.reRender();
}
private genKeys(): Map<string, __RepeatItemInfo<T>> {
const key2Item = new Map<string, __RepeatItemInfo<T>>();
this.arr_.forEach((item, index) => {
const key = this.keyGenFunction_(item, index);
key2Item.set(key, { key, index })
});
if (key2Item.size < this.arr_.length) {
stateMgmtConsole.warn("__RepeatImpl: Duplicates detected, fallback to index-based keyGen.")
// Causes all items to be re-rendered
this.keyGenFunction_ = __RepeatDefaultKeyGen.funcWithIndex;
return this.genKeys();
}
return key2Item;
}
private initialRender(): void {
//console.log('__RepeatImpl initialRender() 0')
this.key2Item_ = this.genKeys();
RepeatNative.startRender();
let index = 0;
this.key2Item_.forEach((itemInfo, key) => {
itemInfo.repeatItem = this.mkRepeatItem_(this.arr_[index], index);
this.initialRenderItem(key, itemInfo.repeatItem);
index++;
})
let removedChildElmtIds = new Array<number>();
// Fetch the removedChildElmtIds from C++ to unregister those elmtIds with UINodeRegisterProxy
RepeatNative.onMove(this.onMoveHandler_);
RepeatNative.finishRender(removedChildElmtIds);
UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds);
stateMgmtConsole.debug(`__RepeatImpl: initialRender elmtIds need unregister after repeat render: ${JSON.stringify(removedChildElmtIds)}`);
}
private reRender(): void {
const oldKey2Item: Map<string, __RepeatItemInfo<T>> = this.key2Item_;
this.key2Item_ = this.genKeys();
// identify array items that have been deleted
// these are candidates for re-use
const deletedKeysAndIndex = new Array<__RepeatItemInfo<T>>();
for (const [key, feInfo] of oldKey2Item) {
if (!this.key2Item_.has(key)) {
deletedKeysAndIndex.push(feInfo);
}
}
// C++: mv children_ aside to tempchildren_
RepeatNative.startRender();
let index = 0;
this.key2Item_.forEach((itemInfo, key) => {
const item = this.arr_[index];
let oldItemInfo = oldKey2Item.get(key);
if (oldItemInfo) {
// case #1 retained array item
// moved from oldIndex to index
const oldIndex = oldItemInfo.index;
itemInfo.repeatItem = oldItemInfo!.repeatItem!;
stateMgmtConsole.debug(`__RepeatImpl: retained: key ${key} ${oldIndex}->${index}`)
itemInfo.repeatItem.updateIndex(index);
// C++ mv from tempChildren[oldIndex] to end of children_
RepeatNative.moveChild(oldIndex);
// TBD moveChild() only when item types are same
//const type0 = this.typeGenFunc_(oldItemInfo.repeatItem.item, oldIndex);
//const type1 = this.typeGenFunc_(itemInfo.repeatItem.item, index);
//if (type0 == type1) {
// // C++ mv from tempChildren[oldIndex] to end of children_
// RepeatNative.moveChild(oldIndex);
//} else {
// this.initialRenderItem(key, itemInfo.repeatItem);
//}
} else if (deletedKeysAndIndex.length) {
// case #2:
// new array item, there is an deleted array items whose
// UINode children cab re-used
const oldItemInfo = deletedKeysAndIndex.pop();
const reuseKey = oldItemInfo!.key;
const oldKeyIndex = oldItemInfo!.index;
const oldRepeatItem = oldItemInfo!.repeatItem!;
itemInfo.repeatItem = oldRepeatItem;
stateMgmtConsole.debug(`__RepeatImpl: new: key ${key} reuse key ${reuseKey} ${oldKeyIndex}->${index}`)
itemInfo.repeatItem.updateItem(item);
itemInfo.repeatItem.updateIndex(index);
// update key2item_ Map
this.key2Item_.set(key, itemInfo);
// TBD moveChild() only when item types are same
// C++ mv from tempChildren[oldIndex] to end of children_
RepeatNative.moveChild(oldKeyIndex);
} else {
// case #3:
// new array item, there are no deleted array items
// render new UINode children
itemInfo.repeatItem = this.mkRepeatItem_(item, index);
this.initialRenderItem(key, itemInfo.repeatItem);
}
index++;
})
// keep this.id2item_. by removing all entries for remaining
// deleted items
deletedKeysAndIndex.forEach(delItem => {
this.key2Item_.delete(delItem!.key);
});
// Finish up for.each update
// C++ tempChildren.clear() , trigger re-layout
let removedChildElmtIds = new Array<number>();
// Fetch the removedChildElmtIds from C++ to unregister those elmtIds with UINodeRegisterProxy
RepeatNative.onMove(this.onMoveHandler_);
RepeatNative.finishRender(removedChildElmtIds);
UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds);
stateMgmtConsole.debug(`__RepeatImpl: reRender elmtIds need unregister after repeat render: ${JSON.stringify(removedChildElmtIds)}`);
}
private initialRenderItem(key: string, repeatItem: __RepeatItemFactoryReturn<T>): void {
//console.log('__RepeatImpl initialRenderItem()')
// render new UINode children
stateMgmtConsole.debug(`__RepeatImpl: new: key ${key} n/a->${repeatItem.index}`)
// C++: initial render will render to the end of children_
RepeatNative.createNewChildStart(key);
// execute the itemGen function
const itemType = this.typeGenFunc_(repeatItem.item, repeatItem.index) ?? '';
const itemFunc = this.itemGenFuncs_[itemType] ?? this.itemGenFuncs_[''];
itemFunc(repeatItem);
RepeatNative.createNewChildFinish(key);
}
};

View File

@ -0,0 +1,165 @@
/*
* 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
*/
// Implements ForEach with child re-use for both existing state observation and
// deep observation. For virtual-scroll code paths
class __RepeatVirtualScrollImpl<T> {
private arr_: Array<T>;
private itemGenFuncs_: { [type: string]: RepeatItemGenFunc<T> };
private keyGenFunc_?: RepeatKeyGenFunc<T>;
private typeGenFunc_: RepeatTypeGenFunc<T>;
//
private totalCount_: number;
private templateOptions_: { [type: string]: RepeatTemplateOptions };
//
private mkRepeatItem_: (item: T, index?: number) =>__RepeatItemFactoryReturn<T>;
private onMoveHandler_?: OnMoveHandler;
/**/
constructor() {
}
public render(config: __RepeatAPIConfig<T>, isInitialRender: boolean): void {
this.arr_ = config.arr;
this.itemGenFuncs_ = config.itemGenFuncs;
this.keyGenFunc_ = config.keyGenFunc;
this.typeGenFunc_ = config.typeGenFunc;
this.totalCount_ = config.totalCount;
this.templateOptions_ = config.templateOptions;
this.mkRepeatItem_ = config.mkRepeatItem;
this.onMoveHandler_ = config.onMoveHandler;
if (isInitialRender) {
this.initialRender(ObserveV2.getCurrentRecordedId());
} else {
this.reRender();
}
}
/**/
private initialRender(repeatElmtId: number): void {
// Map key -> RepeatItem
// added to closure of following lambdas
const _repeatItem4Key = new Map<string, __RepeatItemFactoryReturn<T>>();
const repeatElmtId1 = repeatElmtId;
const onCreateNode = (forIndex: number): void => {
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl onCreateNode index ${forIndex} - start`);
if (forIndex < 0) {
// FIXME check also index < totalCount
throw new Error(`__RepeatVirtualScrollImpl onCreateNode: for index=${forIndex} out of range error.`)
}
// create dependency array item [forIndex] -> Repeat
// so Repeat updates when the array item changes
// FIXME observe dependencies, adding the array is insurgent for Array of objects
ObserveV2.getObserve().addRef4Id(repeatElmtId1, this.arr_, forIndex.toString());
const repeatItem = this.mkRepeatItem_(this.arr_[forIndex], forIndex);
const forKey = this.keyGenFunc_(this.arr_[forIndex], forIndex);
_repeatItem4Key.set(forKey, repeatItem);
// execute the itemGen function
this.initialRenderItem(repeatItem);
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl onCreateNode for index ${forIndex} key "${forKey}" - end `);
};
const onUpdateNode = (fromKey: string, forIndex: number): void => {
if (!fromKey || fromKey=="" || forIndex < 0) {
// FIXME check also index < totalCount
throw new Error(`__RepeatVirtualScrollImpl onUpdateNode: fromKey "${fromKey}", forIndex=${forIndex} invalid function input error.`)
}
// create dependency array item [forIndex] -> Repeat
// so Repeat updates when the array item changes
// FIXME observe dependencies, adding the array is insurgent for Array of objects
ObserveV2.getObserve().addRef4Id(repeatElmtId1, this.arr_, forIndex.toString());
const repeatItem = _repeatItem4Key.get(fromKey);
if (!repeatItem) {
stateMgmtConsole.error(`__RepeatVirtualScrollImpl onUpdateNode: fromKey "${fromKey}", forIndex=${forIndex}, can not find RepeatItem for key. Unrecoverable error`);
return;
}
const forKey= this.keyGenFunc_(this.arr_[forIndex], forIndex);
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl onUpdateNode: fromKey "${fromKey}", forIndex=${forIndex} forKey="${forKey}". Updating RepeatItem ...`);
repeatItem.updateItem(this.arr_[forIndex]);
repeatItem.updateIndex(forIndex);
// update Map according to made update:
// del fromKey entry and add forKey
_repeatItem4Key.delete(fromKey);
_repeatItem4Key.set(forKey, repeatItem);
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl onUpdateNode: fromKey "${fromKey}", forIndex=${forIndex} forKey="${forKey}". Initiating update synchronously (TODO)...`);
// FIXME request re-render right away!
ObserveV2.getObserve().updateDirty2(true);
};
const onGetKeys4Range = (from: number, to: number): Array<string> => {
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl: onGetKeys4Range from ${from} to ${to} - start`);
const result = new Array<string>();
for (let i = from; i <= to && i < this.arr_.length; i++) {
// create dependency array item [i] -> Repeat
// so Repeat updates when the array item changes
// FIXME observe dependencies, adding the array is insurgent for Array of objects
ObserveV2.getObserve().addRef4Id(repeatElmtId1, this.arr_, i.toString());
result.push(this.keyGenFunc_(this.arr_[i], i));
}
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl: onGetKeys4Range from ${from} to ${to} - returns ${result.toString()}`);
return result;
};
const onGetTypes4Range = (from: number, to: number): Array<string> => {
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl: onGetTypes4Range from ${from} to ${to} - start`);
const result = new Array<string>();
// FIXME observe dependencies, adding the array is insurgent for Array of objects
for (let i = from; i <= to && i < this.arr_.length; i++) {
// ObserveV2.getObserve().addRef4Id(repeatElmtId1, this.arr_, i.toString());
result.push(this.typeGenFunc_(this.arr_[i], i) ?? '');
}
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl: onGetTypes4Range from ${from} to ${to} - returns ${result.toString()}`);
return result;
};
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl: initialRenderVirtualScroll`);
RepeatVirtualScrollNative.create(this.totalCount_, Object.entries(this.templateOptions_), {
onCreateNode,
onUpdateNode,
onGetKeys4Range,
onGetTypes4Range
});
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl: initialRenderVirtualScroll 2`);
}
private reRender() {
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl: reRender ...`);
RepeatVirtualScrollNative.invalidateKeyCache(this.totalCount_);
stateMgmtConsole.debug(`__RepeatVirtualScrollImpl: reRender - done`);
}
private initialRenderItem(repeatItem: __RepeatItemFactoryReturn<T>): void {
// execute the itemGen function
const itemType = this.typeGenFunc_(repeatItem.item, repeatItem.index) ?? '';
const itemFunc = this.itemGenFuncs_[itemType] ?? this.itemGenFuncs_[''];
itemFunc(repeatItem);
}
};

View File

@ -23,7 +23,9 @@ interface RepeatItem<T> {
}
type RepeatItemGenFunc<T> = (i: RepeatItem<T>) => void;
type RepeatTypeGenFunc<T> = (item: T, index: number) => string;
type RepeatKeyGenFunc<T> = (item: T, index?: number) => string;
type RepeatTemplateOptions = { cachedCount?: number };
type OnMoveHandler = (from: number, to: number) => void;
/*
@ -43,11 +45,26 @@ const Repeat: <T>(arr: Array<T>, owningView?: PUV2ViewBase) => RepeatAPI<T> =
repeat attribute function and internal function render()
*/
interface RepeatAPI<T> {
each: (itemGenFunc: RepeatItemGenFunc<T>) => RepeatAPI<T>; // chainable, call in this order
// unique key from array item, to identify changed, added array items
key: (keyGenFunc: RepeatKeyGenFunc<T>) => RepeatAPI<T>;
virtualScroll: () => RepeatAPI<T>;
onMove: (handler: OnMoveHandler) => RepeatAPI<T>;
// default render function for data items, framework will use when no template found
each: (itemGenFunc: RepeatItemGenFunc<T>) => RepeatAPI<T>;
// when call virtualScroll, framework will use virtual scroll
// totalCount: number of logical items, can be larger than number of loaded
// data items of lazy loading array source, can be larger than that array.length
virtualScroll: (options?: { totalCount?: number }) => RepeatAPI<T>;
// function to decide which template to use, each template has an id
templateId: (typeFunc: RepeatTypeGenFunc<T>) => RepeatAPI<T>;
// template: id + builder function to render specific type of data item
template: (type: string, itemGenFunc: RepeatItemGenFunc<T>, options?: RepeatTemplateOptions) => RepeatAPI<T>;
// do NOT use in app, transpiler adds as last of chained call
render(isInitialRender: boolean): void;
// not used by Repeat
onMove: (handler: OnMoveHandler) => RepeatAPI<T>;
}

View File

@ -125,6 +125,11 @@ class ObserveV2 {
return (value && typeof (value) === 'object' && value[ObserveV2.V2_DECO_META]);
}
public static getCurrentRecordedId(): number {
const bound = ObserveV2.getObserve().stackOfRenderedComponents_.top();
return bound ? bound[0] : -1;
}
// At the start of observeComponentCreation or
// MonitorV2 observeObjectAccess
public startRecordDependencies(cmp: IView | MonitorV2 | ComputedV2 | PersistenceV2Impl, id: number): void {
@ -291,8 +296,15 @@ class ObserveV2 {
}
stateMgmtConsole.propertyAccess(`ObserveV2.addRef '${attrName}' for id ${bound[0]}...`);
const id = bound[0];
this.addRef4IdInternal(bound[0], target, attrName);
}
public addRef4Id(id: number, target: object, attrName: string): void {
stateMgmtConsole.propertyAccess(`ObserveV2.addRef4Id '${attrName}' for id ${id} ...`);
this.addRef4IdInternal(id, target, attrName);
}
private addRef4IdInternal(id: number, target: object, attrName: string): void {
// Map: attribute/symbol -> dependent id
const symRefs = target[ObserveV2.SYMBOL_REFS] ??= {};
symRefs[attrName] ??= new Set();
@ -411,15 +423,29 @@ class ObserveV2 {
} // for
}
private updateDirty(): void {
public updateDirty(): void {
this.startDirty_ = true;
this.updateDirty2();
this.updateDirty2(false);
this.startDirty_ = false;
}
private updateDirty2(): void {
/**
* execute /update in this order
* - @Computed variables
* - @Monitor functions
* - UINode re-render
* three nested loops, means:
* process @Computed until no more @Computed need update
* process @Monitor until no more @Computed and @Monitor
* process UINode update until no more @Computed and @Monitor and UINode rerender
*
* @param updateUISynchronously should be set to true if called during VSYNC only
*
*/
public updateDirty2(updateUISynchronously: boolean = false): void {
aceTrace.begin('updateDirty2');
stateMgmtConsole.debug(`ObservedV3.updateDirty2 ... `);
stateMgmtConsole.debug(`ObservedV3.updateDirty2 updateUISynchronously=${updateUISynchronously} ... `);
// obtain and unregister the removed elmtIds
UINodeRegisterProxy.obtainDeletedElmtIds();
UINodeRegisterProxy.unregisterElmtIdsFromIViews();
@ -456,9 +482,11 @@ class ObserveV2 {
if (this.elmtIdsChanged_.size) {
const elmtIds = Array.from(this.elmtIdsChanged_).sort((elmtId1, elmtId2) => elmtId1 - elmtId2);
this.elmtIdsChanged_ = new Set<number>();
this.updateUINodes(elmtIds);
updateUISynchronously ? this.updateUINodesSynchronously(elmtIds) : this.updateUINodes(elmtIds);
}
} while (this.elmtIdsChanged_.size + this.monitorIdsChanged_.size + this.computedPropIdsChanged_.size > 0);
stateMgmtConsole.debug(`ObservedV3.updateDirty2 updateUISynchronously=${updateUISynchronously} - DONE `);
aceTrace.end();
}
@ -508,10 +536,11 @@ class ObserveV2 {
* FlushDirtyNodesUpdate to CustomNode to ViewV2.updateDirtyElements to UpdateElement
* Code left here to reproduce benchmark measurements, compare with future optimisation
* @param elmtIds
*
*/
private updateUINodesWithoutVSync(elmtIds: Array<number>): void {
stateMgmtConsole.debug(`ObserveV2.updateUINodes: ${elmtIds.length} elmtIds: ${JSON.stringify(elmtIds)} ...`);
aceTrace.begin(`ObserveV2.updateUINodes: ${elmtIds.length} elmtId`);
private updateUINodesSynchronously(elmtIds: Array<number>): void {
stateMgmtConsole.debug(`ObserveV2.updateUINodesSynchronously: ${elmtIds.length} elmtIds: ${JSON.stringify(elmtIds)} ...`);
aceTrace.begin(`ObserveV2.updateUINodesSynchronously: ${elmtIds.length} elmtId`);
let view: Object;
let weak: any;
elmtIds.forEach((elmtId) => {
@ -534,8 +563,8 @@ class ObserveV2 {
// On next VSYNC runs FlushDirtyNodesUpdate to call rerender to call UpdateElement. Much longer code path
// much slower
private updateUINodes(elmtIds: Array<number>): void {
stateMgmtConsole.debug(`ObserveV2.updateUINodesSlow: ${elmtIds.length} elmtIds need rerender: ${JSON.stringify(elmtIds)} ...`);
aceTrace.begin(`ObserveV2.updateUINodesSlow: ${elmtIds.length} elmtId`);
stateMgmtConsole.debug(`ObserveV2.updateUINodes: ${elmtIds.length} elmtIds need rerender: ${JSON.stringify(elmtIds)} ...`);
aceTrace.begin(`ObserveV2.updateUINodes: ${elmtIds.length} elmtId`);
let viewWeak: WeakRef<Object>;
let view: Object | undefined;
elmtIds.forEach((elmtId) => {
@ -874,5 +903,3 @@ const trackInternal = (
// used by IsObservedObjectV2
target[ObserveV2.V2_DECO_META] ??= {};
}; // trackInternal

View File

@ -88,6 +88,8 @@
// partial_update specific
"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",
// sdk
"src/lib/sdk/v2_persistence.ts",

View File

@ -56,7 +56,6 @@
"src/lib/full_update/fu_synced_property_object_nested.ts",
"src/lib/full_update/fu_view.ts",
// partial update and v2 common
"src/lib/puv2_common/puv2_viewstack_processor.d.ts",
"src/lib/puv2_common/iview.ts",
@ -77,6 +76,8 @@
"src/lib/partial_update/pu_view.ts",
"src/lib/partial_update/pu_recycle_manager.ts",
"src/lib/partial_update/pu_builder_proxy.ts",
"src/lib/partial_update/pu_repeat_impl.ts",
"src/lib/partial_update/pu_repeat_virtual_scroll_impl.ts",
// 'new' stateMgmt vers 2
"src/lib/v2/v2_change_observation.ts",

View File

@ -88,6 +88,8 @@
// partial_update specific
"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",
// sdk
"src/lib/sdk/v2_persistence.ts",

View File

@ -76,6 +76,8 @@
// partial_update specific
"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",
// sdk
"src/lib/sdk/v2_persistence.ts",

View File

@ -56,6 +56,7 @@
#include "core/components_ng/property/property.h"
#include "core/components_ng/render/paint_wrapper.h"
#include "core/components_ng/syntax/lazy_for_each_node.h"
#include "core/components_ng/syntax/repeat_virtual_scroll_node.h"
#include "core/components_v2/inspector/inspector_constants.h"
#include "core/event/touch_event.h"
#include "core/gestures/gesture_info.h"
@ -142,9 +143,7 @@ public:
cursor_ = children_.end();
if (prevFrameProxy_->cursor_ != prevFrameProxy_->children_.end()) {
cursor_ = std::find_if(children_.begin(), children_.end(),
[this](FrameChildNode& node) {
return prevFrameProxy_->cursor_->node == node.node;
});
[this](FrameChildNode& node) { return prevFrameProxy_->cursor_->node == node.node; });
}
}
}
@ -178,8 +177,11 @@ public:
return;
}
auto lazyForEachNode = AceType::DynamicCast<LazyForEachNode>(UiNode);
auto repeatVirtualScrollNode = AceType::DynamicCast<RepeatVirtualScrollNode>(UiNode);
if (lazyForEachNode) {
lazyForEachNode->BuildAllChildren();
} else if (repeatVirtualScrollNode) {
TAG_LOGE(AceLogTag::ACE_REPEAT, "repeatVirtualScroll not support in non scoll container!");
} else {
auto customNode = AceType::DynamicCast<CustomNode>(UiNode);
if (customNode) {
@ -225,9 +227,8 @@ public:
}
if (cursor_->startIndex + cursor_->count > index) {
auto frameNode = AceType::DynamicCast<FrameNode>(
cursor_->node->GetFrameChildByIndex(index - cursor_->startIndex,
needBuild, isCache, addToRenderTree));
auto frameNode = AceType::DynamicCast<FrameNode>(cursor_->node->GetFrameChildByIndex(
index - cursor_->startIndex, needBuild, isCache, addToRenderTree));
return frameNode;
}
cursor_++;
@ -272,8 +273,8 @@ public:
void ResetChildren(bool needResetChild = false)
{
if (inUse_) {
LOGF("[%{public}d:%{public}s] reset children while in use",
hostNode_->GetId(), hostNode_->GetTag().c_str());
LOGF(
"[%{public}d:%{public}s] reset children while in use", hostNode_->GetId(), hostNode_->GetTag().c_str());
if (SystemProperties::GetLayoutDetectEnabled()) {
abort();
} else {
@ -320,12 +321,11 @@ public:
}
}
void SetActiveChildRange(int32_t start, int32_t end)
void SetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd)
{
for (auto itor = partFrameNodeChildren_.begin(); itor != partFrameNodeChildren_.end();) {
int32_t index = itor->first;
if ((start <= end && index >= start && index <= end) ||
(start > end && (index <= end || start <= index))) {
if ((start <= end && index >= start && index <= end) || (start > end && (index <= end || start <= index))) {
itor++;
} else {
partFrameNodeChildren_.erase(itor++);
@ -333,7 +333,7 @@ public:
}
auto guard = GetGuard();
for (const auto& child : children_) {
child.node->DoSetActiveChildRange(start - child.startIndex, end - child.startIndex);
child.node->DoSetActiveChildRange(start - child.startIndex, end - child.startIndex, cacheStart, cacheEnd);
}
}
@ -652,11 +652,11 @@ void FrameNode::DumpSafeAreaInfo()
auto manager = pipeline->GetSafeAreaManager();
CHECK_NULL_VOID(manager);
DumpLog::GetInstance().AddDesc(std::string("ignoreSafeArea: ")
.append(std::to_string(manager->IsIgnoreAsfeArea()))
.append(std::string(", isNeedAvoidWindow: ").c_str())
.append(std::to_string(manager->IsNeedAvoidWindow()))
.append(std::string(", isFullScreen: ").c_str())
.append(std::to_string(manager->IsFullScreen())));
.append(std::to_string(manager->IsIgnoreAsfeArea()))
.append(std::string(", isNeedAvoidWindow: ").c_str())
.append(std::to_string(manager->IsNeedAvoidWindow()))
.append(std::string(", isFullScreen: ").c_str())
.append(std::to_string(manager->IsFullScreen())));
}
void FrameNode::DumpAlignRulesInfo()
@ -665,8 +665,7 @@ void FrameNode::DumpAlignRulesInfo()
CHECK_NULL_VOID(flexItemProperties);
auto rulesToString = flexItemProperties->AlignRulesToString();
CHECK_NULL_VOID(!rulesToString.empty());
DumpLog::GetInstance().AddDesc(std::string("AlignRules: ")
.append(rulesToString));
DumpLog::GetInstance().AddDesc(std::string("AlignRules: ").append(rulesToString));
}
void FrameNode::DumpExtensionHandlerInfo()
@ -674,11 +673,10 @@ void FrameNode::DumpExtensionHandlerInfo()
if (!extensionHandler_) {
return;
}
DumpLog::GetInstance().AddDesc(
std::string("ExtensionHandler: HasCustomerMeasure: ")
.append(extensionHandler_->HasCustomerMeasure() ? "true" : "false")
.append(", HasCustomerLayout: ")
.append(extensionHandler_->HasCustomerLayout() ? "true" : "false"));
DumpLog::GetInstance().AddDesc(std::string("ExtensionHandler: HasCustomerMeasure: ")
.append(extensionHandler_->HasCustomerMeasure() ? "true" : "false")
.append(", HasCustomerLayout: ")
.append(extensionHandler_->HasCustomerLayout() ? "true" : "false"));
}
void FrameNode::DumpCommonInfo()
@ -692,7 +690,7 @@ void FrameNode::DumpCommonInfo()
}
if (geometryNode_->GetParentLayoutConstraint().has_value())
DumpLog::GetInstance().AddDesc(std::string("ParentLayoutConstraint: ")
.append(geometryNode_->GetParentLayoutConstraint().value().ToString()));
.append(geometryNode_->GetParentLayoutConstraint().value().ToString()));
if (!(NearZero(GetOffsetRelativeToWindow().GetY()) && NearZero(GetOffsetRelativeToWindow().GetX()))) {
DumpLog::GetInstance().AddDesc(std::string("top: ")
.append(std::to_string(GetOffsetRelativeToWindow().GetY()))
@ -738,8 +736,9 @@ void FrameNode::DumpCommonInfo()
layoutProperty_->GetMarginProperty() || layoutProperty_->GetCalcLayoutConstraint()) {
DumpLog::GetInstance().AddDesc(
std::string("ContentConstraint: ")
.append(layoutProperty_->GetContentLayoutConstraint().has_value() ?
layoutProperty_->GetContentLayoutConstraint().value().ToString() : "NA"));
.append(layoutProperty_->GetContentLayoutConstraint().has_value()
? layoutProperty_->GetContentLayoutConstraint().value().ToString()
: "NA"));
}
DumpAlignRulesInfo();
DumpDragInfo();
@ -753,11 +752,11 @@ void FrameNode::DumpDragInfo()
{
DumpLog::GetInstance().AddDesc("------------start print dragInfo");
DumpLog::GetInstance().AddDesc(std::string("Draggable: ")
.append(draggable_ ? "true" : "false")
.append(" UserSet: ")
.append(userSet_ ? "true" : "false")
.append(" CustomerSet: ")
.append(customerSet_ ? "true" : "false"));
.append(draggable_ ? "true" : "false")
.append(" UserSet: ")
.append(userSet_ ? "true" : "false")
.append(" CustomerSet: ")
.append(customerSet_ ? "true" : "false"));
auto dragPreviewStr =
std::string("DragPreview: Has customNode: ").append(dragPreviewInfo_.customNode ? "YES" : "NO");
dragPreviewStr.append(" Has pixelMap: ").append(dragPreviewInfo_.pixelMap ? "YES" : "NO");
@ -766,30 +765,30 @@ void FrameNode::DumpDragInfo()
DumpLog::GetInstance().AddDesc(dragPreviewStr);
auto eventHub = GetEventHub<EventHub>();
DumpLog::GetInstance().AddDesc(std::string("Event: ")
.append("OnDragStart: ")
.append(eventHub->HasOnDragStart() ? "YES" : "NO")
.append(" OnDragEnter: ")
.append(eventHub->HasOnDragEnter() ? "YES" : "NO")
.append(" OnDragLeave: ")
.append(eventHub->HasOnDragLeave() ? "YES" : "NO")
.append(" OnDragMove: ")
.append(eventHub->HasOnDragMove() ? "YES" : "NO")
.append(" OnDrop: ")
.append(eventHub->HasOnDrop() ? "YES" : "NO")
.append(" OnDragEnd: ")
.append(eventHub->HasOnDragEnd() ? "YES" : "NO"));
.append("OnDragStart: ")
.append(eventHub->HasOnDragStart() ? "YES" : "NO")
.append(" OnDragEnter: ")
.append(eventHub->HasOnDragEnter() ? "YES" : "NO")
.append(" OnDragLeave: ")
.append(eventHub->HasOnDragLeave() ? "YES" : "NO")
.append(" OnDragMove: ")
.append(eventHub->HasOnDragMove() ? "YES" : "NO")
.append(" OnDrop: ")
.append(eventHub->HasOnDrop() ? "YES" : "NO")
.append(" OnDragEnd: ")
.append(eventHub->HasOnDragEnd() ? "YES" : "NO"));
DumpLog::GetInstance().AddDesc(std::string("DefaultOnDragStart: ")
.append(eventHub->HasDefaultOnDragStart() ? "YES" : "NO")
.append(" CustomerOnDragEnter: ")
.append(eventHub->HasCustomerOnDragEnter() ? "YES" : "NO")
.append(" CustomerOnDragLeave: ")
.append(eventHub->HasCustomerOnDragLeave() ? "YES" : "NO")
.append(" CustomerOnDragMove: ")
.append(eventHub->HasCustomerOnDragMove() ? "YES" : "NO")
.append(" CustomerOnDrop: ")
.append(eventHub->HasCustomerOnDrop() ? "YES" : "NO")
.append(" CustomerOnDragEnd: ")
.append(eventHub->HasCustomerOnDragEnd() ? "YES" : "NO"));
.append(eventHub->HasDefaultOnDragStart() ? "YES" : "NO")
.append(" CustomerOnDragEnter: ")
.append(eventHub->HasCustomerOnDragEnter() ? "YES" : "NO")
.append(" CustomerOnDragLeave: ")
.append(eventHub->HasCustomerOnDragLeave() ? "YES" : "NO")
.append(" CustomerOnDragMove: ")
.append(eventHub->HasCustomerOnDragMove() ? "YES" : "NO")
.append(" CustomerOnDrop: ")
.append(eventHub->HasCustomerOnDrop() ? "YES" : "NO")
.append(" CustomerOnDragEnd: ")
.append(eventHub->HasCustomerOnDragEnd() ? "YES" : "NO"));
DumpLog::GetInstance().AddDesc("------------end print dragInfo");
}
@ -797,11 +796,11 @@ void FrameNode::DumpOnSizeChangeInfo()
{
for (auto it = onSizeChangeDumpInfos.rbegin(); it != onSizeChangeDumpInfos.rend(); ++it) {
DumpLog::GetInstance().AddDesc(std::string("onSizeChange Time: ")
.append(ConvertTimestampToStr(it->onSizeChangeTimeStamp))
.append(" lastFrameRect: ")
.append(it->lastFrameRect.ToString())
.append(" currFrameRect: ")
.append(it->currFrameRect.ToString()));
.append(ConvertTimestampToStr(it->onSizeChangeTimeStamp))
.append(" lastFrameRect: ")
.append(it->lastFrameRect.ToString())
.append(" currFrameRect: ")
.append(it->currFrameRect.ToString()));
}
}
@ -963,8 +962,7 @@ void FrameNode::GeometryNodeToJsonValue(std::unique_ptr<JsonValue>& json, const
auto jsonSize = json->GetValue("size");
if (!hasIdealWidth) {
auto idealWidthVpStr = std::to_string(Dimension(geometryNode_->GetFrameSize().Width()).ConvertToVp());
auto widthStr =
(idealWidthVpStr.substr(0, idealWidthVpStr.find(".") + SUBSTR_LENGTH) + DIMENSION_UNIT_VP);
auto widthStr = (idealWidthVpStr.substr(0, idealWidthVpStr.find(".") + SUBSTR_LENGTH) + DIMENSION_UNIT_VP);
json->PutExtAttr("width", widthStr.c_str(), filter);
if (jsonSize) {
jsonSize->Put("width", widthStr.c_str());
@ -973,8 +971,7 @@ void FrameNode::GeometryNodeToJsonValue(std::unique_ptr<JsonValue>& json, const
if (!hasIdealHeight) {
auto idealHeightVpStr = std::to_string(Dimension(geometryNode_->GetFrameSize().Height()).ConvertToVp());
auto heightStr =
(idealHeightVpStr.substr(0, idealHeightVpStr.find(".") + SUBSTR_LENGTH) + DIMENSION_UNIT_VP);
auto heightStr = (idealHeightVpStr.substr(0, idealHeightVpStr.find(".") + SUBSTR_LENGTH) + DIMENSION_UNIT_VP);
json->PutExtAttr("height", heightStr.c_str(), filter);
if (jsonSize) {
jsonSize->Put("height", heightStr.c_str());
@ -1031,7 +1028,7 @@ void FrameNode::UpdateGeometryTransition()
MarkDirtyNode();
}
auto children = GetChildren();
for (const auto& child: children) {
for (const auto& child : children) {
child->UpdateGeometryTransition();
}
}
@ -1123,8 +1120,7 @@ void FrameNode::NotifyVisibleChange(bool isVisible)
void FrameNode::TryVisibleChangeOnDescendant(bool isVisible)
{
auto layoutProperty = GetLayoutProperty();
if (layoutProperty &&
layoutProperty->GetVisibilityValue(VisibleType::VISIBLE) != VisibleType::VISIBLE) {
if (layoutProperty && layoutProperty->GetVisibilityValue(VisibleType::VISIBLE) != VisibleType::VISIBLE) {
return;
}
NotifyVisibleChange(isVisible);
@ -1357,8 +1353,8 @@ RectF FrameNode::GetRectWithRender()
}
if (renderContext_ && renderContext_->GetPositionProperty()) {
if (renderContext_->GetPositionProperty()->HasPosition()) {
auto renderPosition = ContextPositionConvertToPX(
renderContext_, layoutProperty_->GetLayoutConstraint()->percentReference);
auto renderPosition =
ContextPositionConvertToPX(renderContext_, layoutProperty_->GetLayoutConstraint()->percentReference);
currFrameRect.SetOffset(
{ static_cast<float>(renderPosition.first), static_cast<float>(renderPosition.second) });
}
@ -1529,7 +1525,8 @@ void FrameNode::ThrottledVisibleTask()
GetVisibleRect(visibleRect, frameRect);
double ratio = IsFrameDisappear() ? VISIBLE_RATIO_MIN
: std::clamp(CalculateCurrentVisibleRatio(visibleRect, frameRect),
VISIBLE_RATIO_MIN, VISIBLE_RATIO_MAX);
VISIBLE_RATIO_MIN,
VISIBLE_RATIO_MAX);
if (NearEqual(ratio, lastThrottledVisibleRatio_)) {
throttledCallbackOnTheWay_ = false;
return;
@ -1800,7 +1797,7 @@ RefPtr<ContentModifier> FrameNode::GetContentModifier()
auto wrapper = CreatePaintWrapper();
CHECK_NULL_RETURN(wrapper, nullptr);
auto paintMethod = pattern_->CreateNodePaintMethod();
if (!paintMethod || extensionHandler_ || renderContext_->GetAccessibilityFocus().value_or(false)) {
if (!paintMethod || extensionHandler_ || renderContext_->GetAccessibilityFocus().value_or(false)) {
paintMethod = pattern_->CreateDefaultNodePaintMethod();
}
CHECK_NULL_RETURN(paintMethod, nullptr);
@ -1969,8 +1966,8 @@ RefPtr<FrameNode> FrameNode::GetFirstAutoFillContainerNode()
return AceType::DynamicCast<FrameNode>(parent);
}
void FrameNode::NotifyFillRequestSuccess(RefPtr<ViewDataWrap> viewDataWrap,
RefPtr<PageNodeInfoWrap> nodeWrap, AceAutoFillType autoFillType)
void FrameNode::NotifyFillRequestSuccess(
RefPtr<ViewDataWrap> viewDataWrap, RefPtr<PageNodeInfoWrap> nodeWrap, AceAutoFillType autoFillType)
{
if (pattern_) {
pattern_->NotifyFillRequestSuccess(viewDataWrap, nodeWrap, autoFillType);
@ -2100,8 +2097,7 @@ void FrameNode::OnGenerateOneDepthVisibleFrameWithTransition(std::list<RefPtr<Fr
visibleList.emplace_back(Claim(this));
}
void FrameNode::OnGenerateOneDepthVisibleFrameWithOffset(
std::list<RefPtr<FrameNode>>& visibleList, OffsetF& offset)
void FrameNode::OnGenerateOneDepthVisibleFrameWithOffset(std::list<RefPtr<FrameNode>>& visibleList, OffsetF& offset)
{
if (isLayoutNode_) {
isFind_ = true;
@ -2189,8 +2185,7 @@ bool FrameNode::IsOutOfTouchTestRegion(const PointF& parentRevertPoint, int32_t
auto subRevertPoint = revertPoint - paintRect.GetOffset();
auto clip = renderContext_->GetClipEdge().value_or(false);
if (!InResponseRegionList(revertPoint, responseRegionList) || !GetTouchable() ||
NearZero(paintRectWithTransform.Width() ||
NearZero(paintRectWithTransform.Height()))) {
NearZero(paintRectWithTransform.Width() || NearZero(paintRectWithTransform.Height()))) {
if (clip) {
return true;
}
@ -2382,7 +2377,7 @@ HitTestResult FrameNode::TouchTest(const PointF& globalPoint, const PointF& pare
if (childHitResult == HitTestResult::BUBBLING &&
((child->GetHitTestMode() == HitTestMode::HTMDEFAULT) ||
(child->GetHitTestMode() == HitTestMode::HTMTRANSPARENT_SELF) ||
((child->GetHitTestMode() != HitTestMode::HTMTRANSPARENT) && IsExclusiveEventForChild()))) {
((child->GetHitTestMode() != HitTestMode::HTMTRANSPARENT) && IsExclusiveEventForChild()))) {
consumed = true;
break;
}
@ -3026,9 +3021,8 @@ RefPtr<FrameNode> FrameNode::FindChildByName(const RefPtr<FrameNode>& parentNode
return nullptr;
}
void FrameNode::CreateAnimatablePropertyFloat(
const std::string& propertyName, float value, const std::function<void(float)>& onCallbackEvent,
const PropertyUnit& propertyType)
void FrameNode::CreateAnimatablePropertyFloat(const std::string& propertyName, float value,
const std::function<void(float)>& onCallbackEvent, const PropertyUnit& propertyType)
{
auto context = GetRenderContext();
CHECK_NULL_VOID(context);
@ -3166,7 +3160,7 @@ int32_t FrameNode::GetNodeExpectedRate()
void FrameNode::AddFRCSceneInfo(const std::string& scene, float speed, SceneStatus status)
{
if (SystemProperties::GetDebugEnabled()) {
const std::string sceneStatusStrs[] = {"START", "RUNNING", "END"};
const std::string sceneStatusStrs[] = { "START", "RUNNING", "END" };
LOGI("%{public}s AddFRCSceneInfo scene:%{public}s speed:%{public}f status:%{public}s", GetTag().c_str(),
scene.c_str(), std::abs(speed), sceneStatusStrs[static_cast<int32_t>(status)].c_str());
}
@ -3299,8 +3293,8 @@ void FrameNode::UpdatePercentSensitive()
// This will call child and self measure process.
void FrameNode::Measure(const std::optional<LayoutConstraintF>& parentConstraint)
{
ACE_LAYOUT_TRACE_BEGIN("Measure[%s][self:%d][parent:%d][key:%s]", GetTag().c_str(),
GetId(), GetAncestorNodeOfFrame() ? GetAncestorNodeOfFrame()->GetId() : 0, GetInspectorIdValue("").c_str());
ACE_LAYOUT_TRACE_BEGIN("Measure[%s][self:%d][parent:%d][key:%s]", GetTag().c_str(), GetId(),
GetAncestorNodeOfFrame() ? GetAncestorNodeOfFrame()->GetId() : 0, GetInspectorIdValue("").c_str());
ArkUIPerfMonitor::GetInstance().RecordLayoutNode();
isLayoutComplete_ = false;
if (!oldGeometryNode_) {
@ -3406,8 +3400,8 @@ void FrameNode::Measure(const std::optional<LayoutConstraintF>& parentConstraint
// Called to perform layout children.
void FrameNode::Layout()
{
ACE_LAYOUT_TRACE_BEGIN("Layout[%s][self:%d][parent:%d][key:%s]", GetTag().c_str(),
GetId(), GetAncestorNodeOfFrame() ? GetAncestorNodeOfFrame()->GetId() : 0, GetInspectorIdValue("").c_str());
ACE_LAYOUT_TRACE_BEGIN("Layout[%s][self:%d][parent:%d][key:%s]", GetTag().c_str(), GetId(),
GetAncestorNodeOfFrame() ? GetAncestorNodeOfFrame()->GetId() : 0, GetInspectorIdValue("").c_str());
if (layoutProperty_->GetLayoutRect()) {
GetGeometryNode()->SetFrameOffset(layoutProperty_->GetLayoutRect().value().GetOffset());
}
@ -3604,8 +3598,8 @@ bool FrameNode::OnLayoutFinish(bool& needSyncRsNode, DirtySwapConfig& config)
void FrameNode::SyncGeometryNode(bool needSyncRsNode, const DirtySwapConfig& config)
{
if (SystemProperties::GetSyncDebugTraceEnabled()) {
ACE_LAYOUT_TRACE_BEGIN("SyncGeometryNode[%s][self:%d][parent:%d][key:%s]", GetTag().c_str(),
GetId(), GetParent() ? GetParent()->GetId() : 0, GetInspectorIdValue("").c_str());
ACE_LAYOUT_TRACE_BEGIN("SyncGeometryNode[%s][self:%d][parent:%d][key:%s]", GetTag().c_str(), GetId(),
GetParent() ? GetParent()->GetId() : 0, GetInspectorIdValue("").c_str());
ACE_LAYOUT_TRACE_END()
}
@ -3727,9 +3721,9 @@ void FrameNode::RemoveAllChildInRenderTree()
frameProxy_->RemoveAllChildInRenderTree();
}
void FrameNode::SetActiveChildRange(int32_t start, int32_t end)
void FrameNode::SetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd)
{
frameProxy_->SetActiveChildRange(start, end);
frameProxy_->SetActiveChildRange(start, end, cacheStart, cacheEnd);
}
void FrameNode::RecycleItemsByIndex(int32_t start, int32_t end)
@ -3858,7 +3852,7 @@ void FrameNode::DoRemoveChildInRenderTree(uint32_t index, bool isAll)
SetActive(false);
}
void FrameNode::DoSetActiveChildRange(int32_t start, int32_t end)
void FrameNode::DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd)
{
if (start <= end) {
if (start > 0 || end < 0) {
@ -3949,16 +3943,14 @@ void FrameNode::AddFrameNodeSnapshot(bool isHit, int32_t parentId, std::vector<R
auto eventMgr = context->GetEventManager();
CHECK_NULL_VOID(eventMgr);
FrameNodeSnapshot info = {
.nodeId = GetId(),
FrameNodeSnapshot info = { .nodeId = GetId(),
.parentNodeId = parentId,
.tag = GetTag(),
.comId = propInspectorId_.value_or(""),
.monopolizeEvents = GetMonopolizeEvents(),
.isHit = isHit,
.hitTestMode = static_cast<int32_t>(GetHitTestMode()),
.responseRegionList = responseRegionList
};
.responseRegionList = responseRegionList };
eventMgr->GetEventTreeRecord().AddFrameNodeSnapshot(std::move(info));
}
@ -3978,40 +3970,40 @@ int64_t FrameNode::WrapExtensionAbilityId(int64_t extensionOffset, int64_t abili
return -1;
}
void FrameNode::SearchExtensionElementInfoByAccessibilityIdNG(int64_t elementId, int32_t mode,
int64_t offset, std::list<Accessibility::AccessibilityElementInfo>& output)
void FrameNode::SearchExtensionElementInfoByAccessibilityIdNG(
int64_t elementId, int32_t mode, int64_t offset, std::list<Accessibility::AccessibilityElementInfo>& output)
{
if (pattern_) {
pattern_->SearchExtensionElementInfoByAccessibilityId(elementId, mode, offset, output);
}
}
void FrameNode::SearchElementInfosByTextNG(int64_t elementId, const std::string& text,
int64_t offset, std::list<Accessibility::AccessibilityElementInfo>& output)
void FrameNode::SearchElementInfosByTextNG(int64_t elementId, const std::string& text, int64_t offset,
std::list<Accessibility::AccessibilityElementInfo>& output)
{
if (pattern_) {
pattern_->SearchElementInfosByText(elementId, text, offset, output);
}
}
void FrameNode::FindFocusedExtensionElementInfoNG(int64_t elementId, int32_t focusType,
int64_t offset, Accessibility::AccessibilityElementInfo& output)
void FrameNode::FindFocusedExtensionElementInfoNG(
int64_t elementId, int32_t focusType, int64_t offset, Accessibility::AccessibilityElementInfo& output)
{
if (pattern_) {
pattern_->FindFocusedElementInfo(elementId, focusType, offset, output);
}
}
void FrameNode::FocusMoveSearchNG(int64_t elementId, int32_t direction,
int64_t offset, Accessibility::AccessibilityElementInfo& output)
void FrameNode::FocusMoveSearchNG(
int64_t elementId, int32_t direction, int64_t offset, Accessibility::AccessibilityElementInfo& output)
{
if (pattern_) {
pattern_->FocusMoveSearch(elementId, direction, offset, output);
}
}
bool FrameNode::TransferExecuteAction(int64_t elementId, const std::map<std::string, std::string>& actionArguments,
int32_t action, int64_t offset)
bool FrameNode::TransferExecuteAction(
int64_t elementId, const std::map<std::string, std::string>& actionArguments, int32_t action, int64_t offset)
{
bool isExecuted = false;
if (pattern_) {
@ -4551,11 +4543,7 @@ OPINC_TYPE_E FrameNode::FindSuggestOpIncNode(std::string& path, const SizeF& bou
path = path + " --> " + hostTag;
LOGI("FindSuggestOpIncNode : %{public}s, with depth %{public}d, boundary: %{public}f, self: %{public}f, "
"status: %{public}d",
path.c_str(),
depth,
boundary.Height(),
GetGeometryNode()->GetFrameSize().Height(),
status);
path.c_str(), depth, boundary.Height(), GetGeometryNode()->GetFrameSize().Height(), status);
}
if (status == OPINC_NODE) {
MarkSuggestOpIncGroup(true, true);

View File

@ -716,8 +716,8 @@ public:
void RemoveChildInRenderTree(uint32_t index) override;
void RemoveAllChildInRenderTree() override;
void DoRemoveChildInRenderTree(uint32_t index, bool isAll) override;
void SetActiveChildRange(int32_t start, int32_t end) override;
void DoSetActiveChildRange(int32_t start, int32_t end) override;
void SetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart = 0, int32_t cacheEnd = 0) override;
void DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd) override;
void RecycleItemsByIndex(int32_t start, int32_t end) override;
const std::string& GetHostTag() const override
{

View File

@ -1233,7 +1233,7 @@ RefPtr<UINode> UINode::GetFrameChildByIndexWithoutExpanded(uint32_t index)
return nullptr;
}
int32_t UINode::GetFrameNodeIndex(RefPtr<FrameNode> node, bool isExpanded)
int32_t UINode::GetFrameNodeIndex(const RefPtr<FrameNode>& node, bool isExpanded)
{
int32_t index = 0;
for (const auto& child : GetChildren()) {
@ -1271,11 +1271,11 @@ void UINode::DoRemoveChildInRenderTree(uint32_t index, bool isAll)
}
}
void UINode::DoSetActiveChildRange(int32_t start, int32_t end)
void UINode::DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd)
{
for (const auto& child : children_) {
uint32_t count = static_cast<uint32_t>(child->FrameCount());
child->DoSetActiveChildRange(start, end);
child->DoSetActiveChildRange(start, end, cacheStart, cacheEnd);
start -= static_cast<int32_t>(count);
end -= static_cast<int32_t>(count);
}

View File

@ -420,7 +420,7 @@ public:
bool addToRenderTree = false);
virtual RefPtr<UINode> GetFrameChildByIndexWithoutExpanded(uint32_t index);
// Get current frameNode index with or without expanded all LazyForEachNode;
virtual int32_t GetFrameNodeIndex(RefPtr<FrameNode> node, bool isExpanded = true);
virtual int32_t GetFrameNodeIndex(const RefPtr<FrameNode>& node, bool isExpanded = true);
void SetDebugLine(const std::string& line)
{
debugLine_ = line;
@ -538,7 +538,7 @@ public:
// --------------------------------------------------------------------------------
virtual void DoRemoveChildInRenderTree(uint32_t index, bool isAll = false);
virtual void DoSetActiveChildRange(int32_t start, int32_t end);
virtual void DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd);
virtual void OnSetCacheCount(int32_t cacheCount, const std::optional<LayoutConstraintF>& itemConstraint);
// return value: true if the node can be removed immediately.

View File

@ -16,8 +16,10 @@
#ifndef FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_LAYOUTS_LAYOUT_WRAPPER_H
#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_LAYOUTS_LAYOUT_WRAPPER_H
#include <cstdint>
#include <map>
#include <optional>
#include <set>
#include <string>
#include <unordered_map>
@ -48,9 +50,19 @@ public:
class RecursionGuard final {
public:
RecursionGuard(RecursiveLock& lock) : lock_(lock) { lock_.Lock(); }
~RecursionGuard() { lock_.Unlock(); }
RecursionGuard(const RecursionGuard& rhs) : lock_(rhs.lock_) { lock_.Lock(); }
RecursionGuard(RecursiveLock& lock) : lock_(lock)
{
lock_.Lock();
}
~RecursionGuard()
{
lock_.Unlock();
}
RecursionGuard(const RecursionGuard& rhs) : lock_(rhs.lock_)
{
lock_.Lock();
}
private:
RecursiveLock& lock_;
};
@ -58,16 +70,45 @@ private:
class ChildrenListWithGuard final {
public:
ChildrenListWithGuard(const std::list<RefPtr<LayoutWrapper>>& children, RecursiveLock& lock)
: children_(children), guard_(lock) {}
auto begin() const { return children_.begin(); }
auto end() const { return children_.end(); }
auto rbegin() const { return children_.rbegin(); }
auto rend() const { return children_.rend(); }
auto empty() const { return children_.empty(); }
auto size() const { return children_.size(); }
auto& front() const { return children_.front(); }
auto& back() const { return children_.back(); }
operator std::list<RefPtr<LayoutWrapper>>() const { return children_; }
: children_(children), guard_(lock)
{}
auto begin() const
{
return children_.begin();
}
auto end() const
{
return children_.end();
}
auto rbegin() const
{
return children_.rbegin();
}
auto rend() const
{
return children_.rend();
}
auto empty() const
{
return children_.empty();
}
auto size() const
{
return children_.size();
}
auto& front() const
{
return children_.front();
}
auto& back() const
{
return children_.back();
}
operator std::list<RefPtr<LayoutWrapper>>() const
{
return children_;
}
private:
const std::list<RefPtr<LayoutWrapper>>& children_;
RecursionGuard guard_;
@ -96,9 +137,12 @@ public:
virtual ChildrenListWithGuard GetAllChildrenWithBuild(bool addToRenderTree = true) = 0;
virtual void RemoveChildInRenderTree(uint32_t index) = 0;
virtual void RemoveAllChildInRenderTree() = 0;
virtual void SetActiveChildRange(int32_t start, int32_t end) = 0;
virtual void SetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart = 0, int32_t cacheEnd = 0) = 0;
virtual void RecycleItemsByIndex(int32_t start, int32_t end) = 0;
virtual void SetActiveChildRange(const std::set<int32_t>& activeIndexes, const std::set<int32_t>& cachedIndexes) {}
virtual void RecycleItemsByIndex(const std::set<int32_t>& indexes) {}
RefPtr<FrameNode> GetHostNode() const;
virtual const std::string& GetHostTag() const = 0;
virtual bool IsActive() const = 0;

View File

@ -119,7 +119,7 @@ public:
void RemoveChildInRenderTree(uint32_t index) override;
void RemoveAllChildInRenderTree() override;
void SetActiveChildRange(int32_t start, int32_t end) override {}
void SetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart = 0, int32_t cacheEnd = 0) override {}
void RecycleItemsByIndex(int32_t start, int32_t end) override {}
void ResetHostNode();

View File

@ -172,7 +172,7 @@ RefPtr<UINode> CustomNode::GetFrameChildByIndex(uint32_t index, bool needBuild,
return UINode::GetFrameChildByIndex(index, needBuild, isCache, addToRenderTree);
}
void CustomNode::DoSetActiveChildRange(int32_t start, int32_t end)
void CustomNode::DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd)
{
if (start <= end) {
if (start > 0 || end < 0) {

View File

@ -114,7 +114,7 @@ public:
return extraInfos_;
}
void DoSetActiveChildRange(int32_t start, int32_t end) override;
void DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd) override;
const WeakPtr<UINode>& GetNavigationNode() const
{

View File

@ -18,6 +18,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/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"
@ -95,16 +96,16 @@ public:
if (AceType::InstanceOf<FrameNode>(child)) {
auto frameNode = AceType::DynamicCast<FrameNode>(child);
CalculateFrameNode(frameNode);
} else if (AceType::InstanceOf<LazyForEachNode>(child)) {
auto lazyForEach = AceType::DynamicCast<LazyForEachNode>(child);
CalculateLazyForEachNode(lazyForEach);
} else if (AceType::InstanceOf<LazyForEachNode>(child) ||
AceType::InstanceOf<RepeatVirtualScrollNode>(child)) {
CalculateLazyForEachNode(child);
} else {
CalculateUINode(child);
}
}
}
float GetLazyForEachIndexAverageHeight(RefPtr<LazyForEachNode> node,
float GetLazyForEachIndexAverageHeight(RefPtr<UINode> node,
int32_t startIndex, int32_t endIndex, bool &hasGroup)
{
auto itor = itemPosition_.find(startIndex);
@ -143,7 +144,7 @@ public:
return totalHeight / itemCount;
}
float CalculateOffset(RefPtr<LazyForEachNode> node, float averageHeight)
float CalculateOffset(RefPtr<UINode> node, float averageHeight)
{
auto itor = itemPosition_.begin();
float skipHeight = 0.0f;
@ -163,7 +164,7 @@ public:
return estimateOffset_ - targetPos_.first;
}
void CalculateLazyForEachNode(RefPtr<LazyForEachNode> node)
void CalculateLazyForEachNode(RefPtr<UINode> node)
{
int32_t count = node->FrameCount();
if (count <= 0) {

View File

@ -33,18 +33,24 @@ void ListItemModelNG::Create(std::function<void(int32_t)>&& deepRenderFunc, V2::
{
auto* stack = ViewStackProcessor::GetInstance();
auto nodeId = stack->ClaimNodeId();
auto deepRender = [nodeId, deepRenderFunc = std::move(deepRenderFunc)]() -> RefPtr<UINode> {
CHECK_NULL_RETURN(deepRenderFunc, nullptr);
ScopedViewStackProcessor scopedViewStackProcessor;
deepRenderFunc(nodeId);
return ViewStackProcessor::GetInstance()->Finish();
};
ACE_LAYOUT_SCOPED_TRACE("Create[%s][self:%d]", V2::LIST_ITEM_ETS_TAG, nodeId);
auto frameNode = ScrollableItemPool::GetInstance().Allocate(V2::LIST_ITEM_ETS_TAG, nodeId,
[shallowBuilder = AceType::MakeRefPtr<ShallowBuilder>(std::move(deepRender)), itemStyle = listItemStyle]() {
return AceType::MakeRefPtr<ListItemPattern>(shallowBuilder, itemStyle);
});
stack->Push(frameNode);
if (deepRenderFunc) {
auto deepRender = [nodeId, deepRenderFunc = std::move(deepRenderFunc)]() -> RefPtr<UINode> {
CHECK_NULL_RETURN(deepRenderFunc, nullptr);
ScopedViewStackProcessor scopedViewStackProcessor;
deepRenderFunc(nodeId);
return ViewStackProcessor::GetInstance()->Finish();
};
auto frameNode = ScrollableItemPool::GetInstance().Allocate(V2::LIST_ITEM_ETS_TAG, nodeId,
[shallowBuilder = AceType::MakeRefPtr<ShallowBuilder>(std::move(deepRender)), itemStyle = listItemStyle]() {
return AceType::MakeRefPtr<ListItemPattern>(shallowBuilder, itemStyle);
});
stack->Push(frameNode);
} else {
auto frameNode = FrameNode::GetOrCreateFrameNode(V2::LIST_ITEM_ETS_TAG, nodeId,
[listItemStyle]() { return AceType::MakeRefPtr<ListItemPattern>(nullptr, listItemStyle); });
stack->Push(frameNode);
}
}
void ListItemModelNG::Create()

View File

@ -348,9 +348,18 @@ float ListLanesLayoutAlgorithm::CalculateLaneCrossOffset(float crossSize, float
int32_t ListLanesLayoutAlgorithm::GetLazyForEachIndex(const RefPtr<FrameNode>& host)
{
CHECK_NULL_RETURN(host, -1);
auto lazyForEach = AceType::DynamicCast<LazyForEachNode>(host->GetParent());
CHECK_NULL_RETURN(lazyForEach, -1);
return lazyForEach->GetIndexByUINode(host);
auto parent = host->GetParent();
auto lazyForEach = AceType::DynamicCast<LazyForEachNode>(parent);
if (lazyForEach) {
return lazyForEach->GetIndexByUINode(host);
}
while (parent && !AceType::InstanceOf<FrameNode>(parent)) {
if (AceType::InstanceOf<RepeatVirtualScrollNode>(parent)) {
return parent->GetFrameNodeIndex(host);
}
parent = parent->GetParent();
}
return -1;
}
int32_t ListLanesLayoutAlgorithm::FindLanesStartIndex(LayoutWrapper* layoutWrapper, int32_t startIndex, int32_t index)

View File

@ -162,10 +162,11 @@ void ListLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
}
}
auto cacheCount = listLayoutProperty->GetCachedCountValue(1) * GetLanes();
if (itemPosition_.empty()) {
layoutWrapper->SetActiveChildRange(-1, -1);
} else {
layoutWrapper->SetActiveChildRange(itemPosition_.begin()->first, itemPosition_.rbegin()->first);
layoutWrapper->SetActiveChildRange(itemPosition_.begin()->first, itemPosition_.rbegin()->first, cacheCount, cacheCount);
}
auto crossSize = contentIdealSize.CrossSize(axis_);

View File

@ -29,6 +29,7 @@
#include "base/utils/utils.h"
#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/pattern/list/list_children_main_size.h"
#include "core/components_ng/pattern/list/list_item_group_pattern.h"
#include "core/components_ng/pattern/list/list_layout_algorithm.h"
@ -162,28 +163,17 @@ public:
if (AceType::InstanceOf<FrameNode>(child)) {
auto frameNode = AceType::DynamicCast<FrameNode>(child);
CalculateFrameNode(frameNode);
} else if (AceType::InstanceOf<LazyForEachNode>(child)) {
auto lazyForEach = AceType::DynamicCast<LazyForEachNode>(child);
} else if (AceType::InstanceOf<LazyForEachNode>(child) ||
AceType::InstanceOf<RepeatVirtualScrollNode>(child)) {
// Rules: only one type node(ListItem or ListItemGroup) can exist in LazyForEach.
CalculateLazyForEachNode(lazyForEach);
CalculateLazyForEachNode(child);
} else {
CalculateUINode(child);
}
}
}
RefPtr<FrameNode> GetListFrameNode(RefPtr<LazyForEachNode> node) const
{
auto parent = node->GetParent();
RefPtr<FrameNode> frameNode = AceType::DynamicCast<FrameNode>(parent);
while (parent && (!frameNode)) {
parent = parent->GetParent();
frameNode = AceType::DynamicCast<FrameNode>(parent);
}
return frameNode;
}
std::optional<bool> GetLazyForEachChildIsGroup(RefPtr<LazyForEachNode> node)
std::optional<bool> GetLazyForEachChildIsGroup(RefPtr<UINode> node)
{
std::optional<bool> isGroup;
auto children = node->GetChildren();
@ -198,7 +188,7 @@ public:
}
}
if (!(isGroup.has_value())) {
auto listNode = GetListFrameNode(node);
auto listNode = node->GetParentFrameNode();
CHECK_NULL_RETURN(listNode, isGroup);
auto wrapper = listNode->GetOrCreateChildByIndex(curIndex_);
CHECK_NULL_RETURN(wrapper, isGroup);
@ -207,7 +197,7 @@ public:
return isGroup;
}
void CalculateLazyForEachNode(RefPtr<LazyForEachNode> node)
void CalculateLazyForEachNode(RefPtr<UINode> node)
{
int32_t count = node->FrameCount();
if (count <= 0) {

View File

@ -13,24 +13,26 @@
* limitations under the License.
*/
#include "core/components_ng/pattern/scrollable/scrollable_utils.h"
#include "core/components_ng/syntax/for_each_base_node.h"
#include "core/components_ng/syntax/lazy_for_each_node.h"
#include "core/pipeline_ng/pipeline_context.h"
namespace OHOS::Ace::NG {
namespace {
std::vector<RefPtr<LazyForEachNode>> GetLazyForEachNodes(RefPtr<FrameNode>& host)
std::vector<RefPtr<ForEachBaseNode>> GetForEachNodes(RefPtr<FrameNode>& host)
{
std::vector<RefPtr<LazyForEachNode>> lazyNodes;
std::vector<RefPtr<ForEachBaseNode>> foreachNodes;
for (const auto& child : host->GetChildren()) {
if (!AceType::InstanceOf<LazyForEachNode>(child)) {
if (!AceType::InstanceOf<ForEachBaseNode>(child)) {
continue;
}
auto lazyNode = AceType::DynamicCast<LazyForEachNode>(child);
if (!lazyNode) {
auto node = AceType::DynamicCast<ForEachBaseNode>(child);
if (!node) {
continue;
}
lazyNodes.push_back(lazyNode);
foreachNodes.push_back(node);
}
return lazyNodes;
return foreachNodes;
}
bool OutOfBottomOrRightBoundary(
@ -93,8 +95,8 @@ int32_t GetScrollUpOrLeftItemIndex(Axis axis, float offset, int32_t start, int32
return outIndex;
}
void RecycleItemsByIndex(int32_t start, int32_t end,
std::vector<RefPtr<LazyForEachNode>>& lazyNodes, LayoutWrapper* wrapper)
void RecycleItemsByIndex(
int32_t start, int32_t end, std::vector<RefPtr<ForEachBaseNode>>& lazyNodes, LayoutWrapper* wrapper)
{
wrapper->RecycleItemsByIndex(start, end);
for (const auto& node : lazyNodes) {
@ -129,8 +131,8 @@ void ScrollableUtils::RecycleItemsOutOfBoundary(
}
auto host = wrapper->GetHostNode();
std::vector<RefPtr<LazyForEachNode>> lazyNodes = GetLazyForEachNodes(host);
if (lazyNodes.empty()) {
std::vector<RefPtr<ForEachBaseNode>> foreachNodes = GetForEachNodes(host);
if (foreachNodes.empty()) {
return;
}
if (offset >= 0) {
@ -138,13 +140,13 @@ void ScrollableUtils::RecycleItemsOutOfBoundary(
if (inIndex >= end) {
return;
}
RecycleItemsByIndex(inIndex + 1, end + 1, lazyNodes, wrapper);
RecycleItemsByIndex(inIndex + 1, end + 1, foreachNodes, wrapper);
} else {
int32_t outIndex = GetScrollUpOrLeftItemIndex(axis, offset, start, end, host);
if (outIndex <= start) {
return;
}
RecycleItemsByIndex(start, outIndex, lazyNodes, wrapper);
RecycleItemsByIndex(start, outIndex, foreachNodes, wrapper);
}
}

View File

@ -27,5 +27,9 @@ build_component_ng("syntax_ng") {
"lazy_layout_wrapper_builder.cpp",
"node_content.cpp",
"repeat_model_ng.cpp",
"repeat_node.cpp",
"repeat_virtual_scroll_caches.cpp",
"repeat_virtual_scroll_model_ng.cpp",
"repeat_virtual_scroll_node.cpp",
]
}

View File

@ -23,16 +23,17 @@ class ACE_EXPORT ForEachBaseNode : public UINode {
DECLARE_ACE_TYPE(ForEachBaseNode, UINode);
public:
ForEachBaseNode(const std::string& tag, int32_t nodeId, bool isRoot = false)
: UINode(tag, nodeId, isRoot) {}
ForEachBaseNode(const std::string& tag, int32_t nodeId, bool isRoot = false) : UINode(tag, nodeId, isRoot) {}
virtual void MoveData(int32_t from, int32_t to) = 0;
virtual RefPtr<FrameNode> GetFrameNode(int32_t index) = 0;
virtual void RecycleItems(int32_t from, int32_t to) {}
virtual void FireOnMove(int32_t from, int32_t to)
{
if (onMoveEvent_) {
onMoveEvent_(from, to);
}
}
protected:
std::function<void(int32_t, int32_t)> onMoveEvent_;
};

View File

@ -388,7 +388,7 @@ void LazyForEachNode::DoRemoveChildInRenderTree(uint32_t index, bool isAll)
}
}
void LazyForEachNode::DoSetActiveChildRange(int32_t start, int32_t end)
void LazyForEachNode::DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd)
{
if (!builder_) {
return;
@ -473,7 +473,7 @@ RefPtr<FrameNode> LazyForEachNode::GetFrameNode(int32_t index)
return AceType::DynamicCast<FrameNode>(child.second->GetFrameChildByIndex(0, true));
}
int32_t LazyForEachNode::GetFrameNodeIndex(RefPtr<FrameNode> node, bool isExpanded)
int32_t LazyForEachNode::GetFrameNodeIndex(const RefPtr<FrameNode>& node, bool isExpanded)
{
if (!isExpanded) {
return UINode::GetFrameNodeIndex(node, false);

View File

@ -112,7 +112,7 @@ public:
RefPtr<UINode> GetFrameChildByIndex(uint32_t index, bool needBuild, bool isCache = false,
bool addToRenderTree = false) override;
void DoRemoveChildInRenderTree(uint32_t index, bool isAll) override;
void DoSetActiveChildRange(int32_t start, int32_t end) override;
void DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd) override;
const std::list<RefPtr<UINode>>& GetChildren() const override;
void OnSetCacheCount(int32_t cacheCount, const std::optional<LayoutConstraintF>& itemConstraint) override
@ -141,7 +141,7 @@ public:
startIndex_ = start;
count_ = count;
}
void RecycleItems(int32_t from, int32_t to);
void RecycleItems(int32_t from, int32_t to) override;
const RefPtr<LazyForEachBuilder>& GetBuilder() const
{
@ -160,7 +160,7 @@ public:
void MoveData(int32_t from, int32_t to) override;
void FireOnMove(int32_t from, int32_t to) override;
RefPtr<FrameNode> GetFrameNode(int32_t index) override;
int32_t GetFrameNodeIndex(RefPtr<FrameNode> node, bool isExpanded = true) override;
int32_t GetFrameNodeIndex(const RefPtr<FrameNode>& node, bool isExpanded = true) override;
void InitDragManager(const RefPtr<FrameNode>& childNode);
void InitAllChilrenDragManager(bool init);
private:

View File

@ -18,7 +18,7 @@
#include "base/utils/utils.h"
#include "core/common/container.h"
#include "core/components_ng/base/view_stack_processor.h"
#include "core/components_ng/syntax/for_each_node.h"
#include "core/components_ng/syntax/repeat_node.h"
#include "core/components_ng/syntax/syntax_item.h"
namespace OHOS::Ace::NG {
@ -29,7 +29,7 @@ void RepeatModelNG::StartRender()
ACE_SCOPED_TRACE("RepeatModelNG::StartRender");
auto* stack = ViewStackProcessor::GetInstance();
auto nodeId = stack->ClaimNodeId();
auto repeatNode = ForEachNode::GetOrCreateRepeatNode(nodeId);
auto repeatNode = RepeatNode::GetOrCreateRepeatNode(nodeId);
stack->Push(repeatNode);
// move current id array and children to temp
@ -39,7 +39,7 @@ void RepeatModelNG::StartRender()
void RepeatModelNG::FinishRender(std::list<int32_t>& removedElmtId)
{
auto* stack = ViewStackProcessor::GetInstance();
auto repeatNode = AceType::DynamicCast<ForEachNode>(stack->GetMainElementNode());
auto repeatNode = AceType::DynamicCast<RepeatNode>(stack->GetMainElementNode());
CHECK_NULL_VOID(repeatNode);
repeatNode->FinishRepeatRender(removedElmtId);
stack->PopContainer();
@ -49,7 +49,7 @@ void RepeatModelNG::MoveChild(uint32_t fromIndex)
{
ACE_SCOPED_TRACE("RepeatModelNG::MoveChild()");
auto* stack = ViewStackProcessor::GetInstance();
auto repeatNode = AceType::DynamicCast<ForEachNode>(stack->GetMainElementNode());
auto repeatNode = AceType::DynamicCast<RepeatNode>(stack->GetMainElementNode());
CHECK_NULL_VOID(repeatNode);
repeatNode->MoveChild(fromIndex);
}
@ -76,7 +76,7 @@ void RepeatModelNG::CreateNewChildFinish(const std::string& key)
void RepeatModelNG::OnMove(std::function<void(int32_t, int32_t)>&& onMove)
{
auto* stack = ViewStackProcessor::GetInstance();
auto node = AceType::DynamicCast<ForEachNode>(stack->GetMainElementNode());
auto node = AceType::DynamicCast<RepeatNode>(stack->GetMainElementNode());
CHECK_NULL_VOID(node);
node->SetOnMove(std::move(onMove));
}

View File

@ -0,0 +1,133 @@
/*
* 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_node.h"
#include "base/log/ace_trace.h"
#include "core/components_ng/base/frame_node.h"
#include "core/components_ng/pattern/list/list_item_pattern.h"
#include "core/pipeline/base/element_register.h"
#include "core/pipeline_ng/pipeline_context.h"
namespace OHOS::Ace::NG {
// REPEAT
RefPtr<RepeatNode> RepeatNode::GetOrCreateRepeatNode(int32_t nodeId)
{
auto node = ElementRegister::GetInstance()->GetSpecificItemById<RepeatNode>(nodeId);
if (node) {
return node;
}
node = MakeRefPtr<RepeatNode>(nodeId);
ElementRegister::GetInstance()->AddUINode(node);
return node;
}
// REPEAT
void RepeatNode::CreateTempItems()
{
std::swap(ids_, tempIds_);
std::swap(ModifyChildren(), tempChildren_);
tempChildrenOfRepeat_ = std::vector<RefPtr<UINode>>(tempChildren_.begin(), tempChildren_.end());
}
// RepeatNode only
void RepeatNode::FinishRepeatRender(std::list<int32_t>& removedElmtId)
{
ACE_SCOPED_TRACE("RepeatNode::FinishRepeatRender");
// Required to build unordered_set of RefPtr<UINodes>
struct Hash {
size_t operator()(const RefPtr<UINode>& node) const
{
return node->GetId();
}
};
// includes "newly-added" and "reused" children
const auto& children = GetChildren();
std::unordered_set<RefPtr<UINode>, Hash>
newNodeSet(children.begin(), children.end());
// remove "unused" children
for (const auto& oldNode: tempChildrenOfRepeat_) {
if (newNodeSet.find(oldNode) == newNodeSet.end()) {
// Adding silently, so that upon removal node is a part the tree.
AddChild(oldNode, DEFAULT_NODE_SLOT, true);
// Remove and trigger all Detach callback.
RemoveChild(oldNode, true);
// Collect IDs of removed nodes starting from 'oldNode' (incl.)
CollectRemovedChildren({ oldNode }, removedElmtId, false);
}
}
tempChildren_.clear();
tempChildrenOfRepeat_.clear();
auto frameNode = GetParentFrameNode();
if (frameNode) {
frameNode->ChildrenUpdatedFrom(0);
}
}
// RepeatNode only
void RepeatNode::MoveChild(uint32_t fromIndex)
{
// copy child from tempChildrenOfRepeat_[fromIndex] and append to children_
if (fromIndex < tempChildrenOfRepeat_.size()) {
auto& node = tempChildrenOfRepeat_.at(fromIndex);
AddChild(node, DEFAULT_NODE_SLOT, true);
}
}
// Repeat
void RepeatNode::SetOnMove(std::function<void(int32_t, int32_t)>&& onMove)
{
// To do
}
// FOREACH
void RepeatNode::MoveData(int32_t from, int32_t to)
{
// To do
}
void RepeatNode::FlushUpdateAndMarkDirty()
{
tempIds_.clear();
// mark parent dirty to flush measure.
MarkNeedSyncRenderTree(true);
MarkNeedFrameFlushDirty(PROPERTY_UPDATE_MEASURE_SELF_AND_PARENT | PROPERTY_UPDATE_BY_CHILD_REQUEST);
}
// FIXME called from where ?
RefPtr<FrameNode> RepeatNode::GetFrameNode(int32_t index)
{
return AceType::DynamicCast<FrameNode>(GetFrameChildByIndex(index, false, false));
}
void RepeatNode::InitDragManager(const RefPtr<UINode>& child)
{
// To do
}
void RepeatNode::InitAllChildrenDragManager(bool init)
{
// To do
}
} // namespace OHOS::Ace::NG

View File

@ -0,0 +1,87 @@
/*
* 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_NODE_H
#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_NODE_H
#include <cstdint>
#include <list>
#include <string>
#include <type_traits>
#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_v2/inspector/inspector_constants.h"
namespace OHOS::Ace::NG {
class ACE_EXPORT RepeatNode : public ForEachBaseNode {
DECLARE_ACE_TYPE(RepeatNode, ForEachBaseNode);
public:
static RefPtr<RepeatNode> GetOrCreateRepeatNode(int32_t nodeId);
explicit RepeatNode(int32_t nodeId) : ForEachBaseNode(V2::JS_REPEAT_ETS_TAG, nodeId) {}
~RepeatNode() override = default;
bool IsAtomicNode() const override
{
return false;
}
void CreateTempItems();
// RepeatNode only
void FinishRepeatRender(std::list<int32_t>& removedElmtId);
void FlushUpdateAndMarkDirty() override;
// RepeatNode only
void MoveChild(uint32_t fromIndex);
const std::list<std::string>& GetTempIds() const
{
return tempIds_;
}
void SetIds(std::list<std::string>&& ids)
{
ids_ = std::move(ids);
}
void SetOnMove(std::function<void(int32_t, int32_t)>&& onMove);
void MoveData(int32_t from, int32_t to) override;
RefPtr<FrameNode> GetFrameNode(int32_t index) override;
void InitDragManager(const RefPtr<UINode>& childNode);
void InitAllChildrenDragManager(bool init);
private:
std::list<std::string> ids_;
// temp items use to compare each update.
std::list<std::string> tempIds_;
std::list<RefPtr<UINode>> tempChildren_;
// RepeatNode only
std::vector<RefPtr<UINode>> tempChildrenOfRepeat_;
ACE_DISALLOW_COPY_AND_MOVE(RepeatNode);
};
} // namespace OHOS::Ace::NG
#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_NODE_H

View File

@ -0,0 +1,647 @@
/*
* 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_caches.h"
#include <cstdint>
#include <optional>
#include <unordered_map>
#include <unordered_set>
#include "base/log/log_wrapper.h"
#include "core/components_ng/base/frame_node.h"
#include "core/components_ng/base/view_stack_processor.h"
#include "core/pipeline/base/element_register.h"
namespace OHOS::Ace::NG {
bool KeySorterClass::operator()(const std::string& left, const std::string& right) const
{
return virtualScroll_->CompareKeyByIndexDistance(left, right);
}
RepeatVirtualScrollCaches::RepeatVirtualScrollCaches(const std::map<std::string, uint32_t>& cacheCountL24ttype,
const std::function<void(uint32_t)>& onCreateNode,
const std::function<void(const std::string&, uint32_t)>& onUpdateNode,
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetKeys4Range,
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetTypes4Range)
: // each ttype incl default has own L2 cache size
cacheCountL24ttype_(cacheCountL24ttype),
// request TS to create new sub-tree for given index or update existing
// update subtree cached for (old) index
// API might need to change to tell which old item to update
onCreateNode_(onCreateNode),
onUpdateNode_(onUpdateNode),
onGetTypes4Range_(onGetTypes4Range),
onGetKeys4Range_(onGetKeys4Range)
{}
std::optional<std::string> RepeatVirtualScrollCaches::GetKey4Index(uint32_t index)
{
auto it = key4index_.find(index);
if (it == key4index_.end()) {
// request more keys from TS key gen
if (!FetchMoreKeysTTypes(index, index)) {
return std::nullopt;
}
}
return key4index_[index];
}
/**
* get more index -> key and index -> ttype from TS side
*/
bool RepeatVirtualScrollCaches::FetchMoreKeysTTypes(uint32_t from, uint32_t to)
{
// always request the same range for keys and ttype
// optimism by merging the two calls into one
std::list<std::string> keysFrom = onGetKeys4Range_(from, to);
std::list<std::string> ttypesFrom = onGetTypes4Range_(from, to);
auto requestCount = to - from + 1;
auto keySize = keysFrom.size();
if (keySize != requestCount) {
TAG_LOGE(AceLogTag::ACE_REPEAT, "fail to fetch keys: requst %{public}d, fetch %{public}d",
static_cast<int32_t>(keySize), requestCount);
return false;
}
auto ttypeSize = ttypesFrom.size();
if (ttypeSize != requestCount) {
TAG_LOGE(AceLogTag::ACE_REPEAT, "fail to fetch keys: requst %{public}d, fetch %{public}d",
static_cast<int32_t>(keySize), requestCount);
return false;
}
// fill-in index maps
auto from1 = from;
for (const auto& key : keysFrom) {
key4index_[from1] = key;
index4Key_[key] = from1;
from1++;
}
from1 = from;
for (const auto& ttype : ttypesFrom) {
ttype4index_[from1] = ttype;
index4ttype_[ttype] = from1;
from1++;
}
return true;
}
// get UINode for given index without create.
RefPtr<UINode> RepeatVirtualScrollCaches::GetNode4Index(uint32_t index) const
{
const auto it = key4index_.find(index);
if (it == key4index_.end()) {
TAG_LOGD(AceLogTag::ACE_REPEAT, "no UINode for index %{public}d", static_cast<int32_t>(index));
return nullptr;
}
const std::string& key4Index = it->second;
const auto nodeIter = node4key_.find(key4Index);
if (nodeIter == node4key_.end()) {
TAG_LOGE(AceLogTag::ACE_REPEAT, "no UINode for index %{public}d and key %{public}s",
static_cast<int32_t>(index), key4Index.c_str());
return nullptr;
}
return nodeIter->second;
}
/** scenario:
* Repeat gets updated due to data change.
* 1. TS calls RepeatVirtualScrollNode,
* then calls this function.
* 2. RepeatVirtualScrollNode requests layout to rebuild the UI
* 3. layout sends RepeatVirtualScrollNode::GetFrameChild calls
* 4. how to service GetFrameChild call:
* - first check for L1 keys (same template type) if any can be updated.
* These UINodes remain in the render tree.
* - if no L1 item, the look for L2 keys (same template type)
*/
void RepeatVirtualScrollCaches::InvalidateKeyAndTTypeCaches()
{
key4index_.clear();
index4Key_.clear();
ttype4index_.clear();
index4ttype_.clear();
// request new index -> key and index -> ttype
// only fetch keys for the active range
// note the updated key -> index reverse lookup is used
// to decide if a key is still used and its UINode
// should be packed. If key is not in the reverse map then
// its UINode is subject to update.
//
FetchMoreKeysTTypes(lastActiveRanges_[0].first, lastActiveRanges_[0].second);
}
/**
* scenario: scroll, try to update an existing UINode
*
* find an key / UINode in L2 and update the UINode to
* render the data source item 'forIndex'.
*/
RefPtr<UINode> RepeatVirtualScrollCaches::UpdateFromL2(uint32_t forIndex)
{
const auto iterTType = ttype4index_.find(forIndex);
if (iterTType == ttype4index_.end()) {
TAG_LOGD(AceLogTag::ACE_REPEAT, "no ttype for index %{public}d", forIndex);
return nullptr;
}
const auto& ttype = iterTType->second;
const auto iterNewKey = key4index_.find(forIndex);
if (iterNewKey == key4index_.end()) {
TAG_LOGW(AceLogTag::ACE_REPEAT, "no key for index %{public}d", forIndex);
return nullptr;
}
const std::string& forKey = iterNewKey->second;
const auto& oldKey = GetL2KeyToUpdate(ttype);
if (!oldKey) {
// no key for this ttype available to update
TAG_LOGW(AceLogTag::ACE_REPEAT, "for index %{public}d, ttype %{public}s, no UINode found to update", forIndex,
ttype.c_str());
return nullptr;
}
// call TS to do the RepeatItem update
onUpdateNode_(oldKey.value(), forIndex);
return HasUINodeBeenUpdated(ttype, oldKey.value(), forKey);
}
RefPtr<UINode> RepeatVirtualScrollCaches::CreateNewNode(uint32_t forIndex)
{
// key key
const auto iter = key4index_.find(forIndex);
if (iter == key4index_.end()) {
TAG_LOGE(AceLogTag::ACE_REPEAT, "fail to create node of %{public}d", forIndex);
return nullptr;
}
const auto& forKey = iter->second;
// see if node already created, just for safety
const auto nodeIter = node4key_.find(forKey);
if (nodeIter != node4key_.end()) {
// have a node for this key already, just return
return nodeIter->second;
}
// need to create a new node for key
// get ttype
const auto ttypeIter = ttype4index_.find(forIndex);
if (ttypeIter == ttype4index_.end()) {
TAG_LOGE(AceLogTag::ACE_REPEAT, "fail to create %{public}d node due to type is missing", forIndex);
return nullptr;
}
const auto& ttype = ttypeIter->second;
// 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();
// call TS side
onCreateNode_(forIndex);
const auto& node4Index = viewStack->Finish();
if (!node4Index) {
TAG_LOGE(AceLogTag::ACE_REPEAT,
"New Node create: For index %{public}d -> key %{public}s -> ttype %{public}s item builder FAILED to gen "
"FrameNode. ERROR",
forIndex, forKey.c_str(), ttype.c_str());
return nullptr;
}
// add node to node4key4ttype_
const auto node4KeyIter = node4key4ttype_.find(ttype);
if (node4KeyIter != node4key4ttype_.end()) {
node4KeyIter->second.emplace(forKey, node4Index);
} else {
std::unordered_map<std::string, RefPtr<UINode>> node4Key;
node4Key[forKey] = node4Index;
node4key4ttype_.emplace(ttype, std::move(node4Key));
}
// add node to node4key_
node4key_.emplace(forKey, node4Index);
return node4Index;
}
void RepeatVirtualScrollCaches::ForEachL1IndexUINode(
const std::function<void(uint32_t index, const RefPtr<UINode>& node)>& cbFunc)
{
for (const auto& key : activeNodeKeysInL1_) {
const RefPtr<UINode>& node = node4key_[key];
const auto& indexIter = index4Key_.find(key);
if (indexIter == index4Key_.end()) {
TAG_LOGE(AceLogTag::ACE_REPEAT, "fail to get index for %{public}s key", key.c_str());
continue;
}
cbFunc(indexIter->second, node);
}
}
void RepeatVirtualScrollCaches::RecycleItemsByIndex(int32_t index)
{
auto keyIter = key4index_.find(index);
if (keyIter != key4index_.end()) {
activeNodeKeysInL1_.erase(keyIter->second);
}
}
/**
* iterate over all entries of L1 and call function for each entry
* if function returns true, entry is added to rebuild L1
* cbFunc return true, key
* cbFunc returns false drop from L1
*/
bool RepeatVirtualScrollCaches::RebuildL1(const std::function<bool(int32_t index, const RefPtr<UINode>& node)>& cbFunc)
{
std::unordered_set<std::string> l1Copy;
std::swap(l1Copy, activeNodeKeysInL1_);
bool modified = false;
for (const auto& key : l1Copy) {
const auto& indexIter = index4Key_.find(key);
if (indexIter == index4Key_.end()) {
continue;
}
const auto& node = node4key_[key];
int32_t index = indexIter->second;
if (cbFunc(index, node)) {
activeNodeKeysInL1_.emplace(key);
} else {
modified = true;
}
}
return modified;
}
void RepeatVirtualScrollCaches::SetLastActiveRange(uint32_t from, uint32_t to)
{
lastActiveRanges_[1] = lastActiveRanges_[0];
lastActiveRanges_[0] = { from, to };
}
/**
* Get the Index of frameNode
* return -1 if not find the frameNode
*/
int32_t RepeatVirtualScrollCaches::GetFrameNodeIndex(const RefPtr<FrameNode>& frameNode) const
{
for (const auto& key : activeNodeKeysInL1_) {
const auto nodeIter = node4key_.find(key);
if (nodeIter == node4key_.end() || !nodeIter->second) {
continue;
}
const auto& node = nodeIter->second->GetFrameChildByIndex(0, true);
if (node != frameNode) {
continue;
}
const auto& indexIter = index4Key_.find(key);
if (indexIter == index4Key_.end()) {
return -1;
}
return indexIter->second;
}
return -1;
}
/**
* intended scenario: scroll
* servicing GetFrameChild, search for key that can be updated.
*
* return a key whose UINode can be updated
* the key must not be in L1, i.e. activeNodeKeysInL1_
* the given ttype must match the template type the UINode for this key
* has been rendered for (this info is available from node4key4ttype_)
*/
std::optional<std::string> RepeatVirtualScrollCaches::GetL2KeyToUpdate(const std::string& ttype) const
{
const auto itNodes = node4key4ttype_.find(ttype);
if (itNodes == node4key4ttype_.end()) {
return std::nullopt;
}
const auto& keys2UINode = itNodes->second;
std::set<std::string, KeySorterClass> l2Keys = GetSortedL2KeysForTType(keys2UINode);
auto keyIter = l2Keys.rbegin();
if (keyIter == l2Keys.rend()) {
return std::nullopt;
}
return *keyIter;
}
/**
* scenario: UI rebuild following key invalidation by TS side
* L1 includes keys that are no longer used, the linked UINodes
* should be updated.
*
* This function checks all L1 keys (of active UINodes) if the key
* can still be found from
* (previously updated following invalidation) key -> index map and
*
*/
std::optional<std::string> RepeatVirtualScrollCaches::GetL1KeyToUpdate(const std::string& ttype) const
{
for (const auto& keyIter : activeNodeKeysInL1_) {
const std::string& key = keyIter;
if (index4Key_.find(key) == index4Key_.end()) {
// key is no longer used
// check if key rendered the expected ttype
const auto ttypeIter = node4key4ttype_.find(ttype);
if (ttypeIter != node4key4ttype_.end()) {
const std::unordered_map<std::string, RefPtr<UINode>>& node4Key = ttypeIter->second;
if (node4Key.find(key) != node4Key.end()) {
return key;
}
}
}
}
return std::nullopt;
}
/**
* scenario: UINode of fromKey has been updated to render data for 'forKey'
* the template type (ttype) remains unchanged
* update node4key4ttype_ and node4key_ entries to use new key point to same UINode
*/
RefPtr<UINode> RepeatVirtualScrollCaches::HasUINodeBeenUpdated(
const std::string& ttype, const std::string& fromKey, const std::string& forKey)
{
// 1. update fromKey -> forKey in node4key4ttype_
const auto nodesIter = node4key4ttype_.find(ttype);
if (nodesIter != node4key4ttype_.end()) {
auto& node4key = nodesIter->second;
auto iter = node4key.find(fromKey);
if (iter != node4key.end()) {
const auto& node = iter->second;
node4key.erase(iter);
node4key.emplace(forKey, node);
}
}
// 2. update the key: fromKey to forKey in node4key_
auto iter = node4key_.find(fromKey);
if (iter != node4key_.end()) {
const auto& node = iter->second;
node4key_.erase(iter);
node4key_.emplace(forKey, node);
return node;
}
return nullptr;
}
/** scenario: keys cache has been updated
*
* find which keys in key -> UINode map are no longer used
* returned set entries are pairs:
* pair.first: is this key a L1 item,
* pair.second: key
*/
void RepeatVirtualScrollCaches::FindUnusedKeys(std::set<std::pair<bool, std::string>>& result) const
{
for (const auto& iter : node4key_) {
const std::string key = iter.first;
const auto indexIter = index4Key_.find(key);
if (indexIter == index4Key_.end()) {
// key is no longer used
// is it in L1 ?
const bool keyInL1 = (index4Key_.find(key) != index4Key_.end());
result.emplace(keyInL1, key);
}
}
}
/**
* scenario: in idle process , following GetChildren()
* execute purge()
*
* enforce L2 cacheCount for each ttype
* logical L2 cache is map key->UINode map filtered out L1 keys
* purge by by deleting UINodes, delete their entry from
* node4key4ttype_ and node4key_
*
*/
bool RepeatVirtualScrollCaches::Purge()
{
bool didMakeChanges = false;
for (auto& itTType : node4key4ttype_) {
const auto& ttype = itTType.first;
auto& uiNode4Key = itTType.second;
const uint32_t cacheCount = cacheCountL24ttype_[ttype];
std::set<std::string, KeySorterClass> l2Keys = GetSortedL2KeysForTType(uiNode4Key);
// l2_keys is sorted by increasing distance from lastActiveRange
// will drop those keys and their UINodes with largest distance
// improvement idea: in addition to distance from range use the
// scroll direction for selecting these keys
auto safeDist = std::min(cacheCount, static_cast<uint32_t>(l2Keys.size()));
auto itL2Key = std::next(l2Keys.begin(), safeDist);
while (itL2Key != l2Keys.end()) {
// delete remaining keys
uiNode4Key.erase(*itL2Key);
node4key_.erase(*itL2Key);
// check out transition case.
itL2Key++;
didMakeChanges = true;
}
}
return didMakeChanges;
}
/**
* given key return the index position (reverse lookup)
* invalidated keys (after Repeat rerender/ data change)
* are keys for which no index exists anymore,
* method returns int max value for these.
* int max value causes that distance from active range is max
* these keys will be selected for update first.
*/
uint32_t RepeatVirtualScrollCaches::GetIndex4Key(const std::string& key) const
{
auto it = index4Key_.find(key);
if (it != index4Key_.end()) {
return it->second;
}
// key is no longer used
// return max uint32_t value
return std::numeric_limits<uint32_t>::max();
}
/**
* for given index return distance from active range,
* or 0 if within active range
* distance is int max for invalidated keys
*
* instead of just using previous active range
* use the ranges informed by previous two SetActiveRaneg calls.
* Obtain the scroll direction and use it to calc the distance.
* Items left 'behind' when scrolling get larger distance and are more
* likely updated or purged from L2 cache.
*/
int32_t RepeatVirtualScrollCaches::GetDistanceFromRange(uint32_t index) const
{
int32_t last[2] = { lastActiveRanges_[0].first, lastActiveRanges_[0].second };
int32_t prev[2] = { lastActiveRanges_[1].first, lastActiveRanges_[1].second };
// this is experimental optimization, based on scrolling detection
// here we assume this is a scrolling, if previous range and last range has
// not empty intersection
// if scrolling up, return 0 for any lower index
if (last[0] < prev[0] && prev[0] < last[1]) {
if (index < last[0]) {
return 0;
}
}
// if scrolling down, return 0 for any greater index
if (last[0] < prev[1] && prev[1] < last[1]) {
if (index > last[1]) {
return 0;
}
}
// this is not scrolling
if (index < last[0]) {
return last[0] - index;
}
if (index > last[1]) {
return index - last[1];
}
return 0;
}
/**
* scenario: find L1 key that should be updated
* choose the key whose index is the furthest away from active range
* given two keys compare their distFromRange
*/
bool RepeatVirtualScrollCaches::CompareKeyByIndexDistance(const std::string& key1, const std::string& key2) const
{
return GetDistanceFromRange(GetIndex4Key(key1)) < GetDistanceFromRange(GetIndex4Key(key2));
}
/**
* scenario: find L1 key(s) that should be updated
*
* return a sorted set of L2 keys, sorted by increasing distance from active range
*/
std::set<std::string, KeySorterClass> RepeatVirtualScrollCaches::GetSortedL2KeysForTType(
const std::unordered_map<std::string, RefPtr<UINode>>& uiNode4Key) const
{
KeySorterClass sorter(this);
std::set<std::string, KeySorterClass> l2Keys(sorter);
for (const auto& itUINode : uiNode4Key) {
const auto& key = itUINode.first;
if (activeNodeKeysInL1_.find(key) == activeNodeKeysInL1_.end()) {
// key is not in L1
// add to l2Keys
l2Keys.emplace(key);
}
}
return l2Keys;
}
std::string RepeatVirtualScrollCaches::DumpL1() const
{
std::string result = "activeNodeKeysInL1_: size=" + std::to_string(activeNodeKeysInL1_.size()) + "--------------\n";
for (const auto& it : activeNodeKeysInL1_) {
const std::string& key = it;
result += "\", node: " + DumpUINodeWithKey(key) + "\n";
}
return result;
}
std::string RepeatVirtualScrollCaches::DumpL2() const
{
std::set<std::string, KeySorterClass> l2KeyResult = GetSortedL2KeysForTType(node4key_);
std::string result =
"l2_keys (sorted by distance): size=" + std::to_string(l2KeyResult.size()) + "--------------\n";
for (const auto& it : l2KeyResult) {
result += " \"" + it + "\", node: " + DumpUINodeWithKey(it) + "\n";
}
return result;
}
std::string RepeatVirtualScrollCaches::DumpKey4Index() const
{
std::string result = "key4index_: size=" + std::to_string(key4index_.size()) + "--------------\n";
for (const auto& it : key4index_) {
result += " " + std::to_string(it.first) + " -> \"" + it.second +
"\", node: " + DumpUINodeWithKey(it.second) + "\n";
}
result += "index4Key_: size=" + std::to_string(index4Key_.size()) + "--------------\n";
for (const auto& it : index4Key_) {
result += " \"" + it.first + "\" -> " + std::to_string(it.second) + "\n";
}
return result;
}
std::string RepeatVirtualScrollCaches::DumpTType4Index() const
{
std::string result = "ttype4index_: size=" + std::to_string(ttype4index_.size()) + "--------------\n";
for (const auto& it : ttype4index_) {
result += " " + std::to_string(it.first) + " -> \"" + it.second + "\n";
}
result += "index4ttype_: size=" + std::to_string(index4ttype_.size()) + "--------------\n";
for (const auto& it : index4ttype_) {
result += " \"" + it.first + "\" -> " + std::to_string(it.second) + "\n";
}
return result;
}
std::string RepeatVirtualScrollCaches::DumpUINode4Key4TType() const
{
std::string result = "node4key_: size=" + std::to_string(node4key_.size()) + "--------------\n";
for (const auto& it : node4key_) {
result += " \"" + it.first + "\" -> node: " + it.second->GetTag() + "(" + std::to_string(it.second->GetId()) +
") \n";
}
return result;
}
std::string RepeatVirtualScrollCaches::DumpUINode4Key() const
{
std::string result = "node4key4ttype_: size=" + std::to_string(node4key4ttype_.size()) + "--------------\n";
for (const auto& ttypeIter : node4key4ttype_) {
const auto& ttype = ttypeIter.first;
const auto& node4key = ttypeIter.second;
result += "ttype " + ttype + ": node4key: size=" + std::to_string(node4key.size()) + "--------------\n";
for (const auto& it : node4key) {
result += " \"" + it.first + "\" -> node: " + it.second->GetTag() + "(" +
std::to_string(it.second->GetId()) + ") \n";
}
}
return result;
}
std::string RepeatVirtualScrollCaches::DumpUINodeWithKey(const std::string& key) const
{
const auto it = node4key_.find(key);
return (it == node4key_.end()) ? "no UINode on file"
: it->second->GetTag() + "(" + std::to_string(it->second->GetId()) + ")";
}
std::string RepeatVirtualScrollCaches::DumpUINode(const RefPtr<UINode>& node) const
{
return (node == nullptr) ? "UINode nullptr" : node->GetTag() + "(" + std::to_string(node->GetId()) + ")";
}
} // namespace OHOS::Ace::NG

View File

@ -0,0 +1,269 @@
/*
* 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_CACHES_H
#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_CACHES_H
#include <functional>
#include <map>
#include <optional>
#include <set>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include "core/components_ng/base/ui_node.h"
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:
RepeatVirtualScrollCaches(const std::map<std::string, uint32_t>& cacheCountL24ttype,
const std::function<void(uint32_t)>& onCreateNode,
const std::function<void(const std::string&, uint32_t)>& onUpdateNode,
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetKeys4Range,
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetTypes4Range);
/** scenario:
* Repeat gets updated due to data change.
* 1. TS calls RepeatVirtualScrollNode,
* then calls this function.
* 2. RepeatVirtualScrollNode requests layout to rebuild the UI
* 3. layout sends RepeatVirtualScrollNode::GetFrameChild calls
* 4. how to service GetFrameChild call:
* - first check for L1 keys (same template type) if any can be updated.
* These UINodes remain in the render tree.
* - if no L1 item, the look for L2 keys (same template type)
*/
void InvalidateKeyAndTTypeCaches();
/**
* scenario: scroll, try to update an existing UINode
*
* find an key / UINode in L2 and update the UINode to
* render the data source item 'forIndex'.
*/
RefPtr<UINode> UpdateFromL2(uint32_t forIndex);
/**
* request TS to create a new node for given index / key/
*/
RefPtr<UINode> CreateNewNode(uint32_t forIndex);
// iterate over L1 keys, not allowed to modify L1
void ForEachL1IndexUINode(const std::function<void(uint32_t index, const RefPtr<UINode>& node)>& cbFunc);
void RecycleItemsByIndex(int32_t index);
/**
* for given index get key
* fetch from TS if not in cache
* return false if index out of range
*/
std::optional<std::string> GetKey4Index(uint32_t index);
/**
* iterate over all entries of L1 and call function for each entry
* if function returns true, entry is added to rebuild L1, otherwise it is moved to L2
* cbFunc return true, [index, key] pair stays in L1 (index remains unchanged)
* cbFunc returns false, enqueue key in L2
*/
bool RebuildL1(const std::function<bool(int32_t index, const RefPtr<UINode>& node)>& cbFunc);
int32_t GetFrameNodeIndex(const RefPtr<FrameNode>& frameNode) const;
/**
* scenario: in idle process , following GetChildren()
*
* enforce L2 cacheCount for each ttype
* by deleting UINodes, delete their entry from
* node4key4ttype_ and node4key_
* any other processing steps needed before UINode
* tree can be deleted
*/
bool Purge();
/**
* return the UINode for given index
* bool return indicates if in L1
*
* resolve index -> key -> UINode
*
*/
RefPtr<UINode> GetNode4Index(uint32_t forIndex) const;
void AddKeyToL1(const std::string& key)
{
activeNodeKeysInL1_.emplace(key);
}
bool IsInL1Cache(const std::string& key) const
{
return activeNodeKeysInL1_.find(key) != activeNodeKeysInL1_.end();
}
const std::unordered_map<std::string, RefPtr<UINode>>& GetAllNodes() const
{
return node4key_;
}
/**
* memorize last active range(s)
*/
void SetLastActiveRange(uint32_t from, uint32_t to);
// formatting internal structures to string for debug output
// and possibly in modified form for DFX in the future
std::string DumpL1() const;
std::string DumpL2() const;
std::string DumpKey4Index() const;
std::string DumpTType4Index() const;
std::string DumpUINode4Key4TType() const;
std::string DumpUINode4Key() const;
std::string DumpUINodeWithKey(const std::string& key) const;
std::string DumpUINode(const RefPtr<UINode>& node) const;
private:
/**
* intended scenario: scroll
* servicing GetFrameChild, search for key that can be updated.
*
* return a key whose UINode can be updated
* the key must not be in L1, i.e. activeNodeKeysInL1_
* the given ttype must match the template type the UINode for this key
* has been rendered for (this info is available from node4key4ttype_)
*/
std::optional<std::string> GetL2KeyToUpdate(const std::string& ttype) const;
/**
* scenario: UI rebuild following key invalidation by TS side
* L1 includes keys that are no longer used, the linked UINodes
* should be updated.
*
* This function checks all L1 keys (of active UINodes) if the key
* can still be found from
* (previously updated following invalidation) key -> index map and
*
*/
std::optional<std::string> GetL1KeyToUpdate(const std::string& ttype) const;
/**
* scenario: UINode of fromKey has been updated to render data for 'forKey'
* the template type (ttype) remains unchanged
* update node4key4ttype_ and node4key_ entries to use new key point to same UINode
*/
RefPtr<UINode> HasUINodeBeenUpdated(
const std::string& ttype, const std::string& fromKey, const std::string& forKey);
/** scenario: keys cache has been updated
*
* find which keys in key -> UINode map are no longer used
* returned set entries are pairs:
* pair.first: is this key a L1 item,
* pair.second: key
*/
void FindUnusedKeys(std::set<std::pair<bool, std::string>>& result) const;
/**
* given key return the index position (reverse lookup)
* invalidated keys (after Repeat rerender/ data change)
* are keys for which no index exists anymore,
* method returns int max value for these.
* int max value causes that distance from active range is max
* these keys will be selected for update first.
*/
uint32_t GetIndex4Key(const std::string& key) const;
/**
* for given index return distance from active range,
* or 0 if within active range
* distance is int max for invalidated keys
*/
int32_t GetDistanceFromRange(uint32_t index) const;
/**
* scenario: find L1 key that should be updated
* choose the key whose index is the furthest away from active range
* given two keys compare their distFromRange
*/
bool CompareKeyByIndexDistance(const std::string& key1, const std::string& key2) const;
std::set<std::string, KeySorterClass> GetSortedL2KeysForTType(
const std::unordered_map<std::string, RefPtr<UINode>>& uiNode4Key) const;
/**
* get more index -> key and index -> ttype from TS side
*/
bool FetchMoreKeysTTypes(uint32_t from, uint32_t to);
// Map ttype -> cacheSize. Each ttype incl default has own L2 size
std::map<std::string, uint32_t> cacheCountL24ttype_;
// request TS to create new sub-tree for given index or update existing
// update subtree cached for (old) index
// API might need to change to tell which old item to update
std::function<void(uint32_t)> onCreateNode_;
std::function<void(const std::string&, uint32_t)> onUpdateNode_;
// get index -> key for given range
// resulting list starts with 'from' but might end before 'to' if Array shorter
std::function<std::list<std::string>(uint32_t, uint32_t)> onGetTypes4Range_;
// get index -> ttype for given range
// resulting list starts with 'from' but might end before 'to' if Array shorter
std::function<std::list<std::string>(uint32_t, uint32_t)> onGetKeys4Range_;
// memorize active ranges of past 2 (last, prev)
// SetActiveRange calls and use to calc scroll direction
std::pair<uint32_t, uint32_t> lastActiveRanges_[2] = { { 0, 0 }, { 0, 0 } };
// keys of active nodes, UINodes must be on the UI tree,
// this list is also known as L1
// all keys not in this set are in "L2"
std::unordered_set<std::string> activeNodeKeysInL1_;
// L1
// index -> key and reverse
// lazy request from TS side can be invalidated
std::unordered_map<uint32_t, std::string> key4index_;
std::unordered_map<std::string, uint32_t> index4Key_;
// index -> ttype
// lazy request from TS side can be invalidated
std::unordered_map<uint32_t, std::string> ttype4index_;
std::unordered_map<std::string, uint32_t> index4ttype_;
// Map ttype -> Map key -> UINode
std::unordered_map<std::string, std::unordered_map<std::string, RefPtr<UINode>>> node4key4ttype_;
// Map Map key -> UINode
std::unordered_map<std::string, RefPtr<UINode>> node4key_;
}; // class NodeCache
} // namespace OHOS::Ace::NG
#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_CACHES_H

View File

@ -0,0 +1,50 @@
/*
* 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_MODEL_H
#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_SYNTAX_REPEAT_MODEL_H
#include <cstdint>
#include <functional>
#include <list>
#include <map>
#include <memory>
#include <string>
#include "base/utils/macros.h"
namespace OHOS::Ace {
class ACE_EXPORT RepeatVirtualScrollModel {
public:
RepeatVirtualScrollModel() = default;
virtual ~RepeatVirtualScrollModel() = default;
static RepeatVirtualScrollModel* GetInstance();
virtual void Create(
uint32_t totalCount,
const std::map<std::string, uint32_t>& templateCachedCountMap,
const std::function<void(uint32_t forIndex)>& onCreateNode,
const std::function<void(const std::string& fromKey, uint32_t forIndex)>& onUpdateNode,
const std::function<std::list<std::string>(uint32_t from, uint32_t to)>& onGetKeys4Range,
const std::function<std::list<std::string>(uint32_t from, uint32_t to)>& onGetTypes4Range) = 0;
virtual void InvalidateKeyCache(uint32_t totalCount) = 0;
private:
static std::unique_ptr<RepeatVirtualScrollModel> instance_;
};
} // namespace OHOS::Ace
#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_H

View File

@ -0,0 +1,60 @@
/*
* 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_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_node.h"
#include "core/components_ng/syntax/syntax_item.h"
namespace OHOS::Ace::NG {
void RepeatVirtualScrollModelNG::Create(
uint32_t totalCount,
const std::map<std::string, uint32_t>& templateCachedCountMap,
const std::function<void(uint32_t forIndex)>& onCreateNode,
const std::function<void(const std::string& fromKey, uint32_t forIndex)>& onUpdateNode,
const std::function<std::list<std::string>(uint32_t from, uint32_t to)>& onGetKeys4Range,
const std::function<std::list<std::string>(uint32_t from, uint32_t to)>& onGetTypes4Range)
{
ACE_SCOPED_TRACE("RepeatVirtualScrollModelNG::Create");
auto* stack = ViewStackProcessor::GetInstance();
auto nodeId = stack->ClaimNodeId();
auto repeatNode = RepeatVirtualScrollNode::GetOrCreateRepeatNode(
nodeId,
totalCount,
templateCachedCountMap,
onCreateNode,
onUpdateNode,
onGetKeys4Range,
onGetTypes4Range
);
stack->Push(repeatNode);
stack->PopContainer();
}
void RepeatVirtualScrollModelNG::InvalidateKeyCache(uint32_t totalCount)
{
auto* stack = ViewStackProcessor::GetInstance();
auto nodeId = stack->ClaimNodeId();
auto repeatNode = ElementRegister::GetInstance()->GetSpecificItemById<RepeatVirtualScrollNode>(nodeId);
CHECK_NULL_VOID(repeatNode);
repeatNode->UpdateTotalCount(totalCount);
repeatNode->InvalidateKeyCache();
}
} // namespace OHOS::Ace::NG

View File

@ -0,0 +1,45 @@
/*
* 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_H
#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_H
#include <cstdint>
#include <functional>
#include <list>
#include <string>
#include <vector>
#include "base/utils/macros.h"
#include "core/components_ng/syntax/repeat_virtual_scroll_model.h"
namespace OHOS::Ace::NG {
class ACE_EXPORT RepeatVirtualScrollModelNG : public RepeatVirtualScrollModel {
public:
void Create(
uint32_t totalCount,
const std::map<std::string, uint32_t>& templateCachedCountMap,
const std::function<void(uint32_t forIndex)>& onCreateNode,
const std::function<void(const std::string& fromKey, uint32_t forIndex)>& onUpdateNode,
const std::function<std::list<std::string>(uint32_t from, uint32_t to)>& onGetKeys4Range,
const std::function<std::list<std::string>(uint32_t from, uint32_t to)>& onGetTypes4Range) override;
void InvalidateKeyCache(uint32_t totalCount) override;
};
} // namespace OHOS::Ace::NG
#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_H

View File

@ -0,0 +1,321 @@
/*
* 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_node.h"
#include <cstdint>
#include <functional>
#include <utility>
#include "base/log/ace_trace.h"
#include "base/log/log_wrapper.h"
#include "core/components_ng/base/frame_node.h"
#include "core/components_ng/pattern/list/list_item_pattern.h"
#include "core/pipeline/base/element_register.h"
#include "core/pipeline_ng/pipeline_context.h"
namespace OHOS::Ace::NG {
// REPEAT
RefPtr<RepeatVirtualScrollNode> RepeatVirtualScrollNode::GetOrCreateRepeatNode(int32_t nodeId, uint32_t totalCount,
const std::map<std::string, uint32_t>& templateCachedCountMap, const std::function<void(uint32_t)>& onCreateNode,
const std::function<void(const std::string&, uint32_t)>& onUpdateNode,
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetKeys4Range,
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetTypes4Range)
{
auto node = ElementRegister::GetInstance()->GetSpecificItemById<RepeatVirtualScrollNode>(nodeId);
if (node) {
node->UpdateTotalCount(totalCount);
return node;
}
node = MakeRefPtr<RepeatVirtualScrollNode>(
nodeId, totalCount, templateCachedCountMap, onCreateNode, onUpdateNode, onGetKeys4Range, onGetTypes4Range);
ElementRegister::GetInstance()->AddUINode(node);
return node;
}
RepeatVirtualScrollNode::RepeatVirtualScrollNode(int32_t nodeId, int32_t totalCount,
const std::map<std::string, uint32_t>& templateCachedCountMap, const std::function<void(uint32_t)>& onCreateNode,
const std::function<void(const std::string&, uint32_t)>& onUpdateNode,
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetKeys4Range,
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetTypes4Range)
: ForEachBaseNode(V2::JS_REPEAT_ETS_TAG, nodeId), totalCount_(totalCount),
caches_(templateCachedCountMap, onCreateNode, onUpdateNode, onGetKeys4Range, onGetTypes4Range),
postUpdateTaskHasBeenScheduled_(false)
{
// no preduct task scheduled
}
void RepeatVirtualScrollNode::DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd)
{
TAG_LOGD(AceLogTag::ACE_REPEAT,
"DoSetActiveChildRange: nodeId: %{public}d: start: %{public}d, end: %{public}d, cacheStart: %{public}d, "
"cacheEnd: %{public}d",
GetId(), start, end, cacheStart, cacheEnd);
// memorize active range
caches_.SetLastActiveRange(static_cast<uint32_t>(start), static_cast<uint32_t>(end));
bool needSync =
caches_.RebuildL1([start, end, cacheStart, cacheEnd, this](int32_t index, const RefPtr<UINode>& node) -> bool {
if (node == nullptr) {
return false;
}
// Get the first child of FrameNode.
auto frameNode = AceType::DynamicCast<FrameNode>(node->GetFrameChildByIndex(0, true));
if (!frameNode) {
return false;
}
// DoSetActiveChildRange uses int32_t , while other functions use uint32_t
// need to convert
if ((start <= index) && (index <= end)) {
frameNode->SetActive(true);
} else {
frameNode->SetActive(false);
}
if ((start - cacheStart <= index) && (index <= end + cacheEnd)) {
return true;
}
// active node moved into L2 cached.
// check transition flag.
if (node->OnRemoveFromParent(true)) {
// OnRemoveFromParent returns true means the child can be removed from tree immediately.
RemoveDisappearingChild(node);
} else {
// else move child into disappearing children, skip syncing render tree
AddDisappearingChild(node);
}
return false;
});
// TODO see if loop leads to any changes to active states
// only in that case do the re-sync , re-assembly of children
if (needSync) {
UINode::MarkNeedSyncRenderTree(false);
children_.clear();
// re-assemble children_
PostIdleTask();
}
}
void RepeatVirtualScrollNode::InvalidateKeyCache()
{
// empty the cache index -> key
// C++ will need to ask all new keys from JS side
caches_.InvalidateKeyAndTTypeCaches();
children_.clear();
auto frameNode = GetParentFrameNode();
if (frameNode) {
frameNode->ChildrenUpdatedFrom(0);
}
MarkNeedSyncRenderTree(true);
MarkNeedFrameFlushDirty(PROPERTY_UPDATE_MEASURE_SELF_AND_PARENT | PROPERTY_UPDATE_BY_CHILD_REQUEST);
}
/**
* a index -> key -> node does not exists, caller has verified before calling this function
*
* Ask TS to update a Node, if possible
* If no suitable node, request to crete a new node
*/
RefPtr<UINode> RepeatVirtualScrollNode::CreateOrUpdateFrameChild4Index(uint32_t forIndex, const std::string& forKey)
{
RefPtr<UINode> node4Index = caches_.UpdateFromL2(forIndex);
if (!node4Index) {
return node4Index;
}
return caches_.CreateNewNode(forIndex);
}
// FIXME added
// 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<UINode> RepeatVirtualScrollNode::GetFrameChildByIndex(
uint32_t index, bool needBuild, bool isCache, bool addToRenderTree)
{
// It will get or create new key.
const auto& key = caches_.GetKey4Index(index);
if (!key) {
TAG_LOGE(AceLogTag::ACE_REPEAT, "fail to get key for %{public}d", index);
return nullptr;
}
// search if index -> key -> Node exist
// pair.first tells of key is in L1
auto node4Index = GetFromCaches(index);
if (!node4Index && !needBuild) {
TAG_LOGD(AceLogTag::ACE_REPEAT,
"index %{public}d not in caches && needBuild==false, GetFrameChildByIndex returns nullptr .", index);
return nullptr;
}
// node4Index needs to be created or updated on JS side
if (!node4Index) {
// TS to either make new or update existing nodes
node4Index = CreateOrUpdateFrameChild4Index(index, key.value());
if (!node4Index) {
TAG_LOGW(AceLogTag::ACE_REPEAT, "index %{public}d not in caches and failed to build.", index);
return nullptr;
}
// move item to L1 cache.
caches_.AddKeyToL1(key.value());
} else {
// TODO need update existed node4Index with new index.
}
// if the item was in L2 cache, remove it from there
if (isActive_) {
node4Index->SetJSViewActive(true);
}
if (addToRenderTree && !isCache) {
node4Index->SetActive(true);
}
if (node4Index->GetDepth() != GetDepth() + 1) {
node4Index->SetDepth(GetDepth() + 1);
}
// attach to repeat node and pass context to it.
node4Index->SetParent(WeakClaim(this));
if (IsOnMainTree()) {
node4Index->AttachToMainTree(false, GetContext());
}
MarkNeedSyncRenderTree();
children_.clear();
// re-assemble children_
PostIdleTask();
auto childNode = node4Index->GetFrameChildByIndex(0, needBuild);
if (onMoveEvent_) {
InitDragManager(AceType::DynamicCast<FrameNode>(childNode));
}
return childNode;
}
int32_t RepeatVirtualScrollNode::GetFrameNodeIndex(const RefPtr<FrameNode>& node, bool /*isExpanded*/)
{
return caches_.GetFrameNodeIndex(node);
}
const std::list<RefPtr<UINode>>& RepeatVirtualScrollNode::GetChildren() const
{
if (!children_.empty()) {
return children_;
}
// can not modify l1_cache while iterating
// GetChildren is overloaded, can not change it to non-const
// need to order the child.
std::map<int32_t, RefPtr<UINode>> children;
caches_.ForEachL1IndexUINode(
[&children](int32_t index, const RefPtr<UINode>& node) -> void { children.emplace(index, node); });
for (const auto& [index, child] : children) {
children_.emplace_back(child);
}
return children_;
}
void RepeatVirtualScrollNode::RecycleItems(int32_t from, int32_t to)
{
offscreenItems_.from = from;
offscreenItems_.to = to;
for (auto i = from; i < to; i++) {
if (i >= startIndex_ && i < startIndex_ + totalCount_) {
caches_.RecycleItemsByIndex(i - startIndex_);
}
}
}
void RepeatVirtualScrollNode::SetNodeIndexOffset(int32_t start, int32_t /*count*/)
{
startIndex_ = start;
}
int32_t RepeatVirtualScrollNode::FrameCount() const
{
return totalCount_;
}
void RepeatVirtualScrollNode::PostIdleTask()
{
if (postUpdateTaskHasBeenScheduled_) {
return;
}
postUpdateTaskHasBeenScheduled_ = true;
auto* context = GetContext();
CHECK_NULL_VOID(context);
context->AddPredictTask([weak = AceType::WeakClaim(this)](int64_t /*deadline*/, bool /*canUseLongPredictTask*/) {
ACE_SCOPED_TRACE("RepeatVirtualScrollNode predict");
auto node = weak.Upgrade();
CHECK_NULL_VOID(node);
node->postUpdateTaskHasBeenScheduled_ = false;
node->GetChildren();
node->caches_.Purge();
});
}
void RepeatVirtualScrollNode::OnConfigurationUpdate(const ConfigurationChange& configurationChange)
{
if ((configurationChange.colorModeUpdate || configurationChange.fontUpdate)) {
const auto& children = caches_.GetAllNodes();
for (const auto& [key, child] : children) {
if (child) {
child->UpdateConfigurationUpdate(configurationChange);
}
}
}
}
// FIXME Which of the following methods are actually needed ?
void RepeatVirtualScrollNode::SetOnMove(std::function<void(int32_t, int32_t)>&& onMove)
{
// To do
}
// FOREAch
void RepeatVirtualScrollNode::MoveData(int32_t from, int32_t to)
{
// To do
}
RefPtr<FrameNode> RepeatVirtualScrollNode::GetFrameNode(int32_t index)
{
return AceType::DynamicCast<FrameNode>(GetFrameChildByIndex(index, false, false));
}
void RepeatVirtualScrollNode::InitDragManager(const RefPtr<UINode>& child)
{
// To do
}
void RepeatVirtualScrollNode::InitAllChildrenDragManager(bool init)
{
// To do
}
} // namespace OHOS::Ace::NG

View File

@ -0,0 +1,183 @@
/*
* 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_NODE_H
#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_NODE_H
#include <cstdint>
#include <list>
#include <string>
#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_caches.h"
namespace OHOS::Ace::NG {
struct OffscreenItems {
int32_t from = -1;
int32_t to = -1;
};
class ACE_EXPORT RepeatVirtualScrollNode : public ForEachBaseNode {
DECLARE_ACE_TYPE(RepeatVirtualScrollNode, ForEachBaseNode);
public:
static RefPtr<RepeatVirtualScrollNode> GetOrCreateRepeatNode(int32_t nodeId,
uint32_t totalCount,
const std::map<std::string, uint32_t>& templateCachedCountMap,
const std::function<void(uint32_t)>& onCreateNode,
const std::function<void(const std::string&, uint32_t)>& onUpdateNode,
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetKeys4Range,
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetTypes4Range);
RepeatVirtualScrollNode(int32_t nodeId, int32_t totalCount,
const std::map<std::string, uint32_t>& templateCacheCountMap, const std::function<void(uint32_t)>& onCreateNode,
const std::function<void(const std::string&, uint32_t)>& onUpdateNode,
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetKeys4Range,
const std::function<std::list<std::string>(uint32_t, uint32_t)>& onGetTypes4Range);
~RepeatVirtualScrollNode() 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
void InvalidateKeyCache();
/**
* 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<RefPtr<UINode>>& GetChildren() const override;
/**
* update range of Active items inside Repeat:
* iterative L1 cache entries
* those items with index in range [ start ... end ] are marked active
* those out of range marked inactive.
* retests idle task
*/
void DoSetActiveChildRange(int32_t start, int32_t end, int32_t cacheStart, int32_t cacheEnd) 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<UINode> 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<void(int32_t, int32_t)>&& onMove);
void MoveData(int32_t from, int32_t to) override;
RefPtr<FrameNode> GetFrameNode(int32_t index) override;
int32_t GetFrameNodeIndex(const RefPtr<FrameNode>& node, bool isExpanded = true) override;
void InitDragManager(const RefPtr<UINode>& childNode);
void InitAllChildrenDragManager(bool init);
void OnConfigurationUpdate(const ConfigurationChange& configurationChange) override;
void SetJSViewActive(bool active = true, bool isLazyForEachNode = false) override
{
const auto& children = caches_.GetAllNodes();
for (const auto& [key, child] : children) {
child->SetJSViewActive(active);
}
isActive_ = active;
}
void PaintDebugBoundaryTreeAll(bool flag) override
{
const auto& children = caches_.GetAllNodes();
for (const auto& [key, child] : children) {
child->PaintDebugBoundaryTreeAll(flag);
}
}
private:
void PostIdleTask();
// try to find entry for given index in L1 or L2 cache
RefPtr<UINode> GetFromCaches(uint32_t forIndex)
{
return caches_.GetNode4Index(forIndex);
}
// index is not in L1 or L2 cache, need to make it
// either by TS rendering new children or by TS updating
// a L2 cache item from old to new index
RefPtr<UINode> CreateOrUpdateFrameChild4Index(uint32_t index, const std::string& forKey);
// get farthest (from L1 indexes) index in L2 cache or -1
int32_t GetFarthestL2CacheIndex();
// RepeatVirtualScrollNode 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;
// caches:
mutable RepeatVirtualScrollCaches caches_;
// used by one of the unknown functions
std::list<std::string> ids_;
// re-assembled by GetChildren called from idle task
mutable std::list<RefPtr<UINode>> children_;
// true in the time from requesting idle / predict task until exec predict tsk.
bool postUpdateTaskHasBeenScheduled_;
OffscreenItems offscreenItems_;
int32_t startIndex_ = 0;
ACE_DISALLOW_COPY_AND_MOVE(RepeatVirtualScrollNode);
};
} // namespace OHOS::Ace::NG
#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_SYNTAX_REPEAT_VIRTUAL_SCROLL_NODE_H

View File

@ -70,7 +70,9 @@ const char JS_LAZY_FOR_EACH_ETS_TAG[] = "LazyForEach";
const char JS_FOR_EACH_ETS_TAG[] = "ForEach";
// js lazy foreach node
const char JS_SYNTAX_ITEM_ETS_TAG[] = "SyntaxItem";
// js if lese node
// js repeat node
const char JS_REPEAT_ETS_TAG[] = "Repeat";
// js if else node
const char JS_IF_ELSE_ETS_TAG[] = "IfElse";
// js node slot
const char JS_NODE_SLOT_ETS_TAG[] = "NodeSlot";

View File

@ -75,7 +75,9 @@ ACE_EXPORT extern const char JS_LAZY_FOR_EACH_ETS_TAG[];
ACE_EXPORT extern const char JS_FOR_EACH_ETS_TAG[];
// js lazy foreach node
ACE_EXPORT extern const char JS_SYNTAX_ITEM_ETS_TAG[];
// js if lese node
// js repeat node
ACE_EXPORT extern const char JS_REPEAT_ETS_TAG[];
// js if else node
ACE_EXPORT extern const char JS_IF_ELSE_ETS_TAG[];
// js node slot node
ACE_EXPORT extern const char JS_NODE_SLOT_ETS_TAG[];

View File

@ -1346,7 +1346,7 @@ HWTEST_F(LazyForEachSyntaxTestNg, ForEachSyntaxDoSetActiveChildRangeTest001, Tes
* @tc.steps: step3. Invoke DoSetActiveChildRange.
* @tc.expected: LazyForEachNode ids_ will be cleared.
*/
lazyForEachNode->DoSetActiveChildRange(0, 0);
lazyForEachNode->DoSetActiveChildRange(0, 0, 0, 0);
EXPECT_TRUE(lazyForEachNode->ids_.empty());
/**
@ -1354,7 +1354,7 @@ HWTEST_F(LazyForEachSyntaxTestNg, ForEachSyntaxDoSetActiveChildRangeTest001, Tes
* @tc.expected: LazyForEachNode ids_ will be cleared.
*/
lazyForEachNode->builder_ = nullptr;
lazyForEachNode->DoSetActiveChildRange(0, 0);
lazyForEachNode->DoSetActiveChildRange(0, 0, 0, 0);
EXPECT_TRUE(lazyForEachNode->ids_.empty());
}