diff --git a/src/fiber/index.ts b/src/fiber/index.ts new file mode 100644 index 0000000..4e2b6f1 --- /dev/null +++ b/src/fiber/index.ts @@ -0,0 +1,392 @@ +/* **************** */ +/* Fiber Module Exports */ +/* **************** */ + +// Types +export { + // WorkTag + WorkTag, + type WorkTag as WorkTagType, + // Branded types + type Lane, + type Lanes, + type Flags, + createLane, + createLanes, + createFlags, + // Lane constants + NoLane, + NoLanes, + SyncLane, + InputContinuousLane, + DefaultLane, + TransitionLane1, + TransitionLane2, + IdleLane, + OffscreenLane, + // Flag constants + NoFlags, + PerformedWork, + Placement, + Update as UpdateFlag, + ChildDeletion, + ContentReset, + Callback, + DidCapture, + ForceClientRender, + Ref, + Snapshot, + Passive, + Hydrating, + Visibility, + StoreConsistency, + PlacementAndUpdate, + Deletion, + MutationMask, + LayoutMask, + PassiveMask, + // Hook effect tags + HookEffectTag, + type HookEffectTag as HookEffectTagType, + // Core types + type Effect, + type Hook, + type Update, + type UpdateQueue, + type Fiber, + type FiberRoot, + type PortalStateNode, + type RefObject, + type RefCallback, + type ContextDependency, + type Dependencies, + type UpdateQueueType, + type SharedQueue, + // Root tag + RootTag, + type RootTag as RootTagType, + // Utility types + type FiberOfTag, + type FiberPropsFor, + type StateNodeFor, + type MiniReactContext, + // Type guards + isHostFiber, + isFunctionComponent, + isHostRoot, + isPortal, +} from "./types"; + +// Fiber creation +export { + createFiber, + createFiberFromElement, + createFiberFromText, + createFiberFromFragment, + createFiberFromPortal, + createHostRootFiber, + createFiberRoot, + createWorkInProgress, + resetWorkInProgress, + isTextElement, + isPortalElement, + isFragmentElement, + getElementKey, + isSameElementType, + cloneFiber, +} from "./createFiber"; + +// Child reconciliation +export { + createChildReconciler, + reconcileChildFibers, + mountChildFibers, +} from "./childReconciler"; + +// Fiber utilities +export { + // Tree traversal + getNextFiber, + completeUnitOfWork, + // Host parent/sibling finding + findHostParent, + getHostParentNode, + findHostSibling, + // State helpers + getStateNode, + getFirstHostChild, + collectHostChildren, + // Root finding + findFiberRoot, + findPortalContainer, + // Debug utilities + getFiberDebugName, + printFiberTree, +} from "./fiberUtils"; + +// Effect list +export { + type FunctionComponentUpdateQueue, + type CollectedEffects, + type EffectInstance, + createEffect, + createEffectToFiber, + collectEffects, + runPassiveEffectCleanups, + runPassiveEffectCreates, + runLayoutEffectCleanups, + runLayoutEffectCreates, + areHookInputsEqual, + collectDeletionEffects, + markFiberWithPassiveEffect, + doesFiberHavePassiveEffects, + schedulePassiveEffects, + flushPassiveEffects, +} from "./effectList"; + +// Commit work +export { + commitPlacement, + commitUpdate, + commitDeletion, + commitAttachRef, + commitDetachRef, + createInstance, + createTextInstance, + appendChild, + appendChildToContainer, + insertBefore, + removeChild, + finalizeInitialChildren, + prepareUpdate, +} from "./commitWork"; + +// Commit root +export { + commitRoot, + flushLayoutEffects, + commitUnmountEffects, + prepareForCommit, + resetAfterCommit, + commitDeletions, +} from "./commitRoot"; + +// Work in progress tree +export { + getWorkInProgress, + setWorkInProgress, + getWorkInProgressRoot, + setWorkInProgressRoot, + getWorkInProgressRootRenderLanes, + setWorkInProgressRootRenderLanes, + prepareFreshStack, + createWorkInProgressFiber, + resetWorkInProgressFiber, + cloneChildFibers, + finishConcurrentRender, + commitTreeSwap, + checkIfWorkInProgressReceivedUpdate, + markWorkInProgressReceivedUpdate, + resetDidReceiveUpdate, + getDidReceiveUpdate, + getWorkInProgressDebugInfo, +} from "./workInProgress"; + +// Begin work +export { beginWork } from "./beginWork"; + +// Complete work +export { + completeWork, + resetCompleteWork, + unwindWork, + unwindInterruptedWork, +} from "./completeWork"; + +// Work loop +export { + scheduleUpdateOnFiber, + performSyncWorkOnRoot, + createRoot, + updateContainer, + flushSync, + flushPassiveEffectsImpl, + isRendering, + isCommitting, + handleError, +} from "./workLoop"; + +// Scheduler +export { + Priority, + type Priority as PriorityType, + getCurrentTime, + shouldYield, + shouldYieldForPriority, + scheduleCallback, + cancelCallback, + getCurrentPriorityLevel, + runWithPriority, + lanesToSchedulerPriority, + schedulerPriorityToLane, + setWorkInProgressConcurrent, + isWorkInProgressConcurrent, + requestEventTime, + requestUpdateLane, + scheduleIdleCallback, + cancelIdleCallback, + flushWork, +} from "./scheduler"; + +// Lanes (priority system) +export { + NonIdleLanes, + TransitionLanes, + UpdateLanes, + mergeLanes, + removeLanes, + intersectLanes, + includesLane, + includesAnyLanes, + includesOnlyNonUrgentLanes, + includesBlockingLane, + isLaneEmpty, + getHighestPriorityLane, + getHighestPriorityLanes, + isSubsetOfLanes, + getLanePriority, + getLanesLabel, + getNextLanes, + markRootUpdated, + markRootFinished, + markRootSuspended, + markRootPinged, + markRootExpired, + scheduleUpdateOnFiber as scheduleLaneUpdateOnFiber, + fiberHasWork, + fiberSubtreeHasWork, + resetFiberLanes, + requestEventTime as requestLaneEventTime, + setCurrentEventTime, + clearCurrentEventTime, + requestUpdateLane as requestLaneUpdate, + claimNextTransitionLane, + entangleLanes, + getEntangledLanes, + addEntangledLanes, + formatLanes, + logLanes, +} from "./lanes"; + +// Fiber hooks +export { + renderWithHooks, + useStateFiber, + useReducerFiber, + useEffectFiber, + useLayoutEffectFiber, + useRefFiber, + useMemoFiber, + useCallbackFiber, + useContextFiber, + getCurrentlyRenderingFiber, + isRenderingHooks, +} from "./fiberHooks"; + +// Resumability (serialization/deserialization) +export { + type SerializedFiber, + type SerializedFiberRoot, + serializeFiberTree, + dehydrateRoot, + parseSerializedRoot, + extractComponentState, + createFiberFromSerialized, +} from "./resumability"; + +// Hydration (SSR) +export { + type HydrateRootOptions, + hydrateRoot, + enterHydrationState, + exitHydrationState, + getIsHydrating, + tryToClaimNextHydratableInstance, + tryToClaimNextHydratableTextInstance, + prepareToHydrateHostInstance, + popHydrationState, + getRestoredState, +} from "./hydration"; + +// Type Guards and Assertions +export { + // Fiber tag type guards + isHostComponentFiber, + isHostTextFiber, + isHostRootFiber, + isHostPortalFiber, + isFunctionComponentFiber, + isMemoComponentFiber, + isFragmentFiber, + isContextProviderFiber, + isContextConsumerFiber, + // Narrowed fiber types + type HostComponentFiber, + type HostTextFiber, + type HostRootFiber, + type HostPortalFiber, + // StateNode assertions (use asserts syntax for type narrowing) + assertHostComponentFiber, + assertHostTextFiber, + assertHostRootFiber, + assertHostPortalFiber, + getHostStateNode, + assertHostFiber, + // MemoizedState helpers + isHookState, + isEffectState, + assertHookState, + getMemoizedState, + assertMemoizedState, + // Props helpers + type TextProps, + isTextProps, + getPropsAsRecord, + assertPropsAsRecord, + assertTextProps, + // Update queue helpers + isUpdateQueue, + assertUpdateQueue, + // Branded type helpers + mergeLanesUnsafe, + intersectLanesUnsafe, + removeLanesUnsafe, + lanesIncludeLane, + isLanesEmpty, + flagsInclude, + mergeFlags, + isFlagsEmpty, + // DOM type guards + isElement, + isTextNode, + isHTMLElement, + isHTMLInputElement, + assertElement, + assertTextNode, + // FiberRoot helpers + isFiberRoot, + assertFiberRoot, + // Fiber navigation helpers + hasFiberParent, + hasFiberChild, + hasFiberSibling, + hasFiberAlternate, + assertFiberParent, + // Property access helpers + setDynamicProperty, + getDynamicProperty, + // Component type guards + type MemoComponent, + isMemoComponent, + isFunctionType, +} from "./typeGuards"; diff --git a/src/fiber/workLoop.ts b/src/fiber/workLoop.ts new file mode 100644 index 0000000..1d204f6 --- /dev/null +++ b/src/fiber/workLoop.ts @@ -0,0 +1,425 @@ +/* **************** */ +/* Work Loop - Core Fiber Scheduler */ +/* **************** */ + +/** + * Implements the core work loop of the fiber reconciler. + * This is the heart of React's scheduling system. + */ + +import type { AnyMiniReactElement } from "../core/types"; +import { eventSystem } from "../events/eventSystem"; +import { trackRenderEnd, trackRenderStart } from "../performance"; +import { beginWork } from "./beginWork"; +import { commitRoot } from "./commitRoot"; +import { completeWork, unwindInterruptedWork } from "./completeWork"; +import type { Fiber, FiberRoot, Lane, Lanes } from "./types"; +import { NoFlags, NoLanes, SyncLane, WorkTag, createLanes } from "./types"; +import { + getWorkInProgress, + getWorkInProgressRoot, + prepareFreshStack, + setWorkInProgress, + setWorkInProgressRoot, +} from "./workInProgress"; + +// ============================================ +// Work Loop State +// ============================================ + +/** + * Execution context flags. + */ +const NoContext = 0b0000; +const RenderContext = 0b0010; +const CommitContext = 0b0100; + +let executionContext = NoContext; + +/** + * Current render lanes. + */ +let workInProgressRootRenderLanes: Lanes = NoLanes; + +// ============================================ +// Root Entry Points +// ============================================ + +/** + * Schedules an update on the root. + * This is the main entry point for triggering a re-render. + */ +export function scheduleUpdateOnFiber( + root: FiberRoot, + fiber: Fiber, + lane: Lane, +): void { + // Mark the fiber as having pending work + markUpdateLaneFromFiberToRoot(fiber, lane); + + // Schedule the work + ensureRootIsScheduled(root); +} + +/** + * Marks update lanes from a fiber up to the root. + * Handles the case where the fiber might be an alternate. + */ +function markUpdateLaneFromFiberToRoot(fiber: Fiber, lane: Lane): void { + // Mark this fiber with the lane + fiber.lanes = createLanes((fiber.lanes as number) | (lane as number)); + + // Also mark the alternate if it exists (for double-buffering) + if (fiber.alternate !== null) { + fiber.alternate.lanes = createLanes( + (fiber.alternate.lanes as number) | (lane as number), + ); + } + + // Walk up and mark parent childLanes + let node = fiber; + let parent = fiber.return; + while (parent !== null) { + parent.childLanes = createLanes( + (parent.childLanes as number) | (lane as number), + ); + // Also mark the alternate parent's childLanes + if (parent.alternate !== null) { + parent.alternate.childLanes = createLanes( + (parent.alternate.childLanes as number) | (lane as number), + ); + } + node = parent; + parent = parent.return; + } + + // node is now the host root fiber - mark the FiberRoot's pendingLanes + if (node.tag === WorkTag.HostRoot && node.stateNode !== null) { + const root = node.stateNode as FiberRoot; + root.pendingLanes = createLanes( + (root.pendingLanes as number) | (lane as number), + ); + } +} + +/** + * Ensures the root has work scheduled. + */ +function ensureRootIsScheduled(root: FiberRoot): void { + // Check if we already have scheduled work + const existingCallbackNode = root.callbackNode; + const nextLanes = getNextLanes(root); + + if (nextLanes === NoLanes) { + // No work to do + if (existingCallbackNode !== null) { + root.callbackNode = null; + root.callbackPriority = 0 as Lane; + } + return; + } + + // For now, always use sync rendering + performSyncWorkOnRoot(root); +} + +/** + * Gets the next lanes to work on. + */ +function getNextLanes(root: FiberRoot): Lanes { + const pendingLanes = root.pendingLanes; + + if (pendingLanes === NoLanes) { + return NoLanes; + } + + // For now, just return all pending lanes + return pendingLanes; +} + +// ============================================ +// Sync Work +// ============================================ + +/** + * Performs synchronous work on a root. + * This is the main entry point for sync rendering. + */ +export function performSyncWorkOnRoot(root: FiberRoot): void { + const lanes = getNextLanes(root); + + if (lanes === NoLanes) { + return; + } + + // Track render performance + trackRenderStart(); + + // Render phase + renderRootSync(root, lanes); + + // Commit phase + const finishedWork = root.current.alternate; + root.finishedWork = finishedWork; + root.finishedLanes = lanes; + + commitRoot(root); + + trackRenderEnd(); +} + +/** + * Renders the root synchronously. + */ +function renderRootSync(root: FiberRoot, lanes: Lanes): number { + const prevExecutionContext = executionContext; + executionContext |= RenderContext; + + // Check if we're resuming work or starting fresh + if ( + getWorkInProgressRoot() !== root || + workInProgressRootRenderLanes !== lanes + ) { + // Start fresh + prepareFreshStack(root, lanes as number); + workInProgressRootRenderLanes = lanes; + } + + // Run the work loop + workLoopSync(); + + // Reset context + executionContext = prevExecutionContext; + setWorkInProgressRoot(null); + + return 0; // Success +} + +/** + * The synchronous work loop. + * Processes all work without yielding. + */ +function workLoopSync(): void { + let wip = getWorkInProgress(); + while (wip !== null) { + performUnitOfWork(wip); + wip = getWorkInProgress(); + } +} + +// ============================================ +// Unit of Work +// ============================================ + +/** + * Performs one unit of work. + * This processes a single fiber and returns the next one to work on. + */ +function performUnitOfWork(unitOfWork: Fiber): void { + const current = unitOfWork.alternate; + + // Begin phase: render this component + const next = beginWork(current, unitOfWork, workInProgressRootRenderLanes); + + // Memoize props after rendering + unitOfWork.memoizedProps = unitOfWork.pendingProps; + + if (next === null) { + // No more children, complete this unit of work + completeUnitOfWork(unitOfWork); + } else { + // Continue with the child + setWorkInProgress(next); + } +} + +/** + * Completes a unit of work and finds the next sibling/uncle. + */ +function completeUnitOfWork(unitOfWork: Fiber): void { + let completedWork: Fiber | null = unitOfWork; + + while (completedWork !== null) { + const current = completedWork.alternate; + + // Complete phase: create DOM nodes, bubble flags + const next = completeWork( + current, + completedWork, + workInProgressRootRenderLanes, + ); + + // If this produced more work, do it + if (next !== null) { + setWorkInProgress(next); + return; + } + + // Check for sibling + const sibling = completedWork.sibling; + if (sibling !== null) { + setWorkInProgress(sibling); + return; + } + + // Move up to parent + completedWork = completedWork.return; + setWorkInProgress(completedWork); + } +} + +// ============================================ +// Render Root API +// ============================================ + +/** + * Creates a fiber root for a container. + */ +export function createRoot(containerInfo: Element): FiberRoot { + // Initialize event system for this container + eventSystem.initialize(containerInfo); + eventSystem.enableFiberMode(); + + // Create the root fiber + const hostRootFiber: Fiber = { + tag: WorkTag.HostRoot, + key: null, + elementType: null, + type: null, + stateNode: null, // Will be set to root + return: null, + child: null, + sibling: null, + index: 0, + ref: null, + refCleanup: null, + pendingProps: {}, + memoizedProps: null, + memoizedState: null, + updateQueue: null, + dependencies: null, + flags: NoFlags, + subtreeFlags: NoFlags, + deletions: null, + lanes: NoLanes, + childLanes: NoLanes, + alternate: null, + }; + + // Create the FiberRoot + const root: FiberRoot = { + tag: 0, // LegacyRoot + containerInfo, + current: hostRootFiber, + finishedWork: null, + pendingChildren: null, + pendingLanes: NoLanes, + suspendedLanes: NoLanes, + pingedLanes: NoLanes, + expiredLanes: NoLanes, + finishedLanes: NoLanes, + callbackNode: null, + callbackPriority: 0 as Lane, + expirationTimes: new Map(), + isDehydrated: false, + mutableSourceEagerHydrationData: null, + }; + + // Link them + hostRootFiber.stateNode = root; + + return root; +} + +/** + * Updates a root with new children. + */ +export function updateContainer( + element: AnyMiniReactElement | null, + root: FiberRoot, +): void { + const current = root.current; + const lane = SyncLane; + + // Store the element to render + root.pendingChildren = element; + + // Mark the root as having pending work + root.pendingLanes = createLanes( + (root.pendingLanes as number) | (lane as number), + ); + + // Schedule the update + scheduleUpdateOnFiber(root, current, lane); +} + +// ============================================ +// Flush Operations +// ============================================ + +/** + * Flushes all sync work. + */ +export function flushSync(fn?: () => R): R | undefined { + const prevExecutionContext = executionContext; + executionContext |= RenderContext; + + try { + if (fn) { + return fn(); + } + } finally { + executionContext = prevExecutionContext; + } +} + +/** + * Flushes all passive effects. + */ +export function flushPassiveEffectsImpl(): boolean { + // This will be implemented with effects + return false; +} + +// ============================================ +// Context Helpers +// ============================================ + +/** + * Checks if we're in a render context. + */ +export function isRendering(): boolean { + return (executionContext & RenderContext) !== NoContext; +} + +/** + * Checks if we're in a commit context. + */ +export function isCommitting(): boolean { + return (executionContext & CommitContext) !== NoContext; +} + +// ============================================ +// Error Handling +// ============================================ + +/** + * Handles an error during rendering. + */ +export function handleError(_root: FiberRoot, thrownValue: unknown): void { + console.error("Error during render:", thrownValue); + + // Unwind the work + const workInProgress = getWorkInProgress(); + if (workInProgress !== null) { + unwindInterruptedWork( + workInProgress.alternate, + workInProgress, + workInProgressRootRenderLanes, + ); + } + + // Reset state + setWorkInProgress(null); + setWorkInProgressRoot(null); +}