@samitouri / QOS-React / commits / ecf468e556

Batch nested updates when in sync mode

Almost working...

Andrew Clark committed Oct 28, 2016 at 13:26 UTC ecf468e5567586192d61b0e426398878cb5cefcb
4 files changed +46 -23
src/renderers/dom/fiber/ReactDOMFiber.js
+2
@@ -112,6 +112,8 @@ var DOMRenderer = ReactFiberReconciler({
112
113 scheduleDeferredCallback: window.requestIdleCallback,
114
115 + useSyncScheduling: true,
116 +
117 });
118
119 var warned = false;
src/renderers/shared/fiber/ReactFiberClassComponent.js
+3 -3
@@ -130,7 +130,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : ?Prior
130 // process them now.
131 const updateQueue = workInProgress.updateQueue;
132 if (updateQueue) {
133 - instance.state = mergeUpdateQueue(updateQueue, state, props);
133 + instance.state = mergeUpdateQueue(updateQueue, instance, state, props);
134 }
135 }
136 }
@@ -174,7 +174,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : ?Prior
174 // process them now.
175 const newUpdateQueue = workInProgress.updateQueue;
176 if (newUpdateQueue) {
177 - newInstance.state = mergeUpdateQueue(newUpdateQueue, newState, newProps);
177 + newInstance.state = mergeUpdateQueue(newUpdateQueue, newInstance, newState, newProps);
178 }
179 }
180 return true;
@@ -211,7 +211,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : ?Prior
211 // TODO: Previous state can be null.
212 let newState;
213 if (updateQueue) {
214 - newState = mergeUpdateQueue(updateQueue, previousState, newProps);
214 + newState = mergeUpdateQueue(updateQueue, instance, previousState, newProps);
215 } else {
216 newState = previousState;
217 }
src/renderers/shared/fiber/ReactFiberScheduler.js
+39 -18
@@ -71,6 +71,9 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
71 SynchronousPriority :
72 LowPriority;
73
74 + // Whether updates should be batched. Only applies when using sync scheduling.
75 + let shouldBatchUpdates : boolean = false;
76 +
77 // The next work in progress fiber that we're currently working on.
78 let nextUnitOfWork : ?Fiber = null;
79 let nextPriorityLevel : PriorityLevel = NoWork;
@@ -490,9 +493,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
493 while (nextUnitOfWork &&
494 nextPriorityLevel === SynchronousPriority) {
495 nextUnitOfWork = performUnitOfWork(nextUnitOfWork, false);
493 - // If there's no nextUnitOfWork, we don't need to search for more
494 - // because it shouldn't be possible to schedule sync work without
495 - // immediately performing it
496 +
497 + if (!nextUnitOfWork) {
498 + nextUnitOfWork = findNextUnitOfWork();
499 + }
500 }
501 if (nextUnitOfWork) {
502 if (nextPriorityLevel > AnimationPriority) {
@@ -504,7 +508,35 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
508 }
509
510 function performSynchronousWork() {
511 + if (useSyncScheduling) {
512 + // Start batching updates
513 + shouldBatchUpdates = true;
514 + }
515 performAndHandleErrors(performSynchronousWorkUnsafe);
516 + shouldBatchUpdates = false;
517 + }
518 +
519 + function scheduleSynchronousWork(root : FiberRoot) {
520 + root.current.pendingWorkPriority = SynchronousPriority;
521 +
522 + if (root.isScheduled) {
523 + // If we're already scheduled, we can bail out.
524 + return;
525 + }
526 + root.isScheduled = true;
527 + if (lastScheduledRoot) {
528 + // Schedule ourselves to the end.
529 + lastScheduledRoot.nextScheduledRoot = root;
530 + lastScheduledRoot = root;
531 + } else {
532 + // We're the only work scheduled.
533 + nextScheduledRoot = root;
534 + lastScheduledRoot = root;
535 +
536 + if (!shouldBatchUpdates) {
537 + performSynchronousWork();
538 + }
539 + }
540 }
541
542 function performAndHandleErrors<A>(fn: (a: A) => void, a: A) {
@@ -560,10 +592,9 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
592
593 // We will process an update caused by each error boundary synchronously.
594 affectedBoundaries.forEach(boundary => {
563 - // FIXME: We only specify LowPriority here so that setState() calls from the error
564 - // boundaries are respected. Instead we should set default priority level or something
565 - // like this. Reconsider this piece when synchronous scheduling is in place.
566 - const priority = LowPriority;
595 + const priority = priorityContext !== null ?
596 + priorityContext :
597 + defaultPriorityContext;
598 const root = scheduleErrorBoundaryWork(boundary, priority);
599 // This should use findNextUnitOfWork() when synchronous scheduling is implemented.
600 let fiber = cloneFiber(root.current, priority);
@@ -604,8 +635,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
635 }
636
637 if (priorityLevel === SynchronousPriority) {
607 - root.current.pendingWorkPriority = SynchronousPriority;
608 - performSynchronousWork();
638 + scheduleSynchronousWork(root);
639 }
640
641 if (priorityLevel === NoWork) {
@@ -626,15 +656,6 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
656 defaultPriorityContext;
657 }
658
629 - // Don't bother bubbling the priority to the root if it is synchronous. Just
630 - // perform it now.
631 - if (priorityLevel === SynchronousPriority) {
632 - fiber.pendingWorkPriority = SynchronousPriority;
633 - nextUnitOfWork = fiber;
634 - performSynchronousWork();
635 - return;
636 - }
637 -
659 while (true) {
660 if (fiber.pendingWorkPriority === NoWork ||
661 fiber.pendingWorkPriority >= priorityLevel) {
src/renderers/shared/fiber/ReactFiberUpdateQueue.js
+2 -2
@@ -73,14 +73,14 @@ exports.callCallbacks = function(queue : UpdateQueue, context : any) {
73 }
74 };
75
76 -exports.mergeUpdateQueue = function(queue : UpdateQueue, prevState : any, props : any) : any {
76 +exports.mergeUpdateQueue = function(queue : UpdateQueue, instance : any, prevState : any, props : any) : any {
77 let node : ?UpdateQueueNode = queue;
78 let state = queue.isReplace ? null : Object.assign({}, prevState);
79 while (node) {
80 let partialState;
81 if (typeof node.partialState === 'function') {
82 const updateFn = node.partialState;
83 - partialState = updateFn(state, props);
83 + partialState = updateFn.call(instance, state, props);
84 } else {
85 partialState = node.partialState;
86 }