From c19e6fb1e32ee84644d0029af0ac439b6137dd06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 19 Oct 2023 18:57:30 +0200 Subject: [PATCH] Remove `State['_internalQueue']` (#4372) * Remove `State['_internalQueue']` * add a changeset --- .changeset/proud-apricots-double.md | 5 ++ packages/core/src/State.ts | 2 - packages/core/src/StateMachine.ts | 27 ++++-- packages/core/src/actions/raise.ts | 19 ++--- packages/core/src/actions/send.ts | 4 +- packages/core/src/stateUtils.ts | 103 +++++++++++++---------- packages/core/src/types.ts | 1 - packages/core/test/invoke.test.ts | 1 - packages/core/test/microstep.test.ts | 10 +-- packages/xstate-inspect/src/serialize.ts | 3 +- 10 files changed, 97 insertions(+), 78 deletions(-) create mode 100644 .changeset/proud-apricots-double.md diff --git a/.changeset/proud-apricots-double.md b/.changeset/proud-apricots-double.md new file mode 100644 index 0000000000..1bc6b769bd --- /dev/null +++ b/.changeset/proud-apricots-double.md @@ -0,0 +1,5 @@ +--- +'xstate': major +--- + +Removed `State['_internalQueue']`. diff --git a/packages/core/src/State.ts b/packages/core/src/State.ts index 4109417c6e..f3641ceb5c 100644 --- a/packages/core/src/State.ts +++ b/packages/core/src/State.ts @@ -89,7 +89,6 @@ export class State< public error: unknown; public context: TContext; public historyValue: Readonly> = {}; - public _internalQueue: Array; /** * The enabled state nodes representative of the state value. */ @@ -171,7 +170,6 @@ export class State< public machine: AnyStateMachine ) { this.context = config.context; - this._internalQueue = config._internalQueue ?? []; this.historyValue = config.historyValue || {}; this.matches = this.matches.bind(this); this.toStrings = this.toStrings.bind(this); diff --git a/packages/core/src/StateMachine.ts b/packages/core/src/StateMachine.ts index 37a72144bd..39ea295c1d 100644 --- a/packages/core/src/StateMachine.ts +++ b/packages/core/src/StateMachine.ts @@ -393,7 +393,8 @@ export class StateMachine< */ private getPreInitialState( actorCtx: AnyActorContext, - initEvent: any + initEvent: any, + internalQueue: AnyEventObject[] ): MachineSnapshot< TContext, TEvent, @@ -419,9 +420,13 @@ export class StateMachine< if (typeof context === 'function') { const assignment = ({ spawn, event }: any) => context({ spawn, input: event.input }); - return resolveActionsAndContext(preInitial, initEvent, actorCtx, [ - assign(assignment) - ]) as SnapshotFrom; + return resolveActionsAndContext( + preInitial, + initEvent, + actorCtx, + [assign(assignment)], + internalQueue + ) as SnapshotFrom; } return preInitial; @@ -452,8 +457,12 @@ export class StateMachine< TResolvedTypesMeta > { const initEvent = createInitEvent(input) as unknown as TEvent; // TODO: fix; - - const preInitialState = this.getPreInitialState(actorCtx, initEvent); + const internalQueue: AnyEventObject[] = []; + const preInitialState = this.getPreInitialState( + actorCtx, + initEvent, + internalQueue + ); const nextState = microstep( [ { @@ -468,13 +477,15 @@ export class StateMachine< preInitialState, actorCtx, initEvent, - true + true, + internalQueue ); const { state: macroState } = macrostep( nextState, initEvent as AnyEventObject, - actorCtx + actorCtx, + internalQueue ); return macroState as SnapshotFrom; diff --git a/packages/core/src/actions/raise.ts b/packages/core/src/actions/raise.ts index 4e8242621e..3271d5b2a9 100644 --- a/packages/core/src/actions/raise.ts +++ b/packages/core/src/actions/raise.ts @@ -1,5 +1,4 @@ import isDevelopment from '#is-development'; -import { cloneState } from '../State.ts'; import { ActionArgs, AnyActorContext, @@ -11,7 +10,8 @@ import { NoInfer, RaiseActionOptions, SendExpr, - ParameterizedObject + ParameterizedObject, + AnyEventObject } from '../types.ts'; function resolveRaise( @@ -43,7 +43,8 @@ function resolveRaise( EventObject > | undefined; - } + }, + { internalQueue }: { internalQueue: AnyEventObject[] } ) { const delaysMap = state.machine.implementations.delays; @@ -63,14 +64,10 @@ function resolveRaise( } else { resolvedDelay = typeof delay === 'function' ? delay(args) : delay; } - return [ - typeof resolvedDelay !== 'number' - ? cloneState(state, { - _internalQueue: state._internalQueue.concat(resolvedEvent) - }) - : state, - { event: resolvedEvent, id, delay: resolvedDelay } - ]; + if (typeof resolvedDelay !== 'number') { + internalQueue.push(resolvedEvent); + } + return [state, { event: resolvedEvent, id, delay: resolvedDelay }]; } function executeRaise( diff --git a/packages/core/src/actions/send.ts b/packages/core/src/actions/send.ts index f4d5085ba2..050f0557d4 100644 --- a/packages/core/src/actions/send.ts +++ b/packages/core/src/actions/send.ts @@ -61,7 +61,7 @@ function resolveSendTo( > | undefined; }, - extra: { deferredActorIds: string[] } | undefined + extra: { deferredActorIds: string[] | undefined } ) { const delaysMap = state.machine.implementations.delays; @@ -95,7 +95,7 @@ function resolveSendTo( // #_invokeid. If the target is the special term '#_invokeid', where invokeid is the invokeid of an SCXML session that the sending session has created by , the Processor must add the event to the external queue of that session. targetActorRef = state.children[resolvedTarget.slice(2)]; } else { - targetActorRef = extra?.deferredActorIds.includes(resolvedTarget) + targetActorRef = extra.deferredActorIds?.includes(resolvedTarget) ? resolvedTarget : state.children[resolvedTarget]; } diff --git a/packages/core/src/stateUtils.ts b/packages/core/src/stateUtils.ts index d88b92deb0..d176d93ef9 100644 --- a/packages/core/src/stateUtils.ts +++ b/packages/core/src/stateUtils.ts @@ -974,7 +974,8 @@ export function microstep< currentState: AnyState, actorCtx: AnyActorContext, event: TEvent, - isInitial: boolean + isInitial: boolean, + internalQueue: Array ): AnyState { const mutConfiguration = new Set(currentState.configuration); @@ -982,18 +983,15 @@ export function microstep< return currentState; } - const microstate = microstepProcedure( + return microstepProcedure( transitions, currentState, mutConfiguration, event, actorCtx, - isInitial + isInitial, + internalQueue ); - - return cloneState(microstate, { - value: {} // TODO: make optional - }); } function microstepProcedure( @@ -1002,7 +1000,8 @@ function microstepProcedure( mutConfiguration: Set, event: AnyEventObject, actorCtx: AnyActorContext, - isInitial: boolean + isInitial: boolean, + internalQueue: Array ): typeof currentState { const historyValue = { ...currentState.historyValue @@ -1014,12 +1013,7 @@ function microstepProcedure( historyValue ); - const internalQueue = [...currentState._internalQueue]; - // TODO: this `cloneState` is really just a hack to prevent infinite loops - // we need to take another look at how internal queue is managed - let nextState = cloneState(currentState, { - _internalQueue: [] - }); + let nextState = currentState; // Exit states if (!isInitial) { @@ -1029,7 +1023,8 @@ function microstepProcedure( actorCtx, filteredTransitions, mutConfiguration, - historyValue + historyValue, + internalQueue ); } @@ -1038,7 +1033,8 @@ function microstepProcedure( nextState, event, actorCtx, - filteredTransitions.flatMap((t) => t.actions) + filteredTransitions.flatMap((t) => t.actions), + internalQueue ); // Enter states @@ -1062,17 +1058,15 @@ function microstepProcedure( actorCtx, nextConfiguration .sort((a, b) => b.order - a.order) - .flatMap((state) => state.exit) + .flatMap((state) => state.exit), + internalQueue ); } try { - internalQueue.push(...nextState._internalQueue); - return cloneState(nextState, { configuration: nextConfiguration, - historyValue, - _internalQueue: internalQueue + historyValue }); } catch (e) { // TODO: Refactor this once proper error handling is implemented. @@ -1160,6 +1154,7 @@ function enterStates( event, actorCtx, actions, + internalQueue, stateNodeToEnter.invoke.map((invokeDef) => invokeDef.id) ); @@ -1374,7 +1369,8 @@ function exitStates( actorCtx: AnyActorContext, transitions: AnyTransitionDefinition[], mutConfiguration: Set, - historyValue: HistoryValue + historyValue: HistoryValue, + internalQueue: AnyEventObject[] ) { let nextState = currentState; const statesToExit = computeExitSet( @@ -1403,10 +1399,13 @@ function exitStates( } for (const s of statesToExit) { - nextState = resolveActionsAndContext(nextState, event, actorCtx, [ - ...s.exit, - ...s.invoke.map((def) => stop(def.id)) - ]); + nextState = resolveActionsAndContext( + nextState, + event, + actorCtx, + [...s.exit, ...s.invoke.map((def) => stop(def.id))], + internalQueue + ); mutConfiguration.delete(s); } return nextState; @@ -1434,7 +1433,10 @@ function resolveActionsAndContextWorker( event: AnyEventObject, actorCtx: AnyActorContext, actions: UnknownAction[], - extra: { deferredActorIds: string[] } | undefined, + extra: { + internalQueue: AnyEventObject[]; + deferredActorIds: string[] | undefined; + }, retries: (readonly [BuiltinAction, unknown])[] | undefined ): AnyState { const { machine } = currentState; @@ -1539,6 +1541,7 @@ export function resolveActionsAndContext( event: AnyEventObject, actorCtx: AnyActorContext, actions: UnknownAction[], + internalQueue: AnyEventObject[], deferredActorIds?: string[] ): AnyState { const retries: (readonly [BuiltinAction, unknown])[] | undefined = @@ -1548,7 +1551,7 @@ export function resolveActionsAndContext( event, actorCtx, actions, - deferredActorIds && { deferredActorIds }, + { internalQueue, deferredActorIds }, retries ); retries?.forEach(([builtinAction, params]) => { @@ -1560,7 +1563,8 @@ export function resolveActionsAndContext( export function macrostep( state: AnyState, event: EventObject, - actorCtx: AnyActorContext + actorCtx: AnyActorContext, + internalQueue: AnyEventObject[] = [] ): { state: typeof state; microstates: Array; @@ -1589,7 +1593,14 @@ export function macrostep( // Determine the next state based on the next microstep if (nextEvent.type !== XSTATE_INIT) { const transitions = selectTransitions(nextEvent, nextState); - nextState = microstep(transitions, state, actorCtx, nextEvent, false); + nextState = microstep( + transitions, + state, + actorCtx, + nextEvent, + false, + internalQueue + ); states.push(nextState); } @@ -1597,29 +1608,29 @@ export function macrostep( let enabledTransitions = selectEventlessTransitions(nextState, nextEvent); if (!enabledTransitions.length) { - if (!nextState._internalQueue.length) { + if (!internalQueue.length) { break; - } else { - nextEvent = nextState._internalQueue[0]; - const transitions = selectTransitions(nextEvent, nextState); - nextState = microstep( - transitions, - nextState, - actorCtx, - nextEvent, - false - ); - nextState._internalQueue.shift(); - - states.push(nextState); } + nextEvent = internalQueue.shift()!; + const transitions = selectTransitions(nextEvent, nextState); + nextState = microstep( + transitions, + nextState, + actorCtx, + nextEvent, + false, + internalQueue + ); + + states.push(nextState); } else { nextState = microstep( enabledTransitions, nextState, actorCtx, nextEvent, - false + false, + internalQueue ); states.push(nextState); @@ -1654,7 +1665,7 @@ function stopStep( actions.push(stop(child)); } - return resolveActionsAndContext(nextState, event, actorCtx, actions); + return resolveActionsAndContext(nextState, event, actorCtx, actions, []); } function selectTransitions( diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 8ad2eb79b9..6e2eceed22 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1684,7 +1684,6 @@ export interface StateConfig< error?: unknown; tags?: Set; machine?: StateMachine; - _internalQueue?: Array; } export interface ActorOptions { diff --git a/packages/core/test/invoke.test.ts b/packages/core/test/invoke.test.ts index 2745e3d79d..14a439e240 100644 --- a/packages/core/test/invoke.test.ts +++ b/packages/core/test/invoke.test.ts @@ -61,7 +61,6 @@ describe('invoke', () => { actors: { src: 'child'; id: 'someService'; - events: EventFrom; logic: typeof childMachine; }; }, diff --git a/packages/core/test/microstep.test.ts b/packages/core/test/microstep.test.ts index 28285ef91e..35119509f2 100644 --- a/packages/core/test/microstep.test.ts +++ b/packages/core/test/microstep.test.ts @@ -160,11 +160,11 @@ describe('machine.microstep()', () => { actorContext ); - expect(states.map((s) => [s.value, s._internalQueue.length])).toEqual([ - ['second', 2], // foo, bar - ['third', 1], // bar - ['fourth', 0], // (eventless) - ['fifth', 0] + expect(states.map((s) => s.value)).toEqual([ + 'second', + 'third', + 'fourth', + 'fifth' ]); }); }); diff --git a/packages/xstate-inspect/src/serialize.ts b/packages/xstate-inspect/src/serialize.ts index caeb801329..3bae20417a 100644 --- a/packages/xstate-inspect/src/serialize.ts +++ b/packages/xstate-inspect/src/serialize.ts @@ -21,8 +21,7 @@ export function selectivelyStringify( } export function stringifyState(state: AnyState, replacer?: Replacer): string { - const { machine, configuration, _internalQueue, tags, ...stateToStringify } = - state; + const { machine, configuration, tags, ...stateToStringify } = state; return selectivelyStringify( { ...stateToStringify, tags: Array.from(tags) }, ['context'],