An overview of the

Lane is the priority of a task in React. Priority is divided into high priority and low priority. When users operate the interface, in order to avoid page lag, they need to give up the execution right of the thread and execute the events triggered by the user first. This is called high-priority tasks, and other events that are not so important are called low-priority tasks.

There is a phenomenon between tasks with different priorities: when low-priority tasks are being performed, a high-priority task is suddenly inserted, which will interrupt the low-priority task and execute the high-priority task first. We can call this phenomenon task queue-jumping. When the high-priority task is finished and the low-priority task is ready to be executed, another high-priority task will be inserted, and then the high-priority task will be executed again. If there are constantly high-priority tasks jumping the queue for execution, the low-priority task will not be executed all the time. We call this phenomenon task hunger.

Different priority mechanisms

React has three priority mechanisms:

  1. React Indicates the event priority
  2. Lane priority
  3. The Scheduler priority

React Indicates the event priority

// The priority of discrete events, such as click events, input, etc., has the highest priority
export const DiscreteEventPriority: EventPriority = SyncLane;
// Sequential event priority, such as: scroll event, drag event, etc
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
// Default event priority, for example, setTimeout triggered update task
export const DefaultEventPriority: EventPriority = DefaultLane;
// Idle events have the lowest priority
export const IdleEventPriority: EventPriority = IdleLane;
Copy the code

React’s event priority value is still the same as Lane, so why not use Lane? In order not to be coupled with the Lane mechanism, the priority of subsequent events can be changed directly without affecting Lane.

Change the Lane priority to the React event priority:

export function lanesToEventPriority(lanes: Lanes) :EventPriority {
  // Find the lane with the highest priority
  const lane = getHighestPriorityLane(lanes);
  if(! isHigherEventPriority(DiscreteEventPriority, lane)) {return DiscreteEventPriority;
  }
  if(! isHigherEventPriority(ContinuousEventPriority, lane)) {return ContinuousEventPriority;
  }
  if (includesNonIdleWork(lane)) {
    return DefaultEventPriority;
  }
  return IdleEventPriority;
}
Copy the code

The Scheduler priority

export const NoPriority = 0; // No priority
export const ImmediatePriority = 1; // Priority of the task to be executed immediately, highest level
export const UserBlockingPriority = 2; // Priority of user blocking
export const NormalPriority = 3; // Normal priority
export const LowPriority = 4; // Lower priority
export const IdlePriority = 5; // The task has the lowest priority. Idle indicates that the task can be idle
Copy the code

React Event priority is changed to Scheduler priority

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {...switch (lanesToEventPriority(nextLanes)) {
      case DiscreteEventPriority:
        schedulerPriorityLevel = ImmediateSchedulerPriority;
        break;
      case ContinuousEventPriority:
        schedulerPriorityLevel = UserBlockingSchedulerPriority;
        break;
      case DefaultEventPriority:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
      case IdleEventPriority:
        schedulerPriorityLevel = IdleSchedulerPriority;
        break;
      default:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break; }}Copy the code

The lanesToEventPriority function converts the priority of a Lane to the priority of a React event, and then converts the priority of the React event to the priority of the Scheduler.

Lane priority

// Lane uses the 31-bit binary to represent the 31 lanes of priority. The smaller the number of bits (the position of 1 is closer to the right), the higher the priority
export const TotalLanes = 31;

// No priority
export const NoLanes: Lanes = / * * / 0b0000000000000000000000000000000;
export const NoLane: Lane = / * * / 0b0000000000000000000000000000000;

// Synchronization priority: indicates that only one synchronization task can be executed at a time, for example, the update task generated by user interaction events
export const SyncLane: Lane = / * * / 0b0000000000000000000000000000001;

// Continuously trigger priority, such as: scroll event, drag event, etc
export const InputContinuousHydrationLane: Lane = / * * / 0b0000000000000000000000000000010;
export const InputContinuousLane: Lanes = / * * / 0b0000000000000000000000000000100;

// Default priority, such as using setTimeout, request data return, etc
export const DefaultHydrationLane: Lane = / * * / 0b0000000000000000000000000001000;
export const DefaultLane: Lanes = / * * / 0b0000000000000000000000000010000;

Suspense, useTransition, useDeferredValue, etc
const TransitionHydrationLane: Lane = / * * / 0b0000000000000000000000000100000;
const TransitionLanes: Lanes = / * * / 0b0000000001111111111111111000000;
const TransitionLane1: Lane = / * * / 0b0000000000000000000000001000000;
const TransitionLane2: Lane = / * * / 0b0000000000000000000000010000000;
const TransitionLane3: Lane = / * * / 0b0000000000000000000000100000000;
const TransitionLane4: Lane = / * * / 0b0000000000000000000001000000000;
const TransitionLane5: Lane = / * * / 0b0000000000000000000010000000000;
const TransitionLane6: Lane = / * * / 0b0000000000000000000100000000000;
const TransitionLane7: Lane = / * * / 0b0000000000000000001000000000000;
const TransitionLane8: Lane = / * * / 0b0000000000000000010000000000000;
const TransitionLane9: Lane = / * * / 0b0000000000000000100000000000000;
const TransitionLane10: Lane = / * * / 0b0000000000000001000000000000000;
const TransitionLane11: Lane = / * * / 0b0000000000000010000000000000000;
const TransitionLane12: Lane = / * * / 0b0000000000000100000000000000000;
const TransitionLane13: Lane = / * * / 0b0000000000001000000000000000000;
const TransitionLane14: Lane = / * * / 0b0000000000010000000000000000000;
const TransitionLane15: Lane = / * * / 0b0000000000100000000000000000000;
const TransitionLane16: Lane = / * * / 0b0000000001000000000000000000000;

const RetryLanes: Lanes = / * * / 0b0000111110000000000000000000000;
const RetryLane1: Lane = / * * / 0b0000000010000000000000000000000;
const RetryLane2: Lane = / * * / 0b0000000100000000000000000000000;
const RetryLane3: Lane = / * * / 0b0000001000000000000000000000000;
const RetryLane4: Lane = / * * / 0b0000010000000000000000000000000;
const RetryLane5: Lane = / * * / 0b0000100000000000000000000000000;

export const SomeRetryLane: Lane = RetryLane1;

export const SelectiveHydrationLane: Lane = / * * / 0b0001000000000000000000000000000;
Copy the code

It can be seen that lane uses 31-bit binary to represent priority lanes, with a total of 31 lanes. The smaller the number of bits (the position of 1 is closer to the right), the higher the priority.

React prioritizers React prioritizers React prioritizers

How do I add different priorities to React events

When we trigger a React event to create a task, it comes with a priority. How is the priority assigned to the event?

When our project was first rendered:

const root = document.getElementById('root');
ReactDOM.createRoot(root).render(<div>Hello World</div>);
Copy the code

When the root node is created by calling the createRoot method, the event delegate is done for the root node:

export function createRoot(container: Container, options? : CreateRootOptions,) :RootType {...// Create the container-fiber root node
  const root = createContainer(
    container,
    ConcurrentRoot,
    hydrate,
    hydrationCallbacks,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
  );
  
  // Add event delegate to root container
  listenToAllSupportedEvents(rootContainerElement);
  
}
Copy the code

At this point, all supported events are prioritized and assigned different priorities:

export function createEventListenerWrapperWithPriority(targetContainer: EventTarget, domEventName: DOMEventName, eventSystemFlags: EventSystemFlags,) :Function {
  // Prioritize different events
  const eventPriority = getEventPriority(domEventName);

  // According to the priority classification, set the priority when the event is triggered
  let listenerWrapper;
  switch (eventPriority) {
    case DiscreteEventPriority:
      listenerWrapper = dispatchDiscreteEvent;
      break;
    case ContinuousEventPriority:
      listenerWrapper = dispatchContinuousEvent;
      break;
    case DefaultEventPriority:
    default:
      listenerWrapper = dispatchEvent;
      break;
  }
  return listenerWrapper.bind(
    null,
    domEventName,
    eventSystemFlags,
    targetContainer,
  );
}
Copy the code

We see the first call to the getEventPriority method, which internally classifies different events into different priorities:

export function getEventPriority(domEventName: DOMEventName) : *{
  switch (domEventName) {
    case 'cancel':
    case 'click':
    case 'copy':
    case 'dragend':
    case 'dragstart':
    case 'drop':...case 'focusin':
    case 'focusout':
    case 'input':
    case 'change':
    case 'textInput':
    case 'blur':
    case 'focus':
    case 'select':
      // Synchronization priority
      return DiscreteEventPriority;
    case 'drag':
    case 'mousemove':
    case 'mouseout':
    case 'mouseover':
    case 'scroll':...case 'touchmove':
    case 'wheel':
    case 'mouseenter':
    case 'mouseleave':
      // Sequential trigger priority
      returnContinuousEventPriority; .default:
      returnDefaultEventPriority; }}Copy the code

React sets the synchronization priority for user clicks, input boxes, etc. This is because users need immediate feedback during operations. If there is no feedback after operations, the interface will feel stuck.

Next, we will set the callback function that has the corresponding priority when the event is fired according to the priority of the event:

let listenerWrapper;
switch (eventPriority) {
    case DiscreteEventPriority:
      listenerWrapper = dispatchDiscreteEvent;
      break;
    case ContinuousEventPriority:
      listenerWrapper = dispatchContinuousEvent;
      break;
    case DefaultEventPriority:
    default:
      listenerWrapper = dispatchEvent;
      break;
}
  
function dispatchDiscreteEvent(domEventName, eventSystemFlags, container, nativeEvent,) {... setCurrentUpdatePriority(DiscreteEventPriority); }function dispatchContinuousEvent(domEventName, eventSystemFlags, container, nativeEvent,) {... setCurrentUpdatePriority(ContinuousEventPriority); }Copy the code

You can see that the same setCurrentUpdatePriority method is called in each of the corresponding callback functions, and the event priority value corresponding to the current event is set.

How does Lane work in React

When we trigger a click event and call setState to generate an update task:

Component.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Copy the code

The update is initiated by calling the setState function, which calls enqueueSetState internally:

enqueueSetState(inst, payload, callback) {
    const fiber = getInstance(inst); // Get the fiber node corresponding to the current component
    const eventTime = requestEventTime(); // Get the current event trigger time
    const lane = requestUpdateLane(fiber); // Get the Lane priority of the current event

    // Create an update object and mount the content that needs to be updated to the payload
    const update = createUpdate(eventTime, lane); 
    update.payload = payload;
    if(callback ! = =undefined&& callback ! = =null) {
      update.callback = callback;
    }

    // Add the update object to the update queue
    enqueueUpdate(fiber, update, lane);
    constroot = scheduleUpdateOnFiber(fiber, lane, eventTime); . }Copy the code

Gets the priority of the event

We call requestUpdateLane to get the priority of the current event. We call requestUpdateLane to get the priority of the current event.

export function requestUpdateLane(fiber: Fiber) :Lane {
  // Retrieve the current render mode: sync mode or Concurrent mode
  const mode = fiber.mode;
  if ((mode & ConcurrentMode) === NoMode) {
    // Check whether the current render mode is concurrent. If NoMode is not, use synchronous render mode
    return (SyncLane: Lane);
  } else if(! deferRenderPhaseUpdateToNextBatch && (executionContext & RenderContext) ! == NoContext && workInProgressRootRenderLanes ! == NoLanes ) {/ / workInProgressRootRenderLanes is given in task execution phase need to update the lane on the fiber node values
    / / when the new update task workInProgressRootRenderLanes is empty, not have tasks are executed
    // The lane of the executing task is returned directly, and the current new task will be batch updated with the existing task
    return pickArbitraryLane(workInProgressRootRenderLanes);
  }

  // Check whether the current event is a transition priority
  // If so, a transition priority is returned
  // Transition priority allocation rules:
  / / the task assigned to it as A TransitionLanes first: TransitionLane1 b0000000000000000000000001000000 = 0
  Task B / / now, so from A position move to the left one: TransitionLane2 b0000000000000000000000010000000 = 0
  // Subsequent tasks are moved backward one at a time until they reach the last bit
  / / transition a total of 16 priority: TransitionLanes b0000000001111111111111111000000 = 0
  // When all bits are used up, the transition priority is assigned to the event from the first
  constisTransition = requestCurrentTransition() ! == NoTransition;if (isTransition) {
    if (currentEventTransitionLane === NoLane) {
      currentEventTransitionLane = claimNextTransitionLane();
    }
    return currentEventTransitionLane;
  }

  // Update events triggered by react internal events, such as onClick, set a priority for the current event when the event is triggered
  const updateLane: Lane = (getCurrentUpdatePriority(): any);
  if(updateLane ! == NoLane) {return updateLane;
  }

  // Update events triggered by react external events, such as setTimeout, set a priority for the current event when the event is triggered
  const eventLane: Lane = (getCurrentEventPriority(): any);
  return eventLane;
}
Copy the code
The concurrent mode

The current render mode is checked to see if it is in concurrent mode, and if it is not, the synchronization priority will be used:

if ((mode & ConcurrentMode) === NoMode) {
    return (SyncLane: Lane);
} 
Copy the code
Concurrent mode

If so, it will then check whether there is the current task being performed, workInProgressRootRenderLanes is when initializing workInProgress tree, the priority of the currently executing task assigned to the workInProgressRootRenderLanes, If workInProgressRootRenderLanes is not null, then the direct return to the currently executing task lane, the new mission will and existing task for a batch update:

if(! deferRenderPhaseUpdateToNextBatch && (executionContext & RenderContext) ! == NoContext && workInProgressRootRenderLanes ! == NoLanes ) {return pickArbitraryLane(workInProgressRootRenderLanes);
  }
Copy the code

If none of the above is true, the current event is determined to be a transition priority, and if so, one of the transition priorities is assigned.

The rules for assigning transition priorities are as follows: When assigning a priority, it starts from the rightmost position of the transition priority, and subsequent tasks move one bit to the left until the last position is allocated, and subsequent tasks start from the first position on the right:

Task A is currently generated, then the first right-most position of the transition priority is assigned:

TransitionLane1 = 0b0000000000000000000000001000000
Copy the code

Now that task B has been created, move one bit to the left from A’s position:

TransitionLane2 = 0b0000000000000000000000010000000
Copy the code

Subsequent tasks will be moved one bit to the left, with a total of 16 bits of transition priority:

TransitionLanes = 0b0000000001111111111111111000000
Copy the code

When the leftmost 1 position is assigned, the event transition priority is assigned from the rightmost 1 position.

If it’s not a transition-priority task, you can go down and see that the getCurrentUpdatePriority function is called. Remember from the beginning, when the project is first rendered, it does event delegation on the root container and prioritized all supported events. The setCurrentUpdatePriority function is called when an event is triggered to set the priority of the current event. Call getCurrentUpdatePriority to get the event priority set when the event is triggered. If the obtained event priority is not empty, the priority of the event is directly returned.

const updateLane: Lane = (getCurrentUpdatePriority(): any);
  if(updateLane ! == NoLane) {return updateLane;
  }
Copy the code

The React external event priority is obtained by calling getCurrentEventPriority. For example, setTimeout calls setState:

const eventLane: Lane = (getCurrentEventPriority(): any);
return eventLane;
Copy the code

Finally, the priority of the found event is returned.

Use the priority of events

Now we have seen that if we get the priority of the event, what if we use Lane? Let’s see.

An update object is first created to which the lane of the event is added:

const update = createUpdate(eventTime, lane); 

export function createUpdate(eventTime: number, lane: Lane) :Update< * >{
  const update: Update<*> = {
    eventTime, // Update the event firing time
    lane, // Priority of event updates

    tag: UpdateState, // Type: update, replace, force update, etc
    payload: null.// What needs to be updated
    callback: null.// Update the second argument to the setState callback

    next: null.// Next update object
  };
  return update;
}
Copy the code

Mount the update callback function to the callback attribute of the update object:

update.payload = payload;
if(callback ! = =undefined&& callback ! = =null) {
  update.callback = callback;
}
Copy the code

Then add the update object to the update queue on the Fiber node corresponding to the current component:

 enqueueUpdate(fiber, update, lane);
Copy the code

The update queue is a circular linked list structure.

ScheduleUpdateOnFiber is then called to prepare for the task. Here are a few important points:

export function scheduleUpdateOnFiber(fiber: Fiber, lane: Lane, eventTime: number,) :FiberRoot | null {
  // Check for an infinite loop update, such as a setState call in the render function, if so, an error message will be displayedcheckForNestedUpdates(); .// collect the lanes of the child nodes that need to be updated and store them in childLanes on the parent fiber
  // Update lannes of the current fiber node, indicating that the current node needs to be updated
  // Walk up from the current fiber node to the root node and update the childLanes attribute on each fiber node
  // childLanes If the value is childLanes, there are child nodes that need to be updated
  const root = markUpdateLaneFromFiberToRoot(fiber, lane);
  if (root === null) {
    return null;
  }

  // Add the lanes that need to be updated to the pendingLanes property of fiber root to indicate that a new update task needs to be performed
  // Add the event time to eventTimes by calculating the current lane positionmarkRootUpdated(root, lane, eventTime); . ensureRootIsScheduled(root, eventTime); .return root;
}
Copy the code

We see the internal function call markUpdateLaneFromFiberToRoot, this function is the main role is to update the current fiber node lannes, said the current node needs to be updated, and then collect the child nodes of the need to update the lane, Stored in the childLanes property on the parent fiber. During the update process, lannes* on the fiber node determines whether the current fiber node needs to be updated, and childLanes determines whether the current fiber node needs to be updated. How we look at markUpdateLaneFromFiberToRoot internal implementation:

function markUpdateLaneFromFiberToRoot(sourceFiber: Fiber, lane: Lane,) :FiberRoot | null {
  // Update lanes on the current node, which indicates that the node needs to be updated
  sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
  let alternate = sourceFiber.alternate;
  if(alternate ! = =null) { alternate.lanes = mergeLanes(alternate.lanes, lane); }...Update childLanes on each fiber node by traversing the current fiber node up to the root fiber node
  // Then childLanes is used to determine whether there are any children under the current fiber node that need to be updated
  let node = sourceFiber;
  let parent = sourceFiber.return;
  while(parent ! = =null) {
    parent.childLanes = mergeLanes(parent.childLanes, lane);
    alternate = parent.alternate;
    if(alternate ! = =null) {
      alternate.childLanes = mergeLanes(alternate.childLanes, lane);
    } else {
      if (__DEV__) {
        if((parent.flags & (Placement | Hydrating)) ! == NoFlags) { warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); } } } node = parent; parent = parent.return; }if (node.tag === HostRoot) {
    const root: FiberRoot = node.stateNode;
    return root;
  } else {
    return null; }}Copy the code

First, the lanes attribute on the current fiber node of the new task indicates that the current fiber needs to be updated. If the corresponding alternate of the fiber node is not empty, it indicates that the fiber is being updated, and the alternate lanes on the fiber node will be updated synchronously.

// Update lanes on the current node, which indicates that the node needs to be updated
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
let alternate = sourceFiber.alternate;
if(alternate ! = =null) {
    alternate.lanes = mergeLanes(alternate.lanes, lane);
}
Copy the code

Update the childLanes attribute on each fiber node, indicating that the child nodes under the current fiber need to be updated:

  Update childLanes on each fiber node by traversing the current fiber node up to the root fiber node
  // Then childLanes is used to determine whether there are any children under the current fiber node that need to be updated
  let node = sourceFiber;
  let parent = sourceFiber.return;
  while(parent ! = =null) {
    parent.childLanes = mergeLanes(parent.childLanes, lane);
    alternate = parent.alternate;
    if(alternate ! = =null) {
      alternate.childLanes = mergeLanes(alternate.childLanes, lane);
    }
    node = parent;
    parent = parent.return;
  }
Copy the code

The sourceFiber.return method is used to get the parent of the current fiber node.

After the traversal is complete, the Fiber root node is returned.

MarkUpdateLaneFromFiberToRoot is performed, followed by the call of markRootUpdated function, this function is the function of add the current need to update the lane to the fiber pendingLanes attribute of the root, Indicates that a new update task needs to be executed, and then logs the event trigger time on the eventTimes property:

export function markRootUpdated(root: FiberRoot, updateLane: Lane, eventTime: number,) {
  // Add the lanes that need to be updated to the pendingLanes property of fiber root
  root.pendingLanes |= updateLane;

  if(updateLane ! == IdleLane) { root.suspendedLanes = NoLanes; root.pingedLanes = NoLanes; }// Assume that updateLane is 0b000100
  // eventTimes is of the form: [-1, -1, -1, 44573.3452, -1, -1]
  // Use an array to store eventTime. -1 represents a vacancy, and non-1 positions are the same as 1 positions in lane
  const eventTimes = root.eventTimes;
  const index = laneToIndex(updateLane);
  eventTimes[index] = eventTime;
}
Copy the code

EventTimes is a 31-bit Array, and the Lane uses 31-bit binary.

If updateLane is: 0b000100, it will look like this in eventTimes:

[-1, -1.44573.3452, -1, -1...]
Copy the code

The markRootUpdated call is then preceded by a call to the ensureRootIsScheduled function that is ready to initiate the scheduling of the task.

EnsureRootIsScheduled is one of the important functions that has issues with queue-cutting and job starvation for high priority tasks, and batch updates. So let’s take a look at how these things are handled in this function.

Task hunger

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {...// Add an expiration time based on the priority for the current task
  // Check whether any of the unexecuted tasks have expired. If any tasks have expired, add lanes of the task to expiredLanes
  // Execute in synchronous mode in follow-up task execution to avoid hunger problemmarkStarvedLanesAsExpired(root, currentTime); . }Copy the code

Hunger problem about task processing, the main logic in markStarvedLanesAsExpired function, its main function is added for the current task according to the priority expiration time, and check whether there is a task in the did not perform the task of expired, with task overdue, on the other hand, the task is added in the expiredLanes lane, In the subsequent execution of this task, the execution shall be carried out in synchronous mode to avoid the problem of hunger:

export function markStarvedLanesAsExpired(root: FiberRoot, currentTime: number,) :void {

  const pendingLanes = root.pendingLanes;
  const suspendedLanes = root.suspendedLanes;
  const pingedLanes = root.pingedLanes;
  const expirationTimes = root.expirationTimes;

  // Tasks to be executed generate an expiration time based on their priority
  // when a task expires, add the lane of the task to expiredLanes expiredLanes
  // Perform subsequent tasks by checking whether the lane of the current task exists at expiredLanes,
  // If so, the task will be executed in synchronous mode to avoid task starvation
  // ps: What hunger problem?
  // The problem of hunger refers to the constant insertion of multiple tasks with higher priority than the task when executing a task
  // This task will never be executed
  let lanes = pendingLanes;
  while (lanes > 0) {
    // Gets the leftmost 1 position of current Lanes
    / / such as:
    // lanes = 28 = 0b0000000000000000000000000011100
    // The leftmost 1 would be in position 5
    // But Lanes sets the total length to 31, so we can also subtract 1 to see that we are in position 4
    // If this is difficult to understand, look at the source code in pickArbitraryLaneIndex:
    // 31 - clz32(lanes), an API in Math that takes the number of zeros before the leftmost 1
    const index = pickArbitraryLaneIndex(lanes);

    // After we get the leftmost 1 above, we need to get the value of that position
    // index = 4
    // 16 = 10000 = 1 << 4
    const lane = 1 << index;

    // Gets the expiration time of the task at the current location, if not, an expiration time is created based on the priority of the task
    // If so, it determines whether the task is expired, and if so, lane of the current task is added to expiredLanes
    const expirationTime = expirationTimes[index];
    if (expirationTime === NoTimestamp) {
      if (
        (lane & suspendedLanes) === NoLanes ||
        (lane & pingedLanes) !== NoLanes
      ) {
        expirationTimes[index] = computeExpirationTime(lane, currentTime);
      }
    } else if (expirationTime <= currentTime) {
      root.expiredLanes |= lane;
    }

    // Remove lanes from lanes, one at a time, until lanes equals 0
    / / such as:
    // lane = 16 = 10000
    // ~lane = 01111
    // lanes = 28 = 11100
    // lanes = 12 = 01100 = lanes & ~lanelanes &= ~lane; }}Copy the code

You can see that the main logic handles pendingLanes in a loop.

PickArbitraryLaneIndex is called to retrieve the leftmost 1 of pendingLanes, for example:

lanes = 28 = 0b0000000000000000000000000011100
Copy the code

Then use:

27 = Math.clz32(lanes);
Copy the code

Get the number of zeros before the leftmost 1 and calculate the leftmost 1:

const index = 31 - 27; / / 4
Copy the code

Then use index to obtain the expiration time in the corresponding expirationTimes. If the expiration time is empty, an expiration time is generated based on the current priority. The higher the priority, the smaller the expiration time. Then add the expiration time to the appropriate location.

Like eventTimes, expirationTimes is a 31-bit Array, and the corresponding Lane uses 31-bit binary.

 expirationTimes[index] = computeExpirationTime(lane, currentTime);
Copy the code

If the current location has an expiration time, it checks to see if it is expired, and if it is, it adds the current Lane to expiredLanes and uses synchronous rendering for subsequent execution of the task to avoid task starvation.

if (expirationTime <= currentTime) {
  root.expiredLanes |= lane;
}
Copy the code

Lanes are then removed from lanes, one at a time, until lanes equals 0:

 lanes &= ~lane;
Copy the code

Task to jump the queue

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  const existingCallbackNode = root.callbackNode;
 
  // Add an expiration time based on the priority for the current task
  // Check whether any of the unexecuted tasks have expired. If any tasks have expired, add lanes of the task to expiredLanes
  // Execute in synchronous mode in follow-up task execution to avoid hunger problem
  markStarvedLanesAsExpired(root, currentTime);

  // Get the priority of the highest priority task
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );

  // If nextLanes is empty, there are no tasks to execute and the update is interrupted
  if (nextLanes === NoLanes) {
    if(existingCallbackNode ! = =null) {
      cancelCallback(existingCallbackNode);
    }
    root.callbackNode = null;
    root.callbackPriority = NoLane;
    return;
  }

  NextLanes gets the lane with the highest priority of all tasks
  // Then there are only two results compared to the priority of the current task:
  // 1. If the priority of the current task is the same as that of the existing task, the current new task will be interrupted and the previous existing task will be reused
  // 2. If the priority of the new task is greater than that of the existing task, the execution of the existing task will be cancelled, and the higher priority task will be executed first

  // The same priority as the existing task
  if (
    existingCallbackPriority === newCallbackPriority
  ) {
    return;
  }

  // The new task has a higher priority than the existing task
  // Cancel the execution of the existing task
  if(existingCallbackNode ! =null) {
    cancelCallback(existingCallbackNode);
  }

  // Start scheduling tasks
  // Check whether the priority of the new task is synchronization priority
  // If yes, use synchronous rendering, otherwise use concurrent rendering (time sharding)
  let newCallbackNode;
  if (newCallbackPriority === SyncLane) {
    ...
    newCallbackNode = null;
  } else{... newCallbackNode = scheduleCallback( schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root),
    );
  }

  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
}
Copy the code

After tackling the hunger problem, we call getNextLanes to get the lane of the task with the highest priority.

export function getNextLanes(root: FiberRoot, wipLanes: Lanes) :Lanes {
  const pendingLanes = root.pendingLanes;

  // When there are no tasks left, update
  if (pendingLanes === NoLanes) {
    returnNoLanes; }... }Copy the code

First determine whether pendingLanes is empty. According to the previous code, each generated task adds its own priority to the Attribute of pendingLanes of fiber root. This means that pendingLanes hold lanes for all tasks to be executed. If pendingLanes are empty, Then, it means that the task is complete, and there is no need to update, directly jump out.

EnsureRootIsScheduled can be seen with a null return to getNextLanes: ensureRootIsScheduled

// If nextLanes is empty, there are no tasks to execute and the update is interrupted
if (nextLanes === NoLanes) {
    // existingCallbackNode not empty indicates that a task is invoked by scheduler in concurrent mode but has not yet been executed
    // If nextLanes is empty, there is no task left. Even if the task is executed, no update can be done, so it needs to be cancelled
    if(existingCallbackNode ! = =null) {
      CancelCallback sets the task's callback to null
      // When scheduler loops through the taskQueue, it checks if the current task's callback is null
      // If the value is null, the taskQueue will be deleted
      cancelCallback(existingCallbackNode);
    }
    root.callbackNode = null;
    root.callbackPriority = NoLane;
    return;
}
Copy the code

Back to the code in getNextLanes:

// Check whether there are idle tasks in the task to be processed. If there are idle tasks, execute them first. Do not execute pending tasks
  / / such as:
  / / the current pendingLanes is: 17 b0000000000000000000000000010001 = 0
  // NonIdleLanes = 0b0001111111111111111111111111111
  / / the result is: the = 0 b0000000000000000000000000010001 = 17
  const nonIdlePendingLanes = pendingLanes & NonIdleLanes;

  // Check yes or no there are not idle and will be executed tasks
  if(nonIdlePendingLanes ! == NoLanes) {// Check if there are any unblocked tasks, if there are
    // From these unblocked tasks, find the task with the highest priority to execute
    & ~suspendedLanes = remove suspendedLanes from nonIdlePendingLanes
    const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;
    if(nonIdleUnblockedLanes ! == NoLanes) { nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes); }else {
      // nonIdleUnblockedLanes (tasks that are not idle and not blocking) are the remaining tasks that are not idle when pending tasks are removed
      // If nonIdleUnblockedLanes is empty, the task with the highest priority is selected from the remaining pending tasks
      const nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;
      if(nonIdlePingedLanes ! == NoLanes) { nextLanes = getHighestPriorityLanes(nonIdlePingedLanes); }}}else {
    // The only remaining work is Idle.
    // The rest of the tasks are idle
    // Find the tasks that are not blocked, and then find the highest priority execution
    const unblockedLanes = pendingLanes & ~suspendedLanes;
    if(unblockedLanes ! == NoLanes) { nextLanes = getHighestPriorityLanes(unblockedLanes); }else {
      // There are no unblocked tasks in the current task
      // We need to find the execution with the highest priority from the pending tasks
      if(pingedLanes ! == NoLanes) { nextLanes = getHighestPriorityLanes(pingedLanes); }}}Copy the code

If pengdingLanes are not empty, then the lanes that are not idle to be processed are fetched from pengdingLanes, for example:

PendingLanes are: =0b0100000000000000000000000010001 
Copy the code

The position of 1 on the leftmost is the idle position, which represents the idle task. The idle task has the lowest priority and needs to be processed after all other priority tasks are processed.

NonIdleLanes          = 0b0001111111111111111111111111111
Copy the code

NonIdleLanes represents the position of all unidle 1s, and uses an ampersand operation (in parity comparison, both values are 1, the result is 1, otherwise 0) to extract unidle tasks:

The result is: =0b0000000000000000000000000010001 = 17  
Copy the code

If there are lanes that are not idle, lanes that are not blocked are found first. If there are no lanes, lanes with the highest priority are found from suspended lanes.

If there are no idle lanes, the system searches for lanes that are not blocked from idle lanes. If there are no idle lanes, the system searches for all suspended lanes from idle lanes and finds the lane with the highest priority.

If none of the above is found, a null will be returned and the update will be displayed:

If no task is found from pendingLanes, return null
if (nextLanes === NoLanes) {
    return NoLanes;
}
Copy the code

If a task is currently executing, then wipLanes is not empty and the priority of the new task needs to be compared with that of the task being executed. If the new task has a lower priority than the ongoing task, it will continue rendering, or if the new task has a higher priority than the ongoing task, the current task will be cancelled and the new task will be executed first:

  // wipLanes are the lanes that are executing tasks, and nextLanes are the lanes that need to execute tasks this time
  // wipLanes ! == NoLanes: wipLanes Is not empty, which indicates that a task is being executed
  // If you are rendering and suddenly a new task is added, but the new task has a lower priority than the ongoing task, you will continue rendering
  // If the priority of the new task is higher than that of the ongoing task, cancel the current task and execute the new task
  if( wipLanes ! == NoLanes && wipLanes ! == nextLanes && (wipLanes & suspendedLanes) === NoLanes ) {const nextLane = getHighestPriorityLane(nextLanes);
    const wipLane = getHighestPriorityLane(wipLanes);
    if( nextLane >= wipLane || (nextLane === DefaultLane && (wipLane & TransitionLanes) ! == NoLanes) ) {returnwipLanes; }}Copy the code

EnsureRootIsScheduled let’s see how that is handled with one:

  constexistingCallbackNode = root.callbackNode; .const newCallbackPriority = getHighestPriorityLane(nextLanes);
  const existingCallbackPriority = root.callbackPriority;

  NextLanes gets the lane with the highest priority of all tasks
  // Then there are only two results compared to the priority of the current task:
  // 1. If the priority of the current task is the same as that of the existing task, the current new task will be interrupted and the previous existing task will be reused
  // 2. If the priority of the new task is greater than that of the existing task, the execution of the existing task will be cancelled, and the higher priority task will be executed first


  // The same priority as the existing task
  if (
    existingCallbackPriority === newCallbackPriority
  ) {
    return;
  }

  // The new task has a higher priority than the existing task
  // Cancel the execution of the existing task
  if(existingCallbackNode ! =null) {
    cancelCallback(existingCallbackNode);
  }
Copy the code

Now that lane has the highest priority of all tasks and the priority of the existing task existingCallbackPriority, there are only two results for comparing nextLanes with the current existing task:

  1. If the priority is the same as the existing task, the current new task will be interrupted and the previous existing task will be reused
  2. If the priority of the new task is higher than that of the existing task, the existing task will be cancelled and the higher-priority task will be executed first. In this way, the higher-priority task will jump the queue

Task scheduling starts

Next, start to create tasks and perform task scheduling:

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {...// Start scheduling tasks
  // Check whether the priority of the new task is synchronization priority
  // If yes, use synchronous rendering, otherwise use concurrent rendering (time sharding)
  let newCallbackNode;
  if (newCallbackPriority === SyncLane) {
    ...
    newCallbackNode = null;
  } else {
    let schedulerPriorityLevel;
    // The lanesToEventPriority function converts the priority of lane to the priority of the React event and then to the priority of the Scheduler based on the priority of the React event
    switch (lanesToEventPriority(nextLanes)) {
      case DiscreteEventPriority:
        schedulerPriorityLevel = ImmediateSchedulerPriority;
        break;
      case ContinuousEventPriority:
        schedulerPriorityLevel = UserBlockingSchedulerPriority;
        break;
      case DefaultEventPriority:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
      case IdleEventPriority:
        schedulerPriorityLevel = IdleSchedulerPriority;
        break;
      default:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
    }
    // Connect react to scheduler. Events generated by React are scheduled by scheduler as tasks
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  }

  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
}
Copy the code

Determine whether the priority of the new task is synchronous. If yes, use synchronous rendering mode; otherwise, use concurrent rendering mode and scheduler to schedule tasks. When using concurrent rendering mode, the priority of Lane will be converted to the priority of React events. The React event priority is then converted to the priority of the Scheduler, which splits tasks according to its own priority.

I’m going to update the use of Lanes in the React source code in the priority Lane model.