text: /**
   * Copyright (c) Facebook, Inc. and its affiliates.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @flow
   */
  
  import type {FiberRoot, ReactPriorityLevel} from './ReactInternalTypes';
  
  // TODO: Ideally these types would be opaque but that doesn't work well with
  // our reconciler fork infra, since these leak into non-reconciler packages.
  export type LanePriority =
    | 0
    | 1
    | 2
    | 3
    | 4
    | 5
    | 6
    | 7
    | 8
    | 9
    | 10
    | 11
    | 12
    | 13
    | 14
    | 15
    | 16
    | 17;
  
  export type Lanes = number;
  export type Lane = number;
  export type LaneMap<T> = Array<T>;
  
  import invariant from 'shared/invariant';
  import {
    enableCache,
    enableTransitionEntanglement,
  } from 'shared/ReactFeatureFlags';
  
  import {
    ImmediatePriority as ImmediateSchedulerPriority,
    UserBlockingPriority as UserBlockingSchedulerPriority,
    NormalPriority as NormalSchedulerPriority,
    LowPriority as LowSchedulerPriority,
    IdlePriority as IdleSchedulerPriority,
    NoPriority as NoSchedulerPriority,
  } from './SchedulerWithReactIntegration.old';
  
  export const SyncLanePriority: LanePriority = 15;
  export const SyncBatchedLanePriority: LanePriority = 14;
  
  const InputDiscreteHydrationLanePriority: LanePriority = 13;
  export const InputDiscreteLanePriority: LanePriority = 12;
  
  const InputContinuousHydrationLanePriority: LanePriority = 11;
  export const InputContinuousLanePriority: LanePriority = 10;
  
  const DefaultHydrationLanePriority: LanePriority = 9;
  export const DefaultLanePriority: LanePriority = 8;
  
  const TransitionHydrationPriority: LanePriority = 7;
  export const TransitionPriority: LanePriority = 6;
  
  const RetryLanePriority: LanePriority = 5;
  
  const SelectiveHydrationLanePriority: LanePriority = 4;
  
  const IdleHydrationLanePriority: LanePriority = 3;
  const IdleLanePriority: LanePriority = 2;
  
  const OffscreenLanePriority: LanePriority = 1;
  
  export const NoLanePriority: LanePriority = 0;
  
  const TotalLanes = 31;
  
  export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
  export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;
  
  export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;
  export const SyncBatchedLane: Lane = /*                 */ 0b0000000000000000000000000000010;
  
  export const InputDiscreteHydrationLane: Lane = /*      */ 0b0000000000000000000000000000100;
  const InputDiscreteLanes: Lanes = /*                    */ 0b0000000000000000000000000011000;
  
  const InputContinuousHydrationLane: Lane = /*           */ 0b0000000000000000000000000100000;
  const InputContinuousLanes: Lanes = /*                  */ 0b0000000000000000000000011000000;
  
  export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000100000000;
  export const DefaultLanes: Lanes = /*                   */ 0b0000000000000000000111000000000;
  
  const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000001000000000000;
  const TransitionLanes: Lanes = /*                       */ 0b0000000001111111110000000000000;
  const FirstTransitionLane: Lane = /*                    */ 0b0000000000000000010000000000000;
  
  const RetryLanes: Lanes = /*                            */ 0b0000011110000000000000000000000;
  
  const FirstRetryLane: Lanes = /*                        */ 0b0000000010000000000000000000000;
  export const SomeRetryLane: Lane = FirstRetryLane;
  
  export const SelectiveHydrationLane: Lane = /*          */ 0b0000100000000000000000000000000;
  
  const NonIdleLanes = /*                                 */ 0b0000111111111111111111111111111;
  
  export const IdleHydrationLane: Lane = /*               */ 0b0001000000000000000000000000000;
  const IdleLanes: Lanes = /*                             */ 0b0110000000000000000000000000000;
  
  export const OffscreenLane: Lane = /*                   */ 0b1000000000000000000000000000000;
  
  export const NoTimestamp = -1;
  
  let currentUpdateLanePriority: LanePriority = NoLanePriority;
  
  let nextTransitionLane: Lane = FirstTransitionLane;
  let nextRetryLane: Lane = FirstRetryLane;
  
  export function getCurrentUpdateLanePriority(): LanePriority {
    return currentUpdateLanePriority;
  }
  
  export function setCurrentUpdateLanePriority(newLanePriority: LanePriority) {
    currentUpdateLanePriority = newLanePriority;
  }
  
  // "Registers" used to "return" multiple values
  // Used by getHighestPriorityLanes and getNextLanes:
  let return_highestLanePriority: LanePriority = DefaultLanePriority;
  
  function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
    if ((SyncLane & lanes) !== NoLanes) {
      return_highestLanePriority = SyncLanePriority;
      return SyncLane;
    }
    if ((SyncBatchedLane & lanes) !== NoLanes) {
      return_highestLanePriority = SyncBatchedLanePriority;
      return SyncBatchedLane;
    }
    if ((InputDiscreteHydrationLane & lanes) !== NoLanes) {
      return_highestLanePriority = InputDiscreteHydrationLanePriority;
      return InputDiscreteHydrationLane;
    }
    const inputDiscreteLanes = InputDiscreteLanes & lanes;
    if (inputDiscreteLanes !== NoLanes) {
      return_highestLanePriority = InputDiscreteLanePriority;
      return inputDiscreteLanes;
    }
    if ((lanes & InputContinuousHydrationLane) !== NoLanes) {
      return_highestLanePriority = InputContinuousHydrationLanePriority;
      return InputContinuousHydrationLane;
    }
    const inputContinuousLanes = InputContinuousLanes & lanes;
    if (inputContinuousLanes !== NoLanes) {
      return_highestLanePriority = InputContinuousLanePriority;
      return inputContinuousLanes;
    }
    if ((lanes & DefaultHydrationLane) !== NoLanes) {
      return_highestLanePriority = DefaultHydrationLanePriority;
      return DefaultHydrationLane;
    }
    const defaultLanes = DefaultLanes & lanes;
    if (defaultLanes !== NoLanes) {
      return_highestLanePriority = DefaultLanePriority;
      return defaultLanes;
    }
    if ((lanes & TransitionHydrationLane) !== NoLanes) {
      return_highestLanePriority = TransitionHydrationPriority;
      return TransitionHydrationLane;
    }
    const transitionLanes = TransitionLanes & lanes;
    if (transitionLanes !== NoLanes) {
      return_highestLanePriority = TransitionPriority;
      return transitionLanes;
    }
    const retryLanes = RetryLanes & lanes;
    if (retryLanes !== NoLanes) {
      return_highestLanePriority = RetryLanePriority;
      return retryLanes;
    }
    if (lanes & SelectiveHydrationLane) {
      return_highestLanePriority = SelectiveHydrationLanePriority;
      return SelectiveHydrationLane;
    }
    if ((lanes & IdleHydrationLane) !== NoLanes) {
      return_highestLanePriority = IdleHydrationLanePriority;
      return IdleHydrationLane;
    }
    const idleLanes = IdleLanes & lanes;
    if (idleLanes !== NoLanes) {
      return_highestLanePriority = IdleLanePriority;
      return idleLanes;
    }
    if ((OffscreenLane & lanes) !== NoLanes) {
      return_highestLanePriority = OffscreenLanePriority;
      return OffscreenLane;
    }
    if (__DEV__) {
      console.error('Should have found matching lanes. This is a bug in React.');
    }
    // This shouldn't be reachable, but as a fallback, return the entire bitmask.
    return_highestLanePriority = DefaultLanePriority;
    return lanes;
  }
  
  export function schedulerPriorityToLanePriority(
    schedulerPriorityLevel: ReactPriorityLevel,
  ): LanePriority {
    switch (schedulerPriorityLevel) {
      case ImmediateSchedulerPriority:
        return SyncLanePriority;
      case UserBlockingSchedulerPriority:
        return InputContinuousLanePriority;
      case NormalSchedulerPriority:
      case LowSchedulerPriority:
        // TODO: Handle LowSchedulerPriority, somehow. Maybe the same lane as hydration.
        return DefaultLanePriority;
      case IdleSchedulerPriority:
        return IdleLanePriority;
      default:
        return NoLanePriority;
    }
  }
  
  export function lanePriorityToSchedulerPriority(
    lanePriority: LanePriority,
  ): ReactPriorityLevel {
    switch (lanePriority) {
      case SyncLanePriority:
      case SyncBatchedLanePriority:
        return ImmediateSchedulerPriority;
      case InputDiscreteHydrationLanePriority:
      case InputDiscreteLanePriority:
      case InputContinuousHydrationLanePriority:
      case InputContinuousLanePriority:
        return UserBlockingSchedulerPriority;
      case DefaultHydrationLanePriority:
      case DefaultLanePriority:
      case TransitionHydrationPriority:
      case TransitionPriority:
      case SelectiveHydrationLanePriority:
      case RetryLanePriority:
        return NormalSchedulerPriority;
      case IdleHydrationLanePriority:
      case IdleLanePriority:
      case OffscreenLanePriority:
        return IdleSchedulerPriority;
      case NoLanePriority:
        return NoSchedulerPriority;
      default:
        invariant(
          false,
          'Invalid update priority: %s. This is a bug in React.',
          lanePriority,
        );
    }
  }
  
  export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
    // Early bailout if there's no pending work left.
    const pendingLanes = root.pendingLanes;
    if (pendingLanes === NoLanes) {
      return_highestLanePriority = NoLanePriority;
      return NoLanes;
    }
  
    let nextLanes = NoLanes;
    let nextLanePriority = NoLanePriority;
  
    const expiredLanes = root.expiredLanes;
    const suspendedLanes = root.suspendedLanes;
    const pingedLanes = root.pingedLanes;
  
    // Check if any work has expired.
    if (expiredLanes !== NoLanes) {
      nextLanes = expiredLanes;
      nextLanePriority = return_highestLanePriority = SyncLanePriority;
    } else {
      // Do not work on any idle work until all the non-idle work has finished,
      // even if the work is suspended.
      const nonIdlePendingLanes = pendingLanes & NonIdleLanes;
      if (nonIdlePendingLanes !== NoLanes) {
        const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;
        if (nonIdleUnblockedLanes !== NoLanes) {
          nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
          nextLanePriority = return_highestLanePriority;
        } else {
          const nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;
          if (nonIdlePingedLanes !== NoLanes) {
            nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);
            nextLanePriority = return_highestLanePriority;
          }
        }
      } else {
        // The only remaining work is Idle.
        const unblockedLanes = pendingLanes & ~suspendedLanes;
        if (unblockedLanes !== NoLanes) {
          nextLanes = getHighestPriorityLanes(unblockedLanes);
          nextLanePriority = return_highestLanePriority;
        } else {
          if (pingedLanes !== NoLanes) {
            nextLanes = getHighestPriorityLanes(pingedLanes);
            nextLanePriority = return_highestLanePriority;
          }
        }
      }
    }
  
    if (nextLanes === NoLanes) {
      // This should only be reachable if we're suspended
      // TODO: Consider warning in this path if a fallback timer is not scheduled.
      return NoLanes;
    }
  
    // If we're already in the middle of a render, switching lanes will interrupt
    // it and we'll lose our progress. We should only do this if the new lanes are
    // higher priority.
    if (
      wipLanes !== NoLanes &&
      wipLanes !== nextLanes &&
      // If we already suspended with a delay, then interrupting is fine. Don't
      // bother waiting until the root is complete.
      (wipLanes & suspendedLanes) === NoLanes
    ) {
      getHighestPriorityLanes(wipLanes);
      const wipLanePriority = return_highestLanePriority;
      if (nextLanePriority <= wipLanePriority) {
        return wipLanes;
      } else {
        return_highestLanePriority = nextLanePriority;
      }
    }
  
    // Check for entangled lanes and add them to the batch.
    //
    // A lane is said to be entangled with another when it's not allowed to render
    // in a batch that does not also include the other lane. Typically we do this
    // when multiple updates have the same source, and we only want to respond to
    // the most recent event from that source.
    //
    // Note that we apply entanglements *after* checking for partial work above.
    // This means that if a lane is entangled during an interleaved event while
    // it's already rendering, we won't interrupt it. This is intentional, since
    // entanglement is usually "best effort": we'll try our best to render the
    // lanes in the same batch, but it's not worth throwing out partially
    // completed work in order to do it.
    // TODO: Reconsider this. The counter-argument is that the partial work
    // represents an intermediate state, which we don't want to show to the user.
    // And by spending extra time finishing it, we're increasing the amount of
    // time it takes to show the final state, which is what they are actually
    // waiting for.
    //
    // For those exceptions where entanglement is semantically important, like
    // useMutableSource, we should ensure that there is no partial work at the
    // time we apply the entanglement.
    const entangledLanes = root.entangledLanes;
    if (entangledLanes !== NoLanes) {
      const entanglements = root.entanglements;
      let lanes = nextLanes & entangledLanes;
      while (lanes > 0) {
        const index = pickArbitraryLaneIndex(lanes);
        const lane = 1 << index;
  
        nextLanes |= entanglements[index];
  
        lanes &= ~lane;
      }
    }
  
    return nextLanes;
  }
  
  export function getMostRecentEventTime(root: FiberRoot, lanes: Lanes): number {
    const eventTimes = root.eventTimes;
  
    let mostRecentEventTime = NoTimestamp;
    while (lanes > 0) {
      const index = pickArbitraryLaneIndex(lanes);
      const lane = 1 << index;
  
      const eventTime = eventTimes[index];
      if (eventTime > mostRecentEventTime) {
        mostRecentEventTime = eventTime;
      }
  
      lanes &= ~lane;
    }
  
    return mostRecentEventTime;
  }
  
  function computeExpirationTime(lane: Lane, currentTime: number) {
    // TODO: Expiration heuristic is constant per lane, so could use a map.
    getHighestPriorityLanes(lane);
    const priority = return_highestLanePriority;
    if (priority >= InputContinuousLanePriority) {
      // User interactions should expire slightly more quickly.
      //
      // NOTE: This is set to the corresponding constant as in Scheduler.js. When
      // we made it larger, a product metric in www regressed, suggesting there's
      // a user interaction that's being starved by a series of synchronous
      // updates. If that theory is correct, the proper solution is to fix the
      // starvation. However, this scenario supports the idea that expiration
      // times are an important safeguard when starvation does happen.
      //
      // Also note that, in the case of user input specifically, this will soon no
      // longer be an issue because we plan to make user input synchronous by
      // default (until you enter `startTransition`, of course.)
      //
      // If weren't planning to make these updates synchronous soon anyway, I
      // would probably make this number a configurable parameter.
      return currentTime + 250;
    } else if (priority >= TransitionPriority) {
      return currentTime + 5000;
    } else {
      // Anything idle priority or lower should never expire.
      return NoTimestamp;
    }
  }
  
  export function markStarvedLanesAsExpired(
    root: FiberRoot,
    currentTime: number,
  ): void {
    // TODO: This gets called every time we yield. We can optimize by storing
    // the earliest expiration time on the root. Then use that to quickly bail out
    // of this function.
  
    const pendingLanes = root.pendingLanes;
    const suspendedLanes = root.suspendedLanes;
    const pingedLanes = root.pingedLanes;
    const expirationTimes = root.expirationTimes;
  
    // Iterate through the pending lanes and check if we've reached their
    // expiration time. If so, we'll assume the update is being starved and mark
    // it as expired to force it to finish.
    let lanes = pendingLanes;
    while (lanes > 0) {
      const index = pickArbitraryLaneIndex(lanes);
      const lane = 1 << index;
  
      const expirationTime = expirationTimes[index];
      if (expirationTime === NoTimestamp) {
        // Found a pending lane with no expiration time. If it's not suspended, or
        // if it's pinged, assume it's CPU-bound. Compute a new expiration time
        // using the current time.
        if (
          (lane & suspendedLanes) === NoLanes ||
          (lane & pingedLanes) !== NoLanes
        ) {
          // Assumes timestamps are monotonically increasing.
          expirationTimes[index] = computeExpirationTime(lane, currentTime);
        }
      } else if (expirationTime <= currentTime) {
        // This lane expired
        root.expiredLanes |= lane;
      }
  
      lanes &= ~lane;
    }
  }
  
  // This returns the highest priority pending lanes regardless of whether they
  // are suspended.
  export function getHighestPriorityPendingLanes(root: FiberRoot) {
    return getHighestPriorityLanes(root.pendingLanes);
  }
  
  export function getLanesToRetrySynchronouslyOnError(root: FiberRoot): Lanes {
    const everythingButOffscreen = root.pendingLanes & ~OffscreenLane;
    if (everythingButOffscreen !== NoLanes) {
      return everythingButOffscreen;
    }
    if (everythingButOffscreen & OffscreenLane) {
      return OffscreenLane;
    }
    return NoLanes;
  }
  
  export function returnNextLanesPriority() {
    return return_highestLanePriority;
  }
  export function includesNonIdleWork(lanes: Lanes) {
    return (lanes & NonIdleLanes) !== NoLanes;
  }
  export function includesOnlyRetries(lanes: Lanes) {
    return (lanes & RetryLanes) === lanes;
  }
  export function includesOnlyTransitions(lanes: Lanes) {
    return (lanes & TransitionLanes) === lanes;
  }
  
  export function isTransitionLane(lane: Lane) {
    return (lane & TransitionLanes) !== 0;
  }
  
  // To ensure consistency across multiple updates in the same event, this should
  // be a pure function, so that it always returns the same lane for given inputs.
  export function findUpdateLane(
    lanePriority: LanePriority,
    wipLanes: Lanes,
  ): Lane {
    if (enableTransitionEntanglement) {
      // Ignore wipLanes. Always assign to the same bit per priority.
      switch (lanePriority) {
        case NoLanePriority:
          break;
        case SyncLanePriority:
          return SyncLane;
        case SyncBatchedLanePriority:
          return SyncBatchedLane;
        case InputDiscreteLanePriority: {
          return pickArbitraryLane(InputDiscreteLanes);
        }
        case InputContinuousLanePriority: {
          return pickArbitraryLane(InputContinuousLanes);
        }
        case DefaultLanePriority: {
          return pickArbitraryLane(DefaultLanes);
        }
        case TransitionPriority: // Should be handled by findTransitionLane instead
        case RetryLanePriority: // Should be handled by findRetryLane instead
          break;
        case IdleLanePriority:
          return pickArbitraryLane(IdleLanes);
        default:
          // The remaining priorities are not valid for updates
          break;
      }
    } else {
      // Old behavior that uses wipLanes to shift interleaved updates into a
      // separate lane. This is no longer needed because we put interleaved
      // updates on a special queue.
      switch (lanePriority) {
        case NoLanePriority:
          break;
        case SyncLanePriority:
          return SyncLane;
        case SyncBatchedLanePriority:
          return SyncBatchedLane;
        case InputDiscreteLanePriority: {
          const lane = pickArbitraryLane(InputDiscreteLanes & ~wipLanes);
          if (lane === NoLane) {
            // Shift to the next priority level
            return findUpdateLane(InputContinuousLanePriority, wipLanes);
          }
          return lane;
        }
        case InputContinuousLanePriority: {
          const lane = pickArbitraryLane(InputContinuousLanes & ~wipLanes);
          if (lane === NoLane) {
            // Shift to the next priority level
            return findUpdateLane(DefaultLanePriority, wipLanes);
          }
          return lane;
        }
        case DefaultLanePriority: {
          let lane = pickArbitraryLane(DefaultLanes & ~wipLanes);
          if (lane === NoLane) {
            // If all the default lanes are already being worked on, look for a
            // lane in the transition range.
            lane = pickArbitraryLane(TransitionLanes & ~wipLanes);
            if (lane === NoLane) {
              // All the transition lanes are taken, too. This should be very
              // rare, but as a last resort, pick a default lane. This will have
              // the effect of interrupting the current work-in-progress render.
              lane = pickArbitraryLane(DefaultLanes);
            }
          }
          return lane;
        }
        case TransitionPriority: // Should be handled by findTransitionLane instead
        case RetryLanePriority: // Should be handled by findRetryLane instead
          break;
        case IdleLanePriority:
          let lane = pickArbitraryLane(IdleLanes & ~wipLanes);
          if (lane === NoLane) {
            lane = pickArbitraryLane(IdleLanes);
          }
          return lane;
        default:
          // The remaining priorities are not valid for updates
          break;
      }
    }
    invariant(
      false,
      'Invalid update priority: %s. This is a bug in React.',
      lanePriority,
    );
  }
  
  export function claimNextTransitionLane(): Lane {
    // Cycle through the lanes, assigning each new transition to the next lane.
    // In most cases, this means every transition gets its own lane, until we
    // run out of lanes and cycle back to the beginning.
    const lane = nextTransitionLane;
    nextTransitionLane <<= 1;
    if ((nextTransitionLane & TransitionLanes) === 0) {
      nextTransitionLane = FirstTransitionLane;
    }
    return lane;
  }
  
  export function claimNextRetryLane(): Lane {
    const lane = nextRetryLane;
    nextRetryLane <<= 1;
    if ((nextRetryLane & RetryLanes) === 0) {
      nextRetryLane = FirstRetryLane;
    }
    return lane;
  }
  
  function getHighestPriorityLane(lanes: Lanes) {
    return lanes & -lanes;
  }
  
  export function pickArbitraryLane(lanes: Lanes): Lane {
    // This wrapper function gets inlined. Only exists so to communicate that it
    // doesn't matter which bit is selected; you can pick any bit without
    // affecting the algorithms where its used. Here I'm using
    // getHighestPriorityLane because it requires the fewest operations.
    return getHighestPriorityLane(lanes);
  }
  
  function pickArbitraryLaneIndex(lanes: Lanes) {
    return 31 - clz32(lanes);
  }
  
  function laneToIndex(lane: Lane) {
    return pickArbitraryLaneIndex(lane);
  }
  
  export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane) {
    return (a & b) !== NoLanes;
  }
  
  export function isSubsetOfLanes(set: Lanes, subset: Lanes | Lane) {
    return (set & subset) === subset;
  }
  
  export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
    return a | b;
  }
  
  export function removeLanes(set: Lanes, subset: Lanes | Lane): Lanes {
    return set & ~subset;
  }
  
  export function intersectLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
    return a & b;
  }
  
  // Seems redundant, but it changes the type from a single lane (used for
  // updates) to a group of lanes (used for flushing work).
  export function laneToLanes(lane: Lane): Lanes {
    return lane;
  }
  
  export function higherPriorityLane(a: Lane, b: Lane) {
    // This works because the bit ranges decrease in priority as you go left.
    return a !== NoLane && a < b ? a : b;
  }
  
  export function higherLanePriority(
    a: LanePriority,
    b: LanePriority,
  ): LanePriority {
    return a !== NoLanePriority && a > b ? a : b;
  }
  
  export function createLaneMap<T>(initial: T): LaneMap<T> {
    // Intentionally pushing one by one.
    // https://v8.dev/blog/elements-kinds#avoid-creating-holes
    const laneMap = [];
    for (let i = 0; i < TotalLanes; i++) {
      laneMap.push(initial);
    }
    return laneMap;
  }
  
  export function markRootUpdated(
    root: FiberRoot,
    updateLane: Lane,
    eventTime: number,
  ) {
    root.pendingLanes |= updateLane;
  
    // If there are any suspended transitions, it's possible this new update
    // could unblock them. Clear the suspended lanes so that we can try rendering
    // them again.
    //
    // TODO: We really only need to unsuspend only lanes that are in the
    // `subtreeLanes` of the updated fiber, or the update lanes of the return
    // path. This would exclude suspended updates in an unrelated sibling tree,
    // since there's no way for this update to unblock it.
    //
    // We don't do this if the incoming update is idle, because we never process
    // idle updates until after all the regular updates have finished; there's no
    // way it could unblock a transition.
    if ((updateLane & IdleLanes) === NoLanes) {
      root.suspendedLanes = NoLanes;
      root.pingedLanes = NoLanes;
    }
  
    const eventTimes = root.eventTimes;
    const index = laneToIndex(updateLane);
    // We can always overwrite an existing timestamp because we prefer the most
    // recent event, and we assume time is monotonically increasing.
    eventTimes[index] = eventTime;
  }
  
  export function markRootSuspended(root: FiberRoot, suspendedLanes: Lanes) {
    root.suspendedLanes |= suspendedLanes;
    root.pingedLanes &= ~suspendedLanes;
  
    // The suspended lanes are no longer CPU-bound. Clear their expiration times.
    const expirationTimes = root.expirationTimes;
    let lanes = suspendedLanes;
    while (lanes > 0) {
      const index = pickArbitraryLaneIndex(lanes);
      const lane = 1 << index;
  
      expirationTimes[index] = NoTimestamp;
  
      lanes &= ~lane;
    }
  }
  
  export function markRootPinged(
    root: FiberRoot,
    pingedLanes: Lanes,
    eventTime: number,
  ) {
    root.pingedLanes |= root.suspendedLanes & pingedLanes;
  }
  
  export function markRootExpired(root: FiberRoot, expiredLanes: Lanes) {
    root.expiredLanes |= expiredLanes & root.pendingLanes;
  }
  
  export function markDiscreteUpdatesExpired(root: FiberRoot) {
    root.expiredLanes |= InputDiscreteLanes & root.pendingLanes;
  }
  
  export function hasDiscreteLanes(lanes: Lanes) {
    return (lanes & InputDiscreteLanes) !== NoLanes;
  }
  
  export function markRootMutableRead(root: FiberRoot, updateLane: Lane) {
    root.mutableReadLanes |= updateLane & root.pendingLanes;
  }
  
  export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
    const noLongerPendingLanes = root.pendingLanes & ~remainingLanes;
  
    root.pendingLanes = remainingLanes;
  
    // Let's try everything again
    root.suspendedLanes = 0;
    root.pingedLanes = 0;
  
    root.expiredLanes &= remainingLanes;
    root.mutableReadLanes &= remainingLanes;
  
    root.entangledLanes &= remainingLanes;
  
    if (enableCache) {
      const pooledCacheLanes = (root.pooledCacheLanes &= remainingLanes);
      if (pooledCacheLanes === NoLanes) {
        // None of the remaining work relies on the cache pool. Clear it so
        // subsequent requests get a new cache.
        root.pooledCache = null;
      }
    }
  
    const entanglements = root.entanglements;
    const eventTimes = root.eventTimes;
    const expirationTimes = root.expirationTimes;
  
    // Clear the lanes that no longer have pending work
    let lanes = noLongerPendingLanes;
    while (lanes > 0) {
      const index = pickArbitraryLaneIndex(lanes);
      const lane = 1 << index;
  
      entanglements[index] = NoLanes;
      eventTimes[index] = NoTimestamp;
      expirationTimes[index] = NoTimestamp;
  
      lanes &= ~lane;
    }
  }
  
  export function markRootEntangled(root: FiberRoot, entangledLanes: Lanes) {
    // In addition to entangling each of the given lanes with each other, we also
    // have to consider _transitive_ entanglements. For each lane that is already
    // entangled with *any* of the given lanes, that lane is now transitively
    // entangled with *all* the given lanes.
    //
    // Translated: If C is entangled with A, then entangling A with B also
    // entangles C with B.
    //
    // If this is hard to grasp, it might help to intentionally break this
    // function and look at the tests that fail in ReactTransition-test.js. Try
    // commenting out one of the conditions below.
  
    const rootEntangledLanes = (root.entangledLanes |= entangledLanes);
    const entanglements = root.entanglements;
    let lanes = rootEntangledLanes;
    while (lanes) {
      const index = pickArbitraryLaneIndex(lanes);
      const lane = 1 << index;
      if (
        // Is this one of the newly entangled lanes?
        (lane & entangledLanes) |
        // Is this lane transitively entangled with the newly entangled lanes?
        (entanglements[index] & entangledLanes)
      ) {
        entanglements[index] |= entangledLanes;
      }
      lanes &= ~lane;
    }
  }
  
  export function getBumpedLaneForHydration(
    root: FiberRoot,
    renderLanes: Lanes,
  ): Lane {
    getHighestPriorityLanes(renderLanes);
    const highestLanePriority = return_highestLanePriority;
  
    let lane;
    switch (highestLanePriority) {
      case SyncLanePriority:
      case SyncBatchedLanePriority:
        lane = NoLane;
        break;
      case InputDiscreteHydrationLanePriority:
      case InputDiscreteLanePriority:
        lane = InputDiscreteHydrationLane;
        break;
      case InputContinuousHydrationLanePriority:
      case InputContinuousLanePriority:
        lane = InputContinuousHydrationLane;
        break;
      case DefaultHydrationLanePriority:
      case DefaultLanePriority:
        lane = DefaultHydrationLane;
        break;
      case TransitionHydrationPriority:
      case TransitionPriority:
        lane = TransitionHydrationLane;
        break;
      case RetryLanePriority:
        // Shouldn't be reachable under normal circumstances, so there's no
        // dedicated lane for retry priority. Use the one for long transitions.
        lane = TransitionHydrationLane;
        break;
      case SelectiveHydrationLanePriority:
        lane = SelectiveHydrationLane;
        break;
      case IdleHydrationLanePriority:
      case IdleLanePriority:
        lane = IdleHydrationLane;
        break;
      case OffscreenLanePriority:
      case NoLanePriority:
        lane = NoLane;
        break;
      default:
        invariant(false, 'Invalid lane: %s. This is a bug in React.', lane);
    }
  
    // Check if the lane we chose is suspended. If so, that indicates that we
    // already attempted and failed to hydrate at that level. Also check if we're
    // already rendering that lane, which is rare but could happen.
    if ((lane & (root.suspendedLanes | renderLanes)) !== NoLane) {
      // Give up trying to hydrate and fall back to client render.
      return NoLane;
    }
  
    return lane;
  }
  
  const clz32 = Math.clz32 ? Math.clz32 : clz32Fallback;
  
  // Count leading zeros. Only used on lanes, so assume input is an integer.
  // Based on:
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32
  const log = Math.log;
  const LN2 = Math.LN2;
  function clz32Fallback(lanes: Lanes | Lane) {
    if (lanes === 0) {
      return 32;
    }
    return (31 - ((log(lanes) / LN2) | 0)) | 0;
  }
  