Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI #78

Merged
merged 2 commits into from
Oct 11, 2023
Merged

UI #78

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const axium = createAxium('ownershipTest', [
createCounterConcept(),
createExperimentConcept(createExperimentActionQueState(), [checkInQuality], [experimentActionQuePrinciple])
], true, true);
const staged = axium.stage(
const plan = axium.stage(
'Testing Ownership Staging', [
(cpts, dispatch) => {
const axiumState = cpts[0].state as AxiumState;
Expand Down
156 changes: 93 additions & 63 deletions Stage.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
## Stage
### Abstract
This is derived from the newly created UnifiedSubject to handle the main point of vulnerability that a recursive machine carries. As the main point of dispatching new actions in the system would traditionally be informed via the subscription to listen to state changes. This Design Pattern allows one to safely dispatch in a tightly patterned subscription. This design pattern watches each stage for the potential of a runaway configuration which would normally prevent this machine from halting. But since the Unified Turing Machine was created to be halting complete. The UnifiedSubject internally watches each stage of your application independently and the actions that it dispatches via the supplied dispatch function. If a similar action is dispatched in rapid Succession denoted by its type and no debounce option. That Stage will close and be added to the axium's badPlans property.
This is derived from the newly created UnifiedSubject to handle the main point of vulnerability that a recursive machine carries. As the main point of dispatching new actions in the system would traditionally be informed via the subscription to listen to state changes. This Design Pattern allows one to safely dispatch in a tightly patterned subscription. This design pattern watches each stage for the potential of a runaway configuration which would normally prevent this machine from halting. But since the Unified Turing Machine was created to be halting complete. The UnifiedSubject internally watches each stage of your application independently and the actions that it dispatches via the supplied dispatch function. If a similar action is dispatched in rapid Succession denoted by its type and no debounce option. That plan will conclude and be added to the axium's badPlans property.

Once attached to the badPlan property, it would be possible to reinitialize said stage via your concept's principle utilizing the stage's title. But places that burden of responsibility on the developer. As the scope of a Unified Turing Machine is to be designed to specification and halt appropriately. We accept failure as likewise the ability to halt.

"You stage a plan and a plan has multiple stages."
*"You stage a plan and a plan has multiple stages."*

## Working with the Stage Paradigm
The added benefit of the creation of a plan to control the flow of actions. Allows the ability to create a series of stages to handle how the dispatch would be handled within a subscription, but with the added benefit of iterating through each stage your plan. A typical plan would typically be composed of an initialization, main run time, and likewise the ability to conclude. 3 acts if you will.
```typescript
// Multiple Stages are a Plan
export type Plan = {
title: string;
stages: Staging[],
stage: number;
stages: Staging[],
stageFailed: number;
}

```
* title - Title of your plan, used to determine your plan within badPlans stored on the axium if they fail.
* stage - This is your stage index, starting at 0.
* stages - Is an array of function that will be called upon each notification depending on the current stage index.
* stageFailed - Is a fail safe mechanism to prevent action overflow due to branch prediction errors.
### Stage Options
```typescript
export type dispatchOptions = {
runOnce?: boolean;
debounce?: number;
Expand All @@ -27,88 +34,111 @@ export type dispatchOptions = {
},
}

```
* dispatchOptions
* runOnce - If enabled on the dispatch options, this will permit only one dispatch of that action within its stage.
* debounce - 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.
* on - Simple handler that will prevent dispatch until the selected value is set to what is expected. Keep in mind this should also be occupied by a debounce, as this dispatch will run on each successful state update. This should be utilized alongside iterateStage, setStage, or debounce to prevent action overflow.

### Internals
```typescript
export type Dispatcher = (action: Action, options: dispatchOptions) => void;
export type Staging = (
concepts: Concept[],
dispatch: (action: Action, options: dispatchOptions) => void
) => void;
export type Stage = (id: number) => () => void;

export class UnifiedSubject extends Subject<Concept[]> {
stage(title: string, stages: Staging[]) {}
}
```
The added benefit of the creation of staging to control the flow of actions, is also the ability to create an additional abstraction to handle how the dispatch would be handled within a subscription, but with the added benefit of iterating through each step of a stage your applications. That these steps would typically be an initialization, a main run time, and likewise the ability to close. 3 acts if you will.

* dispatchOptions
* runOnce - If enabled on the dispatch options, this will permit only one dispatch of that action within its stage.
* debounce - Required to prevent the stage to be considered bad if rerunning the same action within the same step, specific use case is tracking some position over time.
* setStep - This will set the stage to a specific step, useful if some strategy failed and the staging needs to be reset to prepare for that strategy again.
* incrementStep - Will increment the current step of the stage, this should be your default option for dispatching actions or strategies to prevent action overflow.
* on - Simple handler that will prevent dispatch until the selected value is set to what is expected. Keep in mind this should also be occupied by a debounce, as this dispatch will run on each successful state update.
* Dispatcher - This is the supplied dispatch function that is made available each stage.
* Staging - The interface that you will be interacting with when setting up your stages, noting placement of concepts and the dispatch function.
* UnifiedSubject - This is a specialized subject for utilized within STRX to allow for this staging paradigm. This is made available via the createAxium function and likewise within your principles via the concept$ property. Note that your plan will be an array of functions even with just one stage.

## Example

```typescript
let runCount = 0;

// Sets logging to True
const axium = createAxium([createCounterConcept()], true);

const staged = axium.stage('Stage DispatchOptions Test',
[
(concepts, dispatch) => {
const axium = createAxium('axiumStageDispatchOptionsTest', [createCounterConcept()], true);
const sub = axium.subscribe((concepts) => {
const axiumState = concepts[0].state as AxiumState;
if (axiumState.badPlans.length > 0) {
const badPlan = axiumState.badPlans[0];
const counter = selectState<Counter>(concepts, counterName);
console.log('Stage 1 ', counter, runCount);
// Sets count to 1
dispatch(counterAdd(), {
iterateStep: true
});
console.log('Stage Ran Away, badPlans.length: ', axiumState.badPlans.length, 'Count: ', counter.count);
plan.conclude();
sub.unsubscribe();
expect(badPlan.stageFailed).toBe(2);
expect(counter.count).toBe(2);
setTimeout(() => {done();}, 500);
}
});
const plan = axium.stage('Stage DispatchOptions Test',
[
(concepts, dispatch) => {
const counter = selectState<Counter>(concepts, counterName);
console.log('Stage 1 ', counter, runCount);
dispatch(counterAdd(), {
iterateStage: true
});
}, (concepts, dispatch) => {
runCount++;
const counter = selectState<Counter>(concepts, counterName);
console.log('Stage 2 ', counter, runCount);
// Sets count to 2 and only runs once per state update
dispatch(counterAdd(), {
runCount++;
const counter = selectState<Counter>(concepts, counterName);
console.log('Stage 2 ', counter, runCount);
// Sets count to 2 and only runs once per state update
dispatch(counterAdd(), {
runOnce: true
});
// Will wait until count is set to 2, then set the Stage Explicitly to the third Step counting from 0.
dispatch(counterAdd(), {
setStep: 2,
debounce: 0,
});
// Will wait until count is set to 2, then set the Stage Explicitly to the third Step counting from 0.
dispatch(counterAdd(), {
setStage: 2,
on: {
selector: counterSelectCount,
expected: 2
}
});
// }
selector: counterSelectCount,
expected: 2
},
// Requires debounce, because the previous action is of the same type, but runs only once.
debounce: 1
});
// }
}, (concepts, dispatch) => {
runCount++;
const counter = selectState<Counter>(concepts, counterName);
console.log('Stage 3 ', counter, runCount);
// Will cause an action overflow forcing the stage to close and add itself to bad Stages
dispatch(counterSubtract(), {
runCount++;
const counter = selectState<Counter>(concepts, counterName);
console.log('Should run twice, Stage 3 ', counter, runCount);
// Will cause an action overflow forcing the current stage to conclude and add the plan to badPlans
dispatch(counterSubtract(), {
// Enabling will cause this test to timeout via the subscription watching for badPlans to never be ran.
// debounce: 500
// This demonstrates the fault resistance of the Stage paradigm, despite STRX's recursive functionality.
});
}
]);

const sub = axium.subscribe((concepts) => {
const axiumState = concepts[0].state as AxiumState;
// This will run once the last step of the stage we created overflows, this is for demonstration purposes only.
if (axiumState.badPlans.length > 0) {
const badPlan = axiumState.badPlans[0];
const counter = selectState<Counter>(concepts, counterName);
console.log('Stage Ran Away, badPlans.length: ', axiumState.badPlans.length, 'Count: ', counter.count);
expect(badPlan.stepFailed).toBe(2);
expect(counter.count).toBe(2);
sub.unsubscribe();
});
// This dispatch will be invalidated and never dispatched due to the effect of action overflow of the above.
dispatch(counterAdd(), {});
console.log(
'Will also run twice. 1st will be before "Stage Ran Away,"',
'and after "Should run twice." The 2nd will be final console log output.'
);
}
});
]);
```
To prevent action overflow, each stage is paying attention to consecutive actions of the same type. In an action overflow state, sequentially the overflow will call the same dispatch before called the next dispatch even if within the same stage.

Keep in mind behind the scenes during a STRX runtime, there will be multiple strategies running concurrently. Observe the runCount specified in this example. Please look to the STRX's tests folder.

As well that STRX is designed to be run primarily through the loaded concepts and their associated principles. To prevent unexpected behaviors in your own principles. Please utilize the supplied KeyedSelector for axium's open property to begin the stage of your concepts.
## Stage within your Principle
STRX is designed to be ran primarily through its loaded concepts and their associated principles. To prevent unexpected behaviors in your own principles. Please utilize the supplied KeyedSelector for axium's open property to begin the stage of your concepts.
```typescript
const plan = concept$.stage('Principle Stage Example', [
(___, dispatch) => {
dispatch(someAction(), {
iterateStage: true,
on: {
selector: axiumSelectOpen,
expected: true
},
});
},
(concepts, dispatch) => {
// Your principle's run time logic.
}
]);
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@phuire/strx",
"version": "0.0.24",
"version": "0.0.25",
"description": "Unified Turing Machine",
"main": "dist/index.js",
"module": "dist/index.mjs",
Expand Down
29 changes: 19 additions & 10 deletions src/model/unifiedSubject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export type Plan = {

export type dispatchOptions = {
runOnce?: boolean;
iterateStep?: boolean;
setStep?: number;
iterateStage?: boolean;
setStage?: number;
on?: {
selector: KeyedSelector,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -29,7 +29,6 @@ export type Staging = (
concepts: Concept[],
dispatch: (action: Action, options: dispatchOptions) => void
) => void;
// export type Stage = (id: number) => () => void;
export type StageDelimiter = {
stage: number,
prevActions: ActionType[],
Expand Down Expand Up @@ -61,6 +60,16 @@ const handleRun =
];
}
} else {
const unionExpiration: number[] = [];
stageDelimiter.prevActions = stageDelimiter.prevActions.filter((at, i) => {
if (at !== action.type && stageDelimiter.unionExpiration[i] !== action.expiration) {
unionExpiration.push(unionExpiration[i]);
return true;
} else {
return false;
}
});
stageDelimiter.unionExpiration = unionExpiration;
return [
stageDelimiter, false
];
Expand Down Expand Up @@ -134,11 +143,11 @@ export class UnifiedSubject extends Subject<Concept[]> {
this.currentStages.set(this.stageId, {title, stages, stage: 0, stageFailed: -1});
const stageId = this.stageId;
this.stageId++;
const close = () => {
const conclude = () => {
this.currentStages.delete(stageId);
};
return {
close: close.bind(this)
conclude: conclude.bind(this)
};
}

Expand Down Expand Up @@ -178,12 +187,12 @@ export class UnifiedSubject extends Subject<Concept[]> {
}
this.stageDelimiters.set(key, stageDelimiter);
if (!debounce && run) {
if (options?.setStep) {
plan.stage = options.setStep;
}
if (options?.iterateStep) {
if (options?.iterateStage) {
plan.stage += 1;
}
if (options?.setStage) {
plan.stage = options.setStage;
}
// Horrifying
// Keep in place, this prevents branch prediction from creating ghost actions if there is an action overflow.
if (plan.stageFailed === -1) {
Expand All @@ -193,7 +202,7 @@ export class UnifiedSubject extends Subject<Concept[]> {
} else if (
options?.runOnce === undefined &&
(options.on === undefined ||
(options.on && (!options.debounce && (options.iterateStep === undefined || options.setStep === plan.stage)))
(options.on && (!options.debounce && (options.iterateStage === undefined || options.setStage === plan.stage)))
)) {
plan.stageFailed = plan.stage;
plan.stage = plan.stages.length;
Expand Down
8 changes: 4 additions & 4 deletions src/test/addConcepts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import { countingTopic } from '../concepts/counter/strategies/counting.strategy'

test('Axium add Concepts Strategy Test', (done) => {
const axium = createAxium('axiumAddConceptTest',[], true, true);
const staged = axium.stage('Add Concepts Stage',[
const plan = axium.stage('Add Concepts Stage',[
(concepts, dispatch) => {
dispatch(
strategyBegin(
addConceptsToAddQueThenBlockStrategy(concepts,[createCounterConcept()])
),
{
iterateStep: true
iterateStage: true
}
);
},
Expand All @@ -25,7 +25,7 @@ test('Axium add Concepts Strategy Test', (done) => {
if (concepts[1].name === counterName) {
exists = true;
dispatch(strategyBegin(countingStrategy()), {
iterateStep: true
iterateStage: true
});
}
expect(exists).toBe(true);
Expand All @@ -36,7 +36,7 @@ test('Axium add Concepts Strategy Test', (done) => {
const counter = selectState<Counter>(concepts, counterName);
expect(counter.count).toBe(1);
setTimeout(() => {done();}, 500);
staged.close();
plan.conclude();
}
}
]);
Expand Down
10 changes: 5 additions & 5 deletions src/test/ownership.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ test('Ownership Test', (done) => {
createCounterConcept(),
createExperimentConcept(createExperimentActionQueState(), [checkInQuality], [experimentActionQuePrinciple])
], true, true);
const staged = axium.stage(
const plan = axium.stage(
'Testing Ownership Staging', [
(cpts, dispatch) => {
const axiumState = cpts[0].state as AxiumState;
Expand All @@ -38,7 +38,7 @@ test('Ownership Test', (done) => {
// This will place a counting strategy in the experiment actionQue to be later dispatched.
// Via its principle, to simulate an action moving off premise.
dispatch(strategyBegin(puntCountingStrategy()), {
iterateStep: true
iterateStage: true
});
}
},
Expand All @@ -47,15 +47,15 @@ test('Ownership Test', (done) => {
// Will be ran after both counting strategies conclude.
const ownership = selectState<OwnershipState>(cpts, ownershipName);
console.log('Stage 2', ownership.ownershipLedger, ownership.pendingActions);
dispatch(counterSetCount(createPayload<SetCountPayload>({newCount: 1000}), undefined, 7000), { iterateStep: true});
dispatch(counterSetCount(createPayload<SetCountPayload>({newCount: 1000}), undefined, 7000), { iterateStage: true});
},
(cpts, dispatch) => {
const ownership = selectState<OwnershipState>(cpts, ownershipName);
console.log('Stage 3', ownership.ownershipLedger, ownership.pendingActions);
const counter = selectState<Counter>(cpts, counterName);
console.log('Count: ', counter.count);
dispatch(strategyBegin(experimentPrimedCountingStrategy(cpts)), {
iterateStep: true
iterateStage: true
});
},
(cpts, dispatch) => {
Expand All @@ -70,7 +70,7 @@ test('Ownership Test', (done) => {
expect(counter.count).toBe(3);
// Comment in if testing the halting ability of log and setCount stage is commented out.
// setTimeout(() => {done();}, 1000);
staged.close();
plan.conclude();
} else if (
(axiumState.lastStrategy === experimentCountingTopic ||
axiumState.lastStrategy === experimentPrimedCountingTopic) &&
Expand Down
Loading