-
Notifications
You must be signed in to change notification settings - Fork 2
Added simple diagram and graphing infrastructure. #15
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
Draft
zoalasaurusrawr
wants to merge
3
commits into
main
Choose a base branch
from
feature/diagramwriter
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| using System.Text; | ||
|
|
||
| namespace PSIBR.Liminality | ||
| { | ||
| public abstract class DiagramWriter<TStateMachine> | ||
| where TStateMachine : StateMachine<TStateMachine> | ||
| { | ||
| public DiagramWriter(Graph<TStateMachine> graph!!) | ||
| { | ||
| Graph = graph; | ||
| } | ||
|
|
||
| protected readonly Graph<TStateMachine> Graph; | ||
| public abstract Diagram Write(); | ||
|
|
||
| public abstract void WriteNode(Diagram diagram, GraphNode rootNode); | ||
| } | ||
|
|
||
| public abstract class Diagram | ||
| { | ||
| private StringBuilder _stringBuilder = new StringBuilder(); | ||
| public abstract void AddTransition(GraphNode? left, GraphNode? right); | ||
| public virtual void AddSyntaxLine(string syntax) | ||
| { | ||
| _stringBuilder.AppendLine(syntax); | ||
| } | ||
|
|
||
| public virtual string Render() | ||
| { | ||
| return _stringBuilder.ToString(); | ||
| } | ||
| } | ||
|
|
||
| public class MermaidStateDiagramWriter<TStateMachine> : DiagramWriter<TStateMachine> | ||
| where TStateMachine : StateMachine<TStateMachine> | ||
| { | ||
| public MermaidStateDiagramWriter(Graph<TStateMachine> graph) | ||
| : base(graph) | ||
| { | ||
| } | ||
|
|
||
| public override Diagram Write() | ||
| { | ||
| var diagram = new MermaidStateDiagram(); | ||
| diagram.AddTransition(null, Graph); | ||
| WriteNode(diagram, Graph); | ||
| return diagram; | ||
| } | ||
|
|
||
| public override void WriteNode(Diagram diagram!!, GraphNode rootNode!!) | ||
| { | ||
| foreach (var node in rootNode) | ||
| { | ||
| diagram.AddTransition(rootNode, node); | ||
| WriteNode(diagram, node); | ||
| } | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| namespace PSIBR.Liminality | ||
| { | ||
| public static class Extensions | ||
| { | ||
| public static Graph<TStateMachine> GetGraph<TStateMachine>(this TStateMachine stateMachine!!) | ||
| where TStateMachine : StateMachine<TStateMachine> | ||
| { | ||
| var builder = new GraphBuilder<TStateMachine>(stateMachine); | ||
| return builder.Build(); | ||
| } | ||
|
|
||
| public static Diagram GetDiagram<TStateMachine>(this Graph<TStateMachine> graph!!) | ||
| where TStateMachine : StateMachine<TStateMachine> | ||
| { | ||
| var writer = new MermaidStateDiagramWriter<TStateMachine>(graph); | ||
| return writer.Write(); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics; | ||
|
|
||
| namespace PSIBR.Liminality | ||
| { | ||
| public class GraphBuilder<TStateMachine> | ||
| where TStateMachine : StateMachine<TStateMachine> | ||
| { | ||
| public GraphBuilder(TStateMachine stateMachine!!) | ||
| { | ||
| _stateMachine = stateMachine; | ||
| _graph = new Graph<TStateMachine>(_stateMachine); | ||
| } | ||
|
|
||
| private readonly TStateMachine _stateMachine; | ||
| private readonly Graph<TStateMachine> _graph; | ||
| private readonly Dictionary<string, GraphNode> _nodeTypeCache = new(); | ||
|
|
||
| public Graph<TStateMachine> Build() | ||
| { | ||
| var rootNode = new GraphNode(_stateMachine.Definition.StateMap.InitialState); | ||
| foreach (var stateMap in _stateMachine.Definition.StateMap) | ||
| { | ||
| var input = stateMap.Key; | ||
| var transition = stateMap.Value; | ||
|
|
||
| //Enables us to continue node transit mapping | ||
| //in many to many situations | ||
| var subNode = GetOrCreateSubNode(rootNode, input.CurrentStateType, input.SignalType); | ||
| var goesToNode = CreateNode(transition.NewStateType); | ||
| subNode.Add(goesToNode); | ||
| } | ||
|
|
||
| _graph.Add(rootNode); | ||
|
|
||
| return _graph; | ||
| } | ||
|
|
||
| private GraphNode GetOrCreateSubNode(GraphNode rootNode, Type type, Type? signalType) | ||
| { | ||
| //If we know about this, return so we can continue | ||
| //mapping the transit | ||
| if (_nodeTypeCache.ContainsKey(type.Name)) | ||
| return _nodeTypeCache[type.Name]; | ||
| //If we don't know about this but the root | ||
| //and the type are the same, this is a mapping | ||
| //of initial state -> something else | ||
| else if (rootNode.Name == type.Name && rootNode.Condition == signalType?.Name) | ||
| return rootNode; | ||
|
|
||
| var node = CreateNode(type, signalType); | ||
| rootNode.Add(node); | ||
| return node; | ||
| } | ||
|
|
||
| private GraphNode CreateNode(Type type) | ||
| { | ||
| var node = new GraphNode(type, null); | ||
| CacheNodeType(node); | ||
| return node; | ||
| } | ||
|
|
||
| private GraphNode CreateNode(Type type!!, Type? signalType) | ||
| { | ||
| var node = new GraphNode(type, signalType); | ||
| CacheNodeType(node); | ||
| return node; | ||
| } | ||
|
|
||
| private void CacheNodeType(GraphNode graphNode!!) | ||
| { | ||
| var key = MakeCacheKey(graphNode); | ||
| if (!_nodeTypeCache.ContainsKey(key)) | ||
| _nodeTypeCache[key] = graphNode; | ||
| } | ||
|
|
||
| private static string MakeCacheKey(GraphNode graphNode!!) | ||
| { | ||
| var key = $"{graphNode.Name} {(graphNode.Condition is not null ? $":{graphNode.Condition}" : string.Empty)}"; | ||
| return key; | ||
| } | ||
|
|
||
| private static string MakeCacheKey(Type type, Type? signalType) | ||
| { | ||
| var key = $"{type.Name} {(signalType is not null ? $":{signalType.Name}" : string.Empty)}"; | ||
| return key; | ||
| } | ||
| } | ||
|
|
||
| [DebuggerDisplay("Name = {Name}, Goes to: {Count} other node(s)")] | ||
| public class Graph<TStateMachine> : GraphNode | ||
| where TStateMachine : StateMachine<TStateMachine> | ||
| { | ||
| private readonly TStateMachine _stateMachine; | ||
|
|
||
| public Graph(TStateMachine stateMachine!!) | ||
| : base(stateMachine.GetType().Name) | ||
| { | ||
| _stateMachine = stateMachine; | ||
| } | ||
| } | ||
|
|
||
| [DebuggerDisplay("Name = {Name}, Goes to: {Count} other node(s) when signaled with {Condition}")] | ||
| public class GraphNode : List<GraphNode> | ||
| { | ||
| public GraphNode(Type type!!) | ||
| : this(type.Name, null) | ||
| { | ||
| } | ||
|
|
||
| public GraphNode(Type type!!, Type? signalType) | ||
| : this(type.Name, signalType?.Name) | ||
| { | ||
| } | ||
|
|
||
| public GraphNode(string name!!) | ||
| : this(name, null) | ||
| { | ||
| } | ||
|
|
||
| public GraphNode(string name!!, string? condition) | ||
| { | ||
| Name = name; | ||
| Condition = condition; | ||
| } | ||
|
|
||
| public string Name { get; set; } | ||
| public string? Condition { get; set; } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| namespace PSIBR.Liminality | ||
| { | ||
| public class MermaidStateDiagram : Diagram | ||
| { | ||
| private const string DiagramTypeToken = "stateDiagram-v2"; | ||
| private const string Indent = " "; | ||
| private const string GoesToToken = "-->"; | ||
| private const string InitialStateToken = "[*]"; | ||
|
|
||
| public MermaidStateDiagram() | ||
| { | ||
| AddSyntaxLine($"{DiagramTypeToken}"); | ||
| } | ||
|
|
||
| public override void AddTransition(GraphNode? left, GraphNode? right) | ||
| { | ||
| string leftSyntax = left is null ? InitialStateToken : left.Name; | ||
| string rightSyntax = right is null ? InitialStateToken : right.Name; | ||
| string signalSyntax = left?.Condition is not null ? $":{left.Condition}" : string.Empty; | ||
|
|
||
| if (leftSyntax != rightSyntax && !string.IsNullOrWhiteSpace(signalSyntax)) | ||
| { | ||
| var syntax = $"{Indent}{leftSyntax} {GoesToToken} {rightSyntax}{signalSyntax}"; | ||
| AddSyntaxLine(syntax); | ||
| } | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,5 +44,6 @@ public class Finished { } | |
| // Inputs | ||
| public class Start { } | ||
| public class Finish { } | ||
| public class Cancel { } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
test/Liminality.Tests/Fixtures/BasicStateMachineFixture.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| using Lamar; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using System; | ||
| using static PSIBR.Liminality.Tests.BasicStateMachine; | ||
|
|
||
| namespace PSIBR.Liminality.Tests.Fixtures | ||
| { | ||
| public class BasicStateMachineFixture : IDisposable | ||
| { | ||
| public BasicStateMachineFixture() | ||
| { | ||
| var container = new Container(x => | ||
| { | ||
| x.AddStateMachineDependencies<BasicStateMachine>(builder => builder | ||
| .StartsIn<Idle>() | ||
| .For<Idle>().On<Start>().MoveTo<InProgress>() | ||
| .For<InProgress>().On<Finish>().MoveTo<Finished>() | ||
| .For<InProgress>().On<Cancel>().MoveTo<Idle>() | ||
| .Build()); | ||
|
|
||
| x.AddTransient<BasicStateMachine>(); | ||
| }); | ||
|
|
||
| BasicStateMachine = container.GetService<BasicStateMachine>() ?? throw new Exception("Container not properly setup"); | ||
| } | ||
|
|
||
| public BasicStateMachine BasicStateMachine { get; } | ||
|
|
||
| public void Dispose() | ||
| { | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| using PSIBR.Liminality.Tests.Fixtures; | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
| using Xunit; | ||
|
|
||
| namespace PSIBR.Liminality | ||
| { | ||
| public class VisualizationTests : IClassFixture<BasicStateMachineFixture> | ||
| { | ||
| public VisualizationTests(BasicStateMachineFixture fixture) | ||
| { | ||
| Fixture = fixture; | ||
| } | ||
|
|
||
| protected readonly BasicStateMachineFixture Fixture; | ||
|
|
||
| [Fact] | ||
| public void Creating_Graph_Succeeds() | ||
| { | ||
| Fixture.BasicStateMachine.GetGraph(); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Creating_Diagram_Succeeds() | ||
| { | ||
| var graph = Fixture.BasicStateMachine.GetGraph(); | ||
| Assert.NotNull(graph); | ||
| var diagram = graph.GetDiagram(); | ||
| Assert.NotNull(diagram); | ||
| var render = diagram.Render(); | ||
| Assert.NotNull(render); | ||
| } | ||
|
|
||
| } | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Things I found: