matthewp / robot

🤖 A functional, immutable Finite State Machine library
https://thisrobot.life
BSD 2-Clause "Simplified" License
1.92k stars 88 forks source link

invoke dynamic child machines #182

Closed sytabaresa closed 1 year ago

sytabaresa commented 2 years ago

Hi robot team!

It's posible this dynamic root/child machine implementation?:

import {
    createMachine,
    interpret,
    invoke,
    reduce,
    state,
    transition
} from "robot3";
// import 'robot3/debug';

let poolOfMachines = {
    switch: createMachine({
        inactive: state(
            transition('toggle', 'active')
        ),
        active: state(
            transition('toggle', 'end')
        ),
        end: state(),
    }),
    stoplight: createMachine({
        red: state(
            transition('toggle', 'yellow')
        ),
        yellow: state(
            transition('toggle', 'green')
        ),
        green: state(
            transition('toggle', 'red')
        ),
    })
}

let rootMachine = createMachine({
    step1: state(
        transition('LOAD', 'step1',
            reduce((ctx, ev) => ({
                ...ctx,
                machineName: ev.value
            }))
        ),
        transition('RUN', 'step2')
    ),
    // this dynamic kind of invoke
    step2: invoke((ctx, ev) => poolOfMachines[ev.value ? ev.vale : ctx.machineName],
        transition('done', 'step1')
    ),
    // step2: invoke(poolOfMachines['switch'],
    //     transition('done', 'step1')
    // ),
    end: state()
})

let service = interpret(rootMachine, (service) => {
    console.log(service.machine.current)
    if (service.child)
        console.log(service.child.machine.current)

}) // state: step1
service.send({
    type: 'LOAD',
    value: 'switch'
}) // state: step1
service.send('RUN') // invoke switch machine, state: step2.inactive

// switch child machine
service.child.send('toggle') // state:step3.active
service.child.send('toggle') // state:step3.end -> step1

service.send({
    type: 'RUN',
    value: 'stoplight'
}) // invoke stoplight machine, state: step2.red

// stoplight child machine
service.child.send('toggle') // state:step3.yellow
service.child.send('toggle') // state:step3.green
service.child.send('toggle') // state:step3.red
//...

I am trying to implement a plugin oriented machine system that can change the child machine based on runtime arguments, like a UI interface that have a root controller machine and many child plugin command machines. I know that its possible to interpret the child machine inside a promise invoked in step3 but that approach miss the nested send mechanism and other things.

Thanks for you attention.

matthewp commented 2 years ago

I think this is a valid way to model the behavior yeah. Are you saying it isn't working?

sytabaresa commented 2 years ago

I guess that in step2 the invoke form that I use is not valid, because its a function that returns a machine (dynamic machine) instead a promise. I think that could be a good API but in this moment is not supported

matthewp commented 2 years ago

Oh right, sorry. We do treat all functions as being promising-returning at the moment. That's an interesting pattern though. Maybe it's something we could support in the future.

sytabaresa commented 2 years ago

I made a PR to add support to this, hoping that you can review it :grin:

brandonpittman commented 1 year ago

Since https://github.com/matthewp/robot/pull/183 got merged, this issue should be able to be closed.