Skip to content

WIP - Split state machines #83

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

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
split state machines
  • Loading branch information
ShMcK committed Jan 25, 2020
commit 8ff9c644456db62c29543dfa05391610ef2baeba
17 changes: 16 additions & 1 deletion typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ export interface MachineContext {
env: Environment
error: ErrorMessage | null
tutorial: G.Tutorial | null
}

export interface PlayMachineContext extends MachineContext {
position: Position
progress: Progress
processes: ProcessEvent[]
Expand All @@ -57,7 +60,7 @@ export interface MachineEvent {
data?: any
}

export interface MachineStateSchema {
export interface SelectTutorialMachineStateSchema {
states: {
Start: {
states: {
Expand All @@ -68,6 +71,11 @@ export interface MachineStateSchema {
ContinueTutorial: {}
}
}
}
}

export interface PlayTutorialMachineStateSchema {
states: {
Tutorial: {
states: {
Initialize: {}
Expand All @@ -91,6 +99,13 @@ export interface MachineStateSchema {
}
}

export interface MachineStateSchema {
states: {
SelectTutorial: {}
PlayTutorial: {}
}
}

export interface StateMachine {
activate(): void
deactivate(): void
Expand Down
4 changes: 2 additions & 2 deletions web-app/src/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ const Routes = () => {
<OverviewPage send={tempSend} context={{} as CR.MachineContext} />
</Route>
<Route path="Tutorial.Level">
<LevelSummaryPage send={tempSend} context={{} as CR.MachineContext} />
<LevelSummaryPage send={tempSend} context={{} as CR.PlayMachineContext} />
</Route>
<Route path="Tutorial.Completed">
<CompletedPage send={tempSend} context={{} as CR.MachineContext} />
<CompletedPage send={tempSend} context={{} as CR.PlayMachineContext} />
</Route>
</Router>
</Workspace>
Expand Down
2 changes: 1 addition & 1 deletion web-app/src/components/Debugger/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react'
import * as T from 'typings'
import { css, jsx } from '@emotion/core'

interface Props extends T.MachineContext {
interface Props extends T.PlayMachineContext {
state: string
children: React.ReactElement
}
Expand Down
2 changes: 1 addition & 1 deletion web-app/src/containers/Tutorial/LevelPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as selectors from '../../../services/selectors'
import Level from './Level'

interface PageProps {
context: T.MachineContext
context: T.PlayMachineContext
send(action: T.Action): void
}

Expand Down
8 changes: 4 additions & 4 deletions web-app/src/services/selectors/tutorial.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { createSelector } from 'reselect'
import { MachineContext } from 'typings'
import * as CR from 'typings'
import * as G from 'typings/graphql'
import onError from '../../services/sentry/onError'

export const currentTutorial = ({ tutorial }: MachineContext): G.Tutorial => {
export const currentTutorial = ({ tutorial }: CR.MachineContext): G.Tutorial => {
if (!tutorial) {
const error = new Error('Tutorial not found')
onError(error)
Expand All @@ -21,7 +21,7 @@ export const currentVersion = createSelector(currentTutorial, (tutorial: G.Tutor
return tutorial.version
})

export const currentLevel = (context: MachineContext): G.Level =>
export const currentLevel = (context: CR.PlayMachineContext): G.Level =>
createSelector(
currentVersion,
(version: G.TutorialVersion): G.Level => {
Expand All @@ -41,7 +41,7 @@ export const currentLevel = (context: MachineContext): G.Level =>
},
)(context)

export const currentStep = (context: MachineContext): G.Step =>
export const currentStep = (context: CR.PlayMachineContext): G.Step =>
createSelector(
currentLevel,
(level: G.Level): G.Step => {
Expand Down
241 changes: 36 additions & 205 deletions web-app/src/services/state/machine.ts
Original file line number Diff line number Diff line change
@@ -1,215 +1,46 @@
import * as CR from 'typings'
import { Machine, MachineOptions } from 'xstate'
import actions from './actions'
import { Machine } from 'xstate'
import { selectTutorialMachine } from './selectTutorial'
import { playTutorialMachine } from './playTutorial'

const options: MachineOptions<CR.MachineContext, CR.MachineEvent> = {
// @ts-ignore
actions,
}

export const machine = Machine<CR.MachineContext, CR.MachineStateSchema, CR.MachineEvent>(
{
id: 'root',
initial: 'Start',
context: {
error: null,
env: { machineId: '', sessionId: '', token: '' },
tutorial: null,
position: { levelId: '', stepId: '' },
progress: {
levels: {},
steps: {},
complete: false,
},
processes: [],
},
states: {
Start: {
initial: 'Startup',
states: {
Startup: {
onEntry: ['loadEnv'],
on: {
ENV_LOAD: {
target: 'Authenticate',
actions: ['setEnv'],
},
},
},
Authenticate: {
onEntry: ['authenticate'],
on: {
AUTHENTICATED: 'NewOrContinue',
ERROR: {
actions: ['setError'],
},
},
},
NewOrContinue: {
onEntry: ['loadStoredTutorial'],
on: {
CONTINUE_TUTORIAL: {
target: 'ContinueTutorial',
actions: ['continueTutorial'],
},
NEW_TUTORIAL: {
target: 'SelectTutorial',
},
},
},
SelectTutorial: {
onEntry: ['clearStorage'],
id: 'start-new-tutorial',
on: {
TUTORIAL_START: {
target: '#tutorial',
actions: ['newTutorial'],
},
},
},
ContinueTutorial: {
on: {
TUTORIAL_START: {
target: '#tutorial-level',
actions: ['continueConfig'],
},
TUTORIAL_SELECT: 'SelectTutorial',
},
},
export const machine = Machine<CR.MachineContext, CR.MachineStateSchema, CR.MachineEvent>({
id: 'root',
initial: 'SelectTutorial',
context: {
error: null,
env: { machineId: '', sessionId: '', token: '' },
tutorial: null,
},
states: {
// start/continue a tutorial
// select tutorial
// view tutorial summary
SelectTutorial: {
invoke: {
src: selectTutorialMachine,
onDone: 'PlayTutorial',
data: {
env: (context: CR.MachineContext) => context.env,
tutorial: (context: CR.MachineContext) => context.tutorial,
error: null,
},
},
Tutorial: {
id: 'tutorial',
initial: 'Initialize',
on: {
// track commands
COMMAND_START: {
actions: ['commandStart'],
},
COMMAND_SUCCESS: {
actions: ['commandSuccess'],
},
COMMAND_FAIL: {
actions: ['commandFail'],
},
ERROR: {
actions: ['setError'],
},
},
states: {
// TODO move Initialize into New Tutorial setup
Initialize: {
onEntry: ['initializeTutorial'],
on: {
TUTORIAL_CONFIGURED: 'Summary',
// TUTORIAL_CONFIG_ERROR: 'Start' // TODO should handle error
},
},
Summary: {
on: {
LOAD_TUTORIAL: {
target: 'Level',
actions: ['initPosition', 'initTutorial'],
},
},
},
LoadNext: {
id: 'tutorial-load-next',
onEntry: ['loadNext'],
on: {
NEXT_STEP: {
target: 'Level',
actions: ['updatePosition'],
},
NEXT_LEVEL: {
target: 'Level', // TODO should return to levels summary page
actions: ['updatePosition'],
},
COMPLETED: '#completed-tutorial',
},
},
Level: {
initial: 'Load',
states: {
Load: {
onEntry: ['loadLevel', 'loadStep'],
after: {
0: 'Normal',
},
},
Normal: {
id: 'tutorial-level',
on: {
TEST_RUNNING: 'TestRunning',
STEP_SOLUTION_LOAD: {
actions: ['editorLoadSolution'],
},
},
},
TestRunning: {
onEntry: ['testStart'],
on: {
TEST_PASS: {
target: 'TestPass',
actions: ['updateStepProgress'],
},
TEST_FAIL: 'TestFail',
TEST_ERROR: 'TestError',
},
},
TestError: {
onEntry: ['testFail'],
after: {
0: 'Normal',
},
},
TestPass: {
onExit: ['updateStepPosition'],
after: {
1000: 'StepNext',
},
},
TestFail: {
onEntry: ['testFail'],
after: {
0: 'Normal',
},
},
StepNext: {
onEntry: ['stepNext'],
on: {
LOAD_NEXT_STEP: {
target: 'Normal',
actions: ['loadStep'],
},
LEVEL_COMPLETE: {
target: 'LevelComplete',
actions: ['updateLevelProgress'],
},
},
},
LevelComplete: {
on: {
LEVEL_NEXT: '#tutorial-load-next',
},
},
},
},
Completed: {
id: 'completed-tutorial',
onEntry: ['userTutorialComplete'],
on: {
SELECT_TUTORIAL: {
target: '#start-new-tutorial',
actions: ['reset'],
},
},
},
},
// initialize a selected tutorial
// progress through tutorial level/steps
// complete tutorial
PlayTutorial: {
invoke: {
src: playTutorialMachine,
onDone: 'SelectTutorial',
data: {
context: (context: CR.MachineContext) => context.env,
tutorial: (context: CR.MachineContext) => context.tutorial,
error: null,
},
},
},
},
options,
)
})

export default machine
7 changes: 7 additions & 0 deletions web-app/src/services/state/playTutorial/actions/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as CR from 'typings'

export default {
userTutorialComplete(context: CR.PlayMachineContext) {
console.log('should update user tutorial as complete')
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { assign } from 'xstate'
export default {
// @ts-ignore
commandStart: assign({
processes: ({ processes }: T.MachineContext, event: T.MachineEvent): T.ProcessEvent[] => {
processes: ({ processes }: T.PlayMachineContext, event: T.MachineEvent): T.ProcessEvent[] => {
const { process } = event.payload
const isRunning = processes.find(p => p.title === process.title)
if (!isRunning) {
Expand All @@ -15,14 +15,14 @@ export default {
}),
// @ts-ignore
commandSuccess: assign({
processes: ({ processes }: T.MachineContext, event: T.MachineEvent): T.ProcessEvent[] => {
processes: ({ processes }: T.PlayMachineContext, event: T.MachineEvent): T.ProcessEvent[] => {
const { process } = event.payload
return processes.filter(p => p.title !== process.title)
},
}),
// @ts-ignore
commandFail: assign({
processes: ({ processes }: T.MachineContext, event: T.MachineEvent): T.ProcessEvent[] => {
processes: ({ processes }: T.PlayMachineContext, event: T.MachineEvent): T.ProcessEvent[] => {
const { process } = event.payload
return processes.filter(p => p.title !== process.title)
},
Expand Down
Loading