From b9e77f5688bc45a698d92ff6d0ecad0dbd18bc8a Mon Sep 17 00:00:00 2001 From: Tianer Zhou Date: Fri, 23 May 2025 16:28:45 +0800 Subject: [PATCH 1/6] rewrite LazyForEach with a detached items design Signed-off-by: Tianer Zhou --- arkoala-arkts/arkui/src/LazyForEach.ts | 2 +- arkoala-arkts/arkui/src/LazyItemNode.ts | 23 ++++++ arkoala-arkts/arkui/src/PeerNode.ts | 3 +- arkoala-arkts/arkui/src/RewrittenLazy.ts | 89 ++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 arkoala-arkts/arkui/src/LazyItemNode.ts create mode 100644 arkoala-arkts/arkui/src/RewrittenLazy.ts diff --git a/arkoala-arkts/arkui/src/LazyForEach.ts b/arkoala-arkts/arkui/src/LazyForEach.ts index 2a6429ac80..770468c5b8 100644 --- a/arkoala-arkts/arkui/src/LazyForEach.ts +++ b/arkoala-arkts/arkui/src/LazyForEach.ts @@ -135,7 +135,7 @@ class LazyForEachIdentifier { * @returns item offset of LazyForEach in parent's children */ /** @memo */ -function getOffset(parent: PeerNode, id: KoalaCallsiteKey): int32 { +export function getOffset(parent: PeerNode, id: KoalaCallsiteKey): int32 { let offset = 0 for (let child = parent.firstChild; child; child = child!.nextSibling) { // corresponding DataNode is attached after the generated items diff --git a/arkoala-arkts/arkui/src/LazyItemNode.ts b/arkoala-arkts/arkui/src/LazyItemNode.ts new file mode 100644 index 0000000000..1e2b62f4e6 --- /dev/null +++ b/arkoala-arkts/arkui/src/LazyItemNode.ts @@ -0,0 +1,23 @@ +import { KoalaCallsiteKey } from "@koalaui/common"; +import { Disposable, IncrementalNode } from "@koalaui/runtime"; +import { uint32 } from "../../../incremental/compat/src"; +import { LazyItemNodeType, PeerNode } from "./PeerNode"; + +/** + * LazyItemNode is the root node of an item in LazyForEach. + * LazyForEach items are never attached to the main tree, but stored in a separate pool in LazyForEach. + */ +export class LazyItemNode extends IncrementalNode { + constructor(parent: PeerNode) { + super(LazyItemNodeType) + this._container = parent + } + private _container: PeerNode + reuse(reuseKey: string, id: KoalaCallsiteKey): Disposable | undefined { + return this._container.reuse(reuseKey, id) + } + + recycle(reuseKey: string, child: Disposable, id: KoalaCallsiteKey): boolean { + return this._container.recycle(reuseKey, child, id) + } +} \ No newline at end of file diff --git a/arkoala-arkts/arkui/src/PeerNode.ts b/arkoala-arkts/arkui/src/PeerNode.ts index 55c27ecf26..c9eb0cc3bd 100644 --- a/arkoala-arkts/arkui/src/PeerNode.ts +++ b/arkoala-arkts/arkui/src/PeerNode.ts @@ -22,7 +22,8 @@ import { ReusablePool } from "./ReusablePool" export const PeerNodeType = 11 export const RootPeerType = 33 -export const LazyForEachType = 13 +export const LazyForEachType = 13 // corresponds to DataNode in LazyForEach +export const LazyItemNodeType = 17 // LazyItems are detached node trees that are stored privately in LazyForEach const INITIAL_ID = 1000 export class PeerNode extends IncrementalNode { diff --git a/arkoala-arkts/arkui/src/RewrittenLazy.ts b/arkoala-arkts/arkui/src/RewrittenLazy.ts new file mode 100644 index 0000000000..3bc4e56cdc --- /dev/null +++ b/arkoala-arkts/arkui/src/RewrittenLazy.ts @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + */ + +import { __id, ComputableState, contextNode, GlobalStateManager, IncrementalNode, memoEntry2, remember, rememberMutableState, StateContext } from "@koalaui/runtime"; +import { getOffset, IDataSource } from "./LazyForEach"; +import { pointer } from "@koalaui/interop"; +import { PeerNode } from "./PeerNode"; +import { InternalListener } from "./DataChangeListener"; +import {LazyItemNode } from "./LazyItemNode"; + +class LazyItemPool { + private _activeItems: Map> = new Map() + private _keyGenerator?: (item: any, index: number) => string + + updateStates(keyGenerator?: (item: T, index: number) => string) { + this._keyGenerator = keyGenerator + + } + + getOrCreate( + index: number, + data: T, + /** @memo */ + itemGenerator: (item: T, index: number) => void, + ): pointer { + const key = this._keyGenerator?.(data, index) ?? index.toString() + if (this._activeItems.has(key)) { + const node = this._activeItems.get(key)! + if (node.recomputeNeeded) { + console.log(`recomputeNeeded: ${key}`) + } + return (node.value.firstChild as PeerNode).getPeerPtr() + } + + const manager = GlobalStateManager.instance + const node = manager.updatableNode(new LazyItemNode(contextNode()), (context: StateContext) => { + const frozen = manager.frozen + manager.frozen = true + memoEntry2( + context, + 0, + itemGenerator, + data, + index + ) + manager.frozen = frozen + }) + + this._activeItems.set(key, node) + return (node.value.firstChild as PeerNode).getPeerPtr() + } +} + +/** @memo */ +export function LazyForEach(dataSource: IDataSource, + /** @memo */ + itemGenerator: (item: T, index: number) => void, + keyGenerator?: (item: T, index: number) => string, +) { + let changeCounter = rememberMutableState(0) + let parent = contextNode() + const offset = getOffset(parent, __id()) + let listener = remember(() => new InternalListener(parent.peer.ptr, changeCounter)) + const changeIndex = listener.flush(offset) // first item index that's affected by DataChange + + // Entering this method implies that the parameters have changed. + let pool = remember(() => new LazyItemPool()) + + pool.updateStates(keyGenerator) + + let createCallback = (index: number) => { + return pool.getOrCreate(index, dataSource.getData(index), itemGenerator) + } + let updateRangeCallback = (start: number, end: number) => { + + } +} \ No newline at end of file -- Gitee From 35335249b253cdaddc74d0baacabc78dc7f1fc32 Mon Sep 17 00:00:00 2001 From: Tianer Zhou Date: Fri, 23 May 2025 16:35:48 +0800 Subject: [PATCH 2/6] prevent memory leaks in LazyItemPool Signed-off-by: Tianer Zhou --- arkoala-arkts/arkui/src/RewrittenLazy.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/arkoala-arkts/arkui/src/RewrittenLazy.ts b/arkoala-arkts/arkui/src/RewrittenLazy.ts index 3bc4e56cdc..03f824d62f 100644 --- a/arkoala-arkts/arkui/src/RewrittenLazy.ts +++ b/arkoala-arkts/arkui/src/RewrittenLazy.ts @@ -13,17 +13,25 @@ * limitations under the License. */ -import { __id, ComputableState, contextNode, GlobalStateManager, IncrementalNode, memoEntry2, remember, rememberMutableState, StateContext } from "@koalaui/runtime"; +import { __id, ComputableState, contextNode, GlobalStateManager, Disposable, memoEntry2, remember, rememberDisposable, rememberMutableState, StateContext } from "@koalaui/runtime"; import { getOffset, IDataSource } from "./LazyForEach"; import { pointer } from "@koalaui/interop"; import { PeerNode } from "./PeerNode"; import { InternalListener } from "./DataChangeListener"; -import {LazyItemNode } from "./LazyItemNode"; +import { LazyItemNode } from "./LazyItemNode"; -class LazyItemPool { +class LazyItemPool implements Disposable { private _activeItems: Map> = new Map() private _keyGenerator?: (item: any, index: number) => string + disposed: boolean = false + dispose(): void { + for (let node of this._activeItems.values()) { + node.dispose() + } + this.disposed = true + } + updateStates(keyGenerator?: (item: T, index: number) => string) { this._keyGenerator = keyGenerator @@ -61,7 +69,7 @@ class LazyItemPool { this._activeItems.set(key, node) return (node.value.firstChild as PeerNode).getPeerPtr() } -} +} /** @memo */ export function LazyForEach(dataSource: IDataSource, @@ -76,7 +84,9 @@ export function LazyForEach(dataSource: IDataSource, const changeIndex = listener.flush(offset) // first item index that's affected by DataChange // Entering this method implies that the parameters have changed. - let pool = remember(() => new LazyItemPool()) + let pool = rememberDisposable(() => new LazyItemPool(), (pool) => { + pool?.dispose() + }) pool.updateStates(keyGenerator) @@ -84,6 +94,6 @@ export function LazyForEach(dataSource: IDataSource, return pool.getOrCreate(index, dataSource.getData(index), itemGenerator) } let updateRangeCallback = (start: number, end: number) => { - + } } \ No newline at end of file -- Gitee From c8a5ac6891ec3109420b228f9b6c243eadb0ddf4 Mon Sep 17 00:00:00 2001 From: Tianer Zhou Date: Fri, 23 May 2025 16:44:23 +0800 Subject: [PATCH 3/6] simplify pool by using index directly Signed-off-by: Tianer Zhou --- arkoala-arkts/arkui/src/LazyItemNode.ts | 4 ++ arkoala-arkts/arkui/src/RewrittenLazy.ts | 63 +++++++++++++----------- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/arkoala-arkts/arkui/src/LazyItemNode.ts b/arkoala-arkts/arkui/src/LazyItemNode.ts index 1e2b62f4e6..0f06cca315 100644 --- a/arkoala-arkts/arkui/src/LazyItemNode.ts +++ b/arkoala-arkts/arkui/src/LazyItemNode.ts @@ -13,6 +13,10 @@ export class LazyItemNode extends IncrementalNode { this._container = parent } private _container: PeerNode + + /** + * Supports Reusable through redirecting requests to the parent node. + */ reuse(reuseKey: string, id: KoalaCallsiteKey): Disposable | undefined { return this._container.reuse(reuseKey, id) } diff --git a/arkoala-arkts/arkui/src/RewrittenLazy.ts b/arkoala-arkts/arkui/src/RewrittenLazy.ts index 03f824d62f..af74749deb 100644 --- a/arkoala-arkts/arkui/src/RewrittenLazy.ts +++ b/arkoala-arkts/arkui/src/RewrittenLazy.ts @@ -21,8 +21,7 @@ import { InternalListener } from "./DataChangeListener"; import { LazyItemNode } from "./LazyItemNode"; class LazyItemPool implements Disposable { - private _activeItems: Map> = new Map() - private _keyGenerator?: (item: any, index: number) => string + private _activeItems: Map> = new Map() disposed: boolean = false dispose(): void { @@ -32,43 +31,53 @@ class LazyItemPool implements Disposable { this.disposed = true } - updateStates(keyGenerator?: (item: T, index: number) => string) { - this._keyGenerator = keyGenerator - - } - getOrCreate( index: number, data: T, /** @memo */ itemGenerator: (item: T, index: number) => void, ): pointer { - const key = this._keyGenerator?.(data, index) ?? index.toString() - if (this._activeItems.has(key)) { - const node = this._activeItems.get(key)! + if (this._activeItems.has(index)) { + const node = this._activeItems.get(index)! if (node.recomputeNeeded) { - console.log(`recomputeNeeded: ${key}`) + console.log(`recomputeNeeded: ${index}`) } return (node.value.firstChild as PeerNode).getPeerPtr() } const manager = GlobalStateManager.instance - const node = manager.updatableNode(new LazyItemNode(contextNode()), (context: StateContext) => { - const frozen = manager.frozen - manager.frozen = true - memoEntry2( - context, - 0, - itemGenerator, - data, - index - ) - manager.frozen = frozen - }) + const node = manager.updatableNode(new LazyItemNode(contextNode()), + (context: StateContext) => { + const frozen = manager.frozen + manager.frozen = true + memoEntry2( + context, + 0, + itemGenerator, + data, + index + ) + manager.frozen = frozen + } + ) - this._activeItems.set(key, node) + this._activeItems.set(index, node) return (node.value.firstChild as PeerNode).getPeerPtr() } + + /** + * prune items outside the range [start, end] + * @param start + * @param end + */ + prune(start: number, end: number) { + for (let [index, node] of this._activeItems.entries()) { + if (index < start || index > end) { + node.dispose() + this._activeItems.delete(index) + } + } + } } /** @memo */ @@ -88,12 +97,8 @@ export function LazyForEach(dataSource: IDataSource, pool?.dispose() }) - pool.updateStates(keyGenerator) - let createCallback = (index: number) => { return pool.getOrCreate(index, dataSource.getData(index), itemGenerator) } - let updateRangeCallback = (start: number, end: number) => { - - } + let updateRangeCallback = pool.prune } \ No newline at end of file -- Gitee From 7bc293e9a94de56c18c245a53711b42fe161b79a Mon Sep 17 00:00:00 2001 From: Tianer Zhou Date: Fri, 23 May 2025 16:49:38 +0800 Subject: [PATCH 4/6] fix parent access Signed-off-by: Tianer Zhou --- arkoala-arkts/arkui/src/RewrittenLazy.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/arkoala-arkts/arkui/src/RewrittenLazy.ts b/arkoala-arkts/arkui/src/RewrittenLazy.ts index af74749deb..107934e301 100644 --- a/arkoala-arkts/arkui/src/RewrittenLazy.ts +++ b/arkoala-arkts/arkui/src/RewrittenLazy.ts @@ -22,8 +22,13 @@ import { LazyItemNode } from "./LazyItemNode"; class LazyItemPool implements Disposable { private _activeItems: Map> = new Map() - + private _parent: PeerNode disposed: boolean = false + + constructor(parent: PeerNode) { + this._parent = parent + } + dispose(): void { for (let node of this._activeItems.values()) { node.dispose() @@ -46,7 +51,7 @@ class LazyItemPool implements Disposable { } const manager = GlobalStateManager.instance - const node = manager.updatableNode(new LazyItemNode(contextNode()), + const node = manager.updatableNode(new LazyItemNode(this._parent), (context: StateContext) => { const frozen = manager.frozen manager.frozen = true @@ -87,13 +92,13 @@ export function LazyForEach(dataSource: IDataSource, keyGenerator?: (item: T, index: number) => string, ) { let changeCounter = rememberMutableState(0) - let parent = contextNode() + const parent = contextNode() const offset = getOffset(parent, __id()) let listener = remember(() => new InternalListener(parent.peer.ptr, changeCounter)) const changeIndex = listener.flush(offset) // first item index that's affected by DataChange // Entering this method implies that the parameters have changed. - let pool = rememberDisposable(() => new LazyItemPool(), (pool) => { + let pool = rememberDisposable(() => new LazyItemPool(parent), (pool) => { pool?.dispose() }) -- Gitee From 371b9c69369c10db78dc6ed661804dd9718dac5f Mon Sep 17 00:00:00 2001 From: Tianer Zhou Date: Fri, 23 May 2025 16:53:53 +0800 Subject: [PATCH 5/6] create CustomComponent synchronously Signed-off-by: Tianer Zhou --- arkoala-arkts/arkui/src/RewrittenLazy.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arkoala-arkts/arkui/src/RewrittenLazy.ts b/arkoala-arkts/arkui/src/RewrittenLazy.ts index 107934e301..2f2f1d60ae 100644 --- a/arkoala-arkts/arkui/src/RewrittenLazy.ts +++ b/arkoala-arkts/arkui/src/RewrittenLazy.ts @@ -19,6 +19,7 @@ import { pointer } from "@koalaui/interop"; import { PeerNode } from "./PeerNode"; import { InternalListener } from "./DataChangeListener"; import { LazyItemNode } from "./LazyItemNode"; +import { setNeedCreate } from "./ArkComponentRoot"; class LazyItemPool implements Disposable { private _activeItems: Map> = new Map() @@ -55,6 +56,7 @@ class LazyItemPool implements Disposable { (context: StateContext) => { const frozen = manager.frozen manager.frozen = true + setNeedCreate(true) // ensure synchronous creation of CustomComponent memoEntry2( context, 0, @@ -62,6 +64,7 @@ class LazyItemPool implements Disposable { data, index ) + setNeedCreate(false) manager.frozen = frozen } ) -- Gitee From 3978b5d01f1a78c59463ef161567432798b898ca Mon Sep 17 00:00:00 2001 From: Tianer Zhou Date: Thu, 29 May 2025 15:45:05 +0800 Subject: [PATCH 6/6] adopt reusable and update items in pipeline Signed-off-by: Tianer Zhou --- arkoala-arkts/arkui/src/Application.ts | 2 + arkoala-arkts/arkui/src/LazyItemNode.ts | 46 +++++++++++-- arkoala-arkts/arkui/src/PeerNode.ts | 6 +- arkoala-arkts/arkui/src/RewrittenLazy.ts | 88 +++++++++++++++--------- 4 files changed, 102 insertions(+), 40 deletions(-) diff --git a/arkoala-arkts/arkui/src/Application.ts b/arkoala-arkts/arkui/src/Application.ts index 545a7afa06..6820d38ad1 100644 --- a/arkoala-arkts/arkui/src/Application.ts +++ b/arkoala-arkts/arkui/src/Application.ts @@ -29,6 +29,7 @@ import { Routed } from "./handwritten" import { deserializeAndCallCallback } from "./generated/peers/CallbackDeserializeCall" import { Deserializer } from "./generated/peers/Deserializer" import { NativeLog } from "./NativeLog" +import { updateLazyItems } from "./RewrittenLazy" setCustomEventsChecker(checkArkoalaCallbacks) @@ -248,6 +249,7 @@ export class Application { for (const detachedRoot of detachedRoots.values()) { detachedRoot.value } + updateLazyItems() if (partialUpdates.length > 0) { // If there are pending partial updates - we apply them one by one and provide update context. for (let update of partialUpdates) { diff --git a/arkoala-arkts/arkui/src/LazyItemNode.ts b/arkoala-arkts/arkui/src/LazyItemNode.ts index 0f06cca315..f9c070a58a 100644 --- a/arkoala-arkts/arkui/src/LazyItemNode.ts +++ b/arkoala-arkts/arkui/src/LazyItemNode.ts @@ -1,7 +1,22 @@ -import { KoalaCallsiteKey } from "@koalaui/common"; -import { Disposable, IncrementalNode } from "@koalaui/runtime"; -import { uint32 } from "../../../incremental/compat/src"; -import { LazyItemNodeType, PeerNode } from "./PeerNode"; +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + */ + +import { IncrementalNode, Disposable } from "@koalaui/runtime" +import { PeerNode, LazyItemNodeType, PeerNodeType } from "./PeerNode" +import { KoalaCallsiteKey } from "@koalaui/common" +import { nullptr, pointer } from "@koalaui/interop" /** * LazyItemNode is the root node of an item in LazyForEach. @@ -11,6 +26,22 @@ export class LazyItemNode extends IncrementalNode { constructor(parent: PeerNode) { super(LazyItemNodeType) this._container = parent + this.onChildInserted = (node: IncrementalNode) => { + if (!node.isKind(PeerNodeType)) { + return + } + const peer = node as PeerNode + peer.reusable ? peer.onReuse() : peer.reusable = true + } + this.onChildRemoved = (node: IncrementalNode) => { + if (!node.isKind(PeerNodeType)) { + return + } + const peer = node as PeerNode + if (!peer.disposed) { + peer.onRecycle() + } + } } private _container: PeerNode @@ -24,4 +55,9 @@ export class LazyItemNode extends IncrementalNode { recycle(reuseKey: string, child: Disposable, id: KoalaCallsiteKey): boolean { return this._container.recycle(reuseKey, child, id) } -} \ No newline at end of file + + getPeerPtr(): pointer { + const peer = this.firstChild + return peer?.isKind(PeerNodeType) ? (peer as PeerNode).getPeerPtr() : nullptr + } +} diff --git a/arkoala-arkts/arkui/src/PeerNode.ts b/arkoala-arkts/arkui/src/PeerNode.ts index c9eb0cc3bd..729abe9890 100644 --- a/arkoala-arkts/arkui/src/PeerNode.ts +++ b/arkoala-arkts/arkui/src/PeerNode.ts @@ -38,7 +38,7 @@ export class PeerNode extends IncrementalNode { private _onRecycle?: () => void // Pool to store recycled child scopes, grouped by type private _reusePool?: Map - private _reusable: boolean = false + reusable: boolean = false getPeerPtr(): pointer { return this.peer.ptr @@ -56,7 +56,7 @@ export class PeerNode extends IncrementalNode { } onReuse(): void { - if (!this._reusable) { + if (!this.reusable) { return } if (this._onReuse) { @@ -140,7 +140,7 @@ export class PeerNode extends IncrementalNode { // TODO: rework to avoid search let peer = findPeerNode(child) if (peer) { - peer._reusable ? peer!.onReuse() : peer._reusable = true // becomes reusable after initial mount + peer.reusable ? peer!.onReuse() : peer.reusable = true // becomes reusable after initial mount let peerPtr = peer.peer.ptr if (this.insertMark != nullptr) { if (this.insertDirection == 0) { diff --git a/arkoala-arkts/arkui/src/RewrittenLazy.ts b/arkoala-arkts/arkui/src/RewrittenLazy.ts index 2f2f1d60ae..80a71d9cfe 100644 --- a/arkoala-arkts/arkui/src/RewrittenLazy.ts +++ b/arkoala-arkts/arkui/src/RewrittenLazy.ts @@ -21,8 +21,45 @@ import { InternalListener } from "./DataChangeListener"; import { LazyItemNode } from "./LazyItemNode"; import { setNeedCreate } from "./ArkComponentRoot"; +let globalLazyItems = new Set>() +export function updateLazyItems() { + for (let node of globalLazyItems) { + node.value + } +} + +/** @memo */ +export function LazyForEach(dataSource: IDataSource, + /** @memo */ + itemGenerator: (item: T, index: number) => void, + keyGenerator?: (item: T, index: number) => string, +) { + let changeCounter = rememberMutableState(0) + const parent = contextNode() + const offset = getOffset(parent, __id()) + let listener = remember(() => { + let res = new InternalListener(parent.peer.ptr, changeCounter) + dataSource.registerDataChangeListener(res) + return res + }) + const changeIndex = listener.flush(offset) // first item index that's affected by DataChange + + // Entering this method implies that the parameters have changed. + let pool = rememberDisposable(() => new LazyItemPool(parent), (pool?: LazyItemPool) => { + pool?.dispose() + }) + + /** + * provide totalCount and callbacks to the backend + */ + let createCallback = (index: int32) => { + return pool.getOrCreate(index, dataSource.getData(index), itemGenerator) + } + // LazyForEachOps.Sync(parent.getPeerPtr(), dataSource.totalCount() as int32, createCallback, pool.prune) +} + class LazyItemPool implements Disposable { - private _activeItems: Map> = new Map() + private _activeItems = new Map>() private _parent: PeerNode disposed: boolean = false @@ -31,14 +68,21 @@ class LazyItemPool implements Disposable { } dispose(): void { + if (this.disposed) return + for (let node of this._activeItems.values()) { node.dispose() + globalLazyItems.delete(node) } this.disposed = true } + get activeCount(): int32 { + return this._activeItems.size as int32 + } + getOrCreate( - index: number, + index: int32, data: T, /** @memo */ itemGenerator: (item: T, index: number) => void, @@ -48,7 +92,7 @@ class LazyItemPool implements Disposable { if (node.recomputeNeeded) { console.log(`recomputeNeeded: ${index}`) } - return (node.value.firstChild as PeerNode).getPeerPtr() + return node.value.getPeerPtr() } const manager = GlobalStateManager.instance @@ -59,7 +103,7 @@ class LazyItemPool implements Disposable { setNeedCreate(true) // ensure synchronous creation of CustomComponent memoEntry2( context, - 0, + index, // using index to simplify reuse process itemGenerator, data, index @@ -70,7 +114,8 @@ class LazyItemPool implements Disposable { ) this._activeItems.set(index, node) - return (node.value.firstChild as PeerNode).getPeerPtr() + globalLazyItems.add(node) + return node.value.getPeerPtr() } /** @@ -78,35 +123,14 @@ class LazyItemPool implements Disposable { * @param start * @param end */ - prune(start: number, end: number) { - for (let [index, node] of this._activeItems.entries()) { + prune(start: int32, end: int32) { + if (start > end) return + this._activeItems.forEach((node, index) => { if (index < start || index > end) { node.dispose() - this._activeItems.delete(index) + this._activeItems.delete(index) // Delete in-place + globalLazyItems.delete(node) } - } + }) } } - -/** @memo */ -export function LazyForEach(dataSource: IDataSource, - /** @memo */ - itemGenerator: (item: T, index: number) => void, - keyGenerator?: (item: T, index: number) => string, -) { - let changeCounter = rememberMutableState(0) - const parent = contextNode() - const offset = getOffset(parent, __id()) - let listener = remember(() => new InternalListener(parent.peer.ptr, changeCounter)) - const changeIndex = listener.flush(offset) // first item index that's affected by DataChange - - // Entering this method implies that the parameters have changed. - let pool = rememberDisposable(() => new LazyItemPool(parent), (pool) => { - pool?.dispose() - }) - - let createCallback = (index: number) => { - return pool.getOrCreate(index, dataSource.getData(index), itemGenerator) - } - let updateRangeCallback = pool.prune -} \ No newline at end of file -- Gitee