Skip to content

Commit

Permalink
Merge pull request #206 from Phuire-Research/dx
Browse files Browse the repository at this point in the history
v0.1.57
  • Loading branch information
REllEK-IO committed May 2, 2024
2 parents 3d20260 + 89b4907 commit 323abb8
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 29 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 29 additions & 3 deletions StagePlanner.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

```
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
51 changes: 29 additions & 22 deletions src/model/stagePlanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -83,6 +95,9 @@ export type StageDelimiter = {
runOnceMap: Map<string, boolean>
}

/**
* 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(), {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -297,6 +291,18 @@ export class UnifiedSubject extends Subject<Concepts> {
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) {
Expand Down Expand Up @@ -600,6 +606,7 @@ export class UnifiedSubject extends Subject<Concepts> {
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);
Expand Down
4 changes: 2 additions & 2 deletions src/test/onChange.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
133 changes: 133 additions & 0 deletions src/test/setStageOptions.test.ts
Original file line number Diff line number Diff line change
@@ -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<CounterState>(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<CounterState>(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());
});
/*#>*/
2 changes: 1 addition & 1 deletion src/test/strategy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()), {
Expand Down

0 comments on commit 323abb8

Please sign in to comment.