From 5d668f603278a51ae18495a9ed1768935c4bbed7 Mon Sep 17 00:00:00 2001 From: REllEK-IO Date: Mon, 13 May 2024 12:25:40 -0700 Subject: [PATCH 1/4] INIT: Consistency, added Action Priority and Cleaned Up Logic --- README.md | 2 + package.json | 2 +- src/concepts/axium/axium.principle.ts | 4 +- .../qualities/setBlockingMode.quality.ts | 2 +- src/model/action.ts | 27 +++++++--- src/model/actionStrategy.ts | 9 +++- src/model/axium.ts | 24 +++++++-- src/model/priority.ts | 54 +++++++++++++++++++ src/model/selector.ts | 7 ++- src/model/stagePlanner.ts | 8 ++- src/model/time.ts | 7 ++- 11 files changed, 127 insertions(+), 19 deletions(-) create mode 100644 src/model/priority.ts diff --git a/README.md b/README.md index 1506e33..89a1f8e 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,8 @@ When in doubt simplify. * [Unified Turing Machine](https://github.com/Phuire-Research/Stratimux/blob/main/The-Unified-Turing-Machine.md) - The governing concept for this entire framework. ## Change Log ![Tests](https://github.com/Phuire-Research/Stratimux/actions/workflows/node.js.yml/badge.svg) +### v0.1.64 5/13/2024 +* Added Action Priority: This will allow action's assigned a priority of not 0 to be placed accordingly into the action ques. ### Patch v0.1.62 5/09/2024 * Restored DotPath, a type used in the selector creators used to guide the creation of a dot path string. ### **BREAKING** Strong Fast Lock Step v0.1.62 5/08/2024 diff --git a/package.json b/package.json index 835c1f5..73e10c8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "stratimux", "license": "GPL-3.0", - "version": "0.1.63", + "version": "0.1.64", "description": "Unified Turing Machine", "main": "dist/index.js", "module": "dist/index.mjs", diff --git a/src/concepts/axium/axium.principle.ts b/src/concepts/axium/axium.principle.ts index 3b154a6..273b64a 100644 --- a/src/concepts/axium/axium.principle.ts +++ b/src/concepts/axium/axium.principle.ts @@ -71,7 +71,7 @@ export const axiumPrinciple: PrincipleFunction = ( quality.toString = qualityToString(quality); const methodSub = quality.method.subscribe(([act, _]) => { const tail = getAxiumState(concepts).tail; - blockingMethodSubscription(tail, act); + blockingMethodSubscription(concepts, tail, act); }) as Subscriber; getAxiumState(concepts).methodSubscribers.push({name: concept.name, subscription: methodSub}); } @@ -153,7 +153,7 @@ export const axiumPrinciple: PrincipleFunction = ( return caught; })); const methodSub = quality.method.subscribe(([action, _]) => { - blockingMethodSubscription(axiumState.tail, action); + blockingMethodSubscription(newConcepts, axiumState.tail, action); }) as Subscriber; const _axiumState = newConcepts[0].state as AxiumState; _axiumState.methodSubscribers.push({ diff --git a/src/concepts/axium/qualities/setBlockingMode.quality.ts b/src/concepts/axium/qualities/setBlockingMode.quality.ts index 061030f..337b206 100644 --- a/src/concepts/axium/qualities/setBlockingMode.quality.ts +++ b/src/concepts/axium/qualities/setBlockingMode.quality.ts @@ -34,7 +34,7 @@ export const [ if (quality.method) { const sub = quality.method.subscribe(([action, _]) => { const tail = state.tail; - blockingMethodSubscription(tail, action); + blockingMethodSubscription(concepts, tail, action); }); methodSubscribers.push({ name: concept.name, diff --git a/src/model/action.ts b/src/model/action.ts index 2b4c72c..53b3281 100644 --- a/src/model/action.ts +++ b/src/model/action.ts @@ -29,6 +29,7 @@ export type Action = { agreement?: number; expiration: number; axium?: string; + priority?: number; }; const createPayload = >(payload: T) => payload; @@ -172,7 +173,8 @@ export function createAction>( keyedSelectors?: KeyedSelector[], agreement?: number, _semaphore?: [number, number, number, number], - conceptSemaphore?: number + conceptSemaphore?: number, + priority?: number ): Action { const special = getSpecialSemaphore(type); const semaphore = _semaphore !== undefined ? _semaphore : [0, 0, -1, special] as [number, number, number, number]; @@ -183,7 +185,8 @@ export function createAction>( keyedSelectors, agreement, expiration: Date.now() + (agreement !== undefined ? agreement : 5000), - conceptSemaphore + conceptSemaphore, + priority }; } @@ -199,9 +202,18 @@ export function prepareActionCreator(actionType: ActionType) { conceptSemaphore?: number, keyedSelectors?: KeyedSelector[], agreement?: number, - qualitySemaphore?: [number, number, number, number] + qualitySemaphore?: [number, number, number, number], + priority?: number ) => { - return createAction(actionType, undefined, keyedSelectors, agreement, qualitySemaphore, conceptSemaphore); + return createAction( + actionType, + undefined, + keyedSelectors, + agreement, + qualitySemaphore, + conceptSemaphore, + priority + ); }; } @@ -211,9 +223,12 @@ export function prepareActionWithPayloadCreator { - return createAction(actionType, payload, keyedSelectors, agreement, semaphore, conceptSemaphore); + return createAction( + actionType, + payload, keyedSelectors, agreement, semaphore, conceptSemaphore, priority); }; } export type ActionCreatorWithPayload = ( diff --git a/src/model/actionStrategy.ts b/src/model/actionStrategy.ts index 6bfdad5..647a735 100644 --- a/src/model/actionStrategy.ts +++ b/src/model/actionStrategy.ts @@ -22,6 +22,7 @@ import { KeyedSelector } from './selector'; * @param payload - `optional` Will set the payload of the action. * @param semaphore - `optional` This will prime the action to avoid look up at run time. Best practice use getSemaphore(). * @param conceptSemaphore - `optional` Used for Unified Qualities. Must be specified via that principle's passed semaphore value. + * @param priority - `optional` Will allow the action to be placed in the body que accordingly. * @param agreement - `optional` Is time in milliseconds of the actions intended lifetime. * @param decisionNodes - `optional` The third or more option, may override success or failure in your workflows. * @param preposition - `optional` String that prefixes the ActionType when added to the Strategy's ActionList. @@ -35,6 +36,7 @@ export interface ActionNode { actionType: ActionType; payload?: Record; conceptSemaphore?: number; + priority?: number; keyedSelectors?: KeyedSelector[]; semaphore?: [number, number, number, number]; agreement?: number; @@ -53,6 +55,7 @@ export interface ActionNode { * @param failureNode - `optional` ActionStrategy.failed() will fire Axium Conclude Type if left blank or set to null. * @param semaphore - `optional` This will prime the action to avoid look up at run time. Best practice use getSemaphore(). * @param conceptSemaphore - `optional` Used for Unified Qualities. Must be specified via that principle's passed semaphore value. + * @param priority - `optional` Will allow the action to be placed in the body que accordingly. * @param agreement - `optional` Is time in milliseconds of the actions intended lifetime. * @param decisionNodes - `optional` The third or more option, may override success or failure in your workflows. * @param preposition - `optional` String that prefixes the ActionType when added to the Strategy's ActionList. @@ -64,6 +67,7 @@ export interface ActionNode { export interface ActionNodeOptions { keyedSelectors?: KeyedSelector[]; conceptSemaphore?: number; + priority?: number; semaphore?: [number, number, number, number]; agreement?: number; decisionNodes?: Record; @@ -97,6 +101,8 @@ export function createActionNode(action: Action, options?: ActionNodeOptions): A actionType: action.type, payload: action.payload, keyedSelectors: action.keyedSelectors ? action.keyedSelectors : options.keyedSelectors, + conceptSemaphore: action.conceptSemaphore ? action.conceptSemaphore : options.conceptSemaphore, + priority: action.priority ? action.priority : options.priority, agreement: action.agreement ? action.agreement : options.agreement, semaphore: action.semaphore ? action.semaphore : options.semaphore, successNode: options.successNode ? options.successNode : null, @@ -133,7 +139,8 @@ export const createActionNodeFromStrategy = (strategy: ActionStrategy): ActionNo currentNode.keyedSelectors, currentNode.agreement, currentNode.semaphore, - currentNode.conceptSemaphore + currentNode.conceptSemaphore, + currentNode.priority ); } return createActionNode(action, { diff --git a/src/model/axium.ts b/src/model/axium.ts index 9a79117..5bd3123 100644 --- a/src/model/axium.ts +++ b/src/model/axium.ts @@ -28,8 +28,9 @@ import { axiumPreClose } from '../concepts/axium/qualities/preClose.quality'; import { StagePlanner, Staging } from './stagePlanner'; import { axiumKick } from '../concepts/axium/qualities/kick.quality'; import { axiumTimeOut } from './time'; +import { handlePriority } from './priority'; -export const blockingMethodSubscription = (tail: Action[], action: Action) => { +export const blockingMethodSubscription = (concepts: Concepts, tail: Action[], action: Action) => { if ( action.strategy && // Logical Determination: axiumConcludeType @@ -42,7 +43,11 @@ export const blockingMethodSubscription = (tail: Action[], action: Action) => { strategyData: action.strategy.data, }); tail.push(appendToDialog); - tail.push(action); + if (action.priority !== undefined) { + handlePriority(getAxiumState(concepts), action); + } else { + tail.push(action); + } } else if ( action.strategy && // Logical Determination: axiumBadType @@ -66,7 +71,11 @@ export const defaultMethodSubscription = (concepts: Concepts, tail: Action[], ac }); // setTimeout(() => { tail.push(appendToDialog); - tail.push(action); + if (action.priority !== undefined) { + handlePriority(getAxiumState(concepts), action); + } else { + tail.push(action); + } if (async) { axiumTimeOut(concepts, () => { return axiumKick(); @@ -87,6 +96,8 @@ export const defaultMethodSubscription = (concepts: Concepts, tail: Action[], ac } }; + + export function createAxium( name: string, initialConcepts: Concept[], @@ -115,7 +126,7 @@ export function createAxium( })); quality.toString = qualityToString(quality); const methodSub = quality.method.subscribe(([action, _]) => { - blockingMethodSubscription(axiumState.tail, action); + blockingMethodSubscription(concepts, axiumState.tail, action); }) as Subscriber; axiumState = concepts[0].state as AxiumState; axiumState.methodSubscribers.push({ @@ -150,6 +161,7 @@ export function createAxium( .subscribe(([action, _concepts]: [Action, Concepts]) => { // Would be notifying methods const _axiumState = _concepts[0].state as AxiumState; + // console.log('CHECK QUES', _axiumState.head, _axiumState.body, _axiumState.tail); if (_axiumState.head.length === 0) { _axiumState.head.push(action); if (_axiumState.tailTimer.length > 0) { @@ -178,6 +190,10 @@ export function createAxium( getAxiumState(concepts).action$.next(nextAction); } } + // An action dispatched from a priority stage, with a priority set to 0 + // Will override the need to handle priority + } else if (action.priority && action.priority !== 0) { + handlePriority(_axiumState, action); } else { _axiumState.body.push(action); } diff --git a/src/model/priority.ts b/src/model/priority.ts new file mode 100644 index 0000000..388b8b0 --- /dev/null +++ b/src/model/priority.ts @@ -0,0 +1,54 @@ +/*<$ +For the asynchronous graph programming framework Stratimux, define the Priority Model File +This unified concept further enhances the ability to control the halting nature of an Axium +$>*/ +/*<#*/ +import { AxiumState } from '../concepts/axium/axium.concept'; +import { Action } from './action'; + +// Is only called if action has priority +const fillBucket = (body: Action[], bucket: Action[], action: Action, _added = false) => { + // console.log('FILL BUCKET', body, bucket, action, _added); + let added = _added; + const drop = body.shift(); + if (drop) { + if (drop.priority && action.priority) { + if (drop.priority && drop.priority < action.priority && !added) { + bucket.push(action); + bucket.push(drop); + added = true; + } else { + bucket.push(drop); + } + } else if (drop.priority === undefined && !added) { + bucket.push(action); + bucket.push(drop); + added = true; + } + fillBucket(body, bucket, action, added); + } else if (!added) { + bucket.push(action); + } +}; + +const emptyBucket = (body: Action[], bucket: Action[]) => { + const drop = bucket.shift(); + if (drop) { + body.push(drop); + emptyBucket(body, bucket); + } +}; + +export const handlePriority = (axiumState: AxiumState, action: Action) => { + const body = axiumState.body; + // console.log('HIT HANDLE PRIORITY', body[0],); + if (body[0] && body[0].priority !== undefined) { + const bucket: Action[] = []; + fillBucket(body, bucket, action); + emptyBucket(body, bucket); + } else if (body[0]) { + body.unshift(action); + } else { + body.push(action); + } +}; \ No newline at end of file diff --git a/src/model/selector.ts b/src/model/selector.ts index ae0e1a0..d0b29c7 100644 --- a/src/model/selector.ts +++ b/src/model/selector.ts @@ -23,7 +23,8 @@ export type KeyedSelector = { /** * Will create a new KeyedSelector based on a concept name comparison during runtime, mainly used for external usage * @param keys - type string - Format is 'key0.key1.key3' for deep nested key values - * Originally used a DotPath parameter to ease the developer experience, but recent versions made the approach unfeasible + * @tutorial *Note* This will only extend 7 levels into a deeply nested record. + * @tutorial **Use createAdvancedKeys** function in place of keys if attempted to scaffold into through arrays */ export const createConceptKeyedSelector = >(conceptName: string, keys: DotPath, setKeys?: (number|string)[]): KeyedSelector => { @@ -50,7 +51,9 @@ export const createConceptKeyedSelector = /** * Will create a new KeyedSelector during runtime, for usage throughout Stratimux * @param keys - type string - Format is 'key0.key1.key3' for deep nested key values - * Originally used a DotPath parameter to ease the developer experience, but recent versions made the approach unfeasible + * Uses type DotPath to enable some DX in the IDE to quickly create the desired string format. + * @tutorial *Note* This will only extend 7 levels into a deeply nested record. + * @tutorial **Use createAdvancedKeys** function in place of keys if attempted to scaffold into through arrays */ export const createUnifiedKeyedSelector = >( concepts: Concepts, diff --git a/src/model/stagePlanner.ts b/src/model/stagePlanner.ts index c930cb5..a17cebb 100644 --- a/src/model/stagePlanner.ts +++ b/src/model/stagePlanner.ts @@ -657,7 +657,13 @@ export class UnifiedSubject extends Subject { // Horrifying // Keep in place, this prevents branch prediction from creating ghost actions if there is an action overflow. if (plan.stageFailed === -1) { - action$.next(action); + // Will set a the current stage's priority if no priority is set. + if (plan.stages[plan.stage].priority && action.priority === undefined) { + action.priority = plan.stages[plan.stage].priority; + action$.next(action); + } else { + action$.next(action); + } } } } else if ( diff --git a/src/model/time.ts b/src/model/time.ts index cc5e449..d2cd18a 100644 --- a/src/model/time.ts +++ b/src/model/time.ts @@ -7,13 +7,18 @@ import { AxiumState } from '../concepts/axium/axium.concept'; import { Action, createAction } from './action'; import { getAxiumState } from './axium'; import { Concepts } from './concept'; +import { handlePriority } from './priority'; // eslint-disable-next-line @typescript-eslint/no-explicit-any const handleTimedRun = (axiumState: AxiumState, func: (() => Action)[], timed: number) => { func.forEach(f => { const action = f(); if (action.type !== 'Conclude') { - axiumState.tail.push(action); + if (action.priority !== undefined) { + handlePriority(axiumState, action); + } else { + axiumState.tail.push(action); + } } }); axiumState.timer.shift(); From cde6747c4b6b63cbf3da186ad84467d208304b69 Mon Sep 17 00:00:00 2001 From: REllEK-IO Date: Mon, 13 May 2024 14:52:32 -0700 Subject: [PATCH 2/4] Consistency v0.1.64 --- ActionStrategy.md | 22 ++- Axium.md | 13 ++ README.md | 2 + StagePlanner.md | 24 +++ src/concepts/axium/axium.concept.ts | 8 +- src/model/action.ts | 3 +- src/model/axium.ts | 4 +- src/model/dotPath.ts | 39 ++--- src/model/priority.ts | 2 + src/test/priority/priorityAction.test.ts | 205 +++++++++++++++++++++++ 10 files changed, 291 insertions(+), 31 deletions(-) create mode 100644 src/test/priority/priorityAction.test.ts diff --git a/ActionStrategy.md b/ActionStrategy.md index e26a17f..c20743e 100644 --- a/ActionStrategy.md +++ b/ActionStrategy.md @@ -29,20 +29,26 @@ ActionNode represents some node that is capable of being turned into some action export interface ActionNode { action?: Action; actionType: ActionType; - payload?: unknown; + payload?: Record; + conceptSemaphore?: number; + priority?: number; keyedSelectors?: KeyedSelector[]; - semaphore?: [number, number, number]; + semaphore?: [number, number, number, number]; agreement?: number; decisionNodes?: Record; + decisionNotes?: ActionNotes; successNode: ActionNode | null; + successNotes?: ActionNotes; failureNode: ActionNode | null; - preposition?: string; - denoter?: string; + failureNotes?: ActionNotes; + lastActionNode?: ActionNode; } // Used via the createActionNode function. export interface ActionNodeOptions { keyedSelectors?: KeyedSelector[]; + conceptSemaphore?: number; + priority?: number; semaphore?: [number, number, number, number]; agreement?: number; decisionNodes?: Record; @@ -54,16 +60,24 @@ export interface ActionNodeOptions { lastActionNode?: ActionNode; } +export interface ActionNotes { + preposition?: string; + denoter?: string; +} ``` * action - Is an union data pattern to bind the functionality of the ActionNode, ActionStrategy, and Action. This allows for each part to be responsible for itself and to allow for additional functionality at runtime. * actionType - Is merely the type of action to be created at runtime, these should be verbose as to their intended effect as it informs the Stratimux sentence structure's body. * payload - Is set to unknown to allow for the explicit typecasting during consumption, reducer, method, or principle. Be sure to import actions directly to ensure payload type safety in the reducer. This is a logical determination in line with Javascript core functionality. +* conceptSemaphore - Used when dispatching to a foreign Axium. Where the effecting action will trigger on some unified concept on such that the current axium has observation of. +* priority - By default all action's dispatched via quality observations will be pushed to the end of the tail que on the Axium. By setting this value, it will force the handlePriority internal function to trigger and assign the action to the determined slot on the body que instead. * keyedSelectors - An Array of KeyedSelector that locks some property during the life time of the created action. * semaphore - First is concept's index, second is the quality's index, and the final is the generation of the sets of concepts currently stored on the Axium. *Explicitly setting this value, denotes a primed action without additional look up at runtime.* * agreement - Is time in milliseconds of the lock's expiration time. Default is currently 5000, but such will be reduced upon testing and feedback. * decisionNodes - Is a record of all possible decisions that the ActionNode may take depending upon some test. And should be seen as a replacement for SuccessNode if utilized. This is what makes the ActionStrategy capable of complexity beyond squared. * successNode - Is the default chain of actions. Can be replaced by decisionNodes to enable additional behaviors. * failureNode - Is the default failure mode of each action. And will be called if ownership is loaded in an axium. If null the ActionStrategy will conclude which will free the current lock supplied. Otherwise it will be added to the pendingActions que. + +**ActionNotes** * preposition - Coincides with the Stratimux sentence structure. And is the logical linking between the previous sentences and the current ActionType. * denoter - Also enhances the Stratimux sentence as either a punctuation mark or additional description of the ActionType's intended effect. *NOTE: Just like all optional fields, except action, these are designed to be set at runtime if dynamic or can be prefilled if the values are known prior.* diff --git a/Axium.md b/Axium.md index dc036e2..2760ed9 100644 --- a/Axium.md +++ b/Axium.md @@ -30,6 +30,7 @@ The design decision here allows us to forgo the need for dependency injection. A ```typescript export type AxiumState = { + // Would be unique identifier on a network name: string; open: boolean; prepareClose: boolean; @@ -58,6 +59,12 @@ export type AxiumState = { removeConceptQue: Concept[], badPlans: Plan[]; badActions: Action[]; + timer: NodeJS.Timeout[]; + timerLedger: Map Action)[], number]> + head: Action[]; + body: Action[]; + tail: Action[]; + tailTimer: NodeJS.Timeout[]; } ``` * name - This should be set to a unique network identifier, and/or the concept of your system. @@ -87,6 +94,12 @@ export type AxiumState = { * removeConceptQue - The inverse of the above to remove concepts. * badPlans - Stores plans that have experienced action overflow. * badActions - Keeps track of all actions that cannot be registered via the cachedSemaphores property. +* timer - This internal timer is set via the timerLedger, each subsequent call creating a new timer that is held as the only entry in this array. +* timerLedger - Is modified when using axiumTimeOut, that will assign a callback function that returns an action to be used in a single setTimeout function. +* head - A single action is allowed to exist in the head and ensures that the axium performs a lock step sequence of actions. +* body - Mostly updated via plans. Taking precedent to actions that are issued via ActionStrategies or the axiumTimeOut functionality unless the correlated actions have some priority. Then such are assigned to the body que, ordering the body according to each action's priority or prior placement. +* tail - The final que to be called when body has been depleted. This is a low priority que that is informed via qualities via the axiumTimeOut method. +* tailTimer - This timer will be enabled each time a tail entry has been assigned, but will be cleared each time an action enters the action stream. ## The Anatomy of an Axium * Action - Is a dumb object that is supplied some data as payload or performs some transformation of state based on its semaphore which is inferred via its type. Is the messaging protocol of an axium. diff --git a/README.md b/README.md index 89a1f8e..3223228 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ When in doubt simplify. ## Change Log ![Tests](https://github.com/Phuire-Research/Stratimux/actions/workflows/node.js.yml/badge.svg) ### v0.1.64 5/13/2024 * Added Action Priority: This will allow action's assigned a priority of not 0 to be placed accordingly into the action ques. +* Adjusted DotPath type to improve type checking performance. Massively degrades with an additional level. +* Updated documentation to reflect recent changes ### Patch v0.1.62 5/09/2024 * Restored DotPath, a type used in the selector creators used to guide the creation of a dot path string. ### **BREAKING** Strong Fast Lock Step v0.1.62 5/08/2024 diff --git a/StagePlanner.md b/StagePlanner.md index d27af3e..53e965e 100644 --- a/StagePlanner.md +++ b/StagePlanner.md @@ -196,3 +196,27 @@ const plan = concept$.plan('Principle Stage Example', [ }) ]); ``` +## How Stage Priority effects Actions +Whenever an action is dispatched into the action stream. They are checked into an action que system. By default any priority assigned to a stage, will likewise associate that to an action. This assumes that the stage itself is taking priority in observation and action when set. + +If you require a high priority observation, but want the action ques to deplete accordingly without effecting such. Set the action's priority to 0. This will cause the internal checks to skip over handling that action's priority. Otherwise if such is set to undefined, then the dispatching stage's priority will once again be set. + +```typescript +// Will have high observation priority, but no action priority. +createStage((_, dispatch) => { + const action = axiumKick(); + action.priority = 0; + dispatch(action, { + iterateStage: true + }); +}, {priority: 100}) + +// No observation priority, but high action priority. +createStage((_, dispatch) => { + const action = axiumKick(); + action.priority = 100; + dispatch(action, { + iterateStage: true + }); +}) +``` \ No newline at end of file diff --git a/src/concepts/axium/axium.concept.ts b/src/concepts/axium/axium.concept.ts index 4bb27d8..0364c9a 100644 --- a/src/concepts/axium/axium.concept.ts +++ b/src/concepts/axium/axium.concept.ts @@ -66,9 +66,6 @@ export type AxiumState = { generalSubscribers: NamedSubscription[]; stagePlanners: NamedStagePlanner[]; action$: Subject; - head: Action[]; - body: Action[]; - tail: Action[]; actionConcepts$: Subject; concepts$: UnifiedSubject; addConceptQue: Concept[], @@ -76,8 +73,11 @@ export type AxiumState = { badPlans: Plan[]; badActions: Action[]; timer: NodeJS.Timeout[]; - tailTimer: NodeJS.Timeout[]; timerLedger: Map Action)[], number]> + head: Action[]; + body: Action[]; + tail: Action[]; + tailTimer: NodeJS.Timeout[]; } export const axiumName = 'axium'; diff --git a/src/model/action.ts b/src/model/action.ts index 53b3281..71cb140 100644 --- a/src/model/action.ts +++ b/src/model/action.ts @@ -236,7 +236,8 @@ export type ActionCreatorWithPayload = ( conceptSemaphore?: number, keyedSelectors?: KeyedSelector[], agreement?: number, - semaphore?: [number, number, number, number] + semaphore?: [number, number, number, number], + priority?: number ) => Action; /** diff --git a/src/model/axium.ts b/src/model/axium.ts index 5bd3123..dbf37e1 100644 --- a/src/model/axium.ts +++ b/src/model/axium.ts @@ -96,8 +96,6 @@ export const defaultMethodSubscription = (concepts: Concepts, tail: Action[], ac } }; - - export function createAxium( name: string, initialConcepts: Concept[], @@ -192,7 +190,7 @@ export function createAxium( } // An action dispatched from a priority stage, with a priority set to 0 // Will override the need to handle priority - } else if (action.priority && action.priority !== 0) { + } else if (action.priority !== undefined && action.priority !== 0) { handlePriority(_axiumState, action); } else { _axiumState.body.push(action); diff --git a/src/model/dotPath.ts b/src/model/dotPath.ts index cf366d5..78f2cdd 100644 --- a/src/model/dotPath.ts +++ b/src/model/dotPath.ts @@ -157,27 +157,28 @@ type DotPathSix< : Required[K] extends ValidObject[K]> ? - DotPathSevenEnd[K], Union, Join, PrevTypes | T> - : - Union, Join>; - }[keyof T]; - -// Beyond this point will trigger TS excessively deep error or circular reference. -type DotPathSevenEnd< - T extends object, - Prev extends Key | undefined = undefined, - Path extends Key | undefined = undefined, - PrevTypes extends object = T -> = string & - { - [K in keyof T]: - T[K] extends PrevTypes | T - ? Union, Join> - : - Required[K] extends ValidObject[K]> - ? + // DotPathSevenEnd[K], Union, Join, PrevTypes | T> Union, Join> : Union, Join>; }[keyof T]; +// Beyond this point Typescript massively slows down during type checking. +// // After this point will trigger TS excessively deep error and can no longer use DotPath as circular reference. +// type DotPathSevenEnd< +// T extends object, +// Prev extends Key | undefined = undefined, +// Path extends Key | undefined = undefined, +// PrevTypes extends object = T +// > = string & +// { +// [K in keyof T]: +// T[K] extends PrevTypes | T +// ? Union, Join> +// : +// Required[K] extends ValidObject[K]> +// ? +// Union, Join> +// : +// Union, Join>; +// }[keyof T]; /*#>*/ \ No newline at end of file diff --git a/src/model/priority.ts b/src/model/priority.ts index 388b8b0..634fda0 100644 --- a/src/model/priority.ts +++ b/src/model/priority.ts @@ -24,6 +24,8 @@ const fillBucket = (body: Action[], bucket: Action[], action: Action, _added = f bucket.push(action); bucket.push(drop); added = true; + } else { + bucket.push(drop); } fillBucket(body, bucket, action, added); } else if (!added) { diff --git a/src/test/priority/priorityAction.test.ts b/src/test/priority/priorityAction.test.ts new file mode 100644 index 0000000..383cabb --- /dev/null +++ b/src/test/priority/priorityAction.test.ts @@ -0,0 +1,205 @@ +/*<$ +For the asynchronous graph programming framework Stratimux generate a test that ensures that the priority aspect of the axium +is managing plan notifications as intended. +$>*/ +/*<#*/ +import { axiumKick } from '../../concepts/axium/qualities/kick.quality'; +import { experimentName } from '../../concepts/experiment/experiment.concept'; +import { createAxium, getAxiumState } from '../../model/axium'; +import { select, selectPayload, selectState } from '../../model/selector'; +import { StagePlanner, createStage } from '../../model/stagePlanner'; +import { ExperimentPriorityState, createExperimentPriorityConcept } from './priority.concept'; +import { experimentPriorityReadySelector, experimentPriorityValueSelector } from './priority.selector'; +import { experimentPriorityIsReady } from './qualities/isReady.quality'; +import { experimentPriorityAddValue } from './qualities/addValue.quality'; +import { handlePriority } from '../../model/priority'; +import { counterSetCount } from '../../concepts/counter/qualities/setCount.quality'; +import { axiumTimeOut } from '../../model/time'; +import { axiumPreClose } from '../../concepts/axium/qualities/preClose.quality'; +import { axiumLog } from '../../concepts/axium/qualities/log.quality'; +import { CounterState, counterName, createCounterConcept } from '../../concepts/counter/counter.concept'; + +test('Priority Action Test', (done) => { + console.log('Priority Test'); + let concluded = 0; + const finalize = () => { + if (concluded === 2) { + done(); + } else { + concluded++; + } + }; + + const priorityTest = createAxium('Priority Test', [ + createExperimentPriorityConcept() + ], true, true, true); + + const firstStage = (name: string, priority: number) => createStage((concepts, dispatch, changes) => { + const priorityState = select.state(concepts, experimentName); + console.log('HIT: ', name, changes); + if (priorityState?.ready) { + console.log(`${name} Priority BEGIN`); + dispatch(axiumKick(), { + iterateStage: true + }); + } + }, {selectors: [experimentPriorityReadySelector], priority}); + const secondStage = (name: string, newValue: number, priority: number, override?: number) => createStage((concepts, dispatch) => { + const priorityState = select.state(concepts, experimentName); + if (priorityState) { + console.log(`${name} Priority Base Value: `, priorityState.value); + const action = experimentPriorityAddValue({newValue}); + if (override) { + action.priority = override; + } + dispatch(action, { + iterateStage: true + }); + } + }, {priority}); + const thirdStage = (name: string, expected: number, priority: number) => createStage((concepts, dispatch, changes) => { + const priorityState = select.state(concepts, experimentName); + if (priorityState && changes.length > 0) { + // expect(order).toBe(expectedOrder); + console.log(`${name} Incoming Value: ${priorityState.value}, expecting: ${expected}`); + // expect(priorityState.value).toBe(expected); + dispatch(axiumKick(), { + iterateStage: true + }); + } + }, {selectors: [experimentPriorityValueSelector], priority}); + const concludePlan = (name: string, func: () => StagePlanner) => createStage(() => { + console.log(`${name} Priority END`); + func().conclude(); + finalize(); + }); + + const LOW = 'Low'; + const LOW_PRIORITY = 1; + const low = priorityTest.plan( + 'Low Priority Plan', [ + firstStage(LOW, LOW_PRIORITY), + secondStage(LOW, 1, LOW_PRIORITY, 1000), + thirdStage(LOW, 111, LOW_PRIORITY), + concludePlan(LOW, () => low), + ]); + const HIGH = 'High'; + const HIGH_PRIORITY = 100; + const high = priorityTest.plan( + 'High Priority Plan', [ + firstStage(HIGH, HIGH_PRIORITY), + secondStage(HIGH, 100, HIGH_PRIORITY), + thirdStage(HIGH, 101, HIGH_PRIORITY), + concludePlan(HIGH, () => high), + ]); + const MID = 'Mid'; + const MID_PRIORITY = 50; + const mid = priorityTest.plan( + 'Mid Priority Plan', [ + firstStage(MID, MID_PRIORITY), + secondStage(MID, 10, MID_PRIORITY), + thirdStage(MID, 111, MID_PRIORITY), + concludePlan(MID, () => mid), + ]); + setTimeout(() => { + priorityTest.dispatch(experimentPriorityIsReady()); + }, 1000); + priorityTest.subscribe(val => console.log('CHECK STATE: ', select.state(val, experimentName))); +}); + +type SetCount = { + newCount: number; +} + +test('Priority Action Manual Test', (done) => { + const axium = createAxium('Priority Action Manual Axium Extraction', []); + const sub = axium.subscribe(concepts => { + sub.unsubscribe(); + axium.close(); + const axiumState = getAxiumState(concepts); + + const {body} = axiumState; + const kick = axiumKick(); + body.push(kick); + const one = counterSetCount({ + newCount: 1 + }, undefined, undefined, undefined, undefined, 100); + const two = counterSetCount({ + newCount: 2 + }, undefined, undefined, undefined, undefined, 50); + const three = counterSetCount({ + newCount: 3 + }, undefined, undefined, undefined, undefined, 75); + const four = counterSetCount({ + newCount: 4 + }, undefined, undefined, undefined, undefined, 25); + handlePriority(axiumState, one); + expect(body[0].type).toBe(one.type); + expect(body[1].type).toBe(kick.type); + handlePriority(axiumState, two); + handlePriority(axiumState, three); + handlePriority(axiumState, four); + expect(selectPayload(body[0]).newCount).toBe(selectPayload(one).newCount); + expect(selectPayload(body[1]).newCount).toBe(selectPayload(three).newCount); + expect(selectPayload(body[2]).newCount).toBe(selectPayload(two).newCount); + expect(selectPayload(body[3]).newCount).toBe(selectPayload(four).newCount); + expect(body[4].type).toBe(kick.type); + done(); + }); +}); + +test('Priority Action Close Test', (done) => { + const axium = createAxium('Priority Action Manual Axium Extraction', [createCounterConcept()]); + const sub = axium.subscribe(concepts => { + sub.unsubscribe(); + const axiumState = getAxiumState(concepts); + + const {head, body} = axiumState; + if (head.length === 0) { + head.push(axiumLog()); + } + const kick = axiumKick(); + body.push(kick); + const one = counterSetCount({ + newCount: 1 + }, undefined, undefined, undefined, undefined, 100); + const two = counterSetCount({ + newCount: 2 + }, undefined, undefined, undefined, undefined, 50); + const three = counterSetCount({ + newCount: 3 + }, undefined, undefined, undefined, undefined, 75); + const four = counterSetCount({ + newCount: 4 + }, undefined, undefined, undefined, undefined, 25); + handlePriority(axiumState, one); + expect(body[0].type).toBe(one.type); + expect(body[1].type).toBe(kick.type); + handlePriority(axiumState, two); + handlePriority(axiumState, three); + handlePriority(axiumState, four); + expect(selectPayload(body[0]).newCount).toBe(selectPayload(one).newCount); + expect(selectPayload(body[1]).newCount).toBe(selectPayload(three).newCount); + expect(selectPayload(body[2]).newCount).toBe(selectPayload(two).newCount); + expect(selectPayload(body[3]).newCount).toBe(selectPayload(four).newCount); + expect(body[4].type).toBe(kick.type); + let dispatched = false; + axium.subscribe(cpts => { + if (dispatched) { + const preClose = axiumPreClose({ + exit: false + }); + preClose.priority = 100000; + axium.dispatch(preClose); + } + expect(selectState(cpts, counterName)).toBe(0); + if (!dispatched) { + setTimeout(() => { + done(); + }, 100); + } + }); + done(); + }); +}); +/*#>*/ \ No newline at end of file From 8520155924a07b1ead77e1b5ddc48b5fe1767bcb Mon Sep 17 00:00:00 2001 From: REllEK-IO Date: Mon, 13 May 2024 14:53:52 -0700 Subject: [PATCH 3/4] Consistency v0.1.64 --- src/model/priority.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/model/priority.ts b/src/model/priority.ts index 634fda0..29be9e1 100644 --- a/src/model/priority.ts +++ b/src/model/priority.ts @@ -53,4 +53,5 @@ export const handlePriority = (axiumState: AxiumState, action: Action) => { } else { body.push(action); } -}; \ No newline at end of file +}; +/*#>*/ \ No newline at end of file From 9482efe68755a8adb5f77e3c4d31bb8f46dc1512 Mon Sep 17 00:00:00 2001 From: REllEK-IO Date: Mon, 13 May 2024 15:00:21 -0700 Subject: [PATCH 4/4] Consistency v0.1.64 --- src/model/axium.ts | 10 +++++----- src/model/priority.ts | 2 ++ src/model/time.ts | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/model/axium.ts b/src/model/axium.ts index dbf37e1..95bd41c 100644 --- a/src/model/axium.ts +++ b/src/model/axium.ts @@ -13,7 +13,7 @@ import { Subscription, Observer, } from 'rxjs'; -import { Action, createAction, createCacheSemaphores } from './action'; +import { Action, createCacheSemaphores } from './action'; import { strategyBegin } from './actionStrategy'; import { Concept, Concepts, Mode, forEachConcept, qualityToString } from './concept'; import { @@ -28,7 +28,7 @@ import { axiumPreClose } from '../concepts/axium/qualities/preClose.quality'; import { StagePlanner, Staging } from './stagePlanner'; import { axiumKick } from '../concepts/axium/qualities/kick.quality'; import { axiumTimeOut } from './time'; -import { handlePriority } from './priority'; +import { handlePriority, isPriorityValid } from './priority'; export const blockingMethodSubscription = (concepts: Concepts, tail: Action[], action: Action) => { if ( @@ -43,7 +43,7 @@ export const blockingMethodSubscription = (concepts: Concepts, tail: Action[], a strategyData: action.strategy.data, }); tail.push(appendToDialog); - if (action.priority !== undefined) { + if (isPriorityValid(action)) { handlePriority(getAxiumState(concepts), action); } else { tail.push(action); @@ -71,7 +71,7 @@ export const defaultMethodSubscription = (concepts: Concepts, tail: Action[], ac }); // setTimeout(() => { tail.push(appendToDialog); - if (action.priority !== undefined) { + if (isPriorityValid(action)) { handlePriority(getAxiumState(concepts), action); } else { tail.push(action); @@ -190,7 +190,7 @@ export function createAxium( } // An action dispatched from a priority stage, with a priority set to 0 // Will override the need to handle priority - } else if (action.priority !== undefined && action.priority !== 0) { + } else if (isPriorityValid(action)) { handlePriority(_axiumState, action); } else { _axiumState.body.push(action); diff --git a/src/model/priority.ts b/src/model/priority.ts index 29be9e1..c115e69 100644 --- a/src/model/priority.ts +++ b/src/model/priority.ts @@ -54,4 +54,6 @@ export const handlePriority = (axiumState: AxiumState, action: Action) => { body.push(action); } }; + +export const isPriorityValid = (action: Action) => (action.priority !== undefined && action.priority !== 0); /*#>*/ \ No newline at end of file diff --git a/src/model/time.ts b/src/model/time.ts index d2cd18a..3fc3479 100644 --- a/src/model/time.ts +++ b/src/model/time.ts @@ -7,14 +7,14 @@ import { AxiumState } from '../concepts/axium/axium.concept'; import { Action, createAction } from './action'; import { getAxiumState } from './axium'; import { Concepts } from './concept'; -import { handlePriority } from './priority'; +import { handlePriority, isPriorityValid } from './priority'; // eslint-disable-next-line @typescript-eslint/no-explicit-any const handleTimedRun = (axiumState: AxiumState, func: (() => Action)[], timed: number) => { func.forEach(f => { const action = f(); if (action.type !== 'Conclude') { - if (action.priority !== undefined) { + if (isPriorityValid(action)) { handlePriority(axiumState, action); } else { axiumState.tail.push(action);