Skip to content

Commit 6fabe86

Browse files
committed
#7076 WIP
1 parent f8aecea commit 6fabe86

4 files changed

Lines changed: 51 additions & 61 deletions

File tree

.github/ticket-asymmetric-vdom-updates.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,25 @@ We will introduce a new manager to act as a central orchestrator for all collisi
1616
### 1. New Class: `Neo.manager.VDomUpdate` (Orchestrator)
1717
- This new singleton manager will **not** schedule or delay updates.
1818
- It will contain two maps to manage all collision scenarios:
19-
- `mergedCallbackMap`: Stores callbacks for **pre-flight merges**.
19+
- `mergedCallbackMap`: Stores callbacks and relevant update depth information for **pre-flight merges**.
2020
- `postUpdateQueueMap`: Stores child components that need updating after an **in-flight collision**.
2121
- It will expose methods to be called by the `VdomLifecycle` mixin:
22-
- `registerMerged(ownerId, childId, callbacks)`
23-
- `registerPostUpdate(ownerId, childId)`
24-
- `executeCallbacks(ownerId)`
22+
- `registerMerged(ownerId, childId, callbacks, childUpdateDepth, distance)`: Stores the child's callbacks, its `updateDepth`, and its `distance` from the owner.
23+
- `registerPostUpdate(ownerId, childId, resolve)`
24+
- `executeCallbacks(ownerId)`: This method will also be responsible for calculating the maximum required `updateDepth` for the `ownerId` (parent) based on the `childUpdateDepth` and `distance` of all merged children. It will then set the `ownerId` component's `updateDepth` to this calculated maximum *before* the parent's `update()` method is called (if `needsVdomUpdate` is true).
2525
- `triggerPostUpdates(ownerId)`
2626

2727
### 2. `VdomLifecycle.mjs` Refactoring
2828
- The core, high-performance update logic (`updateVdom`, collision detection) will remain.
29-
- **Pre-Flight Merge:** When an update is merged (e.g., in `mergeIntoParentUpdate()`), it will now call `Neo.manager.VDomUpdate.registerMerged(...)` instead of manipulating local caches.
29+
- **Pre-Flight Merge:** When an update is merged (e.g., in `mergeIntoParentUpdate()`), it will now call `Neo.manager.VDomUpdate.registerMerged(parent.id, me.id, me.resolveUpdateCache, me.updateDepth, distance)` instead of manipulating local caches.
3030
- **In-Flight Collision:** When an update collides with an in-flight parent (in `isParentUpdating()`), it will call `Neo.manager.VDomUpdate.registerPostUpdate(...)`. The child component will still set its own `needsVdomUpdate = true` and hold its own callback in its `resolveUpdateCache` for the update it will eventually run.
3131
- **On Update Completion:** When a root update cycle finishes, its `then()` block will call both `manager.executeCallbacks(this.id)` and `manager.triggerPostUpdates(this.id)`.
3232
- This change allows for the complete removal of the complex `childUpdateCache` property and simplifies the logic around `resolveUpdateCache`.
3333

3434
### 3. Asymmetric VDOM Serialization (`ComponentManager.mjs`)
3535
- This part of the plan remains crucial and unchanged.
3636
- `getVdomTree()` and `getVnodeTree()` will be refactored to honor the `updateDepth` of each individual component.
37-
- If a child component is excluded from an update, a lightweight placeholder object `{componentId: 'neo-ignore', id: childComponent.id}` will be inserted into the tree to preserve its structural integrity.
37+
- If a child component is excluded from an update, a lightweight placeholder object `{componentId: 'neo-ignore'}` will be inserted into the tree to preserve its structural integrity.
3838

3939
### 4. VDOM Worker Enhancement (`vdom/Helper.mjs`)
4040
- The VDOM worker's `createDeltas()` method will be enhanced to recognize the `neo-ignore` placeholder. When it encounters this node, it will skip the diffing process, leaving the corresponding DOM element untouched.
@@ -57,4 +57,4 @@ Given the architectural significance of these changes, a comprehensive testing s
5757
- Create a suite of performance tests that simulate high-frequency updates on complex component trees.
5858
- Run these benchmarks on both the `main` branch (old implementation) and the feature branch (new implementation) to rigorously compare performance and ensure there are no regressions.
5959

60-
This final, refined approach provides the best of all worlds: it fixes the code complexity and maintainability issues by centralizing all collision-related state, while fully preserving the framework's proven, real-time performance characteristics.
60+
This final, refined approach provides the best of all worlds: it fixes the code complexity and maintainability issues by centralizing all collision-related state, while fully preserving the framework's proven, real-time performance characteristics.

src/manager/VDomUpdate.mjs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,34 +45,36 @@ class VDomUpdate extends Collection {
4545
* @param {String} ownerId
4646
* @param {String} childId
4747
* @param {Array} callbacks
48+
* @param {Number} childUpdateDepth
49+
* @param {Number} distance
4850
*/
49-
registerMerged(ownerId, childId, callbacks) {
51+
registerMerged(ownerId, childId, callbacks, childUpdateDepth, distance) {
5052
let me = this,
5153
item = me.mergedCallbackMap.get(ownerId);
5254

5355
if (!item) {
54-
item = {ownerId, callbacks: [], childIds: []};
56+
item = {ownerId, children: []};
5557
me.mergedCallbackMap.add(item);
5658
}
5759

58-
item.callbacks.push(...callbacks);
59-
item.childIds.push(childId);
60+
item.children.push({childId, callbacks, childUpdateDepth, distance});
6061
}
6162

6263
/**
6364
* @param {String} ownerId
6465
* @param {String} childId
66+
* @param {Function} [resolve]
6567
*/
66-
registerPostUpdate(ownerId, childId) {
68+
registerPostUpdate(ownerId, childId, resolve) {
6769
let me = this,
6870
item = me.postUpdateQueueMap.get(ownerId);
6971

7072
if (!item) {
71-
item = {ownerId, childIds: []};
73+
item = {ownerId, children: []};
7274
me.postUpdateQueueMap.add(item);
7375
}
7476

75-
item.childIds.push(childId);
77+
item.children.push({childId, resolve});
7678
}
7779

7880
/**
@@ -96,9 +98,12 @@ class VDomUpdate extends Collection {
9698
item = me.postUpdateQueueMap.get(ownerId);
9799

98100
if (item) {
99-
item.childIds.forEach(childId => {
100-
let component = Neo.getComponent(childId);
101-
component?.update();
101+
item.children.forEach(entry => {
102+
let component = Neo.getComponent(entry.childId);
103+
if (component) {
104+
entry.resolve && component.resolveUpdateCache.push(entry.resolve);
105+
component.update();
106+
}
102107
});
103108

104109
me.postUpdateQueueMap.remove(item);

src/mixin/VdomLifecycle.mjs

Lines changed: 25 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Base from '../core/Base.mjs';
22
import ComponentManager from '../manager/Component.mjs';
3+
import VDomUpdate from '../manager/VDomUpdate.mjs'; // New import
34
import NeoArray from '../util/Array.mjs';
45
import VDomUtil from '../util/VDom.mjs';
56
import VNodeUtil from '../util/VNode.mjs';
@@ -345,11 +346,7 @@ class VdomLifecycle extends Base {
345346
console.warn('vdom parent update conflict with:', parent, 'for:', me)
346347
}
347348

348-
parent.childUpdateCache[me.id] = {distance, resolve};
349-
350-
// Adding the resolve fn to its own cache, since the parent will trigger
351-
// a new update() directly on this cmp
352-
resolve && me.resolveUpdateCache.push(resolve);
349+
VDomUpdate.registerPostUpdate(parent.id, me.id, resolve);
353350
return true
354351
}
355352

@@ -371,21 +368,21 @@ class VdomLifecycle extends Base {
371368
* @param {Number} distance=1 Distance inside the component tree
372369
* @returns {Boolean}
373370
*/
374-
needsParentUpdate(parentId=this.parentId, resolve, distance=1) {
371+
mergeIntoParentUpdate(parentId=this.parentId, resolve, distance=1) { // Renamed from needsParentUpdate
375372
if (parentId !== 'document.body') {
376373
let me = this,
377374
parent = Neo.getComponent(parentId);
378375

379376
if (parent) {
380377
// We are checking for parent.updateDepth, since we care about the depth of the next update cycle
381378
if (parent.needsVdomUpdate && me.hasUpdateCollision(parent.updateDepth, distance)) {
382-
parent.resolveUpdateCache.push(...me.resolveUpdateCache);
383-
resolve && parent.resolveUpdateCache.push(resolve);
379+
VDomUpdate.registerMerged(parent.id, me.id, me.resolveUpdateCache);
380+
resolve && NeoArray.add(me.resolveUpdateCache, resolve);
384381
me.resolveUpdateCache = [];
385382
return true
386383
}
387384

388-
return me.needsParentUpdate(parent.parentId, resolve, distance+1)
385+
return me.mergeIntoParentUpdate(parent.parentId, resolve, distance+1)
389386
}
390387
}
391388

@@ -512,47 +509,31 @@ class VdomLifecycle extends Base {
512509
* @protected
513510
*/
514511
resolveVdomUpdate(resolve) {
515-
let me = this,
516-
hasChildUpdateCache = !Neo.isEmpty(me.childUpdateCache),
517-
component;
512+
let me = this;
518513

519514
me.doResolveUpdateCache();
520515

521516
resolve?.();
522517

523518
if (me.needsVdomUpdate) {
524-
if (hasChildUpdateCache) {
525-
Object.entries(me.childUpdateCache).forEach(([key, value]) => {
526-
component = Neo.getComponent(key);
527-
528-
// The component might already got destroyed
529-
if (component) {
530-
// Pass callbacks to the resolver cache => getting executed once the following update is done
531-
value.resolve && NeoArray.add(me.resolveUpdateCache, value.resolve);
532-
533-
// Adjust the updateDepth to include the depth of all merged child updates
534-
if (me.updateDepth !== -1) {
535-
if (component.updateDepth === -1) {
536-
me.updateDepth = -1
537-
} else {
538-
// Since updateDepth is 1-based, we need to subtract 1 level
539-
me.updateDepth = me.updateDepth + value.distance + component.updateDepth - 1
540-
}
541-
}
542-
}
543-
});
544-
545-
me.childUpdateCache = {}
546-
}
519+
// The updateDepth needs to be adjusted before the next update cycle
520+
// This logic is now handled by the VDomUpdate manager during merging
521+
// if (me.updateDepth !== -1) {
522+
// if (component.updateDepth === -1) {
523+
// me.updateDepth = -1
524+
// } else {
525+
// me.updateDepth = me.updateDepth + value.distance + component.updateDepth - 1
526+
// }
527+
// }
528+
529+
me.update();
530+
}
547531

548-
me.update()
549-
} else if (hasChildUpdateCache) {
550-
Object.keys(me.childUpdateCache).forEach(key => {
551-
Neo.getComponent(key)?.update()
552-
});
532+
// Execute callbacks for merged updates
533+
VDomUpdate.executeCallbacks(me.id);
553534

554-
me.childUpdateCache = {}
555-
}
535+
// Trigger updates for components that were in-flight
536+
VDomUpdate.triggerPostUpdates(me.id);
556537
}
557538

558539
/**
@@ -667,7 +648,7 @@ class VdomLifecycle extends Base {
667648
}
668649

669650
if (
670-
!me.needsParentUpdate(parentId, resolve)
651+
!me.mergeIntoParentUpdate(parentId, resolve) // Renamed call
671652
&& !me.isParentUpdating(parentId, resolve)
672653
&& mounted
673654
&& vnode
@@ -690,4 +671,4 @@ class VdomLifecycle extends Base {
690671

691672
Neo.setupClass(VdomLifecycle);
692673

693-
export default VdomLifecycle;
674+
export default VdomLifecycle;

src/vdom/Helper.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,10 @@ class Helper extends Base {
249249
childNode.id === oldChildNode.id ||
250250
(childNode.componentId && childNode.componentId === oldChildNode.componentId))
251251
) {
252+
if (childNode.componentId === 'neo-ignore') {
253+
continue
254+
}
255+
252256
me.createDeltas({deltas, oldVnode: oldChildNode, oldVnodeMap, vnode: childNode, vnodeMap});
253257
continue
254258
}

0 commit comments

Comments
 (0)