import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {
UpdateQueue as HookQueue,
Update as HookUpdate,
} from './ReactFiberHooks';
import type {
SharedQueue as ClassQueue,
Update as ClassUpdate,
} from './ReactFiberClassUpdateQueue';
import type {Lane, Lanes} from './ReactFiberLane';
import type {OffscreenInstance} from './ReactFiberOffscreenComponent';
import {
warnAboutUpdateOnNotYetMountedFiberInDEV,
throwIfInfiniteUpdateLoopDetected,
getWorkInProgressRoot,
} from './ReactFiberWorkLoop';
import {NoLane, NoLanes, mergeLanes, markHiddenUpdate} from './ReactFiberLane';
import {NoFlags, Placement, Hydrating} from './ReactFiberFlags';
import {HostRoot, OffscreenComponent} from './ReactWorkTags';
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
export type ConcurrentUpdate = {
next: ConcurrentUpdate,
lane: Lane,
};
type ConcurrentQueue = {
pending: ConcurrentUpdate | null,
};
const concurrentQueues: Array<any> = [];
let concurrentQueuesIndex = 0;
let concurrentlyUpdatedLanes: Lanes = NoLanes;
export function finishQueueingConcurrentUpdates(): void {
const endIndex = concurrentQueuesIndex;
concurrentQueuesIndex = 0;
concurrentlyUpdatedLanes = NoLanes;
let i = 0;
while (i < endIndex) {
const fiber: Fiber = concurrentQueues[i];
concurrentQueues[i++] = null;
const queue: ConcurrentQueue = concurrentQueues[i];
concurrentQueues[i++] = null;
const update: ConcurrentUpdate = concurrentQueues[i];
concurrentQueues[i++] = null;
const lane: Lane = concurrentQueues[i];
concurrentQueues[i++] = null;
if (queue !== null && update !== null) {
const pending = queue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}
if (lane !== NoLane) {
markUpdateLaneFromFiberToRoot(fiber, update, lane);
}
}
}
export function getConcurrentlyUpdatedLanes(): Lanes {
return concurrentlyUpdatedLanes;
}
function enqueueUpdate(
fiber: Fiber,
queue: ConcurrentQueue | null,
update: ConcurrentUpdate | null,
lane: Lane,
) {
concurrentQueues[concurrentQueuesIndex++] = fiber;
concurrentQueues[concurrentQueuesIndex++] = queue;
concurrentQueues[concurrentQueuesIndex++] = update;
concurrentQueues[concurrentQueuesIndex++] = lane;
concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);
fiber.lanes = mergeLanes(fiber.lanes, lane);
const alternate = fiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, lane);
}
}
export function enqueueConcurrentHookUpdate<S, A>(
fiber: Fiber,
queue: HookQueue<S, A>,
update: HookUpdate<S, A>,
lane: Lane,
): FiberRoot | null {
const concurrentQueue: ConcurrentQueue = (queue: any);
const concurrentUpdate: ConcurrentUpdate = (update: any);
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
return getRootForUpdatedFiber(fiber);
}
export function enqueueConcurrentHookUpdateAndEagerlyBailout<S, A>(
fiber: Fiber,
queue: HookQueue<S, A>,
update: HookUpdate<S, A>,
): void {
const lane = NoLane;
const concurrentQueue: ConcurrentQueue = (queue: any);
const concurrentUpdate: ConcurrentUpdate = (update: any);
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
const isConcurrentlyRendering = getWorkInProgressRoot() !== null;
if (!isConcurrentlyRendering) {
finishQueueingConcurrentUpdates();
}
}
export function enqueueConcurrentClassUpdate<State>(
fiber: Fiber,
queue: ClassQueue<State>,
update: ClassUpdate<State>,
lane: Lane,
): FiberRoot | null {
const concurrentQueue: ConcurrentQueue = (queue: any);
const concurrentUpdate: ConcurrentUpdate = (update: any);
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
return getRootForUpdatedFiber(fiber);
}
export function enqueueConcurrentRenderForLane(
fiber: Fiber,
lane: Lane,
): FiberRoot | null {
enqueueUpdate(fiber, null, null, lane);
return getRootForUpdatedFiber(fiber);
}
export function unsafe_markUpdateLaneFromFiberToRoot(
sourceFiber: Fiber,
lane: Lane,
): FiberRoot | null {
const root = getRootForUpdatedFiber(sourceFiber);
markUpdateLaneFromFiberToRoot(sourceFiber, null, lane);
return root;
}
function markUpdateLaneFromFiberToRoot(
sourceFiber: Fiber,
update: ConcurrentUpdate | null,
lane: Lane,
): null | FiberRoot {
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
let alternate = sourceFiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, lane);
}
let isHidden = false;
let parent = sourceFiber.return;
let node = sourceFiber;
while (parent !== null) {
parent.childLanes = mergeLanes(parent.childLanes, lane);
alternate = parent.alternate;
if (alternate !== null) {
alternate.childLanes = mergeLanes(alternate.childLanes, lane);
}
if (parent.tag === OffscreenComponent) {
const offscreenInstance: OffscreenInstance | null = parent.stateNode;
if (
offscreenInstance !== null &&
!(offscreenInstance._visibility & OffscreenVisible)
) {
isHidden = true;
}
}
node = parent;
parent = parent.return;
}
if (node.tag === HostRoot) {
const root: FiberRoot = node.stateNode;
if (isHidden && update !== null) {
markHiddenUpdate(root, update, lane);
}
return root;
}
return null;
}
function getRootForUpdatedFiber(sourceFiber: Fiber): FiberRoot | null {
throwIfInfiniteUpdateLoopDetected();
detectUpdateOnUnmountedFiber(sourceFiber, sourceFiber);
let node = sourceFiber;
let parent = node.return;
while (parent !== null) {
detectUpdateOnUnmountedFiber(sourceFiber, node);
node = parent;
parent = node.return;
}
return node.tag === HostRoot ? (node.stateNode: FiberRoot) : null;
}
function detectUpdateOnUnmountedFiber(sourceFiber: Fiber, parent: Fiber) {
if (__DEV__) {
const alternate = parent.alternate;
if (
alternate === null &&
(parent.flags & (Placement | Hydrating)) !== NoFlags
) {
warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
}
}
}