From 4e73700643759c2994dac4868daa129157cd5537 Mon Sep 17 00:00:00 2001 From: REllEK-IO Date: Thu, 19 Oct 2023 06:10:22 -0700 Subject: [PATCH 1/8] Checkpoint --- src/concepts/axium/qualities/close.quality.ts | 15 +++++++++++++-- src/index.ts | 2 +- src/model/axium.ts | 8 +++++--- src/model/concept.ts | 2 ++ 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/concepts/axium/qualities/close.quality.ts b/src/concepts/axium/qualities/close.quality.ts index 976b8a6..d5b75f8 100644 --- a/src/concepts/axium/qualities/close.quality.ts +++ b/src/concepts/axium/qualities/close.quality.ts @@ -1,17 +1,28 @@ import { createQuality } from '../../../model/concept'; -import { Action, ActionType, prepareActionCreator } from '../../../model/action'; +import { Action, ActionType, prepareActionWithPayloadCreator } from '../../../model/action'; import { AxiumState } from '../axium.concept'; +import { selectPayload } from '../../../model/selector'; +/** + * @parm exit - If set to true, will exit the current process. + */ +export type ClosePayload = { + exit: boolean +}; export const axiumCloseType: ActionType = 'Close Axium'; -export const axiumClose = prepareActionCreator(axiumCloseType); +export const axiumClose = prepareActionWithPayloadCreator(axiumCloseType); export function closeReducer(state: AxiumState, _action: Action): AxiumState { + const {exit} = selectPayload(_action); state.generalSubscribers.forEach(named => named.subscription.unsubscribe()); state.methodSubscribers.forEach(named => named.subscription.unsubscribe()); state.stagePlanners.forEach(named => named.conclude()); state.action$.complete(); state.concepts$.complete(); state.subConcepts$.complete(); + if (exit) { + process.exit(); + } return { ...state, methodSubscribers: [], diff --git a/src/index.ts b/src/index.ts index aba428a..cc59c5d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -52,7 +52,7 @@ export { axiumSelectOpen, axiumSelectLastStrategy, axiumSelectBadActions, axiumS export { axiumConclude, axiumConcludeType } from './concepts/axium/qualities/conclude.quality'; export { axiumOpen, axiumOpenType } from './concepts/axium/qualities/open.quality'; export { axiumLog, axiumLogType } from './concepts/axium/qualities/log.quality'; -export { axiumClose, axiumCloseType } from './concepts/axium/qualities/close.quality'; +export { axiumClose, axiumCloseType, ClosePayload } from './concepts/axium/qualities/close.quality'; export { axiumBadAction, axiumBadActionType } from './concepts/axium/qualities/badAction.quality'; export { axiumSetMode, axiumSetModeType, SetModePayload } from './concepts/axium/qualities/setMode.quality'; export { diff --git a/src/model/axium.ts b/src/model/axium.ts index 85a22d7..057fb14 100644 --- a/src/model/axium.ts +++ b/src/model/axium.ts @@ -14,7 +14,7 @@ import { initializationStrategy, } from '../concepts/axium/axium.concept'; import { axiumBadActionType } from '../concepts/axium/qualities/badAction.quality'; -import { axiumCloseType } from '../concepts/axium/qualities/close.quality'; +import { axiumClose, axiumCloseType } from '../concepts/axium/qualities/close.quality'; import { axiumAppendActionListToDialog, } from '../concepts/axium/qualities/appendActionListToDialog.quality'; @@ -132,8 +132,10 @@ export function createAxium(name: string, initialConcepts: Concept[], logging?: axiumState.action$.next( strategyBegin(initializationStrategy(concepts)), ); - const close = () => { - action$.next(createAction(axiumCloseType)); + const close = (exit?: boolean) => { + action$.next(axiumClose({ + exit: exit ? exit : false + })); }; return { subscribe: subConcepts$.subscribe.bind(subConcepts$), diff --git a/src/model/concept.ts b/src/model/concept.ts index 9b8b11b..47183c8 100644 --- a/src/model/concept.ts +++ b/src/model/concept.ts @@ -132,3 +132,5 @@ export const defaultMethodCreator: MethodCreator = () : [Method, Subject ); return [defaultMethod, defaultSubject]; }; + +// export type createMethod = (subject: Subject) => Method; \ No newline at end of file From 35dcc0fe3939daac4e7398e62dc50a04791cc8e2 Mon Sep 17 00:00:00 2001 From: REllEK-IO Date: Thu, 19 Oct 2023 10:08:11 -0700 Subject: [PATCH 2/8] Reducing method boilerplate --- Axium.md | 2 + README.md | 2 +- src/concepts/axium/axium.concept.ts | 2 + src/concepts/axium/axium.selector.ts | 5 + .../qualities/addConceptsFromQue.quality.ts | 2 +- .../appendActionListToDialog.quality.ts | 7 +- src/concepts/axium/qualities/log.quality.ts | 25 ++- .../axium/qualities/setMode.quality.ts | 31 ++-- src/concepts/counter/qualities/add.quality.ts | 20 +-- .../counter/qualities/subtract.quality.ts | 20 +-- src/concepts/experiment/experiment.concept.ts | 6 +- .../experiment/experiment.principle.ts | 4 +- .../qualities/checkInStrategy.quality.ts | 4 +- .../experiment/qualities/mockTrue.quality.ts | 21 +++ .../qualities/timerEmitAction.quality.ts | 25 +++ .../timerEmitActionWithConcepts.quality.ts | 32 ++++ .../strategies/timedMockToTrue.strategy.ts | 23 +++ .../timedMockToTrueWithConcepts.strategy.ts | 24 +++ src/concepts/ownership/ownership.mode.ts | 12 +- .../ownership/qualities/backTrack.quality.ts | 31 ++-- ...rStrategyStubsFromLedgerAndSelf.quality.ts | 30 ++-- src/index.ts | 10 +- src/model/actionController.ts | 22 ++- src/model/axium.ts | 8 +- src/model/concept.ts | 14 +- src/model/method.ts | 143 ++++++++++++++++++ src/test/actionController.test.ts | 80 +++++++++- src/test/ownership.test.ts | 4 +- 28 files changed, 466 insertions(+), 143 deletions(-) create mode 100644 src/concepts/experiment/qualities/mockTrue.quality.ts create mode 100644 src/concepts/experiment/qualities/timerEmitAction.quality.ts create mode 100644 src/concepts/experiment/qualities/timerEmitActionWithConcepts.quality.ts create mode 100644 src/concepts/experiment/strategies/timedMockToTrue.strategy.ts create mode 100644 src/concepts/experiment/strategies/timedMockToTrueWithConcepts.strategy.ts create mode 100644 src/model/method.ts diff --git a/Axium.md b/Axium.md index de2e0a9..ecb660b 100644 --- a/Axium.md +++ b/Axium.md @@ -27,6 +27,7 @@ export type AxiumState { dialog: string; storeDialog: boolean; lastStrategy: string; + lastStrategyData: unknown; generation: number; modeIndex: number; defaultModeIndex: number; @@ -46,6 +47,7 @@ export type AxiumState { * dialog - Is the internal representation of the strategies that the axium has ran. * storeDialog - This is set to false by default to save on memory, but if true will store each dialog, and allows such to be subscribed to. * lastStrategy - Informs specifically the of the last ActionStrategy topic to have ran through the system. This is used via testing or the deployment of addition strategies upon completion. +* lastStrategyData - Paired with lastStrategy. Use to access thee last data of the previous strategy. * generation - This iterates each time the Set of Concepts is transformed. And if an action is received of the wrong generation. Will be primed at run time, if not found this will emit a badAction that if logging is set to True. Will be emitted to the console alongside the invalidated action as payload. * modeIndex - This determines which Mode is currently being ran from the lists of modes stored on the axium concept. * defaultModeIndex - This determines what mode will be set by setDefaultMode. Of importance for adding and removing strategies. diff --git a/README.md b/README.md index b89e3f8..1115ef1 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ let finalRun = true; const axium = createAxium('ownershipTest', [ createOwnershipConcept(), createCounterConcept(), - createExperimentConcept(createExperimentActionQueState(), [checkInStrategyQuality], [experimentActionQuePrinciple]) + createExperimentConcept(createExperimentState(), [checkInStrategyQuality], [experimentActionQuePrinciple]) ], true, true); const plan = axium.stage( 'Testing Ownership Staging', [ diff --git a/src/concepts/axium/axium.concept.ts b/src/concepts/axium/axium.concept.ts index 4eb608d..e60379a 100644 --- a/src/concepts/axium/axium.concept.ts +++ b/src/concepts/axium/axium.concept.ts @@ -40,6 +40,7 @@ export type AxiumState = { dialog: string; storeDialog: boolean; lastStrategy: string; + lastStrategyData: unknown; generation: number; cachedSemaphores: Map> modeIndex: number; @@ -67,6 +68,7 @@ const createAxiumState = (name: string, storeDialog?: boolean, logging?: boolean dialog: '', storeDialog: storeDialog ? storeDialog : false, lastStrategy: '', + lastStrategyData: '', generation: 0, cachedSemaphores: new Map>(), modeIndex: 0, diff --git a/src/concepts/axium/axium.selector.ts b/src/concepts/axium/axium.selector.ts index aacb08c..1423115 100644 --- a/src/concepts/axium/axium.selector.ts +++ b/src/concepts/axium/axium.selector.ts @@ -10,6 +10,11 @@ export const axiumSelectLastStrategy: KeyedSelector = { stateKeys: 'lastStrategy', }; +export const axiumSelectLastStrategyData: KeyedSelector = { + conceptName: 'axium', + stateKeys: 'lastStrategyData', +}; + export const axiumSelectBadPlans: KeyedSelector = { conceptName: 'axium', stateKeys: 'badPlans', diff --git a/src/concepts/axium/qualities/addConceptsFromQue.quality.ts b/src/concepts/axium/qualities/addConceptsFromQue.quality.ts index 5b4b9d3..f349b7d 100644 --- a/src/concepts/axium/qualities/addConceptsFromQue.quality.ts +++ b/src/concepts/axium/qualities/addConceptsFromQue.quality.ts @@ -14,7 +14,7 @@ function addConceptsFromQueReducer(state: AxiumState, _ : Action) { addConceptsQue.forEach(concept => { concept.qualities.forEach(quality => { if (quality.methodCreator) { - [quality.method, quality.subject] = quality.methodCreator(state.subConcepts$); + [quality.method, quality.subject] = quality.methodCreator(state.concepts$); quality.method.pipe( catchError((err: unknown, caught: Observable) => { if (state.logging) { diff --git a/src/concepts/axium/qualities/appendActionListToDialog.quality.ts b/src/concepts/axium/qualities/appendActionListToDialog.quality.ts index 359178f..beba967 100644 --- a/src/concepts/axium/qualities/appendActionListToDialog.quality.ts +++ b/src/concepts/axium/qualities/appendActionListToDialog.quality.ts @@ -8,12 +8,13 @@ import { selectPayload } from '../../../model/selector'; export type AppendActionListToDialogPayload = { actionList: Array; strategyTopic: string; + strategyData: unknown; } export const axiumAppendActionListToDialogType: ActionType = 'append Action List to Axium\'s Dialog'; export const axiumAppendActionListToDialog = prepareActionWithPayloadCreator(axiumAppendActionListToDialogType); -export function appendActionListToDialogReducer(state: AxiumState, action: Action) { +export function appendActionListToDialogReducer(state: AxiumState, action: Action): AxiumState { const payload = selectPayload(action); let newDialog = ''; if (state.storeDialog) { @@ -25,11 +26,13 @@ export function appendActionListToDialogReducer(state: AxiumState, action: Actio ...state, dialog: state.dialog + newDialog, lastStrategy: payload.strategyTopic, + lastStrategyData: payload.strategyData }; } return { ...state, - lastStrategy: payload.strategyTopic + lastStrategy: payload.strategyTopic, + lastStrategyData: payload.strategyData }; } diff --git a/src/concepts/axium/qualities/log.quality.ts b/src/concepts/axium/qualities/log.quality.ts index 5ab13ac..6b42fb9 100644 --- a/src/concepts/axium/qualities/log.quality.ts +++ b/src/concepts/axium/qualities/log.quality.ts @@ -4,26 +4,19 @@ import { Action, ActionType, prepareActionCreator } from '../../../model/action' import { createQuality } from '../../../model/concept'; import { axiumConclude } from './conclude.quality'; import { strategySuccess } from '../../../model/actionStrategy'; +import { createMethod } from '../../../model/method'; export const axiumLogType: ActionType = 'logged a message passed to Axium'; export const axiumLog = prepareActionCreator(axiumLogType); -const createLogMethodCreator: MethodCreator = () => { - const logSubject = new Subject(); - const logMethod: Method = logSubject.pipe( - map((action: Action) => { - console.log('Logging: ', action); - if (action.strategy) { - return strategySuccess(action.strategy); - } - return axiumConclude(); - }) - ); - return [ - logMethod, - logSubject - ]; -}; +export const createLogMethodCreator: MethodCreator = () => createMethod((action) => { + console.log('Logging: ', action); + if (action.strategy) { + return strategySuccess(action.strategy); + } else { + return axiumConclude(); + } +}); export const logQuality = createQuality( axiumLogType, diff --git a/src/concepts/axium/qualities/setMode.quality.ts b/src/concepts/axium/qualities/setMode.quality.ts index 3a0345e..ddf8d99 100644 --- a/src/concepts/axium/qualities/setMode.quality.ts +++ b/src/concepts/axium/qualities/setMode.quality.ts @@ -2,9 +2,10 @@ import { AxiumState } from '../axium.concept'; import { Action, createAction, prepareActionWithPayloadCreator} from '../../../model/action'; import { createQuality, MethodCreator, Method } from '../../../model/concept'; import { Subject, map } from 'rxjs'; -import { axiumConcludeType } from './conclude.quality'; +import { axiumConclude, axiumConcludeType } from './conclude.quality'; import { strategySuccess } from '../../../model/actionStrategy'; import { selectPayload } from '../../../model/selector'; +import { createMethod } from '../../../model/method'; export type SetModePayload = { modeIndex: number; @@ -14,22 +15,16 @@ export type SetModePayload = { export const axiumSetModeType = 'set Axium Mode'; export const axiumSetMode = prepareActionWithPayloadCreator(axiumSetModeType); -export const createOwnershipMethodCreator: MethodCreator = () : [Method, Subject] => { - const defaultSubject = new Subject(); - const defaultMethod: Method = defaultSubject.pipe( - map((action: Action) => { - const payload = action.payload as SetModePayload; - if (action.strategy) { - action.strategy.currentNode.successNotes = { - denoter: `to ${payload.modeName}.` - }; - return strategySuccess(action.strategy); - } - return createAction(axiumConcludeType); - }), - ); - return [defaultMethod, defaultSubject]; -}; +export const axiumSetModeMethodCreator: MethodCreator = () => createMethod((action) => { + const payload = action.payload as SetModePayload; + if (action.strategy) { + action.strategy.currentNode.successNotes = { + denoter: `to ${payload.modeName}.` + }; + return strategySuccess(action.strategy); + } + return action; +}); export function setModeReducer(state: AxiumState, _action: Action) { const payload = selectPayload(_action); @@ -42,5 +37,5 @@ export function setModeReducer(state: AxiumState, _action: Action) { export const setModeQuality = createQuality( axiumSetModeType, setModeReducer, - createOwnershipMethodCreator + axiumSetModeMethodCreator ); diff --git a/src/concepts/counter/qualities/add.quality.ts b/src/concepts/counter/qualities/add.quality.ts index be146c1..3c02818 100644 --- a/src/concepts/counter/qualities/add.quality.ts +++ b/src/concepts/counter/qualities/add.quality.ts @@ -1,6 +1,6 @@ import { map, Subject } from 'rxjs'; import { Action, ActionType, prepareActionCreator } from '../../../model/action'; -import { Method, MethodCreator } from '../../../model/concept'; +import { defaultMethodCreator, Method, MethodCreator } from '../../../model/concept'; import { strategySuccess } from '../../../model/actionStrategy'; import { Counter } from '../counter.concept'; import { createQuality } from '../../../model/concept'; @@ -18,25 +18,9 @@ export function addReducer(state: Counter, _: Action) { }; } -const addMethodCreator: MethodCreator = () => { - const addSubject = new Subject(); - const addMethod: Method = addSubject.pipe( - map((action: Action) => { - if (action.strategy) { - return strategySuccess(action.strategy); - } - return axiumConclude(); - }) - ); - return [ - addMethod, - addSubject - ]; -}; - export const addQuality = createQuality( counterAddType, addReducer, - addMethodCreator, + defaultMethodCreator, [counterSelectCount] ); \ No newline at end of file diff --git a/src/concepts/counter/qualities/subtract.quality.ts b/src/concepts/counter/qualities/subtract.quality.ts index c751d5c..ba9d16a 100644 --- a/src/concepts/counter/qualities/subtract.quality.ts +++ b/src/concepts/counter/qualities/subtract.quality.ts @@ -1,6 +1,6 @@ import { map, Subject } from 'rxjs'; import { strategySuccess } from '../../../model/actionStrategy'; -import { Method, MethodCreator } from '../../../model/concept'; +import { defaultMethodCreator, Method, MethodCreator } from '../../../model/concept'; import { Counter } from '../counter.concept'; import { Action, ActionType, createAction, prepareActionCreator } from '../../../model/action'; import { createQuality } from '../../../model/concept'; @@ -18,25 +18,9 @@ export function subtractReducer(state: Counter) { }; } -const subtractMethodCreator: MethodCreator = () => { - const subtractSubject = new Subject(); - const subtractMethod: Method = subtractSubject.pipe( - map((action: Action) => { - if (action.strategy) { - return strategySuccess(action.strategy); - } - return axiumConclude(); - }) - ); - return [ - subtractMethod, - subtractSubject - ]; -}; - export const subtractQuality = createQuality( counterSubtractType, subtractReducer, - subtractMethodCreator, + defaultMethodCreator, [counterSelectCount] ); diff --git a/src/concepts/experiment/experiment.concept.ts b/src/concepts/experiment/experiment.concept.ts index 4902f21..8dadcc2 100644 --- a/src/concepts/experiment/experiment.concept.ts +++ b/src/concepts/experiment/experiment.concept.ts @@ -2,15 +2,17 @@ import { Mode, Quality, createConcept } from '../../model/concept'; import { Action } from '../../model/action'; import { PrincipleFunction } from '../../model/principle'; -export type ExperimentActionQueState = { +export type ExperimentState = { actionQue: Action[], + mock: boolean, } export const experimentName = 'experiment'; -export const createExperimentActionQueState = (): ExperimentActionQueState => { +export const createExperimentState = (): ExperimentState => { return { actionQue: [], + mock: false }; }; diff --git a/src/concepts/experiment/experiment.principle.ts b/src/concepts/experiment/experiment.principle.ts index 64d3bce..46698f8 100644 --- a/src/concepts/experiment/experiment.principle.ts +++ b/src/concepts/experiment/experiment.principle.ts @@ -4,7 +4,7 @@ import { PrincipleFunction } from '../../model/principle'; import { Concept } from '../../model/concept'; import { UnifiedSubject } from '../../model/stagePlanner'; import { selectState } from '../../model/selector'; -import { ExperimentActionQueState, experimentName } from './experiment.concept'; +import { ExperimentState, experimentName } from './experiment.concept'; import { axiumRegisterStagePlanner } from '../axium/qualities/registerStagePlanner.quality'; import { axiumSelectOpen } from '../axium/axium.selector'; @@ -26,7 +26,7 @@ export const experimentActionQuePrinciple: PrincipleFunction = ( }, (cpts, _) => { const concepts = cpts; - const experimentState = selectState(concepts, experimentName); + const experimentState = selectState(concepts, experimentName); // console.log('Check que', experimentState.actionQue); if (experimentState.actionQue.length > 0) { if (!readyToGo) { diff --git a/src/concepts/experiment/qualities/checkInStrategy.quality.ts b/src/concepts/experiment/qualities/checkInStrategy.quality.ts index c2f45d1..878fd64 100644 --- a/src/concepts/experiment/qualities/checkInStrategy.quality.ts +++ b/src/concepts/experiment/qualities/checkInStrategy.quality.ts @@ -3,13 +3,13 @@ import { Action, prepareActionCreator } from '../../../model/action'; import { createQuality } from '../../../model/concept'; import { strategySuccess } from '../../../model/actionStrategy'; import { axiumConcludeType } from '../../axium/qualities/conclude.quality'; -import { ExperimentActionQueState } from '../experiment.concept'; +import { ExperimentState } from '../experiment.concept'; export const experimentCheckInStrategyType = 'Experiment Check in Action'; export const experimentCheckInStrategy = prepareActionCreator(experimentCheckInStrategyType); -export function checkInStrategyReducer(state: ExperimentActionQueState, action: Action): ExperimentActionQueState { +export function checkInStrategyReducer(state: ExperimentState, action: Action): ExperimentState { if (action.strategy) { // console.log('Check in reducer', action); const nextAction = strategySuccess(action.strategy); diff --git a/src/concepts/experiment/qualities/mockTrue.quality.ts b/src/concepts/experiment/qualities/mockTrue.quality.ts new file mode 100644 index 0000000..5637efe --- /dev/null +++ b/src/concepts/experiment/qualities/mockTrue.quality.ts @@ -0,0 +1,21 @@ +import { defaultMethodCreator, defaultReducer } from '../../../model/concept'; +import { Action, prepareActionCreator } from '../../../model/action'; +import { createQuality } from '../../../model/concept'; +import { ExperimentState } from '../experiment.concept'; + +export const experimentMockTrueType = 'Experiment mock set to True'; + +export const experimentMockTrue = prepareActionCreator(experimentMockTrueType); + +export function checkInStrategyReducer(state: ExperimentState, action: Action): ExperimentState { + return { + ...state, + mock: true + }; +} + +export const mockToTrueQuality = createQuality( + experimentMockTrueType, + checkInStrategyReducer, + defaultMethodCreator +); diff --git a/src/concepts/experiment/qualities/timerEmitAction.quality.ts b/src/concepts/experiment/qualities/timerEmitAction.quality.ts new file mode 100644 index 0000000..d67d83f --- /dev/null +++ b/src/concepts/experiment/qualities/timerEmitAction.quality.ts @@ -0,0 +1,25 @@ +import { MethodCreator, defaultReducer } from '../../../model/concept'; +import { prepareActionCreator } from '../../../model/action'; +import { createQuality } from '../../../model/concept'; +import { createAsyncMethod } from '../../../model/method'; +import { strategySuccess } from '../../../model/actionStrategy'; +import { axiumConclude } from '../../axium/qualities/conclude.quality'; + +export const experimentTimerEmitActionType = 'Experiment create async method with timer, to return action'; +export const experimentTimerEmitAction = prepareActionCreator(experimentTimerEmitActionType); + +export const experimentTimerEmitActionMethodCreator: MethodCreator = () => createAsyncMethod((controller, action) => { + setTimeout(() => { + if (action.strategy) { + controller.fire(strategySuccess(action.strategy)); + } else { + controller.fire(axiumConclude()); + } + }, 500); +}); + +export const timerEmitActionQuality = createQuality( + experimentTimerEmitActionType, + defaultReducer, + experimentTimerEmitActionMethodCreator +); diff --git a/src/concepts/experiment/qualities/timerEmitActionWithConcepts.quality.ts b/src/concepts/experiment/qualities/timerEmitActionWithConcepts.quality.ts new file mode 100644 index 0000000..4edd17f --- /dev/null +++ b/src/concepts/experiment/qualities/timerEmitActionWithConcepts.quality.ts @@ -0,0 +1,32 @@ +import { MethodCreator, defaultReducer } from '../../../model/concept'; +import { prepareActionCreator } from '../../../model/action'; +import { createQuality } from '../../../model/concept'; +import { createAsyncMethodWithConcepts } from '../../../model/method'; +import { strategySuccess } from '../../../model/actionStrategy'; +import { axiumConclude } from '../../axium/qualities/conclude.quality'; +import { UnifiedSubject } from '../../../model/stagePlanner'; +import { strategyData_unifyData } from '../../../model/actionStrategyData'; +import { selectState } from '../../../model/selector'; +import { ExperimentState, experimentName } from '../experiment.concept'; + +export const experimentTimerEmitActionWithConceptsType = 'Experiment create async method with timer and concepts, to return action'; +export const experimentTimerEmitActionWithConcepts = prepareActionCreator(experimentTimerEmitActionWithConceptsType); + +export const experimentTimerEmitActionWithConceptsMethodCreator: MethodCreator = (concepts$?: UnifiedSubject) => + createAsyncMethodWithConcepts((controller, action, concepts) => { + setTimeout(() => { + if (action.strategy) { + const experimentState = selectState(concepts, experimentName); + const data = strategyData_unifyData(action.strategy, { mock: experimentState.mock }); + controller.fire(strategySuccess(action.strategy, data)); + } else { + controller.fire(axiumConclude()); + } + }, 500); + }, concepts$ as UnifiedSubject); + +export const timerEmitActionWithConceptsQuality = createQuality( + experimentTimerEmitActionWithConceptsType, + defaultReducer, + experimentTimerEmitActionWithConceptsMethodCreator +); diff --git a/src/concepts/experiment/strategies/timedMockToTrue.strategy.ts b/src/concepts/experiment/strategies/timedMockToTrue.strategy.ts new file mode 100644 index 0000000..ac5a73c --- /dev/null +++ b/src/concepts/experiment/strategies/timedMockToTrue.strategy.ts @@ -0,0 +1,23 @@ +import { ActionStrategy, ActionStrategyParameters, createActionNode, createStrategy } from '../../../model/actionStrategy'; +import { counterSelectCount } from '../../counter/counter.selector'; +import { experimentMockTrue } from '../qualities/mockTrue.quality'; +import { experimentTimerEmitAction } from '../qualities/timerEmitAction.quality'; + +export const timedMockToTrueTopic = 'This will use a async method to eventually set mock to True'; +export function timedMockToTrue(): ActionStrategy { + const stepTwo = createActionNode(experimentMockTrue(), { + successNode: null, + failureNode: null, + }); + const stepOne = createActionNode(experimentTimerEmitAction(), { + successNode: stepTwo, + failureNode: null, + }); + + const params: ActionStrategyParameters = { + topic: timedMockToTrueTopic, + initialNode: stepOne, + }; + + return createStrategy(params); +} \ No newline at end of file diff --git a/src/concepts/experiment/strategies/timedMockToTrueWithConcepts.strategy.ts b/src/concepts/experiment/strategies/timedMockToTrueWithConcepts.strategy.ts new file mode 100644 index 0000000..75c3570 --- /dev/null +++ b/src/concepts/experiment/strategies/timedMockToTrueWithConcepts.strategy.ts @@ -0,0 +1,24 @@ +import { ActionStrategy, ActionStrategyParameters, createActionNode, createStrategy } from '../../../model/actionStrategy'; +import { counterSelectCount } from '../../counter/counter.selector'; +import { experimentMockTrue } from '../qualities/mockTrue.quality'; +import { experimentTimerEmitActionWithConcepts } from '../qualities/timerEmitActionWithConcepts.quality'; + +export const timedMockToTrueWithConceptsTopic = + 'This will use a async method to eventually set mock to TrueWithConcepts and append mock to strategy data.'; +export function timedMockToTrueWithConcepts(): ActionStrategy { + const stepTwo = createActionNode(experimentMockTrue(), { + successNode: null, + failureNode: null, + }); + const stepOne = createActionNode(experimentTimerEmitActionWithConcepts(), { + successNode: stepTwo, + failureNode: null, + }); + + const params: ActionStrategyParameters = { + topic: timedMockToTrueWithConceptsTopic, + initialNode: stepOne, + }; + + return createStrategy(params); +} \ No newline at end of file diff --git a/src/concepts/ownership/ownership.mode.ts b/src/concepts/ownership/ownership.mode.ts index 78c1e57..3497d6a 100644 --- a/src/concepts/ownership/ownership.mode.ts +++ b/src/concepts/ownership/ownership.mode.ts @@ -6,9 +6,7 @@ import { permissiveMode, blockingMode } from '../axium/axium.mode'; import { checkIn, clearStubs, ownershipShouldBlock, updateAddToPendingActions } from '../../model/ownership'; import { ActionStrategy, strategyFailed } from '../../model/actionStrategy'; import { UnifiedSubject } from '../../model/stagePlanner'; -import { AppendActionListToDialogPayload, axiumAppendActionListToDialogType } from '../axium/qualities/appendActionListToDialog.quality'; -import { selectState } from '../../model/selector'; -import { OwnershipState, ownershipName } from './ownership.concept'; +import { AppendActionListToDialogPayload, axiumAppendActionListToDialog, axiumAppendActionListToDialogType } from '../axium/qualities/appendActionListToDialog.quality'; import { AxiumState } from '../axium/axium.concept'; import { failureConditions, strategyData_appendFailure } from '../../model/actionStrategyData'; @@ -44,11 +42,11 @@ export const ownershipMode: Mode = ( // Logical Determination: axiumConcludeType if (nextAction.semaphore[3] === 3) { concepts = clearStubs(concepts, nextAction.strategy as ActionStrategy); - nextAction = createAction(axiumAppendActionListToDialogType); - nextAction.payload = { + nextAction = axiumAppendActionListToDialog({ actionList: action.strategy.actionList, - strategyTopic: action.strategy.topic - } as AppendActionListToDialogPayload; + strategyTopic: action.strategy.topic, + strategyData: action.strategy.data + }); } finalMode([nextAction, concepts, action$, concepts$]); } else { diff --git a/src/concepts/ownership/qualities/backTrack.quality.ts b/src/concepts/ownership/qualities/backTrack.quality.ts index 9a46086..0b05119 100644 --- a/src/concepts/ownership/qualities/backTrack.quality.ts +++ b/src/concepts/ownership/qualities/backTrack.quality.ts @@ -1,28 +1,19 @@ -import { Method, MethodCreator, createQuality, defaultMethodCreator, defaultReducer } from '../../../model/concept'; -import { Action, ActionType, prepareActionCreator } from '../../../model/action'; -import { Subject, map } from 'rxjs'; +import { MethodCreator, createQuality, defaultMethodCreator, defaultReducer } from '../../../model/concept'; +import { ActionType, prepareActionCreator } from '../../../model/action'; import { strategyBackTrack } from '../../../model/actionStrategy'; -import { axiumConclude } from '../../axium/qualities/conclude.quality'; +import { createMethod } from '../../../model/method'; export const ownershipBackTrackType: ActionType = 'backtracking to previous ActionNode'; export const ownershipBackTrack = prepareActionCreator(ownershipBackTrackType); -const createBackTrackMethodCreator: MethodCreator = () => { - const backTrackSubject = new Subject(); - const backTrackMethod: Method = backTrackSubject.pipe( - map((action: Action) => { - if (action.strategy) { - const newAction = strategyBackTrack(action.strategy); - return newAction; - } - return axiumConclude(); - }) - ); - return [ - backTrackMethod, - backTrackSubject - ]; -}; +const createBackTrackMethodCreator: MethodCreator = () => createMethod((action) => { + if (action.strategy) { + const newAction = strategyBackTrack(action.strategy); + return newAction; + } else { + return action; + } +}); export const backTrackQuality = createQuality( ownershipBackTrackType, diff --git a/src/concepts/ownership/qualities/clearStrategyStubsFromLedgerAndSelf.quality.ts b/src/concepts/ownership/qualities/clearStrategyStubsFromLedgerAndSelf.quality.ts index 4235cca..b7e4696 100644 --- a/src/concepts/ownership/qualities/clearStrategyStubsFromLedgerAndSelf.quality.ts +++ b/src/concepts/ownership/qualities/clearStrategyStubsFromLedgerAndSelf.quality.ts @@ -1,30 +1,20 @@ -import { Method, MethodCreator, createQuality, defaultMethodCreator } from '../../../model/concept'; -import { Action, ActionType, createAction, prepareActionCreator } from '../../../model/action'; +import { MethodCreator, createQuality } from '../../../model/concept'; +import { Action, ActionType, prepareActionCreator } from '../../../model/action'; import { OwnershipState } from '../ownership.concept'; import { OwnershipTicket } from '../../../model/ownership'; -import { Subject, map } from 'rxjs'; import { strategySuccess } from '../../../model/actionStrategy'; -import { axiumConclude } from '../../axium/qualities/conclude.quality'; +import { createMethod } from '../../../model/method'; export const ownershipClearStrategyStubsFromLedgerAndSelfType: ActionType = 'clear current Strategy Stubs from Ownership Ledger and Itself'; export const ownershipClearStrategyStubsFromLedgerAndSelf = prepareActionCreator(ownershipClearStrategyStubsFromLedgerAndSelfType); -const createClearStrategyStubsFromLedgerAndSelfMethodCreator: MethodCreator = () => { - const logSubject = new Subject(); - const logMethod: Method = logSubject.pipe( - map((action: Action) => { - if (action.strategy) { - action.strategy.stubs = undefined; - return strategySuccess(action.strategy); - } - return axiumConclude(); - }) - ); - return [ - logMethod, - logSubject - ]; -}; +const createClearStrategyStubsFromLedgerAndSelfMethodCreator: MethodCreator = () => createMethod((action) => { + if (action.strategy) { + action.strategy.stubs = undefined; + return strategySuccess(action.strategy); + } + return action; +}); export function clearStrategyStubsFromLedgerAndSelfReducer(state: OwnershipState, action: Action): OwnershipState { const stubs = action?.strategy?.stubs; diff --git a/src/index.ts b/src/index.ts index cc59c5d..1ee77ce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,6 +23,12 @@ export { strategyData_select, strategyData_unifyData } from './model/actionStrategyData'; +export { + createMethod, + createAsyncMethod, + createMethodWithConcepts, + createAsyncMethodWithConcepts +} from './model/method'; export type { Action, ActionType } from './model/action'; export { primeAction, createAction, getSemaphore, prepareActionCreator, prepareActionWithPayloadCreator } from './model/action'; export { createConcept, createQuality, defaultReducer, defaultMethodCreator } from './model/concept'; @@ -135,8 +141,8 @@ export { // Experiment export { - ExperimentActionQueState, - createExperimentActionQueState, + ExperimentState, + createExperimentState, createExperimentConcept, experimentName } from './concepts/experiment/experiment.concept'; diff --git a/src/model/actionController.ts b/src/model/actionController.ts index abbc38a..961dc26 100644 --- a/src/model/actionController.ts +++ b/src/model/actionController.ts @@ -1,4 +1,4 @@ -import { Action, axiumBadAction, strategyFailed } from '../index'; +import { Action, ActionStrategy, axiumBadAction, axiumConclude, axiumConcludeType, strategyFailed } from '../index'; import { Subject } from 'rxjs'; import { failureConditions, strategyData_appendFailure } from './actionStrategyData'; @@ -20,6 +20,12 @@ export class ActionController extends Subject { strategyData_appendFailure(this.action.strategy, failureConditions.controllerExpired) )); } else { + const badAction = axiumBadAction([this.action]); + if (this.action.strategy) { + badAction.strategy = this.action.strategy; + (badAction.strategy as ActionStrategy).currentNode.action = badAction; + (badAction.strategy as ActionStrategy).currentNode.actionType = badAction.type; + } this.next(axiumBadAction([this.action])); } }, this.expiration - Date.now()); @@ -40,10 +46,22 @@ export class ActionController extends Subject { clearTimeout(this.timer); this.timer.unref(); } + let nextAction; + if (action.strategy) { + nextAction = action; + } else if (action.semaphore[3] !== 1) { + const conclude = axiumConclude(); + nextAction = { + ...action, + ...conclude + }; + } else { + nextAction = action; + } const { observers } = this; const len = observers.length; for (let i = 0; i < len; i++) { - observers[i].next(action); + observers[i].next(nextAction); } this.complete(); } diff --git a/src/model/axium.ts b/src/model/axium.ts index 057fb14..9b19189 100644 --- a/src/model/axium.ts +++ b/src/model/axium.ts @@ -28,7 +28,8 @@ export const blockingMethodSubscription = (action$: Subject, action: Act // Allows for reducer next in sequence const appendToDialog = axiumAppendActionListToDialog({ actionList: action.strategy.actionList, - strategyTopic: action.strategy.topic + strategyTopic: action.strategy.topic, + strategyData: action.strategy.data }); action$.next(appendToDialog); action$.next(action); @@ -49,7 +50,8 @@ export const defaultMethodSubscription = (action$: Subject, action: Acti // Allows for reducer next in sequence const appendToDialog = axiumAppendActionListToDialog({ actionList: action.strategy.actionList, - strategyTopic: action.strategy.topic + strategyTopic: action.strategy.topic, + strategyData: action.strategy.data }); setTimeout(() => { action$.next(appendToDialog); @@ -72,7 +74,7 @@ export function createAxium(name: string, initialConcepts: Concept[], logging?: concepts.forEach((concept, _index) => { concept.qualities.forEach(quality => { if (quality.methodCreator) { - const [method, subject] = quality.methodCreator(axiumState.subConcepts$); + const [method, subject] = quality.methodCreator(axiumState.concepts$); quality.method = method; quality.subject = subject; quality.method.pipe( diff --git a/src/model/concept.ts b/src/model/concept.ts index 47183c8..1e641e1 100644 --- a/src/model/concept.ts +++ b/src/model/concept.ts @@ -1,11 +1,12 @@ -import { Observable, Subject } from 'rxjs'; -import { Action, ActionType, createAction } from './action'; +import { Observable, Subject, debounceTime, switchMap, withLatestFrom } from 'rxjs'; +import { Action, ActionType } from './action'; import { PrincipleFunction } from '../model/principle'; import { strategySuccess } from './actionStrategy'; import { map } from 'rxjs'; import { KeyedSelector } from './selector'; -import { axiumConclude } from '../concepts/axium/qualities/conclude.quality'; +import { axiumConclude, axiumConcludeType } from '../concepts/axium/qualities/conclude.quality'; import { UnifiedSubject } from './stagePlanner'; +import { ActionController, createActionController$ } from './actionController'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type Reducer = (state: any, action: Action) => any; @@ -127,10 +128,11 @@ export const defaultMethodCreator: MethodCreator = () : [Method, Subject if (action.strategy) { return strategySuccess(action.strategy); } - return axiumConclude(); + return { + ...action, + type: axiumConcludeType + }; }), ); return [defaultMethod, defaultSubject]; }; - -// export type createMethod = (subject: Subject) => Method; \ No newline at end of file diff --git a/src/model/method.ts b/src/model/method.ts new file mode 100644 index 0000000..6cdac79 --- /dev/null +++ b/src/model/method.ts @@ -0,0 +1,143 @@ +import { Observable, Subject, debounceTime, map, switchMap, withLatestFrom } from 'rxjs'; +import { Concept } from './concept'; +import { UnifiedSubject } from './stagePlanner'; +import { ActionController, createActionController$ } from './actionController'; +import { ActionStrategy } from './actionStrategy'; +import { KeyedSelector } from './selector'; +import { createExperimentConcept, createExperimentState } from '../concepts/experiment/experiment.concept'; + +export type ActionType = string; +const axiumConcludeType: ActionType = 'Conclude'; +type Action = { + type: ActionType; + semaphore: [number, number, number, number]; + payload?: unknown; + strategy?: ActionStrategy; + keyedSelectors?: KeyedSelector[]; + agreement?: number; + expiration: number; + axium?: string; +}; +type Method = Observable; + +export const createMethod = + (method: (action: Action) => Action): [Method, Subject] => { + const defaultSubject = new Subject(); + const defaultMethod: Method = defaultSubject.pipe( + map((action: Action) => { + const methodAction = method(action); + if (methodAction.strategy) { + return methodAction; + } + return { + ...action, + type: axiumConcludeType + }; + }), + ); + return [defaultMethod, defaultSubject]; + }; +export const createMethodWithConcepts = + (method: (action: Action) => Action, concepts$: UnifiedSubject): [Method, Subject] => { + const defaultSubject = new Subject(); + const defaultMethod: Method = defaultSubject.pipe( + withLatestFrom(concepts$ as UnifiedSubject), + map(([act, concepts] : [Action, Concept[]]) => { + const methodAction = method(act); + if (methodAction.strategy) { + return methodAction; + } + return { + ...act, + type: axiumConcludeType + }; + }), + ); + return [defaultMethod, defaultSubject]; + }; +export const createAsyncMethod = + (asyncMethod: (controller: ActionController, action: Action) => void): [Method, Subject] => { + const defaultSubject = new Subject(); + const defaultMethod: Method = defaultSubject.pipe( + switchMap(act => createActionController$(act, (controller: ActionController, action: Action) => { + asyncMethod(controller, action); + })), + ); + return [defaultMethod, defaultSubject]; + }; +export const createAsyncMethodWithConcepts = + (asyncMethodWithConcepts: (controller: ActionController, action: Action, concepts: Concept[]) => void, concepts$: UnifiedSubject) + : [Method, Subject] => { + const defaultSubject = new Subject(); + const defaultMethod: Method = defaultSubject.pipe( + withLatestFrom(concepts$ as UnifiedSubject), + switchMap(([act, concepts] : [Action, Concept[]]) => + createActionController$(act, (controller: ActionController, action: Action) => { + asyncMethodWithConcepts(controller, action, concepts); + })), + ); + return [defaultMethod, defaultSubject]; + }; +// export const createMethodDebounce = +// (method: (action: Action) => Action, duration: number): [Method, Subject] => { +// const defaultSubject = new Subject(); +// const defaultMethod: Method = defaultSubject.pipe( +// debounceTime(duration), +// map((action: Action) => { +// const methodAction = method(action); +// if (methodAction.strategy) { +// return methodAction; +// } +// return { +// ...action, +// type: axiumConcludeType +// }; +// }), +// ); +// return [defaultMethod, defaultSubject]; +// }; +// export const createMethodDebounceWithConcepts = +// (methodWithConcepts: (action: Action, concepts: Concept[]) => Action, concepts$: UnifiedSubject, duration: number) +// : [Method, Subject] => { +// const defaultSubject = new Subject(); +// const defaultMethod: Method = defaultSubject.pipe( +// debounceTime(duration), +// withLatestFrom(concepts$ as UnifiedSubject), +// map(([act, concepts] : [Action, Concept[]]) => { +// const methodAction = methodWithConcepts(act, concepts); +// if (methodAction.strategy) { +// return methodAction; +// } +// return { +// ...act, +// type: axiumConcludeType +// }; +// }), +// ); +// return [defaultMethod, defaultSubject]; +// }; +// export const createAsyncMethodDebounce = +// (asyncMethod: (controller: ActionController, action: Action) => void, duration: number): [Method, Subject] => { +// const defaultSubject = new Subject(); +// const defaultMethod: Method = defaultSubject.pipe( +// debounceTime(duration), +// switchMap(act => createActionController$(act, (controller: ActionController, action: Action) => { +// asyncMethod(controller, action); +// })), +// ); +// return [defaultMethod, defaultSubject]; +// }; +// export const createAsyncMethodDebounceWithConcepts = +// (asyncMethodWithConcepts: (controller: ActionController, action: Action, concepts: Concept[]) => +// void, concepts$: UnifiedSubject, duration: number): [Method, Subject] => { +// const defaultSubject = new Subject(); +// const defaultMethod: Method = defaultSubject.pipe( +// debounceTime(duration), +// withLatestFrom(concepts$ as UnifiedSubject), +// switchMap(([act, concepts] : [Action, Concept[]]) => +// createActionController$(act, (controller: ActionController, action: Action) => { +// asyncMethodWithConcepts(controller, action, concepts); +// })), +// ); +// return [defaultMethod, defaultSubject]; +// }; \ No newline at end of file diff --git a/src/test/actionController.test.ts b/src/test/actionController.test.ts index dd56552..67c5820 100644 --- a/src/test/actionController.test.ts +++ b/src/test/actionController.test.ts @@ -1,6 +1,20 @@ import { ActionController, createActionController$ } from '../model/actionController'; import { axiumBadActionType } from '../concepts/axium/qualities/badAction.quality'; import { axiumLog, axiumLogType } from '../concepts/axium/qualities/log.quality'; +import { ExperimentState, createExperimentConcept, createExperimentState, experimentName } from '../concepts/experiment/experiment.concept'; +import { timerEmitActionQuality } from '../concepts/experiment/qualities/timerEmitAction.quality'; +import { mockToTrueQuality } from '../concepts/experiment/qualities/mockTrue.quality'; +import { createAxium } from '../model/axium'; +import { timedMockToTrue } from '../concepts/experiment/strategies/timedMockToTrue.strategy'; +import { createActionNode, strategyBegin } from '../model/actionStrategy'; +import { selectSlice, selectState } from '../model/selector'; +import { axiumConcludeType } from '../concepts/axium/qualities/conclude.quality'; +import { timerEmitActionWithConceptsQuality } from '../concepts/experiment/qualities/timerEmitActionWithConcepts.quality'; +import { + timedMockToTrueWithConcepts, + timedMockToTrueWithConceptsTopic +} from '../concepts/experiment/strategies/timedMockToTrueWithConcepts.strategy'; +import { axiumSelectLastStrategy, axiumSelectLastStrategyData } from '../concepts/axium/axium.selector'; test('ActionController Expired Test', (done) => { const act = axiumLog(undefined, 200); @@ -15,7 +29,7 @@ test('ActionController Next Test', (done) => { const act = axiumLog(undefined, 200); const cont = new ActionController(act); cont.subscribe(action => { - expect(action.type).toBe(axiumLogType); + expect(action.type).toBe(axiumConcludeType); done(); }); cont.fire(act); @@ -26,8 +40,72 @@ test('ActionController createActionController$ Test', (done) => { const cont = createActionController$(act, (controller, action) => { controller.fire(action); }); + cont.subscribe(action => { + expect(action.type).toBe(axiumConcludeType); + done(); + }); +}); + +test('ActionController createActionController$ Mock Strategy', (done) => { + const act = axiumLog(undefined, 200); + act.strategy = { + topic: 'Mock Strategy', + actionList: [''], + currentNode: createActionNode(act, { + successNode: null, + failureNode: null + }) + }; + const cont = createActionController$(act, (controller, action) => { + controller.fire(action); + }); cont.subscribe(action => { expect(action.type).toBe(axiumLogType); done(); }); +}); + +test('ActionController async Method Test', (done) => { + const experiment = createExperimentConcept(createExperimentState(), [timerEmitActionQuality, mockToTrueQuality]); + const axium = createAxium('Experiment async method creator', [experiment]); + const plan = axium.stage('timed mock to true', [ + (_, dispatch) => { + dispatch(strategyBegin(timedMockToTrue()), { + iterateStage: true + }); + }, + (concepts, _) => { + const experimentState = selectState(concepts, experimentName); + if (experimentState.mock) { + expect(experimentState.mock).toBe(true); + plan.conclude(); + done(); + } + } + ]); +}); + +test('ActionController async Method Test with Concepts', (done) => { + const experiment = createExperimentConcept(createExperimentState(), [timerEmitActionWithConceptsQuality, mockToTrueQuality]); + const axium = createAxium('Experiment async method creator with Concepts', [experiment]); + const plan = axium.stage('timed mock to true', [ + (_, dispatch) => { + dispatch(strategyBegin(timedMockToTrueWithConcepts()), { + iterateStage: true + }); + }, + (concepts, _) => { + const lastStrategy = selectSlice(concepts, axiumSelectLastStrategy); + const experimentState = selectState(concepts, experimentName); + if (lastStrategy === timedMockToTrueWithConceptsTopic) { + const data = selectSlice(concepts, axiumSelectLastStrategyData); + if (data) { + expect(data.mock).toBe(false); + expect(experimentState.mock).toBe(true); + plan.conclude(); + done(); + } + } + } + ]); }); \ No newline at end of file diff --git a/src/test/ownership.test.ts b/src/test/ownership.test.ts index 30c28e0..484d8e6 100644 --- a/src/test/ownership.test.ts +++ b/src/test/ownership.test.ts @@ -5,7 +5,7 @@ import { OwnershipState, createOwnershipConcept, ownershipName } from '../concep import { AxiumState } from '../concepts/axium/axium.concept'; import { setOwnerShipModeTopic } from '../concepts/ownership/strategies/setOwnerShipMode.strategy'; import { Counter, counterName, createCounterConcept } from '../concepts/counter/counter.concept'; -import { createExperimentActionQueState, createExperimentConcept } from '../concepts/experiment/experiment.concept'; +import { createExperimentState, createExperimentConcept } from '../concepts/experiment/experiment.concept'; import { puntCountingStrategy } from '../concepts/experiment/strategies/puntCounting.strategy'; import { strategyBegin } from '../model/actionStrategy'; import { @@ -24,7 +24,7 @@ test('Ownership Test', (done) => { const axium = createAxium('ownershipTest', [ createOwnershipConcept(), createCounterConcept(), - createExperimentConcept(createExperimentActionQueState(), [checkInStrategyQuality], [experimentActionQuePrinciple]) + createExperimentConcept(createExperimentState(), [checkInStrategyQuality], [experimentActionQuePrinciple]) ], true, true); const plan = axium.stage( 'Testing Ownership Staging', [ From f6272cb4f937c762ced8252f60ea3c795e93a94a Mon Sep 17 00:00:00 2001 From: REllEK-IO Date: Thu, 19 Oct 2023 10:08:48 -0700 Subject: [PATCH 3/8] Version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 02e488f..ec8101f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@phuire/strx", - "version": "0.0.36", + "version": "0.0.37", "description": "Unified Turing Machine", "main": "dist/index.js", "module": "dist/index.mjs", From 338a1e4f6a63eca1b0bdf48a808b9913e786a911 Mon Sep 17 00:00:00 2001 From: REllEK-IO Date: Thu, 19 Oct 2023 11:37:19 -0700 Subject: [PATCH 4/8] Debounce helper methodCreators. --- src/concepts/counter/qualities/add.quality.ts | 3 - .../debounceAsyncNextActionNode.quality.ts | 24 +++ .../debounceNextActionNode.quality.ts | 22 +++ .../asyncDebounceAddOne.strategy.ts | 33 ++++ .../strategies/debounceAddOne.strategy.ts | 32 ++++ src/index.ts | 9 +- src/model/actionController.ts | 17 +- src/model/debounceAction.ts | 165 ++++++++++++++++++ src/model/method.ts | 143 ++++++++------- src/model/stagePlanner.ts | 1 + src/test/actionController.test.ts | 45 ----- src/test/debounceMethods.test.ts | 103 +++++++++++ src/test/methodHelpers.test.ts | 58 ++++++ 13 files changed, 538 insertions(+), 117 deletions(-) create mode 100644 src/concepts/experiment/qualities/debounceAsyncNextActionNode.quality.ts create mode 100644 src/concepts/experiment/qualities/debounceNextActionNode.quality.ts create mode 100644 src/concepts/experiment/strategies/asyncDebounceAddOne.strategy.ts create mode 100644 src/concepts/experiment/strategies/debounceAddOne.strategy.ts create mode 100644 src/model/debounceAction.ts create mode 100644 src/test/debounceMethods.test.ts create mode 100644 src/test/methodHelpers.test.ts diff --git a/src/concepts/counter/qualities/add.quality.ts b/src/concepts/counter/qualities/add.quality.ts index 3c02818..5eeb3ae 100644 --- a/src/concepts/counter/qualities/add.quality.ts +++ b/src/concepts/counter/qualities/add.quality.ts @@ -1,11 +1,8 @@ -import { map, Subject } from 'rxjs'; import { Action, ActionType, prepareActionCreator } from '../../../model/action'; import { defaultMethodCreator, Method, MethodCreator } from '../../../model/concept'; -import { strategySuccess } from '../../../model/actionStrategy'; import { Counter } from '../counter.concept'; import { createQuality } from '../../../model/concept'; import { counterSelectCount } from '../counter.selector'; -import { axiumConclude } from '../../axium/qualities/conclude.quality'; export const counterAddType: ActionType = 'Counter Add'; diff --git a/src/concepts/experiment/qualities/debounceAsyncNextActionNode.quality.ts b/src/concepts/experiment/qualities/debounceAsyncNextActionNode.quality.ts new file mode 100644 index 0000000..5c0f2c7 --- /dev/null +++ b/src/concepts/experiment/qualities/debounceAsyncNextActionNode.quality.ts @@ -0,0 +1,24 @@ +import { MethodCreator, defaultReducer } from '../../../model/concept'; +import { prepareActionCreator } from '../../../model/action'; +import { createQuality } from '../../../model/concept'; +import { createAsyncMethodDebounce } from '../../../model/method'; +import { strategySuccess } from '../../../model/actionStrategy'; + +export const experimentAsyncDebounceNextActionNodeType = 'Experiment will debounce incoming actions within set duration asynchronously'; +export const experimentAsyncDebounceNextActionNode = prepareActionCreator(experimentAsyncDebounceNextActionNodeType); + +export const experimentDebounceNextActionNodeMethodCreator: MethodCreator = () => createAsyncMethodDebounce((controller, action) => { + setTimeout(() => { + if (action.strategy) { + controller.fire(strategySuccess(action.strategy)); + } else { + controller.fire(action); + } + }, 50); +}, 500); + +export const asyncDebounceNextActionNodeQuality = createQuality( + experimentAsyncDebounceNextActionNodeType, + defaultReducer, + experimentDebounceNextActionNodeMethodCreator +); diff --git a/src/concepts/experiment/qualities/debounceNextActionNode.quality.ts b/src/concepts/experiment/qualities/debounceNextActionNode.quality.ts new file mode 100644 index 0000000..eac0e3b --- /dev/null +++ b/src/concepts/experiment/qualities/debounceNextActionNode.quality.ts @@ -0,0 +1,22 @@ +import { MethodCreator, defaultReducer } from '../../../model/concept'; +import { prepareActionCreator } from '../../../model/action'; +import { createQuality } from '../../../model/concept'; +import { createMethodDebounce } from '../../../model/method'; +import { strategySuccess } from '../../../model/actionStrategy'; + +export const experimentDebounceNextActionNodeType = 'Experiment will debounce incoming actions within set duration'; +export const experimentDebounceNextActionNode = prepareActionCreator(experimentDebounceNextActionNodeType); + +export const experimentDebounceNextActionNodeMethodCreator: MethodCreator = () => createMethodDebounce((action) => { + if (action.strategy) { + return strategySuccess(action.strategy); + } else { + return action; + } +}, 500); + +export const debounceNextActionNodeQuality = createQuality( + experimentDebounceNextActionNodeType, + defaultReducer, + experimentDebounceNextActionNodeMethodCreator +); diff --git a/src/concepts/experiment/strategies/asyncDebounceAddOne.strategy.ts b/src/concepts/experiment/strategies/asyncDebounceAddOne.strategy.ts new file mode 100644 index 0000000..8a56328 --- /dev/null +++ b/src/concepts/experiment/strategies/asyncDebounceAddOne.strategy.ts @@ -0,0 +1,33 @@ +import { createStrategy, ActionStrategy, ActionStrategyParameters, createActionNode } from '../../../model/actionStrategy'; +import { counterAdd } from '../../counter/qualities/add.quality'; +import { experimentAsyncDebounceNextActionNode } from '../qualities/debounceAsyncNextActionNode.quality'; +import { experimentDebounceNextActionNode } from '../qualities/debounceNextActionNode.quality'; + +export const experimentAsyncDebounceAddOneTopic = 'Async debounce add one'; +export function experimentAsyncDebounceAddOneStrategy(): ActionStrategy { + const stepTwo = createActionNode(counterAdd(), { + successNode: null, + successNotes: { + preposition: '', + denoter: 'One;', + }, + failureNode: null, + agreement: 1000, + }); + const stepOne = createActionNode(experimentAsyncDebounceNextActionNode(), { + successNode: stepTwo, + successNotes: { + preposition: '', + denoter: 'One;', + }, + failureNode: null, + agreement: 1000, + }); + + const params: ActionStrategyParameters = { + topic: experimentAsyncDebounceAddOneTopic, + initialNode: stepOne, + }; + + return createStrategy(params); +} \ No newline at end of file diff --git a/src/concepts/experiment/strategies/debounceAddOne.strategy.ts b/src/concepts/experiment/strategies/debounceAddOne.strategy.ts new file mode 100644 index 0000000..ff57eea --- /dev/null +++ b/src/concepts/experiment/strategies/debounceAddOne.strategy.ts @@ -0,0 +1,32 @@ +import { createStrategy, ActionStrategy, ActionStrategyParameters, createActionNode } from '../../../model/actionStrategy'; +import { counterAdd } from '../../counter/qualities/add.quality'; +import { experimentDebounceNextActionNode } from '../qualities/debounceNextActionNode.quality'; + +export const experimentDebounceAddOneTopic = 'Debounce add one'; +export function experimentDebounceAddOneStrategy(): ActionStrategy { + const stepTwo = createActionNode(counterAdd(), { + successNode: null, + successNotes: { + preposition: '', + denoter: 'One;', + }, + failureNode: null, + agreement: 1000, + }); + const stepOne = createActionNode(experimentDebounceNextActionNode(), { + successNode: stepTwo, + successNotes: { + preposition: '', + denoter: 'One;', + }, + failureNode: null, + agreement: 1000, + }); + + const params: ActionStrategyParameters = { + topic: experimentDebounceAddOneTopic, + initialNode: stepOne, + }; + + return createStrategy(params); +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 1ee77ce..25ab32c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,8 +27,15 @@ export { createMethod, createAsyncMethod, createMethodWithConcepts, - createAsyncMethodWithConcepts + createAsyncMethodWithConcepts, + createMethodDebounce, + createAsyncMethodDebounce, + createMethodDebounceWithConcepts, + createAsyncMethodDebounceWithConcepts } from './model/method'; +export { + debounceAction +} from './model/debounceAction'; export type { Action, ActionType } from './model/action'; export { primeAction, createAction, getSemaphore, prepareActionCreator, prepareActionWithPayloadCreator } from './model/action'; export { createConcept, createQuality, defaultReducer, defaultMethodCreator } from './model/concept'; diff --git a/src/model/actionController.ts b/src/model/actionController.ts index 961dc26..f07b30f 100644 --- a/src/model/actionController.ts +++ b/src/model/actionController.ts @@ -47,15 +47,24 @@ export class ActionController extends Subject { this.timer.unref(); } let nextAction; + let end = true; + // Logically Determined axiumConclude + if (action.semaphore[3] === 3) { + end = false; + } if (action.strategy) { nextAction = action; - } else if (action.semaphore[3] !== 1) { + // Logically Determined axiumConclude + } else if (action.semaphore[3] === 3) { + nextAction = action; + // Logically Determined axiumBadAction + } else if (!action.strategy && action.semaphore[3] !== 1) { const conclude = axiumConclude(); nextAction = { ...action, ...conclude }; - } else { + } else { nextAction = action; } const { observers } = this; @@ -63,7 +72,9 @@ export class ActionController extends Subject { for (let i = 0; i < len; i++) { observers[i].next(nextAction); } - this.complete(); + if (end) { + this.complete(); + } } } } diff --git a/src/model/debounceAction.ts b/src/model/debounceAction.ts new file mode 100644 index 0000000..7739208 --- /dev/null +++ b/src/model/debounceAction.ts @@ -0,0 +1,165 @@ +import { + MonoTypeOperatorFunction, + Observable, + OperatorFunction, + SchedulerAction, + SchedulerLike, + Subscriber, + Subscription, + asyncScheduler +} from 'rxjs'; +import { Action } from './action'; +import { axiumConclude } from '../concepts/axium/qualities/conclude.quality'; + +function hasLift(source: any): source is { lift: InstanceType['lift'] } { + return typeof source?.lift === 'function'; +} + +function operate( + init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void +): OperatorFunction { + return (source: Observable) => { + if (hasLift(source)) { + // eslint-disable-next-line consistent-return + return source.lift(function (this: Subscriber, liftedSource: Observable) { + try { + return init(liftedSource, this); + } catch (err) { + this.error(err); + } + }); + } + throw new TypeError('Unable to lift unknown Observable type'); + }; +} + +class OperatorSubscriber extends Subscriber { + constructor( + destination: Subscriber, + onNext?: (value: T) => void, + onComplete?: () => void, + onError?: (err: any) => void, + private onFinalize?: () => void, + private shouldUnsubscribe?: () => boolean + ) { + super(destination); + this._next = onNext + ? function (this: OperatorSubscriber, value: T) { + try { + onNext(value); + } catch (err) { + destination.error(err); + } + } + : super._next; + this._error = onError + ? function (this: OperatorSubscriber, err: any) { + try { + onError(err); + } catch (error) { + // Send any errors that occur down stream. + destination.error(error); + } finally { + // Ensure finalization. + this.unsubscribe(); + } + } + : super._error; + this._complete = onComplete + ? function (this: OperatorSubscriber) { + try { + onComplete(); + } catch (err) { + // Send any errors that occur down stream. + destination.error(err); + } finally { + // Ensure finalization. + this.unsubscribe(); + } + } + : super._complete; + } + + unsubscribe() { + if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) { + const { closed } = this; + super.unsubscribe(); + !closed && this.onFinalize?.(); + } + } +} + +function createOperatorSubscriber( + destination: Subscriber, + onNext?: (value: T) => void, + onComplete?: () => void, + onError?: (err: any) => void, + onFinalize?: () => void +): Subscriber { + return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize); +} + +/** + * This will prevent all actions for the specified duration, but will still emit actions as axiumConclude + * Thus this needs to be taken into account in the Method using debounceAction if implemented directly. + * But will be handled automatically in actionControllers and associated debounce createMethods. + */ +export function debounceAction(dueTime: number, scheduler: SchedulerLike = asyncScheduler): MonoTypeOperatorFunction { + return operate((source, subscriber) => { + let activeTask: Subscription | null = null; + let lastValue: Action | null = null; + let lastTime: number | null = null; + + const emit = () => { + if (activeTask) { + activeTask.unsubscribe(); + activeTask = null; + const value = lastValue!; + lastValue = null; + subscriber.next(value); + } + }; + function emitWhenIdle(this: SchedulerAction) { + const targetTime = lastTime! + dueTime; + const now = scheduler.now(); + if (now < targetTime) { + activeTask = this.schedule(undefined, targetTime - now); + subscriber.add(activeTask); + return; + } + + emit(); + } + + source.subscribe( + createOperatorSubscriber( + subscriber, + (value: Action) => { + lastValue = value; + lastTime = scheduler.now(); + if (!activeTask) { + activeTask = scheduler.schedule(emitWhenIdle, dueTime); + subscriber.add(activeTask); + } else { + // All this code just to place this code block. + const conclude = axiumConclude(); + subscriber.next( + { + ...value, + ...conclude, + } + ); + } + }, + () => { + emit(); + subscriber.complete(); + }, + undefined, + () => { + lastValue = activeTask = null; + } + ) + ); + }); +} \ No newline at end of file diff --git a/src/model/method.ts b/src/model/method.ts index 6cdac79..65f1161 100644 --- a/src/model/method.ts +++ b/src/model/method.ts @@ -1,10 +1,10 @@ -import { Observable, Subject, debounceTime, map, switchMap, withLatestFrom } from 'rxjs'; +import { Observable, Subject, map, switchMap, withLatestFrom } from 'rxjs'; import { Concept } from './concept'; import { UnifiedSubject } from './stagePlanner'; import { ActionController, createActionController$ } from './actionController'; import { ActionStrategy } from './actionStrategy'; import { KeyedSelector } from './selector'; -import { createExperimentConcept, createExperimentState } from '../concepts/experiment/experiment.concept'; +import { debounceAction } from './debounceAction'; export type ActionType = string; const axiumConcludeType: ActionType = 'Conclude'; @@ -20,6 +20,9 @@ type Action = { }; type Method = Observable; + + + export const createMethod = (method: (action: Action) => Action): [Method, Subject] => { const defaultSubject = new Subject(); @@ -78,66 +81,76 @@ export const createAsyncMethodWithConcepts = ); return [defaultMethod, defaultSubject]; }; -// export const createMethodDebounce = -// (method: (action: Action) => Action, duration: number): [Method, Subject] => { -// const defaultSubject = new Subject(); -// const defaultMethod: Method = defaultSubject.pipe( -// debounceTime(duration), -// map((action: Action) => { -// const methodAction = method(action); -// if (methodAction.strategy) { -// return methodAction; -// } -// return { -// ...action, -// type: axiumConcludeType -// }; -// }), -// ); -// return [defaultMethod, defaultSubject]; -// }; -// export const createMethodDebounceWithConcepts = -// (methodWithConcepts: (action: Action, concepts: Concept[]) => Action, concepts$: UnifiedSubject, duration: number) -// : [Method, Subject] => { -// const defaultSubject = new Subject(); -// const defaultMethod: Method = defaultSubject.pipe( -// debounceTime(duration), -// withLatestFrom(concepts$ as UnifiedSubject), -// map(([act, concepts] : [Action, Concept[]]) => { -// const methodAction = methodWithConcepts(act, concepts); -// if (methodAction.strategy) { -// return methodAction; -// } -// return { -// ...act, -// type: axiumConcludeType -// }; -// }), -// ); -// return [defaultMethod, defaultSubject]; -// }; -// export const createAsyncMethodDebounce = -// (asyncMethod: (controller: ActionController, action: Action) => void, duration: number): [Method, Subject] => { -// const defaultSubject = new Subject(); -// const defaultMethod: Method = defaultSubject.pipe( -// debounceTime(duration), -// switchMap(act => createActionController$(act, (controller: ActionController, action: Action) => { -// asyncMethod(controller, action); -// })), -// ); -// return [defaultMethod, defaultSubject]; -// }; -// export const createAsyncMethodDebounceWithConcepts = -// (asyncMethodWithConcepts: (controller: ActionController, action: Action, concepts: Concept[]) => -// void, concepts$: UnifiedSubject, duration: number): [Method, Subject] => { -// const defaultSubject = new Subject(); -// const defaultMethod: Method = defaultSubject.pipe( -// debounceTime(duration), -// withLatestFrom(concepts$ as UnifiedSubject), -// switchMap(([act, concepts] : [Action, Concept[]]) => -// createActionController$(act, (controller: ActionController, action: Action) => { -// asyncMethodWithConcepts(controller, action, concepts); -// })), -// ); -// return [defaultMethod, defaultSubject]; -// }; \ No newline at end of file +export const createMethodDebounce = + (method: (action: Action) => Action, duration: number): [Method, Subject] => { + const defaultSubject = new Subject(); + const defaultMethod: Method = defaultSubject.pipe( + debounceAction(duration), + map((action: Action) => { + // Logically Determined axiumConclude + if (action.semaphore[3] !== 3) { + const methodAction = method(action); + if (methodAction.strategy) { + return methodAction; + } + return { + ...action, + type: axiumConcludeType + }; + } else { + return action; + } + }), + ); + return [defaultMethod, defaultSubject]; + }; +export const createMethodDebounceWithConcepts = + (methodWithConcepts: (action: Action, concepts: Concept[]) => Action, concepts$: UnifiedSubject, duration: number) + : [Method, Subject] => { + const defaultSubject = new Subject(); + const defaultMethod: Method = defaultSubject.pipe( + debounceAction(duration), + withLatestFrom(concepts$ as UnifiedSubject), + map(([act, concepts] : [Action, Concept[]]) => { + // Logically Determined axiumConclude + if (act.semaphore[3] !== 3) { + const methodAction = methodWithConcepts(act, concepts); + if (methodAction.strategy) { + return methodAction; + } + return { + ...act, + type: axiumConcludeType + }; + } else { + return act; + } + }), + ); + return [defaultMethod, defaultSubject]; + }; +export const createAsyncMethodDebounce = + (asyncMethod: (controller: ActionController, action: Action) => void, duration: number): [Method, Subject] => { + const defaultSubject = new Subject(); + const defaultMethod: Method = defaultSubject.pipe( + debounceAction(duration), + switchMap(act => createActionController$(act, (controller: ActionController, action: Action) => { + asyncMethod(controller, action); + })), + ); + return [defaultMethod, defaultSubject]; + }; +export const createAsyncMethodDebounceWithConcepts = + (asyncMethodWithConcepts: (controller: ActionController, action: Action, concepts: Concept[]) => + void, concepts$: UnifiedSubject, duration: number): [Method, Subject] => { + const defaultSubject = new Subject(); + const defaultMethod: Method = defaultSubject.pipe( + debounceAction(duration), + withLatestFrom(concepts$ as UnifiedSubject), + switchMap(([act, concepts] : [Action, Concept[]]) => + createActionController$(act, (controller: ActionController, action: Action) => { + asyncMethodWithConcepts(controller, action, concepts); + })), + ); + return [defaultMethod, defaultSubject]; + }; \ No newline at end of file diff --git a/src/model/stagePlanner.ts b/src/model/stagePlanner.ts index 543f574..d1690b5 100644 --- a/src/model/stagePlanner.ts +++ b/src/model/stagePlanner.ts @@ -221,6 +221,7 @@ export class UnifiedSubject extends Subject { let run = true; [stageDelimiter, goodAction] = handleStageDelimiter(plan, action, stageDelimiter, options); [stageDelimiter, run] = handleRun(value, stageDelimiter, plan, action, options); + // console.log('HIT', action, goodAction, run); this.stageDelimiters.set(key, stageDelimiter); if (goodAction && run) { const action$ = axiumState.action$ as Subject; diff --git a/src/test/actionController.test.ts b/src/test/actionController.test.ts index 67c5820..39967c3 100644 --- a/src/test/actionController.test.ts +++ b/src/test/actionController.test.ts @@ -64,48 +64,3 @@ test('ActionController createActionController$ Mock Strategy', (done) => { done(); }); }); - -test('ActionController async Method Test', (done) => { - const experiment = createExperimentConcept(createExperimentState(), [timerEmitActionQuality, mockToTrueQuality]); - const axium = createAxium('Experiment async method creator', [experiment]); - const plan = axium.stage('timed mock to true', [ - (_, dispatch) => { - dispatch(strategyBegin(timedMockToTrue()), { - iterateStage: true - }); - }, - (concepts, _) => { - const experimentState = selectState(concepts, experimentName); - if (experimentState.mock) { - expect(experimentState.mock).toBe(true); - plan.conclude(); - done(); - } - } - ]); -}); - -test('ActionController async Method Test with Concepts', (done) => { - const experiment = createExperimentConcept(createExperimentState(), [timerEmitActionWithConceptsQuality, mockToTrueQuality]); - const axium = createAxium('Experiment async method creator with Concepts', [experiment]); - const plan = axium.stage('timed mock to true', [ - (_, dispatch) => { - dispatch(strategyBegin(timedMockToTrueWithConcepts()), { - iterateStage: true - }); - }, - (concepts, _) => { - const lastStrategy = selectSlice(concepts, axiumSelectLastStrategy); - const experimentState = selectState(concepts, experimentName); - if (lastStrategy === timedMockToTrueWithConceptsTopic) { - const data = selectSlice(concepts, axiumSelectLastStrategyData); - if (data) { - expect(data.mock).toBe(false); - expect(experimentState.mock).toBe(true); - plan.conclude(); - done(); - } - } - } - ]); -}); \ No newline at end of file diff --git a/src/test/debounceMethods.test.ts b/src/test/debounceMethods.test.ts new file mode 100644 index 0000000..0f899b3 --- /dev/null +++ b/src/test/debounceMethods.test.ts @@ -0,0 +1,103 @@ +import { Counter, counterName, createCounterConcept } from '../concepts/counter/counter.concept'; +import { createExperimentConcept, createExperimentState } from '../concepts/experiment/experiment.concept'; +import { asyncDebounceNextActionNodeQuality } from '../concepts/experiment/qualities/debounceAsyncNextActionNode.quality'; +import { debounceNextActionNodeQuality } from '../concepts/experiment/qualities/debounceNextActionNode.quality'; +import { experimentAsyncDebounceAddOneStrategy } from '../concepts/experiment/strategies/asyncDebounceAddOne.strategy'; +import { experimentDebounceAddOneStrategy } from '../concepts/experiment/strategies/debounceAddOne.strategy'; +import { strategyBegin } from '../model/actionStrategy'; +import { createAxium } from '../model/axium'; +import { selectState } from '../model/selector'; + +test('Debounce method prevent excess count', (done) => { + const experiment = createExperimentConcept(createExperimentState(), [debounceNextActionNodeQuality]); + const axium = createAxium('Experiment async method creator with Concepts', [createCounterConcept(), experiment]); + const plan = axium.stage('timed mock to true', [ + (_, dispatch) => { + dispatch(strategyBegin(experimentDebounceAddOneStrategy()), { + iterateStage: true + }); + }, + (_, dispatch) => { + dispatch(strategyBegin(experimentDebounceAddOneStrategy()), { + iterateStage: true + }); + }, + (_, dispatch) => { + dispatch(strategyBegin(experimentDebounceAddOneStrategy()), { + iterateStage: true + }); + }, + (concepts, _) => { + const counterState = selectState(concepts, counterName); + console.log('Debounce HIT 4', counterState); + if (counterState.count === 1) { + console.log('Final Debounce HIT 4', counterState); + expect(counterState.count).toBe(1); + plan.conclude(); + done(); + } + } + ]); +}); + +test('Async debounce method prevent excess count', (done) => { + const experiment = createExperimentConcept(createExperimentState(), [asyncDebounceNextActionNodeQuality]); + const axium = createAxium('Experiment async method creator with Concepts', [createCounterConcept(), experiment]); + const plan = axium.stage('timed mock to true', [ + (_, dispatch) => { + dispatch(strategyBegin(experimentAsyncDebounceAddOneStrategy()), { + iterateStage: true + }); + }, + (_, dispatch) => { + dispatch(strategyBegin(experimentAsyncDebounceAddOneStrategy()), { + iterateStage: true + }); + }, + (_, dispatch) => { + dispatch(strategyBegin(experimentAsyncDebounceAddOneStrategy()), { + iterateStage: true + }); + }, + (concepts, _) => { + const counterState = selectState(concepts, counterName); + console.log('Async Debounce HIT 4', counterState); + if (counterState.count === 1) { + console.log('Async Debounce HIT 4', counterState); + expect(counterState.count).toBe(1); + plan.conclude(); + } + } + ]); + setTimeout(() => { + const secondPlan = axium.stage('timed mock to true', [ + (_, dispatch) => { + dispatch(strategyBegin(experimentAsyncDebounceAddOneStrategy()), { + iterateStage: true + }); + }, + (_, dispatch) => { + dispatch(strategyBegin(experimentAsyncDebounceAddOneStrategy()), { + iterateStage: true + }); + }, + (_, dispatch) => { + dispatch(strategyBegin(experimentAsyncDebounceAddOneStrategy()), { + iterateStage: true + }); + }, + (concepts, _) => { + const counterState = selectState(concepts, counterName); + console.log('Async Plan 2 Debounce HIT 4', counterState); + if (counterState.count === 2) { + console.log('Async Plan 2 Debounce HIT 4', counterState); + expect(counterState.count).toBe(2); + secondPlan.conclude(); + setTimeout(() => { + done(); + }, 500); + } + } + ]); + }, 600); +}); \ No newline at end of file diff --git a/src/test/methodHelpers.test.ts b/src/test/methodHelpers.test.ts new file mode 100644 index 0000000..07b5caa --- /dev/null +++ b/src/test/methodHelpers.test.ts @@ -0,0 +1,58 @@ +import { axiumSelectLastStrategy, axiumSelectLastStrategyData } from '../concepts/axium/axium.selector'; +import { ExperimentState, createExperimentConcept, createExperimentState, experimentName } from '../concepts/experiment/experiment.concept'; +import { mockToTrueQuality } from '../concepts/experiment/qualities/mockTrue.quality'; +import { timerEmitActionQuality } from '../concepts/experiment/qualities/timerEmitAction.quality'; +import { timerEmitActionWithConceptsQuality } from '../concepts/experiment/qualities/timerEmitActionWithConcepts.quality'; +import { timedMockToTrue } from '../concepts/experiment/strategies/timedMockToTrue.strategy'; +import { + timedMockToTrueWithConcepts, + timedMockToTrueWithConceptsTopic +} from '../concepts/experiment/strategies/timedMockToTrueWithConcepts.strategy'; +import { strategyBegin } from '../model/actionStrategy'; +import { createAxium } from '../model/axium'; +import { selectSlice, selectState } from '../model/selector'; + +test('ActionController async Method Test', (done) => { + const experiment = createExperimentConcept(createExperimentState(), [timerEmitActionQuality, mockToTrueQuality]); + const axium = createAxium('Experiment async method creator', [experiment]); + const plan = axium.stage('timed mock to true', [ + (_, dispatch) => { + dispatch(strategyBegin(timedMockToTrue()), { + iterateStage: true + }); + }, + (concepts, _) => { + const experimentState = selectState(concepts, experimentName); + if (experimentState.mock) { + expect(experimentState.mock).toBe(true); + plan.conclude(); + done(); + } + } + ]); +}); + +test('ActionController async Method Test with Concepts', (done) => { + const experiment = createExperimentConcept(createExperimentState(), [timerEmitActionWithConceptsQuality, mockToTrueQuality]); + const axium = createAxium('Experiment async method creator with Concepts', [experiment]); + const plan = axium.stage('timed mock to true', [ + (_, dispatch) => { + dispatch(strategyBegin(timedMockToTrueWithConcepts()), { + iterateStage: true + }); + }, + (concepts, _) => { + const lastStrategy = selectSlice(concepts, axiumSelectLastStrategy); + const experimentState = selectState(concepts, experimentName); + if (lastStrategy === timedMockToTrueWithConceptsTopic) { + const data = selectSlice(concepts, axiumSelectLastStrategyData); + if (data) { + expect(data.mock).toBe(false); + expect(experimentState.mock).toBe(true); + plan.conclude(); + done(); + } + } + } + ]); +}); From 1ff3fe44a8d517c5aa9d74c65573519fae834563 Mon Sep 17 00:00:00 2001 From: REllEK-IO Date: Thu, 19 Oct 2023 11:52:54 -0700 Subject: [PATCH 5/8] Light documentation of helper methods --- ActionStrategy.md | 32 ++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/ActionStrategy.md b/ActionStrategy.md index 1fdbe2c..7b5577e 100644 --- a/ActionStrategy.md +++ b/ActionStrategy.md @@ -118,3 +118,35 @@ The same is true when accessing the payload from a reducer, method, or principle SomethingFactory> ``` This was a purposeful design choice, if you find yourself doing such. Known this system is already complicated enough. + +## Helper Functions for Standard Method Creators +*Note you still need to create a function of MethodCreator to use these Helpers* +```typescript +export const createMethod = + (method: (action: Action) => Action): [Method, Subject] => {} +export const createMethodWithConcepts = + (method: (action: Action) => Action, concepts$: UnifiedSubject): [Method, Subject] => {} +export const createAsyncMethod = + (asyncMethod: (controller: ActionController, action: Action) => void): [Method, Subject] => {} +export const createAsyncMethodWithConcepts = + (asyncMethodWithConcepts: (controller: ActionController, action: Action, concepts: Concept[]) => void, concepts$: UnifiedSubject) + : [Method, Subject] => {} +export const createMethodDebounce = + (method: (action: Action) => Action, duration: number): [Method, Subject] => {} +export const createMethodDebounceWithConcepts = + (methodWithConcepts: (action: Action, concepts: Concept[]) => Action, concepts$: UnifiedSubject, duration: number) + : [Method, Subject] => {} +export const createAsyncMethodDebounce = + (asyncMethod: (controller: ActionController, action: Action) => void, duration: number): [Method, Subject] => {} +export const createAsyncMethodDebounceWithConcepts = + (asyncMethodWithConcepts: (controller: ActionController, action: Action, concepts: Concept[]) => + void, concepts$: UnifiedSubject, duration: number): [Method, Subject] => {} +``` +* createMethod - Your standard method, be sure to handle the action.strategy via one of the strategy decision functions, in addition to passing the action if there is no attached strategy. +* createMethodWithConcepts - This will allow your method to have the most recent concepts to be accessed via the asyncMethod function. +* createAsyncMethod - Handled differently than the rest, you will have to use the passed controller to fire your actions back into the action stream. +* createAsyncMethodWithConcepts - Will also have access to the most recent concepts. +* createMethodDebounce - After the first action, this will filter actions within the duration to be set to the conclude action. +* createMethodDebounceWithConcepts - Will filter actions within the duration while providing access to the most recent concepts. +* createAsyncMethodDebounce - Will not disengage the initial ActionController, but will allow debounced actions to pass through when filtered as conclude actions. And will fire the first action upon its own conditions are met asynchronously. +* createAsyncMethodDebounceWithConcepts - Filters and then first the first action once conditions are met, and provides access to the most recent concepts. \ No newline at end of file diff --git a/package.json b/package.json index ec8101f..da09323 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@phuire/strx", - "version": "0.0.37", + "version": "0.0.38", "description": "Unified Turing Machine", "main": "dist/index.js", "module": "dist/index.mjs", From 4b4f65f6de859b5e244eb6198d0b8c7317f68096 Mon Sep 17 00:00:00 2001 From: REllEK-IO Date: Thu, 19 Oct 2023 11:58:03 -0700 Subject: [PATCH 6/8] Increased timeout for debounceMethods.test.ts --- src/test/debounceMethods.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/debounceMethods.test.ts b/src/test/debounceMethods.test.ts index 0f899b3..c592416 100644 --- a/src/test/debounceMethods.test.ts +++ b/src/test/debounceMethods.test.ts @@ -8,6 +8,7 @@ import { strategyBegin } from '../model/actionStrategy'; import { createAxium } from '../model/axium'; import { selectState } from '../model/selector'; +jest.setTimeout(7000); test('Debounce method prevent excess count', (done) => { const experiment = createExperimentConcept(createExperimentState(), [debounceNextActionNodeQuality]); const axium = createAxium('Experiment async method creator with Concepts', [createCounterConcept(), experiment]); From 747546b3a59f0cc577bf4b11db65613fb166ee5a Mon Sep 17 00:00:00 2001 From: REllEK-IO Date: Thu, 19 Oct 2023 12:00:30 -0700 Subject: [PATCH 7/8] Increased timeout for debounceMethods.test.ts --- src/test/debounceMethods.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/debounceMethods.test.ts b/src/test/debounceMethods.test.ts index c592416..46e7ae0 100644 --- a/src/test/debounceMethods.test.ts +++ b/src/test/debounceMethods.test.ts @@ -8,7 +8,7 @@ import { strategyBegin } from '../model/actionStrategy'; import { createAxium } from '../model/axium'; import { selectState } from '../model/selector'; -jest.setTimeout(7000); +jest.setTimeout(10000); test('Debounce method prevent excess count', (done) => { const experiment = createExperimentConcept(createExperimentState(), [debounceNextActionNodeQuality]); const axium = createAxium('Experiment async method creator with Concepts', [createCounterConcept(), experiment]); From c86ff8e3762d51fbfd679f1101ba0ad899ef6355 Mon Sep 17 00:00:00 2001 From: REllEK-IO Date: Thu, 19 Oct 2023 13:55:18 -0700 Subject: [PATCH 8/8] Tests passing --- ActionStrategy.md | 5 +-- Axium.md | 1 + src/concepts/axium/axium.concept.ts | 2 ++ src/concepts/axium/qualities/kick.quality.ts | 12 +++++++ src/concepts/counter/qualities/add.quality.ts | 9 ++++- src/index.ts | 1 + src/model/actionController.ts | 35 ++----------------- src/model/debounceAction.ts | 10 +++--- src/model/method.ts | 32 +++++++++++------ src/model/stagePlanner.ts | 2 +- src/test/actionController.test.ts | 4 +-- src/test/debounceMethods.test.ts | 15 ++++---- 12 files changed, 69 insertions(+), 59 deletions(-) create mode 100644 src/concepts/axium/qualities/kick.quality.ts diff --git a/ActionStrategy.md b/ActionStrategy.md index 7b5577e..e7cf086 100644 --- a/ActionStrategy.md +++ b/ActionStrategy.md @@ -120,7 +120,7 @@ SomethingFactory> This was a purposeful design choice, if you find yourself doing such. Known this system is already complicated enough. ## Helper Functions for Standard Method Creators -*Note you still need to create a function of MethodCreator to use these Helpers* +*You still need to create a function of type MethodCreator to use these Helpers. :MethodCreator = () => methodCreator* ```typescript export const createMethod = (method: (action: Action) => Action): [Method, Subject] => {} @@ -146,7 +146,8 @@ export const createAsyncMethodDebounceWithConcepts = * createMethodWithConcepts - This will allow your method to have the most recent concepts to be accessed via the asyncMethod function. * createAsyncMethod - Handled differently than the rest, you will have to use the passed controller to fire your actions back into the action stream. * createAsyncMethodWithConcepts - Will also have access to the most recent concepts. +*Note if you are implementing your own debounceAction how these methods work. They are handling a passed conclude from debounceAction within their map/switchMap* * createMethodDebounce - After the first action, this will filter actions within the duration to be set to the conclude action. -* createMethodDebounceWithConcepts - Will filter actions within the duration while providing access to the most recent concepts. +* createMethodDebounceWithConcepts - Will filter actions within the duration while providing access to the most recent concepts. * createAsyncMethodDebounce - Will not disengage the initial ActionController, but will allow debounced actions to pass through when filtered as conclude actions. And will fire the first action upon its own conditions are met asynchronously. * createAsyncMethodDebounceWithConcepts - Filters and then first the first action once conditions are met, and provides access to the most recent concepts. \ No newline at end of file diff --git a/Axium.md b/Axium.md index ecb660b..503312c 100644 --- a/Axium.md +++ b/Axium.md @@ -94,6 +94,7 @@ Please avoid using these qualities, but are providing explanations to understand * clearBadActionTypeFromBadActionList - This is to allow for plans to take into account for expired actions and clear such. * clearBadStrategyTopicFromBadActionList - Allows plans to accounts for specific ActionStrategy topics that might find themselves in badActions and clear such. * clearBadPlanFromBadPlanList - This additionally allows for concepts to take into account potentially failed plans that are set by axium.stage(). Via their topic as payload and clears such. +* kick - This is a pure action that will just trigger the next function via the UnifiedSubject to prime subscribers or stages. Noting that the downside of STRX's halting quality, is you have to kick it into gear if it hasn't received an action recently for your staged Plans to operate as intended. ## Axium Strategies Concept Set Transformation ```typescript diff --git a/src/concepts/axium/axium.concept.ts b/src/concepts/axium/axium.concept.ts index e60379a..2eb8cf2 100644 --- a/src/concepts/axium/axium.concept.ts +++ b/src/concepts/axium/axium.concept.ts @@ -26,6 +26,7 @@ import { clearBadActionTypeFromBadActionListQuality } from './qualities/clearBad import { clearBadStrategyTopicFromBadActionListQuality } from './qualities/clearBadStrategyTopicFromBadActionList.quality'; import { clearBadPlanFromBadPlanListQuality } from './qualities/clearBadPlanFromBadPlanList.quality'; import { registerStagePlannerQuality } from './qualities/registerStagePlanner.quality'; +import { kickQuality } from './qualities/kick.quality'; export type NamedSubscription = { name: string; @@ -92,6 +93,7 @@ export const createAxiumConcept = (name: string, storeDialog?: boolean, logging? axiumName, createAxiumState(name, storeDialog, logging), [ + kickQuality, openQuality, badActionQuality, closeQuality, diff --git a/src/concepts/axium/qualities/kick.quality.ts b/src/concepts/axium/qualities/kick.quality.ts new file mode 100644 index 0000000..7096fe7 --- /dev/null +++ b/src/concepts/axium/qualities/kick.quality.ts @@ -0,0 +1,12 @@ +import { defaultMethodCreator, defaultReducer } from '../../../model/concept'; +import { ActionType, prepareActionCreator } from '../../../model/action'; +import { createQuality } from '../../../model/concept'; + +export const axiumKickType: ActionType = 'Kick Axium'; +export const axiumKick = prepareActionCreator(axiumKickType); + +export const kickQuality = createQuality( + axiumKickType, + defaultReducer, + defaultMethodCreator +); diff --git a/src/concepts/counter/qualities/add.quality.ts b/src/concepts/counter/qualities/add.quality.ts index 5eeb3ae..7690d68 100644 --- a/src/concepts/counter/qualities/add.quality.ts +++ b/src/concepts/counter/qualities/add.quality.ts @@ -3,11 +3,18 @@ import { defaultMethodCreator, Method, MethodCreator } from '../../../model/conc import { Counter } from '../counter.concept'; import { createQuality } from '../../../model/concept'; import { counterSelectCount } from '../counter.selector'; +// import { createMethod } from '../../../model/method'; +// import { strategySuccess } from '../../../model/actionStrategy'; export const counterAddType: ActionType = 'Counter Add'; export const counterAdd = prepareActionCreator(counterAddType); - +// const createAddMethodCreator: MethodCreator = () => createMethod((action) => { +// if (action.strategy) { +// return strategySuccess(action.strategy); +// } +// return action; +// }); export function addReducer(state: Counter, _: Action) { return { ...state, diff --git a/src/index.ts b/src/index.ts index 25ab32c..fd4da28 100644 --- a/src/index.ts +++ b/src/index.ts @@ -62,6 +62,7 @@ export { AxiumState, axiumName, createAxiumConcept } from './concepts/axium/axiu export { blockingMode, permissiveMode } from './concepts/axium/axium.mode'; export { axiumSelectOpen, axiumSelectLastStrategy, axiumSelectBadActions, axiumSelectBadPlans } from './concepts/axium/axium.selector'; // Qualities +export { axiumKick, axiumKickType } from './concepts/axium/qualities/kick.quality'; export { axiumConclude, axiumConcludeType } from './concepts/axium/qualities/conclude.quality'; export { axiumOpen, axiumOpenType } from './concepts/axium/qualities/open.quality'; export { axiumLog, axiumLogType } from './concepts/axium/qualities/log.quality'; diff --git a/src/model/actionController.ts b/src/model/actionController.ts index f07b30f..abbc38a 100644 --- a/src/model/actionController.ts +++ b/src/model/actionController.ts @@ -1,4 +1,4 @@ -import { Action, ActionStrategy, axiumBadAction, axiumConclude, axiumConcludeType, strategyFailed } from '../index'; +import { Action, axiumBadAction, strategyFailed } from '../index'; import { Subject } from 'rxjs'; import { failureConditions, strategyData_appendFailure } from './actionStrategyData'; @@ -20,12 +20,6 @@ export class ActionController extends Subject { strategyData_appendFailure(this.action.strategy, failureConditions.controllerExpired) )); } else { - const badAction = axiumBadAction([this.action]); - if (this.action.strategy) { - badAction.strategy = this.action.strategy; - (badAction.strategy as ActionStrategy).currentNode.action = badAction; - (badAction.strategy as ActionStrategy).currentNode.actionType = badAction.type; - } this.next(axiumBadAction([this.action])); } }, this.expiration - Date.now()); @@ -46,35 +40,12 @@ export class ActionController extends Subject { clearTimeout(this.timer); this.timer.unref(); } - let nextAction; - let end = true; - // Logically Determined axiumConclude - if (action.semaphore[3] === 3) { - end = false; - } - if (action.strategy) { - nextAction = action; - // Logically Determined axiumConclude - } else if (action.semaphore[3] === 3) { - nextAction = action; - // Logically Determined axiumBadAction - } else if (!action.strategy && action.semaphore[3] !== 1) { - const conclude = axiumConclude(); - nextAction = { - ...action, - ...conclude - }; - } else { - nextAction = action; - } const { observers } = this; const len = observers.length; for (let i = 0; i < len; i++) { - observers[i].next(nextAction); - } - if (end) { - this.complete(); + observers[i].next(action); } + this.complete(); } } } diff --git a/src/model/debounceAction.ts b/src/model/debounceAction.ts index 7739208..5ff146c 100644 --- a/src/model/debounceAction.ts +++ b/src/model/debounceAction.ts @@ -142,12 +142,12 @@ export function debounceAction(dueTime: number, scheduler: SchedulerLike = async subscriber.add(activeTask); } else { // All this code just to place this code block. - const conclude = axiumConclude(); + const conclude = { + ...value, + ...axiumConclude(), + }; subscriber.next( - { - ...value, - ...conclude, - } + conclude ); } }, diff --git a/src/model/method.ts b/src/model/method.ts index 65f1161..e732745 100644 --- a/src/model/method.ts +++ b/src/model/method.ts @@ -20,9 +20,6 @@ type Action = { }; type Method = Observable; - - - export const createMethod = (method: (action: Action) => Action): [Method, Subject] => { const defaultSubject = new Subject(); @@ -134,9 +131,17 @@ export const createAsyncMethodDebounce = const defaultSubject = new Subject(); const defaultMethod: Method = defaultSubject.pipe( debounceAction(duration), - switchMap(act => createActionController$(act, (controller: ActionController, action: Action) => { - asyncMethod(controller, action); - })), + switchMap((act) => { + if (act.semaphore[3] !== 3) { + return createActionController$(act, (controller: ActionController, action: Action) => { + asyncMethod(controller, action); + }); + } else { + return createActionController$(act, (controller: ActionController, _) => { + controller.fire(act); + }); + } + }), ); return [defaultMethod, defaultSubject]; }; @@ -147,10 +152,17 @@ export const createAsyncMethodDebounceWithConcepts = const defaultMethod: Method = defaultSubject.pipe( debounceAction(duration), withLatestFrom(concepts$ as UnifiedSubject), - switchMap(([act, concepts] : [Action, Concept[]]) => - createActionController$(act, (controller: ActionController, action: Action) => { - asyncMethodWithConcepts(controller, action, concepts); - })), + switchMap(([act, concepts] : [Action, Concept[]]) => { + if (act.semaphore[3] !== 3) { + return createActionController$(act, (controller: ActionController, action: Action) => { + asyncMethodWithConcepts(controller, action, concepts); + }); + } else { + return createActionController$(act, (controller: ActionController, _) => { + controller.fire(act); + }); + } + }) ); return [defaultMethod, defaultSubject]; }; \ No newline at end of file diff --git a/src/model/stagePlanner.ts b/src/model/stagePlanner.ts index d1690b5..8590cf8 100644 --- a/src/model/stagePlanner.ts +++ b/src/model/stagePlanner.ts @@ -196,9 +196,9 @@ export class UnifiedSubject extends Subject { super(); } stage(title: string, stages: Staging[]): StagePlanner { - this.currentStages.set(this.planId, {title, stages, stage: 0, stageFailed: -1}); const planId = this.planId; this.planId++; + this.currentStages.set(planId, {title, stages, stage: 0, stageFailed: -1}); const conclude = () => { this.currentStages.delete(planId); }; diff --git a/src/test/actionController.test.ts b/src/test/actionController.test.ts index 39967c3..80bd184 100644 --- a/src/test/actionController.test.ts +++ b/src/test/actionController.test.ts @@ -29,7 +29,7 @@ test('ActionController Next Test', (done) => { const act = axiumLog(undefined, 200); const cont = new ActionController(act); cont.subscribe(action => { - expect(action.type).toBe(axiumConcludeType); + expect(action.type).toBe(axiumLogType); done(); }); cont.fire(act); @@ -41,7 +41,7 @@ test('ActionController createActionController$ Test', (done) => { controller.fire(action); }); cont.subscribe(action => { - expect(action.type).toBe(axiumConcludeType); + expect(action.type).toBe(axiumLogType); done(); }); }); diff --git a/src/test/debounceMethods.test.ts b/src/test/debounceMethods.test.ts index 46e7ae0..25b62c4 100644 --- a/src/test/debounceMethods.test.ts +++ b/src/test/debounceMethods.test.ts @@ -1,3 +1,4 @@ +import { axiumKick } from '../concepts/axium/qualities/kick.quality'; import { Counter, counterName, createCounterConcept } from '../concepts/counter/counter.concept'; import { createExperimentConcept, createExperimentState } from '../concepts/experiment/experiment.concept'; import { asyncDebounceNextActionNodeQuality } from '../concepts/experiment/qualities/debounceAsyncNextActionNode.quality'; @@ -8,7 +9,6 @@ import { strategyBegin } from '../model/actionStrategy'; import { createAxium } from '../model/axium'; import { selectState } from '../model/selector'; -jest.setTimeout(10000); test('Debounce method prevent excess count', (done) => { const experiment = createExperimentConcept(createExperimentState(), [debounceNextActionNodeQuality]); const axium = createAxium('Experiment async method creator with Concepts', [createCounterConcept(), experiment]); @@ -64,14 +64,14 @@ test('Async debounce method prevent excess count', (done) => { const counterState = selectState(concepts, counterName); console.log('Async Debounce HIT 4', counterState); if (counterState.count === 1) { - console.log('Async Debounce HIT 4', counterState); + console.log('FINAL Async Debounce HIT 4', counterState); expect(counterState.count).toBe(1); plan.conclude(); } } ]); setTimeout(() => { - const secondPlan = axium.stage('timed mock to true', [ + const secondPlan = axium.stage('second timed mock', [ (_, dispatch) => { dispatch(strategyBegin(experimentAsyncDebounceAddOneStrategy()), { iterateStage: true @@ -89,9 +89,9 @@ test('Async debounce method prevent excess count', (done) => { }, (concepts, _) => { const counterState = selectState(concepts, counterName); - console.log('Async Plan 2 Debounce HIT 4', counterState); + console.log('Async 2 Debounce HIT 4', counterState); if (counterState.count === 2) { - console.log('Async Plan 2 Debounce HIT 4', counterState); + console.log('FINAL Async 2 Debounce HIT 4', counterState); expect(counterState.count).toBe(2); secondPlan.conclude(); setTimeout(() => { @@ -100,5 +100,8 @@ test('Async debounce method prevent excess count', (done) => { } } ]); - }, 600); + // Axium must be primed, therefore we kick it back into gear. + // Downside of halting quality. + axium.dispatch(axiumKick()); + }, 1000); }); \ No newline at end of file