Giter Site home page Giter Site logo

innerspacetrainings / statecharts.net Goto Github PK

View Code? Open in Web Editor NEW
30.0 7.0 4.0 821 KB

.NET Standard SCXML compatible Statechart implementation

License: MIT License

C# 92.60% JavaScript 0.02% XSLT 6.65% PowerShell 0.73%
statecharts scxml state-management netstandard netstandard-libraries netcore unity3d

statecharts.net's Introduction

Statecharts.NET

Statecharts.NET.Core

Quick Example

// Statechart Definition
var behaviour = Define.Statechart
    .WithInitialContext(new FetchContext { Retries = 0 })
    .WithRootState(
        "fetch"
            .AsCompound()
            .WithInitialState("idle")
            .WithStates(
                "idle".WithTransitions(
                    On("FETCH").TransitionTo.Sibling("loading")),
                "loading".WithTransitions(
                    On("RESOLVE").TransitionTo.Sibling("success"),
                    On("REJECT").TransitionTo.Sibling("failure")),
                "failure".WithTransitions(
                    On("RETRY").TransitionTo.Sibling("loading").WithActions<FetchContext>(Assign<FetchContext>(context => context.Retries++))),
                "success".AsFinal()));

// Usage
var parsedStatechart = Parser.Parse(Behaviour);
var statechart = Interpreter.Interpret(parsedStatechart);

statechart.RunAsync();
statechart.Send(new NamedEvent("FETCH"));
/* ... */

// FetchContext
internal class FetchContext : IContext<FetchContext>, IXStateSerializable
{
    public int Retries { get; set; }

    public bool Equals(FetchContext other) => other != null && Retries == other.Retries;
    ObjectValue IXStateSerializable.AsJSObject() => ObjectValue(("retries", Retries));
    public FetchContext CopyDeep() => new FetchContext { Retries = Retries };
    public override string ToString() => $"FetchContext: (Retries = {Retries})";
}

Features

The DSL (Statecharts.NET.Language)

One of the main ideas of Statecharts.NET is to separate the Statechart Interpreter and the way you describe Statecharts. This is one of the mightiest features of the Statecharts Concept. Statecharts.NET.Core includes all the functionality for creating Statecharts and executing them. Statecharts.NET.Language provides a reference implementation of how the core functionality can be wrapped to create a nicely looking syntax for creating Statecharts. It is intended to be as functional/composable as possible while preserving the main idea of explicity/clarity.

โ„น Make sure to add using static Statecharts.NET.Language.Keywords; to your usings!

Your new best friend: Define.<...>

The API is built in a way that enables it to be easily explored via IntelliSense/Autocomplete. If you want to define some Statechart-Element for reusability, just type "Define" followed by a "." to enable auto completion. After that you will be guided most of the available features.

Element Definitions

// Events
var Start = Define.Event("Start");
var IncrementBy = Define.EventWithData<int>("IncrementBy"); // can be sent to the Statechart via .Send(IncrementBy(...))

// Actions
var SideEffect1 = Define.Action.SideEffect(() => Console.WriteLine("I'm a Side Effect"));
var SideEffect2 = Define.Action.SideEffectWithContext<FetchContext>(context => Console.WriteLine($"I have access to the context {context.Retries}"));
var SideEffect3 = Define.Action.SideEffectWithContextAndData<FetchContext, int>((context, amount) => Console.WriteLine($"I have access to the context {context.Retries} and some data {amount}"));
var Assign1 = Define.Action.Assign<FetchContext>(context => context.Retries = 0);
var Assign2 = Define.Action.AssignWithData<FetchContext, int>((context, amount) => context.Retries += amount);

// Services
var TaskService = Define.Service.Task(token => Task.Delay(TimeSpan.FromSeconds(3), token));
var ActivityService = Define.Service.Activity(() => Console.WriteLine("started"), () => Console.WriteLine("stopped"));

Statechart Definition

โ„น The basic definition of a Statenode is simply a string. The Builder Methods are extension methods for string to reduce character cound and improve readability. Ask your autocompletion with .With<...> and .As<...> for help!

// Root Statenode
var CompoundRoot = Define.Statechart
    .WithInitialContext(new NoContext())
    .WithRootState(
        "example"
            .AsCompound()
            .WithInitialState("first")
            .WithStates("first", "second"));
var OrthogonalRoot = Define.Statechart
    .WithInitialContext(new NoContext())
    .WithRootState(
        "example"
            .AsOrthogonal()
            .WithStates("a", "b"));

// Transitions
var TransitionsExample = "example".WithTransitions(
    On("EventName").TransitionTo.Self,
    On(Start).TransitionTo.Self,
    On(IncrementBy).TransitionTo.Self,
                
    On("dummy").If<FetchContext>(context => context.Retries > 10).TransitionTo.Self,
    On(IncrementBy).If<FetchContext>((_, amount) => amount > 5).TransitionTo.Self,
                
    Ignore("EventName"),
    Ignore(Start),
    Ignore(IncrementBy),
                
    Immediately.TransitionTo.Self,
                
    After(3.Seconds()).TransitionTo.Self,
                
    On("dummy").TransitionTo.Self,
    On("dummy").TransitionTo.Child("child", "even", "deep", "children"),
    On("dummy").TransitionTo.Sibling("sibling", "even", "children", "of", "siblings"),
    On("dummy").TransitionTo.Target(Sibling("sibling")),
    On("dummy").TransitionTo.Target(Child("child")),
    On("dummy").TransitionTo.Absolute("rootstatenode", "children", "deeper", "..."),
    On("dummy").TransitionTo.Multiple(Child("paralle", "child1"), Child("parallel", "child2")),
                
    On("dummy").TransitionTo.Self.WithActions(SideEffect1, Log("and another one")));

// Actions
var ActionsExample = "example".WithEntryActions<FetchContext>(
    Run(() => Console.WriteLine("some arbitrary action")),
    Run<FetchContext>(context => Console.WriteLine($"some arbitrary action with {context}")),
    Log("logging a label"),
    Log<FetchContext>(context => $"logging some context {context}"),
    Assign<FetchContext>(context => context.Retries = 0));

// Statenodes OnDone
var OnCompoundDoneExample = "example"
    .AsCompound()
    .WithInitialState("first")
    .WithStates("first".AsFinal())
    .OnDone.TransitionTo.Sibling("sibling");
var OnOrthogonalDoneExample = "example"
    .AsOrthogonal()
    .WithStates("first".AsFinal(), "second".AsFinal())
    .OnDone.TransitionTo.Sibling("sibling");

// Services OnSuccess (OnError is currently missing :/)
var TaskServiceExample = "example"
    .WithInvocations(TaskService.OnSuccess.TransitionTo.Sibling("sibling"));

Reusing Elements in Statecharts

The DSL is built around a type-safe (as much as possible in C#) and ordered (built with git in mind) builder pattern. But the builder methods are only used for a human-friendly, readabilty-optimized syntax, actually stitching together the Elements is done via parameters. Most of the builder methods' function signatures look like (T, params T) or (OneOf<T1, T2>, params OneOf<T1, T2>[]). The idea behind this is, that it doesn't make sense to call a builder method without any argument, but you can't specify this in C# using the params keyword, so a required first parameter is utilized in Statecharts.NET. Please don't get confused by the usage of OneOf in the method signatures, currently this "hack" is used to model tagged unions in C#.

So to reuse elements in Statecharts.NET, just create a function, a method or a property that returns the according element (it's just an object that is handled by Statecharts.NET at runtime) and then include it in the builder pattern. It's as simple as that ๐Ÿค“. Just make sure to use the appropriate return type, then you can also utilize all the existing builder methods.

This is a simple, but also very dumb example:

private static StatenodeDefinition Numbered(int number, Target next) =>
    $"Statenode{number}"
        .WithTransitions(Immediately.TransitionTo.Target(next));

// usage: .WithStates(..., Numbered(2, Sibling("another")))

Executing a Statechart

var parsedStatechart = Parser.Parse(Behaviour);
var statechart = Interpreter.Interpret(parsedStatechart, /* optionally takes InterpreterOptions where logging and timing behaviour can be defined */);

statechart.RunAsync(); // returns a Task that can be awaited
statechart.Send(new NamedEvent("FETCH"));

statechart.NextEvents; // returns the next possible events
statecharts.OnMacroStep += macrostep => { /* ... */ }; // provides introspection for the statechart execution behaviour

Missing Features

  • History States
  • Correctly Resolving Parallel States (see: https://www.w3.org/TR/scxml/#algorithm)
  • Internal/External Transitions
  • Pass Context to Services
  • Statecharts without TContext
  • OnError on TaskService/ActivityService
  • In State Guards
  • DoneData
  • Nesting Statecharts

Roadmap

  • Model the Statechart Types
  • Build basic xstate Serialization
  • Create an Interpreter
  • Set up scion-scxml/test-framework
  • Set up easier Testing using xUnit (using Theories)
  • Clean up the File/Project Hierarchy
  • Add xstate-like "Invoked Services"
  • Create the DSL
  • Build Unity-Integration
  • Fix the xstate Serialization
  • Finish SCXML EcmaScript Parser
  • Fix the Algorithm
  • ...
  • Introduce the "Hole"-Concept into StatechartDefinition
  • Build a wasm-based Visualization Tool
  • ...
  • ...
  • Separate SCXML parsing from datamodel

TODOs

  • <service>.Send(...) should not be callable when <service>.Start(...) wasn't called previously

  • Stricter Types for TargetDefinition and StateNode (child + sibling only on StateNodes with children or siblings)

  • think about access modifiers

  • document public things

  • Tooling

    • Add missing properties to xstate Serialization, and fix it (e.g. same event twice)
  • Blog About

    • Testing using SCION.Tests & JInt
    • XElement to T Mapper
    • CataFold
    • params min 1 parameter
    • params OneOf
    • OneOf Treeshaking
    • Builder without .Build()
    • Builder with Order of With Functions (+ git advantages)
    • Builder with skippable With Functions

statecharts.net's People

Contributors

bemayr avatar danielxbauer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

statecharts.net's Issues

Send event cannot be specified on a context data guarded event

I cannot specify a Send(..)-Event as parameter to the WithActions if I use an if-guard.
I don't know if its intended that way.

public static NamedDataEventFactory<Reaction> Reaction = 
	Define.EventWithData<Reaction>(nameof(Reaction));

public static StatechartDefinition<MyContext> Behaviour = Define.Statechart
  .WithInitialContext(new MyContext())
  .WithRootState(
    "game"
      .WithTransitions(
         On("cancel").TransitionTo.Child("canceled"))
      .AsCompound()
        .WithInitialState("idle")
		.WithStates(
		  "idle".WithTransitions(
		     On("next").TransitionTo.Sibling("do")),
		  "do".WithTransitions(
                     // **This is working:**
                    ////On(Reaction)
                    ////    .TransitionTo.Self
                    ////    .WithActions(Send("cancel"))),
             
                    // **With an if-guard its not compiling any more:**
                    On(Reaction).If<MyContext>((c, r) => r.Emote == ":grin:")
             	       .TransitionTo.Self
             	       .WithActions(Send("cancel"))),
		 "canceled"
			.WithEntryActions(Log("Entered cancel state"))
			.AsFinal()));		
}

Full working sample:
https://gist.github.com/pangeax/f31573d405a1c84368853900bb308ab6#file-statechartsendbug-cs

Usage in Unity

Hello. I would like some help on how to integrate this lib with Unity 3D (as this repository is marked with the unity3d tag)

My use case: I have a bunch of statechart scxml files that I need to read into a state machine inside Unity and fire events, it's very simple.

I cloned the repository and it is a big Visual Studio solution with no release. I tried building it on the latest Visual Studio Community 2019 but there were errors, so I removed the UWP and the Analyzers.Test projects (and also comment a namespace not found in StatechartAnalyzer). After that, the build succeeded and generated a lot on output on the bin/Release folders and I have no ideia what is necessary. I presume the Statecharts.NET.Core is the one I want as the others seem to be testing stuff but I couldn't find any documentation or guide whatsoever. Exploring the Statecharts.NET.Core namespace also didn't ring any bells.

Can you provide information on what dlls are needed to import on Unity and how to read the scxml into a state machine?

EDIT: is this repository dead?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.