profile
viewpoint

Ask questions[Feature] Spawning Actors

ℹ️ UPDATE

Actors are now in version 4.6. Please see the documentation on actors 📖 to see the updated API (slightly different than this proposal).

Bug or feature request?

Feature

Description:

Statecharts describe the behavior of an individual service (or "Actor"). This means that if developers want to manage multiple invoked statechart services (Actors), they either have to:

  • Write external ad-hoc functionality for managing a system of Actors
  • Delegate handling to an external framework, such as React

The React + XState TodoMVC example highlights this. Although the code is idiomatic React code, the child todoMachine Actors are contained in each <Todo> component, and the parent todosMachine Actor is not aware of the existence of those Actors. It only responds to events from those Actors and keeps track of their state through its context.

This is fine, but not ideal for:

  • Modeling systems of Actors (e.g., one-to-many and hierarchical relationships between Actors)
  • Visualizing interconnected Actors (the system)
  • Portability - usage of a single "system of Actors" anywhere instead of having to piece together multiple Actors in differing ad-hoc framework-specific ways

To remedy this (and better mirror actual Actor-model languages like Erlang and Akka), @johnyanarella and I propose this (tentative) API:

(Feature) Potential implementation:

  • What the API would look like

spawn(actorCreator)

This is an action creator that instantiates an Actor (essentially the same as an invoked service 📖) and keeps an internal reference of it in the service:

import { Machine, spawn } from 'xstate';

const createTodoMachine = (id, message) => Machine({ id, ...todoMachine })
  .withContext({ message });

const todosMachine = Machine({
  id: 'todos',
  // ...
  on: {
    // Creates a new "todo" Actor with todoMachine behavior
    ADD_TODO: spawn((ctx, e) => createTodoMachine(e.id))
  }
});

onUpdate

This is a shorthand for something like Events.ActorTransition (up for bikeshedding) or "xstate.actorTransition" which is an event dispatched from the spawned Actors whenever their state transitions:

const todosMachine = Machine({
  id: 'todos',
  context: { todos: {} },
  // ...
  on: {
    // Creates a new "todo" Actor with todoMachine behavior
    ADD_TODO: spawn((ctx, e) => createTodoMachine(e.id, e.message))
  },
  onUpdate: {
    actions: assign({
      todos: (ctx, e) => ({ ...todos, [e.id]: e.state })
    })
  }
});

The event structure would look like:

{
  type: Events.ActorTransition, // special XState event
  id: 'todo-123', // ID of todo
  state: State({ ... }), // State instance from transition
}

state.children

This is a reference to all spawned child Actor instances. For example, if we spawned a "todo Actor", a state would look like:

todosMachine.transition('active', {
  type: 'ADD_TODO',
  id: 'todo-1',
  message: 'hello'
});
// => State {
//   value: 'active',
//   context: {
//     todos: {
//       'todo-1': State {
//         value: 'pending',
//         context: { message: 'hello' }
//         // ...
//       }
//     }
//   },
//   // ...
//   children: Map {
//     'todo-1': Interpreter { ... }
//     // ...
//   }
// }

JSON.stringify(...) will not display Interpreter instances.

  • If this is a breaking change No, not a breaking change.

  • If this is part of the existing SCXML specification This leverages existing parts of SCXML by:

  1. Invoking a proprietary "Supervisor" service for the lifetime of the machine
  2. spawn(...) is just an action object; i.e.:
spawn('todo');
// => {
//   type: 'xstate.send',
//   target: '__supervisor',
//   event: {
//     type: 'xstate.spawn',
//     source: 'todo'
//   }
// }
  1. The "Supervisor" service will automatically subscribe (.onTransition(...)) to spawned machines, keep a reference of them, call sendParent(Events.ActorTransition, ...) on each change, and .stop() each individual child service when the parent service is stopped.
davidkpiano/xstate

Answer questions pedronauck

This is awesome and really useful, I think that the API is clean and easy to understand. So, I have some question... What's happening if I want the same child instance working with two parents or more than one machine? I don't know if this can be a real use case, but this would be possible? Makes sense? 🤔

I'm not an expert in Actor Model, but some solution like that will be easy to manage, maybe can hurt the concept, as I told I don't know so much about it. But, is just an idea.

This will be nice if I want to make some kinda a "broadcast" between machines and since each one has your own ID, maybe would be possible. The problem, of course, it would be the need to have something internal orchestrating these messages.


I made some experiment using some kinda a "broadcast", it's very coupled with react, but it's an example... https://codesandbox.io/s/xstate-broadcast-lg6xw

useful!

Related questions

No questions were found.
source:https://uonfu.com/
answerer
Pedro Nauck pedronauck @upbitco Florianópolis/SC Javascript and open source enthusiast ♥ Creator of docz and reworm.
Github User Rank List