diff --git a/core/signal/src/fsm.ts b/core/signal/src/fsm.ts deleted file mode 100644 index 4acfe99cc..000000000 --- a/core/signal/src/fsm.ts +++ /dev/null @@ -1,333 +0,0 @@ -import {dispatch, getDetail, logger} from './core.js'; - -import type {OmitFirstParam, SingleOrArray, StringifyableRecord} from '@alwatr/type'; - -export interface FsmConfig< - TState extends string = string, - TEventId extends string = string, - TActionName extends string = string, - TContext extends StringifyableRecord = StringifyableRecord -> extends StringifyableRecord { - name: string; - - /** - * Initial context. - */ - context: TContext; - - /** - * Initial state. - */ - initial: TState; - - /** - * Define state list - */ - stateRecord: StateRecord; -} - -export type StateRecord = { - [S in TState | '$all']: { - /** - * On state exit actions - */ - exit?: SingleOrArray; - - /** - * On state entry actions - */ - entry?: SingleOrArray; - - /** - * An object mapping eventId to state. - * - * Example: - * - * ```ts - * stateRecord: { - * on: { - * TIMER: { - * target: 'green', - * condition: () => car.gas > 0, - * actions: () => car.go(), - * } - * } - * } - * ``` - */ - on: { - [E in TEventId]?: TransitionConfig | undefined; - }; - }; -}; - -export interface StateContext extends StringifyableRecord { - /** - * Current state - */ - target: TState; - /** - * Last state - */ - from: TState; - /** - * Transition event - */ - by: TEventId | 'INIT'; -} - -export interface TransitionConfig - extends StringifyableRecord { - target?: TState; - condition?: TActionName; - actions?: SingleOrArray; -} - -export interface SignalDetail< - TState extends string = string, - TEventId extends string = string, - TContext extends StringifyableRecord = StringifyableRecord -> extends StringifyableRecord { - name: string; - state: StateContext; - context: TContext; -} - -// type helper - -export type TState = Exclude; -export type TEventId = keyof TMachine['stateRecord'][TState]['on']; -export type TActionName = TMachine['stateRecord'][TState]['entry']; -export type TContext = TMachine['context']; - -export type StateMachineHelper = Readonly<{ - TState: Exclude; - TEventId: keyof TMachine['stateRecord'][StateMachineHelper['TState']]['on']; - TActionName: TMachine['stateRecord'][StateMachineHelper['TState']]['entry']; - TContext: TMachine['context']; -}>; - -// ---- - -const fsmStorage: Record = {}; - -export function contractStateMachine< - TState extends string = string, - TEventId extends string = string, - TActionName extends string = string, - TContext extends StringifyableRecord = StringifyableRecord ->(config: FsmConfig): FsmConfig { - return config; -} - -export const getState = ( - machineId: string, -): StateContext => { - logger.logMethodArgs('getState', machineId); - const detail = getDetail>(machineId); - if (detail == null) throw new Error('fsm_undefined', {cause: {machineId}}); - return detail.state; -}; - -export const setState = ( - machineId: string, - target: TState, - by: TEventId, -): void => { - logger.logMethodArgs('setState', machineId); - const detail = getDetail>(machineId); - if (detail == null) throw new Error('fsm_undefined', {cause: {machineId}}); - - detail.state = { - target, - from: detail.state.target, - by, - }; - - dispatch(machineId, detail, {debounce: 'NextCycle'}); -}; - -export const getContext = (machineId: string): TContext => { - logger.logMethodArgs('getContext', machineId); - const detail = getDetail>(machineId); - if (detail == null) throw new Error('fsm_undefined', {cause: {machineId}}); - return detail.context; -}; - -export const setContext = ( - machineId: string, - context: Partial, - notify?: boolean, -): void => { - logger.logMethodArgs('setContext', {machineId, context}); - const detail = getDetail>(machineId); - if (detail == null) throw new Error('fsm_undefined', {cause: {machineId}}); - - detail.context = { - ...detail.context, - ...context, - }; - - if (notify) { - dispatch(machineId, detail, {debounce: 'NextCycle'}); - } -}; - -export const transition = async < - TEventId extends string = string, - TContext extends StringifyableRecord = StringifyableRecord ->( - machineId: string, - event: TEventId, - context?: Partial, -): Promise => { - const detail = getDetail>(machineId); - if (detail == null) throw new Error('fsm_undefined', {cause: {machineId}}); - const config = fsmStorage[detail.name]; - if (config == null) throw new Error('fsm_undefined', {cause: {machineName: detail.name}}); - - const fromState = detail.state.target; - const transitionConfig = config.stateRecord[fromState]?.on[event] ?? config.stateRecord.$all?.on[event]; - - logger.logMethodArgs('transition', {machineId, fromState, event, context, target: transitionConfig?.target}); - - if (context !== undefined) { - detail.context = { - ...detail.context, - ...context, - }; - } - - if (transitionConfig == null) { - logger.incident( - 'transition', - 'invalid_target_state', - 'Defined target state for this event not found in state config', - { - fromState, - event, - events: {...config.stateRecord.$all?.on, ...config.stateRecord[fromState]?.on}, - }, - ); - return; - } - - // if ((await this.callFunction(transitionConfig.condition)) === false) { - // return; - // TODO: condition - // } - - transitionConfig.target ??= fromState; - setState(machineId, transitionConfig.target, event); -}; - -export const defineMachine = (machineId: string, config: TMachine): void => { - const detail = getDetail(machineId); - if (detail != null) throw new Error('fsm_exist', {cause: {machineId, config}}); - - fsmStorage[config.name] = config; - dispatch( - machineId, - { - name: config.name, - state: { - target: config.initial, - from: config.initial, - by: 'INIT', - }, - context: config.context, - }, - {debounce: 'NextCycle'}, - ); -}; - -export const stateMachineLookup = < - TMachine extends StateMachineHelper, - TContext extends TMachine['TContext'] = TMachine['TContext'] ->( - machineId: string, - ) => - ({ - defineMachine: defineMachine.bind(null, machineId) as OmitFirstParam, - getState: getState.bind(null, machineId) as OmitFirstParam< - typeof getState - >, - getContext: getContext.bind(null, machineId) as OmitFirstParam>, - setContext: setContext.bind(null, machineId) as OmitFirstParam>, - transition: transition.bind(null, machineId) as OmitFirstParam< - typeof transition - >, - } as const); - -// demo provider - -export const lightMachineConfig = contractStateMachine({ - name: 'light_machine', - context: { - a: 0, - b: 0, - }, - initial: 'green', - stateRecord: { - $all: { - entry: 'action_all_entry', - exit: 'action_all_exit', - on: { - POWER_LOST: { - target: 'flashingRed', - actions: 'action_all_power_lost', - }, - }, - }, - green: { - entry: 'action_green_entry', - exit: 'action_green_exit', - on: { - TIMER: { - target: 'yellow', - actions: 'action_green_timer', - condition: 'condition_green_timer', - }, - }, - }, - yellow: { - on: { - TIMER: { - target: 'red', - }, - }, - }, - red: { - on: { - TIMER: { - target: 'green', - }, - }, - }, - flashingRed: { - on: { - POWER_BACK: { - target: 'green', - }, - }, - }, - }, -}); - -export type LightMachine = StateMachineHelper; -const lightMachine = stateMachineLookup('light_machine_56'); - -lightMachine.defineMachine(lightMachineConfig); - -lightMachine.handleAction({ - 'asdasd': () => { - - }, -}); - -lightMachine.handleSignal([ - { - signalId: 'asdasd', - ... - } -]); diff --git a/ui/element/src/index.ts b/ui/element/src/index.ts index 87b3a35e9..4d86b7efd 100644 --- a/ui/element/src/index.ts +++ b/ui/element/src/index.ts @@ -13,8 +13,6 @@ export * from './mixins/schedule-update-to-frame.js'; export * from './directives/map.js'; -export * from './reactive-controllers/finite-state-machine.js'; - export * from './lit.js'; globalAlwatr.registeredList.push({ diff --git a/ui/element/src/reactive-controllers/finite-state-machine.ts b/ui/element/src/reactive-controllers/finite-state-machine.ts deleted file mode 100644 index 38c09ffbf..000000000 --- a/ui/element/src/reactive-controllers/finite-state-machine.ts +++ /dev/null @@ -1,59 +0,0 @@ -import {FiniteStateMachine, type FsmConfig} from '@alwatr/fsm'; - -import {type ReactiveController} from '../lit.js'; - -import type {LoggerMixinInterface} from '../mixins/logging.js'; -import type {StringifyableRecord} from '@alwatr/type'; - -export class FiniteStateMachineController< - TState extends string, - TEventId extends string, - TContext extends StringifyableRecord - > extends FiniteStateMachine implements ReactiveController { - constructor( - private _host: LoggerMixinInterface, - config: Readonly>, - ) { - super(config); - this._host.addController(this); - if (!this.config.autoSignalUnsubscribe) { - this.subscribeSignals(); - } - } - - render(states: {[P in TState]: (() => unknown) | TState}): unknown { - this._logger.logMethodArgs('render', this.state.target); - let renderFn = states[this.state.target]; - - if (typeof renderFn === 'string') { - renderFn = states[renderFn]; - } - - if (typeof renderFn === 'function') { - return renderFn?.call(this._host); - } - - return; - } - - hostUpdate(): void { - this._host.setAttribute('state', this.state.target); - } - - protected override callFunction(fn?: () => T): T | void { - if (typeof fn !== 'function') return; - return fn.call(this._host); - } - - hostConnected(): void { - if (this.config.autoSignalUnsubscribe) { - this.subscribeSignals(); - } - } - - hostDisconnected(): void { - if (this.config.autoSignalUnsubscribe) { - this.unsubscribeSignals(); - } - } -}