diff --git a/README.md b/README.md index 8f32522..5fdc179 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,10 @@ 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.57 5/02/24 +* Added the ability to set specific stages of their selectors, priority, and beat values. + * Note that by setting these values, this will not force the internal priority selector cache mechanism to trigger. Use set for stages your are iterating to or changing due to some circumstance. The new stage options will force the priority selector cache to trigger. +* Quick pass updating the StagePlanner documentation in regards to stage selectors/priority/beat properties. ### v0.1.56 5/01/24 * May now properly update each plans intended KeyedSelectors to control when they are ran. ### v0.1.55 4/24/24 diff --git a/StagePlanner.md b/StagePlanner.md index 5df185b..2ccd7ab 100644 --- a/StagePlanner.md +++ b/StagePlanner.md @@ -25,9 +25,24 @@ export type Plan = { ```typescript export type dispatchOptions = { runOnce?: boolean; + iterateStage?: boolean; + setStage?: number; + setStageSelectors?: { + stage: number, + selectors: KeyedSelector[] + }; + setStagePriority?: { + stage: number, + priority: number + }; + setStageBeat?: { + stage: number, + beat: number + }; throttle?: number; - setStep?: number; - iterateStep?: boolean; + newSelectors?: KeyedSelector[]; + newPriority?: number; + newBeat?: number; } ``` @@ -36,7 +51,18 @@ export type dispatchOptions = { * throttle - Required to prevent the stage to be considered bad if rerunning the same action within the same stage, specific use case is tracking some position over time. If on is part of options, this will only come into play after that action is first dispatched. * incrementStage - Will increment to the next stage index, this should be your default option for dispatching actions or strategies to prevent action overflow. * setStage - This will set the stage to a specific stage index, useful if some strategy failed and the staging needs to be reset to prepare for that strategy again. This will always override iterateStage. - +* setStageSelectors/Priority/Beat - This will set a specific stage's selectors, but will not trigger a cache of the current priority selector order que. This likewise applies to Priority/Beat +* newSelectors/Priority/Beat - Will set the current stage's selectors/priority/beat + +#### Stage Selectors +This utilizes Stratimux's KeyedSelectors to control when a stage would run as a built in form of change detection. Noting that once a stage is incremented or set, it will always run the first time. But may not receive the specific changes that the selector is actively looking for. The strength of this approach allows us to move beyond ECS into a system that is reactive and atomic that likewise allows for Stratimux to function as a FaaOS(Function as a Operating System). + +#### Stage Priority +Of the the main issues with utilizing a single point of observation, is that some plans you might devise should take precedence over others. For example the Axium's own close principle has the highest priority of all observations and will force a shutdown of the entire Axium upon observation. We have likewise provided the set and new stage options for the priority value to allow some intelligence to be at play, keeping in mind Stratimux is designed to act as a form of logical embodiment for this generation's probabilistic AI. + +#### Stage Beat +The beat value each stage may have, is a new concept similar to the throttle and debounce found in reactive programming. Except here the first observation will run, and any subsequent observations will be delayed until just have the beat value expires. This ensures a constant stream of observations, while allowing for gaps of time that will instantly resume once the observation becomes relevant again. Think Frames Per Second (FPS). + ### Stage Planner Internals ```typescript export type Dispatcher = (action: Action, options: dispatchOptions) => void; diff --git a/package.json b/package.json index 81c4ba5..14a331e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "stratimux", "license": "GPL-3.0", - "version": "0.1.56", + "version": "0.1.57", "description": "Unified Turing Machine", "main": "dist/index.js", "module": "dist/index.mjs", diff --git a/src/model/stagePlanner.ts b/src/model/stagePlanner.ts index a548256..d88d295 100644 --- a/src/model/stagePlanner.ts +++ b/src/model/stagePlanner.ts @@ -66,9 +66,21 @@ export type NamedStagePlanner = { export type dispatchOptions = { runOnce?: boolean; + throttle?: number; iterateStage?: boolean; setStage?: number; - throttle?: number; + setStageSelectors?: { + stage: number, + selectors: KeyedSelector[] + }; + setStagePriority?: { + stage: number, + priority: number + }; + setStageBeat?: { + stage: number, + beat: number + }; newSelectors?: KeyedSelector[]; newPriority?: number; newBeat?: number; @@ -83,6 +95,9 @@ export type StageDelimiter = { runOnceMap: Map } +/** + * Used in principle plans that are loaded during axium initialization + */ export const stageWaitForOpenThenIterate = (func: () => Action): Staging => (createStage((concepts: Concepts, dispatch: Dispatcher) => { if (isAxiumOpen(concepts) && getAxiumState(concepts).lastStrategy === initializeTopic) { dispatch(func(), { @@ -211,27 +226,6 @@ const handleStageDelimiter = ]; }; -// const handleNewStageOptions = (plan: Plan, options: dispatchOptions, next: number): boolean => { -// let evaluate = false; -// if (options.newPriority) { -// plan.stages[plan.stage].priority = options.newPriority; -// evaluate = true; -// } -// if (options.newSelectors) { -// plan.stages[plan.stage].selectors = options.newSelectors; -// // this.handleAddSelector(plan.stages[plan.stage].selectors, plan.id); -// evaluate = true; -// } -// if (options.newBeat) { -// plan.stages[plan.stage].beat = options.newBeat; -// if (next === -1) { -// plan.beat = options.newBeat; -// } -// evaluate = true; -// } -// return evaluate; -// }; - const Inner = 0; const Base = 1; const Outer = 2; @@ -297,6 +291,18 @@ export class UnifiedSubject extends Subject { return evaluate; }; + protected handleSetStageOptions = (plan: Plan, options: dispatchOptions) => { + if (options.setStageSelectors && plan.stages[options.setStageSelectors.stage]) { + plan.stages[options.setStageSelectors.stage].selectors = options.setStageSelectors.selectors; + } + if (options.setStagePriority && plan.stages[options.setStagePriority.stage]) { + plan.stages[options.setStagePriority.stage].priority = options.setStagePriority.priority; + } + if (options.setStageBeat && plan.stages[options.setStageBeat.stage]) { + plan.stages[options.setStageBeat.stage].beat = options.setStageBeat.beat; + } + }; + protected addSelector(selector: KeyedSelector, id: number) { const s = this.selectors.get(selector.keys); if (s) { @@ -600,6 +606,7 @@ export class UnifiedSubject extends Subject { if (!throttle && run) { let next = -1; const evaluate = this.handleNewStageOptions(plan, options, next); + this.handleSetStageOptions(plan, options); if (options?.iterateStage) { next = plan.stage + 1; // this.updatePlanSelector(plan, plan.stage, next < plan.stages.length ? next : undefined); diff --git a/src/test/onChange.test.ts b/src/test/onChange.test.ts index b3148bc..1af852c 100644 --- a/src/test/onChange.test.ts +++ b/src/test/onChange.test.ts @@ -16,13 +16,13 @@ import { axiumKick } from '../concepts/axium/qualities/kick.quality'; import { initializeTopic } from '../concepts/axium/strategies/initialization.strategy'; import { Concepts } from '../model/concept'; -test('Axium Counting Strategy Test', (done) => { +test('Axium onChange Test', (done) => { const selectorRouter = { [axiumSelectLastStrategy.keys]: (concepts: Concepts) => console.log('CHECK: ', selectSlice(concepts, axiumSelectLastStrategy)) }; const axium = createAxium('axiumStrategyTest', [createCounterConcept()], true, true); - const plan = axium.plan('Counting Strategy Stage', + const plan = axium.plan('Counting Strategy Plan with selectors', [ createStage((concepts, dispatch) => { if (selectSlice(concepts, axiumSelectLastStrategy) === initializeTopic) { diff --git a/src/test/setStageOptions.test.ts b/src/test/setStageOptions.test.ts new file mode 100644 index 0000000..9eea150 --- /dev/null +++ b/src/test/setStageOptions.test.ts @@ -0,0 +1,133 @@ +/*<$ +For the asynchronous graph programming framework Stratimux, generate a test that the setStageOption derivatives.are functioning as intended +$>*/ +/*<#*/ +import { createAxium } from '../model/axium'; +import { strategyBegin } from '../model/actionStrategy'; +import { selectSlice, selectState } from '../model/selector'; +import { CounterState, createCounterConcept, countingStrategy, counterName } from '../concepts/counter/counter.concept'; +import { AxiumState } from '../concepts/axium/axium.concept'; +import { countingTopic } from '../concepts/counter/strategies/counting.strategy'; +import { createStage } from '../model/stagePlanner'; +import { axiumSelectLastStrategy } from '../concepts/axium/axium.selector'; +import { axiumKick } from '../concepts/axium/qualities/kick.quality'; +import { initializeTopic } from '../concepts/axium/strategies/initialization.strategy'; + +test('Axium setStageSelectors Test', (done) => { + let tally = 0; + const axium = createAxium('axiumStrategyTest', [createCounterConcept()], true, true); + const plan = axium.plan('Counting Strategy Plan using setStageSelectors', + [ + createStage((concepts, dispatch) => { + if (selectSlice(concepts, axiumSelectLastStrategy) === initializeTopic) { + dispatch(strategyBegin(countingStrategy()), { + iterateStage: true, + setStageSelectors: { + stage: 1, + selectors: [axiumSelectLastStrategy] + } + }); + } + }, {selectors: [axiumSelectLastStrategy]}), + createStage((concepts, _, changes) => { + tally++; + if (tally === 1) { + expect(changes?.length).toBe(0); + } + const axiumState = concepts[0].state as AxiumState; + const counter = selectState(concepts, counterName); + if (axiumState.lastStrategy === countingTopic) { + expect(counter?.count).toBe(1); + expect(tally).toBe(2); + expect(changes?.length).toBe(1); + setTimeout(() => {done();}, 500); + plan.conclude(); + axium.close(); + } + }) + ]); +}); + +test('Axium setStageBeat Test', (done) => { + let tally = 0; + const axium = createAxium('axiumStrategyTest', [createCounterConcept()], true, true); + const plan = axium.plan('Counting Strategy Plan using setStageBeat', + [ + createStage((concepts, dispatch) => { + if (selectSlice(concepts, axiumSelectLastStrategy) === initializeTopic) { + dispatch(strategyBegin(countingStrategy()), { + iterateStage: true, + setStageBeat: { + stage: 1, + beat: 300 + } + }); + } + }, {selectors: [axiumSelectLastStrategy]}), + createStage((concepts) => { + const axiumState = concepts[0].state as AxiumState; + tally++; + const counter = selectState(concepts, counterName); + if (axiumState.lastStrategy === countingTopic) { + expect(counter?.count).toBe(1); + expect(tally).toBe(2); + setTimeout(() => {done();}, 500); + plan.conclude(); + axium.close(); + } + }) + ]); +}); + +test('Axium setStagePriority Test', (done) => { + const axium = createAxium('axiumStrategyTest', [createCounterConcept()], true, true); + let ready = false; + let tally = 0; + const plan = axium.plan('Counting Strategy Plan using setStagePriority', + [ + createStage((_, dispatch) => { + if (ready) { + dispatch(strategyBegin(countingStrategy()), { + iterateStage: true, + setStagePriority: { + stage: 1, + priority: 0 + } + }); + } + }, {selectors: [axiumSelectLastStrategy]}), + createStage(() => { + expect(tally).toBe(1); + tally++; + plan.conclude(); + }, { + priority: Infinity + }) + ]); + const planTwo = axium.plan('Counting Strategy Plan using setStagePriority Two', + [ + createStage((_, dispatch) => { + if (ready) { + dispatch(strategyBegin(countingStrategy()), { + iterateStage: true, + setStagePriority: { + stage: 1, + priority: Infinity + } + }); + } + }, {selectors: [axiumSelectLastStrategy]}), + createStage(() => { + expect(tally).toBe(0); + tally++; + setTimeout(() => {done();}, 500); + planTwo.conclude(); + axium.close(); + }, { + priority: 0 + }) + ]); + ready = true; + axium.dispatch(axiumKick()); +}); +/*#>*/ diff --git a/src/test/strategy.test.ts b/src/test/strategy.test.ts index f261c92..ff6c347 100644 --- a/src/test/strategy.test.ts +++ b/src/test/strategy.test.ts @@ -12,7 +12,7 @@ import { createStage } from '../model/stagePlanner'; test('Axium Counting Strategy Test', (done) => { const axium = createAxium('axiumStrategyTest', [createCounterConcept()], true, true); - const plan = axium.plan('Counting Strategy Stage', + const plan = axium.plan('Counting Strategy Plan', [ createStage((_, dispatch) => { dispatch(strategyBegin(countingStrategy()), {