Description
This feature introduces a synchronous batching mechanism for Neo.core.Effect executions, ensuring that effects triggered by multiple reactive config changes within a single logical operation (e.g., instance.set() or direct assignments that cascade through beforeSet/afterSet hooks) are executed only once per batch. This significantly improves performance and aligns the Effect system with the framework's existing update cycles.
Motivation
Prior to this change, Neo.core.Effect instances would re-execute their functions immediately upon any change to their tracked Neo.core.Config dependencies. While correct for individual changes, this led to inefficiencies in scenarios like:
core.Base#set() operations: When instance.set({propA: valA, propB: valB}) was called, each individual config change (propA, propB) would trigger a separate Effect run, even though the developer intended a single, batched update.
- Cascading
beforeSet/afterSet hooks: If a config change in a beforeSet or afterSet hook triggered another config change, the Effect could run multiple times within a single synchronous call stack.
This resulted in redundant computations and potential performance bottlenecks, especially for complex components with many reactive bindings.
Changes Made
Neo.core.EffectBatchManager (New Singleton: src/core/EffectBatchManager.mjs):
- Introduced as a singleton responsible for managing the global batch state.
- Maintains a
batchCount to track nested batch operations.
- Manages a
pendingEffects Set to store Effect instances queued for execution.
- Provides
startBatch(), endBatch(), isBatchActive(), and queueEffect() methods.
src/core/Effect.mjs - run() method modification:
- The
run() method of Neo.core.Effect now checks EffectBatchManager.isBatchActive().
- If a batch is active, the
Effect is added to EffectBatchManager.pendingEffects and its execution is deferred until the batch completes.
- If no batch is active, the
Effect executes its function immediately.
src/Neo.mjs - autoGenerateGetSet() set() method modification:
- This is the crucial integration point. The
set() method within the public descriptor generated by autoGenerateGetSet() (which is the universal entry point for all reactive config changes) now conditionally starts and ends a batch.
- It checks
!Neo.core.EffectBatchManager?.isBatchActive(). If true, it calls EffectBatchManager.startBatch() at the beginning and EffectBatchManager.endBatch() at the end of the set() operation.
- This ensures that any reactive config change, whether from
core.Base#set() or a direct assignment, is wrapped in a batch, and any cascading changes within beforeSet/afterSet hooks participate in that same batch.
test/siesta/tests/core/EffectBatching.mjs (New Test File):
- A comprehensive test suite has been added to validate the batching behavior.
- Tests cover:
- Multiple config changes via
instance.set() resulting in a single Effect run.
- Individual config changes outside a batch running immediately.
- No-operation batches not triggering
Effect runs.
- Complex scenarios involving
beforeSet and afterSet hooks indirectly changing dependencies, confirming that the Effect still runs only once per batch.
- Nested batch management by
EffectBatchManager.
Benefits
- Significant Performance Improvement: Eliminates redundant
Effect executions, especially in scenarios with batched updates or cascading config changes.
- Predictable Execution: Ensures
Effects always run on the final, consistent state of their dependencies within a batch.
- Framework Consistency: Aligns the
Effect system's update cycle with the existing core.Base#set() batching pattern.
- Robustness: Provides a more reliable and efficient foundation for all reactive data flows in Neo.mjs.
Description
This feature introduces a synchronous batching mechanism for
Neo.core.Effectexecutions, ensuring that effects triggered by multiple reactive config changes within a single logical operation (e.g.,instance.set()or direct assignments that cascade throughbeforeSet/afterSethooks) are executed only once per batch. This significantly improves performance and aligns theEffectsystem with the framework's existing update cycles.Motivation
Prior to this change,
Neo.core.Effectinstances would re-execute their functions immediately upon any change to their trackedNeo.core.Configdependencies. While correct for individual changes, this led to inefficiencies in scenarios like:core.Base#set()operations: Wheninstance.set({propA: valA, propB: valB})was called, each individual config change (propA,propB) would trigger a separateEffectrun, even though the developer intended a single, batched update.beforeSet/afterSethooks: If a config change in abeforeSetorafterSethook triggered another config change, theEffectcould run multiple times within a single synchronous call stack.This resulted in redundant computations and potential performance bottlenecks, especially for complex components with many reactive bindings.
Changes Made
Neo.core.EffectBatchManager(New Singleton:src/core/EffectBatchManager.mjs):batchCountto track nested batch operations.pendingEffectsSet to storeEffectinstances queued for execution.startBatch(),endBatch(),isBatchActive(), andqueueEffect()methods.src/core/Effect.mjs-run()method modification:run()method ofNeo.core.Effectnow checksEffectBatchManager.isBatchActive().Effectis added toEffectBatchManager.pendingEffectsand its execution is deferred until the batch completes.Effectexecutes its function immediately.src/Neo.mjs-autoGenerateGetSet()set()method modification:set()method within the public descriptor generated byautoGenerateGetSet()(which is the universal entry point for all reactive config changes) now conditionally starts and ends a batch.!Neo.core.EffectBatchManager?.isBatchActive(). Iftrue, it callsEffectBatchManager.startBatch()at the beginning andEffectBatchManager.endBatch()at the end of theset()operation.core.Base#set()or a direct assignment, is wrapped in a batch, and any cascading changes withinbeforeSet/afterSethooks participate in that same batch.test/siesta/tests/core/EffectBatching.mjs(New Test File):instance.set()resulting in a singleEffectrun.Effectruns.beforeSetandafterSethooks indirectly changing dependencies, confirming that theEffectstill runs only once per batch.EffectBatchManager.Benefits
Effectexecutions, especially in scenarios with batched updates or cascading config changes.Effects always run on the final, consistent state of their dependencies within a batch.Effectsystem's update cycle with the existingcore.Base#set()batching pattern.