This repository was archived by the owner on Jul 9, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 374
fix: Optimize memory usage when parsing LgFiles #9614
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
fd91340
Add MapOptimizer to reduce memory usage.
sw-joelmut 1d2bfa2
Fix lint issues and add new file header
sw-joelmut 44d3d00
Add MapOptimizer doc and destroy listener
sw-joelmut f495324
Fix typo
sw-joelmut 5c73b9d
Fix imports
sw-joelmut File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
141 changes: 141 additions & 0 deletions
141
Composer/packages/client/src/recoilModel/utils/mapOptimizer.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| /** | ||
| * Internal tree structure to track the oldest elements and their references. | ||
| */ | ||
| interface MapOptimizerTree<Key> { | ||
| timestamp: number; | ||
| references: Key[]; | ||
| } | ||
|
|
||
| /** | ||
| * Context for the MapOptimizer.onUpdate event. | ||
| */ | ||
| interface OnUpdateMapOptimizerContext<Key> { | ||
| /** | ||
| * Sets the related Map keys references of an element, these references are taken into account on the delete event. | ||
| * @param references The Map keys of a related element. | ||
| */ | ||
| setReferences(references: Key[]): void; | ||
| } | ||
|
|
||
| /** | ||
| * Class to optimize a Map object by deleting the oldest elements of the collection based on a capacity limit. | ||
| */ | ||
| export class MapOptimizer<Key, Value> { | ||
OEvgeny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| public tree = new Map<Key, MapOptimizerTree<Key>>(); | ||
| private skipOptimize = new Set<Key>(); | ||
|
|
||
| onUpdateCallback?: (key: Key, value: Value, ctx: OnUpdateMapOptimizerContext<Key>) => void; | ||
| onDeleteCallback?: (key: Key, value: Value) => void; | ||
|
|
||
| /** | ||
| * Initializes a new instance of the MapOptimizer class. | ||
| * @param capacity The capacity limit to trigger the optimization steps. | ||
| * @param list The Map object to optimize. | ||
| */ | ||
| constructor(private capacity: number, public list: Map<Key, Value>) { | ||
| this.attach(); | ||
| } | ||
|
|
||
| /** | ||
| * Event triggered when an element is added or updated in the Map object. | ||
| * @param callback Exposes the element's Key, Value and Context to perform operations. | ||
| */ | ||
| onUpdate(callback: (key: Key, value: Value, ctx: OnUpdateMapOptimizerContext<Key>) => void) { | ||
| this.onUpdateCallback = callback; | ||
| } | ||
|
|
||
| /** | ||
| * Event triggered when an element is marked for deletion. | ||
| * @param callback Exposes the element's Key, Value. | ||
| */ | ||
| onDelete(callback: (key: Key, value: Value) => void) { | ||
| this.onDeleteCallback = callback; | ||
| } | ||
|
|
||
| /** | ||
| * @private | ||
| * Attaches the "set" method to the Map object to listen and trigger the optimization. | ||
| */ | ||
| private attach() { | ||
| const set = this.list.set; | ||
| this.list.set = (key, value) => { | ||
| if (!this.skipOptimize.has(key)) { | ||
| this.optimize(key, value); | ||
| } | ||
| const result = set.apply(this.list, [key, value]); | ||
| return result; | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * @private | ||
| * Optimizes the Map object by performing the onDelete event callback on the oldest element in the collection. | ||
| */ | ||
| private optimize(keyToAdd: Key, valueToAdd: Value) { | ||
| const exists = this.tree.has(keyToAdd); | ||
| const context: MapOptimizerTree<Key> = { timestamp: Date.now(), references: [] }; | ||
| this.onUpdateCallback?.(keyToAdd, valueToAdd, { | ||
| setReferences: (references) => (context.references = references || []), | ||
| }); | ||
| this.tree.set(keyToAdd, context); | ||
|
|
||
| if (exists) { | ||
| return; | ||
| } | ||
|
|
||
| let processed: [Key, MapOptimizerTree<Key>][] = []; | ||
| const itemsToRemove = Array.from(this.tree.entries()) | ||
| .filter(([key]) => key !== keyToAdd) | ||
| .sort(([, v1], [, v2]) => v2.timestamp - v1.timestamp); | ||
|
|
||
| while (this.capacity < this.tree.size) { | ||
| const itemToRemove = itemsToRemove.pop(); | ||
| if (!itemToRemove) { | ||
| break; | ||
| } | ||
|
|
||
| const [key, { references }] = itemToRemove; | ||
| const ids = this.identify([key, ...references]); | ||
|
|
||
| // Re-process previous items if an item gets deleted. | ||
| processed.push(itemToRemove); | ||
| if (ids.length > 0) { | ||
| itemsToRemove.push(...processed); | ||
| processed = []; | ||
| } | ||
|
|
||
| for (const id of ids) { | ||
| this.tree.delete(id); | ||
| const listItem = this.list.get(id)!; | ||
| this.skipOptimize.add(id); | ||
| this.onDeleteCallback ? this.onDeleteCallback(id, listItem) : this.list.delete(id); | ||
| this.skipOptimize.delete(id); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @private | ||
| * Identifies all the keys that are available to delete. | ||
| */ | ||
| private identify(references: Key[], memo: Key[] = []) { | ||
| for (const reference of references) { | ||
| const found = this.tree.get(reference); | ||
| const existsOnMemo = () => memo.some((e) => found!.references.includes(e)); | ||
| const existsOnReferences = () => | ||
| Array.from(this.tree.values()).some(({ references }) => references.includes(reference)); | ||
|
|
||
| if (!found || existsOnMemo() || existsOnReferences()) { | ||
| continue; | ||
| } | ||
|
|
||
| memo.push(reference); | ||
| this.identify(found.references, memo); | ||
| } | ||
|
|
||
| return memo; | ||
| } | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.