diff --git a/README.md b/README.md index 5fdc179..e81bd65 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.58 5/03/24 +* Ensured that changes that happen between a stage's beat interval are accumulated ### 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. diff --git a/StagePlanner.md b/StagePlanner.md index 2ccd7ab..d27af3e 100644 --- a/StagePlanner.md +++ b/StagePlanner.md @@ -25,6 +25,7 @@ export type Plan = { ```typescript export type dispatchOptions = { runOnce?: boolean; + throttle?: number; iterateStage?: boolean; setStage?: number; setStageSelectors?: { @@ -39,7 +40,6 @@ export type dispatchOptions = { stage: number, beat: number }; - throttle?: number; newSelectors?: KeyedSelector[]; newPriority?: number; newBeat?: number; @@ -61,7 +61,7 @@ This utilizes Stratimux's KeyedSelectors to control when a stage would run as a 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). +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, aggregating all changes that occurred between beats. Think Frames Per Second (FPS). ### Stage Planner Internals ```typescript diff --git a/package.json b/package.json index 14a331e..0fffcd3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "stratimux", "license": "GPL-3.0", - "version": "0.1.57", + "version": "0.1.58", "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 d88d295..332719c 100644 --- a/src/model/stagePlanner.ts +++ b/src/model/stagePlanner.ts @@ -27,7 +27,8 @@ export type Plan = { stageFailed: number; beat: number; offBeat: number; - timer: NodeJS.Timeout[] + timer: NodeJS.Timeout[]; + changeAggregator: Record; } export type Stage = ( @@ -344,7 +345,18 @@ export class UnifiedSubject extends Subject { }; }); const beat = staged[0].beat; - return {id: planId, space, title, stages: staged, stage: 0, stageFailed: -1, beat: beat ? beat : -1, offBeat: -1, timer: []}; + return { + id: planId, + space, + title, + stages: staged, + stage: 0, + stageFailed: -1, + beat: beat ? beat : -1, + offBeat: -1, + timer: [], + changeAggregator: {} + }; } protected initPlan(plan: Plan): StagePlanner { @@ -630,6 +642,7 @@ export class UnifiedSubject extends Subject { stageDelimiter.prevActions = []; stageDelimiter.unionExpiration = []; stageDelimiter.runOnceMap = new Map(); + plan.changeAggregator = {}; this.stageDelimiters.set(plan.id, stageDelimiter); } if (evaluate && next === -1) { @@ -679,11 +692,21 @@ export class UnifiedSubject extends Subject { plan.offBeat = Date.now() + plan.beat; this.execute(plan, index, changes); } else if (timer.length === 0 && plan.offBeat > now) { + // Logic to push changes into aggregator + changes.forEach(key => { + plan.changeAggregator[key.keys] = key; + }); timer.push(setTimeout(() => { + const changeAggregation = Object.keys(plan.changeAggregator).map(k => plan.changeAggregator[k]); + plan.changeAggregator = {}; plan.timer = []; plan.offBeat = Date.now() + plan.beat; - this.execute(plan, index, changes); + this.execute(plan, index, changeAggregation); }, plan.offBeat - Date.now())); + } else { + changes.forEach(key => { + plan.changeAggregator[key.keys] = key; + }); } } else { this.execute(plan, index, changes); diff --git a/src/test/beatSelectorChanges/beatSelectorChanges.concept.ts b/src/test/beatSelectorChanges/beatSelectorChanges.concept.ts new file mode 100644 index 0000000..7cd5838 --- /dev/null +++ b/src/test/beatSelectorChanges/beatSelectorChanges.concept.ts @@ -0,0 +1,52 @@ +/*<$ +For the asynchronous graph programming framework Stratimux generate an Experiment Concept that will be used to test deferred changes +accumulated for stage that has a beat and selectors +$>*/ +/*<#*/ +import { beatSelectorChangesAddToCountOneQuality } from './qualities/addToCountOne.quality'; +import { createConcept } from '../../model/concept'; +import { beatSelectorChangesAddToCountTwoQuality } from './qualities/addToCountTwo.quality'; +import { beatSelectorChangesAddToCountThreeQuality } from './qualities/addToCountThree.quality'; +import { beatSelectorChangesAddToCountFourQuality } from './qualities/addToCountFour.quality'; +import { beatSelectorChangesAddToCountFiveQuality } from './qualities/addToCountFive.quality'; +import { beatSelectorChangesAddToCountSixQuality } from './qualities/addToCountSix.quality'; +import { beatSelectorChangesAddToCountSevenQuality } from './qualities/addToCountSeven.quality'; + +export type BeatSelectorChangesState = { + countOne: number + countTwo: number + countThree: number + countFour: number + countFive: number + countSix: number + countSeven: number +} + +export const beatSelectorChangesName = 'beatSelectorChanges'; + +const initialBeatSelectorChangesState: BeatSelectorChangesState = { + countOne: 0, + countTwo: 0, + countThree: 0, + countFour: 0, + countFive: 0, + countSix: 0, + countSeven: 0, +}; + +export const createBeatSelectorChangesConcept = () => { + return createConcept( + beatSelectorChangesName, + initialBeatSelectorChangesState, + [ + beatSelectorChangesAddToCountOneQuality, + beatSelectorChangesAddToCountTwoQuality, + beatSelectorChangesAddToCountThreeQuality, + beatSelectorChangesAddToCountFourQuality, + beatSelectorChangesAddToCountFiveQuality, + beatSelectorChangesAddToCountSixQuality, + beatSelectorChangesAddToCountSevenQuality, + ] + ); +}; +/*#>*/ \ No newline at end of file diff --git a/src/test/beatSelectorChanges/beatSelectorChanges.selector.ts b/src/test/beatSelectorChanges/beatSelectorChanges.selector.ts new file mode 100644 index 0000000..58da4a2 --- /dev/null +++ b/src/test/beatSelectorChanges/beatSelectorChanges.selector.ts @@ -0,0 +1,23 @@ +/*<$ +For the asynchronous graph programming framework Stratimux and BeatSelectorChanges Concept, +generate a KeyedSelector for the BeatSelectorChanges's count variants state properties. +$>*/ +/*<#*/ +import { KeyedSelector, createConceptKeyedSelector } from '../../model/selector'; +import { BeatSelectorChangesState } from './beatSelectorChanges.concept'; + +export const beatSelectorChangesSelectCountOne: KeyedSelector = + createConceptKeyedSelector('beatSelectorChanges', 'countOne'); +export const beatSelectorChangesSelectCountTwo: KeyedSelector = + createConceptKeyedSelector('beatSelectorChanges', 'countTwo'); +export const beatSelectorChangesSelectCountThree: KeyedSelector = + createConceptKeyedSelector('beatSelectorChanges', 'countThree'); +export const beatSelectorChangesSelectCountFour: KeyedSelector = + createConceptKeyedSelector('beatSelectorChanges', 'countFour'); +export const beatSelectorChangesSelectCountFive: KeyedSelector = + createConceptKeyedSelector('beatSelectorChanges', 'countFive'); +export const beatSelectorChangesSelectCountSix: KeyedSelector = + createConceptKeyedSelector('beatSelectorChanges', 'countSix'); +export const beatSelectorChangesSelectCountSeven: KeyedSelector = + createConceptKeyedSelector('beatSelectorChanges', 'countSeven'); +/*#>*/ \ No newline at end of file diff --git a/src/test/beatSelectorChanges/beatSelectorChanges.test.ts b/src/test/beatSelectorChanges/beatSelectorChanges.test.ts new file mode 100644 index 0000000..a443a73 --- /dev/null +++ b/src/test/beatSelectorChanges/beatSelectorChanges.test.ts @@ -0,0 +1,67 @@ +/*<$ +For the asynchronous graph programming framework Stratimux generate a test that ensures that changes are properly deferred +utilizing the provided BeatSelectorChanges concept +$>*/ +/*<#*/ +import { createAxium, getAxiumState } from '../../model/axium'; +import { createStage } from '../../model/stagePlanner'; +import { generateRandomCountingStrategy } from './strategies/generateCountingStrategy.strategy'; +import { beatSelectorChangesName, createBeatSelectorChangesConcept } from './beatSelectorChanges.concept'; +import { initializeTopic } from '../../concepts/axium/strategies/initialization.strategy'; +import { strategyBegin } from '../../model/actionStrategy'; +import { + beatSelectorChangesSelectCountFive, + beatSelectorChangesSelectCountFour, + beatSelectorChangesSelectCountOne, + beatSelectorChangesSelectCountSeven, + beatSelectorChangesSelectCountSix, + beatSelectorChangesSelectCountThree, + beatSelectorChangesSelectCountTwo +} from './beatSelectorChanges.selector'; +import { selectSlice, selectState } from '../../model/selector'; +jest.setTimeout(30000); +test('Deferred Beat Selector Changes Test', (done) => { + const beat = 7000; + const [tally, strategy, topic] = generateRandomCountingStrategy(); + const axium = createAxium('Beat Selector Changes properly defers accumulated changes', [ + createBeatSelectorChangesConcept() + ]); + const plan = axium.plan('Prolonged Counting Strategy', [ + createStage((concepts, dispatch) => { + if (getAxiumState(concepts).lastStrategy === initializeTopic) { + dispatch(strategyBegin(strategy), { + iterateStage: true + }); + } + }), + createStage((concepts, _, changes) => { + if (getAxiumState(concepts).lastStrategy === topic) { + expect(selectSlice(concepts, beatSelectorChangesSelectCountOne)).toBe(tally[0]); + expect(selectSlice(concepts, beatSelectorChangesSelectCountTwo)).toBe(tally[1]); + expect(selectSlice(concepts, beatSelectorChangesSelectCountThree)).toBe(tally[2]); + expect(selectSlice(concepts, beatSelectorChangesSelectCountFour)).toBe(tally[3]); + expect(selectSlice(concepts, beatSelectorChangesSelectCountFive)).toBe(tally[4]); + expect(selectSlice(concepts, beatSelectorChangesSelectCountSix)).toBe(tally[5]); + expect(selectSlice(concepts, beatSelectorChangesSelectCountSeven)).toBe(tally[6]); + expect(changes?.length).toBe(tally.length); + setTimeout(() => { + plan.conclude(); + axium.close(); + done(); + }, 500); + } + }, { + beat, + selectors: [ + beatSelectorChangesSelectCountOne, + beatSelectorChangesSelectCountTwo, + beatSelectorChangesSelectCountThree, + beatSelectorChangesSelectCountFour, + beatSelectorChangesSelectCountFive, + beatSelectorChangesSelectCountSix, + beatSelectorChangesSelectCountSeven, + ] + }) + ]); +}); +/*#>*/ \ No newline at end of file diff --git a/src/test/beatSelectorChanges/qualities/addToCountFive.quality.ts b/src/test/beatSelectorChanges/qualities/addToCountFive.quality.ts new file mode 100644 index 0000000..7edb872 --- /dev/null +++ b/src/test/beatSelectorChanges/qualities/addToCountFive.quality.ts @@ -0,0 +1,24 @@ +/*<$ +For the asynchronous graph programming framework Stratimux and BeatSelectorChanges Concept, +generate a quality that will increment the state's countFive by one. +$>*/ +/*<#*/ +import { defaultMethodCreator } from '../../../model/concept'; +import { BeatSelectorChangesState } from '../beatSelectorChanges.concept'; +import { createQualitySet } from '../../../model/quality'; + +export const [ + beatSelectorChangesAddToCountFive, + beatSelectorChangesAddToCountFiveType, + beatSelectorChangesAddToCountFiveQuality +] = createQualitySet({ + type: 'BeatSelectorChanges AddToCountFive', + reducer: (state: BeatSelectorChangesState) => { + return { + ...state, + countFive: state.countFive + 1 + }; + }, + methodCreator: defaultMethodCreator, +}); +/*#>*/ \ No newline at end of file diff --git a/src/test/beatSelectorChanges/qualities/addToCountFour.quality.ts b/src/test/beatSelectorChanges/qualities/addToCountFour.quality.ts new file mode 100644 index 0000000..97fc9c8 --- /dev/null +++ b/src/test/beatSelectorChanges/qualities/addToCountFour.quality.ts @@ -0,0 +1,24 @@ +/*<$ +For the asynchronous graph programming framework Stratimux and BeatSelectorChanges Concept, +generate a quality that will increment the state's countFour by one. +$>*/ +/*<#*/ +import { defaultMethodCreator } from '../../../model/concept'; +import { BeatSelectorChangesState } from '../beatSelectorChanges.concept'; +import { createQualitySet } from '../../../model/quality'; + +export const [ + beatSelectorChangesAddToCountFour, + beatSelectorChangesAddToCountFourType, + beatSelectorChangesAddToCountFourQuality +] = createQualitySet({ + type: 'BeatSelectorChanges AddToCountFour', + reducer: (state: BeatSelectorChangesState) => { + return { + ...state, + countFour: state.countFour + 1 + }; + }, + methodCreator: defaultMethodCreator, +}); +/*#>*/ \ No newline at end of file diff --git a/src/test/beatSelectorChanges/qualities/addToCountOne.quality.ts b/src/test/beatSelectorChanges/qualities/addToCountOne.quality.ts new file mode 100644 index 0000000..44745b4 --- /dev/null +++ b/src/test/beatSelectorChanges/qualities/addToCountOne.quality.ts @@ -0,0 +1,24 @@ +/*<$ +For the asynchronous graph programming framework Stratimux and BeatSelectorChanges Concept, +generate a quality that will increment the state's countOne by one. +$>*/ +/*<#*/ +import { defaultMethodCreator } from '../../../model/concept'; +import { BeatSelectorChangesState } from '../beatSelectorChanges.concept'; +import { createQualitySet } from '../../../model/quality'; + +export const [ + beatSelectorChangesAddToCountOne, + beatSelectorChangesAddToCountOneType, + beatSelectorChangesAddToCountOneQuality +] = createQualitySet({ + type: 'BeatSelectorChanges AddToCountOne', + reducer: (state: BeatSelectorChangesState) => { + return { + ...state, + countOne: state.countOne + 1 + }; + }, + methodCreator: defaultMethodCreator, +}); +/*#>*/ \ No newline at end of file diff --git a/src/test/beatSelectorChanges/qualities/addToCountSeven.quality.ts b/src/test/beatSelectorChanges/qualities/addToCountSeven.quality.ts new file mode 100644 index 0000000..1f41501 --- /dev/null +++ b/src/test/beatSelectorChanges/qualities/addToCountSeven.quality.ts @@ -0,0 +1,24 @@ +/*<$ +For the asynchronous graph programming framework Stratimux and BeatSelectorChanges Concept, +generate a quality that will increment the state's countSeven by one. +$>*/ +/*<#*/ +import { defaultMethodCreator } from '../../../model/concept'; +import { BeatSelectorChangesState } from '../beatSelectorChanges.concept'; +import { createQualitySet } from '../../../model/quality'; + +export const [ + beatSelectorChangesAddToCountSeven, + beatSelectorChangesAddToCountSevenType, + beatSelectorChangesAddToCountSevenQuality +] = createQualitySet({ + type: 'BeatSelectorChanges AddToCountSeven', + reducer: (state: BeatSelectorChangesState) => { + return { + ...state, + countSeven: state.countSeven + 1 + }; + }, + methodCreator: defaultMethodCreator, +}); +/*#>*/ \ No newline at end of file diff --git a/src/test/beatSelectorChanges/qualities/addToCountSix.quality.ts b/src/test/beatSelectorChanges/qualities/addToCountSix.quality.ts new file mode 100644 index 0000000..eba2b2a --- /dev/null +++ b/src/test/beatSelectorChanges/qualities/addToCountSix.quality.ts @@ -0,0 +1,24 @@ +/*<$ +For the asynchronous graph programming framework Stratimux and BeatSelectorChanges Concept, +generate a quality that will increment the state's countSix by one. +$>*/ +/*<#*/ +import { defaultMethodCreator } from '../../../model/concept'; +import { BeatSelectorChangesState } from '../beatSelectorChanges.concept'; +import { createQualitySet } from '../../../model/quality'; + +export const [ + beatSelectorChangesAddToCountSix, + beatSelectorChangesAddToCountSixType, + beatSelectorChangesAddToCountSixQuality +] = createQualitySet({ + type: 'BeatSelectorChanges AddToCountSix', + reducer: (state: BeatSelectorChangesState) => { + return { + ...state, + countSix: state.countSix + 1 + }; + }, + methodCreator: defaultMethodCreator, +}); +/*#>*/ \ No newline at end of file diff --git a/src/test/beatSelectorChanges/qualities/addToCountThree.quality.ts b/src/test/beatSelectorChanges/qualities/addToCountThree.quality.ts new file mode 100644 index 0000000..99c09b4 --- /dev/null +++ b/src/test/beatSelectorChanges/qualities/addToCountThree.quality.ts @@ -0,0 +1,24 @@ +/*<$ +For the asynchronous graph programming framework Stratimux and BeatSelectorChanges Concept, +generate a quality that will increment the state's countThree by one. +$>*/ +/*<#*/ +import { defaultMethodCreator } from '../../../model/concept'; +import { BeatSelectorChangesState } from '../beatSelectorChanges.concept'; +import { createQualitySet } from '../../../model/quality'; + +export const [ + beatSelectorChangesAddToCountThree, + beatSelectorChangesAddToCountThreeType, + beatSelectorChangesAddToCountThreeQuality +] = createQualitySet({ + type: 'BeatSelectorChanges AddToCountThree', + reducer: (state: BeatSelectorChangesState) => { + return { + ...state, + countThree: state.countThree + 1 + }; + }, + methodCreator: defaultMethodCreator, +}); +/*#>*/ \ No newline at end of file diff --git a/src/test/beatSelectorChanges/qualities/addToCountTwo.quality.ts b/src/test/beatSelectorChanges/qualities/addToCountTwo.quality.ts new file mode 100644 index 0000000..8b70977 --- /dev/null +++ b/src/test/beatSelectorChanges/qualities/addToCountTwo.quality.ts @@ -0,0 +1,24 @@ +/*<$ +For the asynchronous graph programming framework Stratimux and BeatSelectorChanges Concept, +generate a quality that will increment the state's countTwo by one. +$>*/ +/*<#*/ +import { defaultMethodCreator } from '../../../model/concept'; +import { BeatSelectorChangesState } from '../beatSelectorChanges.concept'; +import { createQualitySet } from '../../../model/quality'; + +export const [ + beatSelectorChangesAddToCountTwo, + beatSelectorChangesAddToCountTwoType, + beatSelectorChangesAddToCountTwoQuality +] = createQualitySet({ + type: 'BeatSelectorChanges AddToCountTwo', + reducer: (state: BeatSelectorChangesState) => { + return { + ...state, + countTwo: state.countTwo + 1 + }; + }, + methodCreator: defaultMethodCreator, +}); +/*#>*/ \ No newline at end of file diff --git a/src/test/beatSelectorChanges/strategies/generateCountingStrategy.strategy.ts b/src/test/beatSelectorChanges/strategies/generateCountingStrategy.strategy.ts new file mode 100644 index 0000000..283c3d5 --- /dev/null +++ b/src/test/beatSelectorChanges/strategies/generateCountingStrategy.strategy.ts @@ -0,0 +1,67 @@ +/*<$ +For the asynchronous graph programming framework Stratimux generate an ActionStrategy that will randomly increment each +BeatSelectorChanges count variants for 100 steps. +$>*/ +/*<#*/ +import { ActionNode, ActionStrategy, createActionNode, createStrategy } from '../../../model/actionStrategy'; +import { beatSelectorChangesAddToCountOne } from '../qualities/addToCountOne.quality'; +import { beatSelectorChangesAddToCountThree } from '../qualities/addToCountThree.quality'; +import { beatSelectorChangesAddToCountTwo } from '../qualities/addToCountTwo.quality'; +import { beatSelectorChangesAddToCountFour } from '../qualities/addToCountFour.quality'; +import { beatSelectorChangesAddToCountFive } from '../qualities/addToCountFive.quality'; +import { beatSelectorChangesAddToCountSix } from '../qualities/addToCountSix.quality'; +import { beatSelectorChangesAddToCountSeven } from '../qualities/addToCountSeven.quality'; + +function getRandomRange(min: number, max: number) { + return Math.random() * (max - min) + min; +} + +export const generateRandomCountingStrategy = (): [ + [number, number, number, number, number, number, number], + ActionStrategy, + string +] => { + const steps = 100; + const variantTally = new Array(7).fill(0) as [number, number, number, number, number, number, number]; + const variants = [ + beatSelectorChangesAddToCountOne, + beatSelectorChangesAddToCountTwo, + beatSelectorChangesAddToCountThree, + beatSelectorChangesAddToCountFour, + beatSelectorChangesAddToCountFive, + beatSelectorChangesAddToCountSix, + beatSelectorChangesAddToCountSeven, + ]; + let selection = Math.floor(getRandomRange(0, 6)); + let previousStep: ActionNode = + createActionNode(variants[selection](), { + successNode: null, + failureNode: null + }); + const stepFirst = previousStep; + variantTally[selection]++; + for (let i = 0; i < steps; i++) { + selection = Math.round(getRandomRange(0, 6)); + const newStep: ActionNode = + createActionNode(variants[selection](), { + successNode: null, + failureNode: null + }); + variantTally[selection]++; + + previousStep.successNode = newStep; + previousStep = newStep; + } + previousStep.successNode = null; + + const topic = `Generated Counting Strategy, using ${variantTally}`; + return [ + variantTally, + createStrategy({ + initialNode: stepFirst, + topic, + }), + topic + ]; +}; +/*#>*/ \ No newline at end of file