Skip to content

Commit e72ffac

Browse files
author
Joanna May
authored
Merge pull request #7 from chickensoft-games/feat/better-bindings
fix: improve bindings and improve api's
2 parents af05aff + c5d7d4c commit e72ffac

17 files changed

+519
-385
lines changed

Chickensoft.LogicBlocks.Generator/Chickensoft.LogicBlocks.Generator.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<NoWarn>NU5128</NoWarn>
1212

1313
<Title>LogicBlocks Generator</Title>
14-
<Version>2.0.1</Version>
14+
<Version>2.1.0</Version>
1515
<Description></Description>
1616
<Copyright>© 2023 Chickensoft Games</Copyright>
1717
<Authors>Chickensoft</Authors>

Chickensoft.LogicBlocks.Generator/src/LogicBlocksGenerator.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) {
4848
predicate: static (SyntaxNode node, CancellationToken _) =>
4949
IsLogicBlockCandidate(node),
5050
transform: (GeneratorSyntaxContext context, CancellationToken token) =>
51-
DiscoverStateGraph(
51+
GetStateGraph(
5252
(ClassDeclarationSyntax)context.Node, context.SemanticModel, token
5353
)
5454
).Where(logicBlockImplementation => logicBlockImplementation is not null)
@@ -120,6 +120,20 @@ node is ClassDeclarationSyntax classDeclaration &&
120120
Constants.STATE_MACHINE_ATTRIBUTE_NAME
121121
);
122122

123+
public LogicBlockImplementation? GetStateGraph(
124+
ClassDeclarationSyntax logicBlockClassDecl,
125+
SemanticModel model,
126+
CancellationToken token
127+
) {
128+
try {
129+
return DiscoverStateGraph(logicBlockClassDecl, model, token);
130+
}
131+
catch (Exception e) {
132+
Log.Print($"Exception occurred: {e}");
133+
return null;
134+
}
135+
}
136+
123137
/// <summary>
124138
/// Looks at a logic block subclass, finds the logic block type in its
125139
/// inheritance hierarchy, and builds a state graph from it based on the

Chickensoft.LogicBlocks.Tests/test/fixtures/FakeLogicBlock.cs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
namespace Chickensoft.LogicBlocks.Tests.Fixtures;
22

3-
using System.Collections.Generic;
43
using Chickensoft.LogicBlocks.Generator;
54

65
[StateMachine]
@@ -9,6 +8,8 @@ public abstract record Input {
98
public record InputOne(int Value1, int Value2) : Input;
109
public record InputTwo(string Value1, string Value2)
1110
: Input;
11+
public record InputThree(string Value1, string Value2)
12+
: Input;
1213
public record InputError() : Input;
1314
public record InputUnknown() : Input;
1415
public record GetString() : Input;
@@ -24,6 +25,7 @@ public record Custom(Func<Context, State> Next) : Input;
2425
public abstract record State(Context Context) : StateLogic(Context),
2526
IGet<Input.InputOne>,
2627
IGet<Input.InputTwo>,
28+
IGet<Input.InputThree>,
2729
IGet<Input.InputError>,
2830
IGet<Input.NoNewState>,
2931
IGet<Input.InputCallback>,
@@ -40,6 +42,10 @@ public State On(Input.InputTwo input) {
4042
return new StateB(Context, input.Value1, input.Value2);
4143
}
4244

45+
public State On(Input.InputThree input) => new StateD(
46+
Context, input.Value1, input.Value2
47+
);
48+
4349
public State On(Input.InputError input)
4450
=> throw new InvalidOperationException();
4551

@@ -67,6 +73,8 @@ public record StateB(Context Context, string Value1, string Value2) :
6773
State(Context);
6874
public record StateC(Context Context, string Value) :
6975
State(Context);
76+
public record StateD(Context Context, string Value1, string Value2) :
77+
State(Context);
7078

7179
public record NothingState(Context Context) : State(Context);
7280

@@ -101,18 +109,20 @@ public partial class FakeLogicBlock
101109

102110
public void PublicSet<T>(T value) where T : notnull => Set(value);
103111

104-
public void PublicOnTransition<TStateTypeA, TStateTypeB>(
105-
Transition<TStateTypeA, TStateTypeB> transitionCallback
106-
) where TStateTypeA : State where TStateTypeB : State =>
107-
OnTransition(transitionCallback);
108-
109-
protected override void OnError(Exception e) {
110-
Exceptions.Add(e);
111-
base.OnError(e);
112-
}
113-
114112
public override State GetInitialState(Context context) =>
115113
InitialState?.Invoke(context) ?? new State.StateA(context, 1, 2);
116114

115+
private readonly Action<Exception>? _onError;
116+
117+
public FakeLogicBlock(Action<Exception>? onError = null) {
118+
_onError = onError;
119+
}
120+
117121
~FakeLogicBlock() { }
122+
123+
protected override void HandleError(Exception e) {
124+
Exceptions.Add(e);
125+
_onError?.Invoke(e);
126+
base.HandleError(e);
127+
}
118128
}

Chickensoft.LogicBlocks.Tests/test/fixtures/FakeLogicBlock.g.puml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ state "FakeLogicBlock State" as Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogic
33
state "StateA" as Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogicBlock_State_StateA
44
state "StateB" as Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogicBlock_State_StateB
55
state "StateC" as Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogicBlock_State_StateC
6+
state "StateD" as Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogicBlock_State_StateD
67
state "NothingState" as Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogicBlock_State_NothingState
78
state "Custom" as Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogicBlock_State_Custom
89
state "OnEnterState" as Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogicBlock_State_OnEnterState
@@ -18,6 +19,7 @@ Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogicBlock_State --> Chickensoft_Logi
1819
Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogicBlock_State --> Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogicBlock_State_StateA : InputOne
1920
Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogicBlock_State --> Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogicBlock_State_StateB : InputTwo
2021
Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogicBlock_State --> Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogicBlock_State_StateC : GetString
22+
Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogicBlock_State --> Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogicBlock_State_StateD : InputThree
2123

2224
[*] --> Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogicBlock_State
2325
[*] --> Chickensoft_LogicBlocks_Tests_Fixtures_FakeLogicBlock_State_StateA

Chickensoft.LogicBlocks.Tests/test/fixtures/FakeLogicBlockAsync.cs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
namespace Chickensoft.LogicBlocks.Tests.Fixtures;
2-
3-
using System.Collections.Generic;
4-
52
#pragma warning disable CS1998
63

74
public partial class FakeLogicBlockAsync {
85
public abstract record Input {
96
public record InputOne(int Value1, int Value2) : Input;
107
public record InputTwo(string Value1, string Value2)
118
: Input;
9+
public record InputThree(string Value1, string Value2)
10+
: Input;
1211
public record InputError() : Input;
1312
public record InputUnknown() : Input;
1413
public record GetString() : Input;
@@ -24,6 +23,7 @@ public record Custom(Func<Context, State> Next) : Input;
2423
public abstract record State(Context Context) : StateLogic(Context),
2524
IGet<Input.InputOne>,
2625
IGet<Input.InputTwo>,
26+
IGet<Input.InputThree>,
2727
IGet<Input.InputError>,
2828
IGet<Input.NoNewState>,
2929
IGet<Input.InputCallback>,
@@ -40,6 +40,10 @@ public async Task<State> On(Input.InputTwo input) {
4040
return new StateB(Context, input.Value1, input.Value2);
4141
}
4242

43+
public async Task<State> On(Input.InputThree input) => new StateD(
44+
Context, input.Value1, input.Value2
45+
);
46+
4347
public async Task<State> On(Input.InputError input)
4448
=> throw new InvalidOperationException();
4549

@@ -72,6 +76,8 @@ public record StateB(Context Context, string Value1, string Value2) :
7276
State(Context);
7377
public record StateC(Context Context, string Value) :
7478
State(Context);
79+
public record StateD(Context Context, string Value1, string Value2) :
80+
State(Context);
7581
public record Custom : State {
7682
public Custom(Context context, Action<Context> setupCallback) :
7783
base(context) {
@@ -101,22 +107,22 @@ public partial class FakeLogicBlockAsync
101107

102108
public List<Exception> Exceptions { get; } = new();
103109

104-
public void PublicSet<T>(T value) where T : notnull => Set(value);
110+
public override State GetInitialState(Context context) =>
111+
InitialState?.Invoke(context) ?? new State.StateA(context, 1, 2);
105112

106-
public void PublicOnTransition<TStateTypeA, TStateTypeB>(
107-
Transition<TStateTypeA, TStateTypeB> transitionCallback
108-
) where TStateTypeA : State where TStateTypeB : State =>
109-
OnTransition(transitionCallback);
113+
private readonly Action<Exception>? _onError;
110114

111-
protected override void OnError(Exception e) {
112-
Exceptions.Add(e);
113-
base.OnError(e);
115+
public FakeLogicBlockAsync(Action<Exception>? onError = null) {
116+
_onError = onError;
114117
}
115118

116-
public override State GetInitialState(Context context) =>
117-
InitialState?.Invoke(context) ?? new State.StateA(context, 1, 2);
118-
119119
~FakeLogicBlockAsync() { }
120+
121+
protected override void HandleError(Exception e) {
122+
Exceptions.Add(e);
123+
_onError?.Invoke(e);
124+
base.HandleError(e);
125+
}
120126
}
121127

122128
#pragma warning restore CS1998

Chickensoft.LogicBlocks.Tests/test/fixtures/TestMachineReusable.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,9 @@ public record BoppedCleanUp() : Output;
8484
public TestMachineReusable() {
8585
Set(new State.Activated.Blooped(Context));
8686
Set(new State.Activated.Bopped(Context));
87+
Set(new State.Deactivated(Context));
8788
}
8889

89-
public override State GetInitialState(Context context) {
90-
var deactivated = new State.Deactivated(Context);
91-
Set(deactivated);
92-
return deactivated;
93-
}
90+
public override State GetInitialState(Context context) =>
91+
context.Get<State.Deactivated>();
9492
}

Chickensoft.LogicBlocks.Tests/test/fixtures/TestMachineReusableAsync.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,11 @@ public record BoppedCleanUp() : Output;
113113
public TestMachineReusableAsync() {
114114
Set(new State.Activated.Blooped(Context));
115115
Set(new State.Activated.Bopped(Context));
116+
Set(new State.Deactivated(Context));
116117
}
117118

118-
public override State GetInitialState(Context context) {
119-
var deactivated = new State.Deactivated(Context);
120-
Set(deactivated);
121-
return deactivated;
122-
}
119+
public override State GetInitialState(Context context) =>
120+
context.Get<State.Deactivated>();
123121
}
124122

125123
#pragma warning restore CS1998

Chickensoft.LogicBlocks.Tests/test/src/LogicBlock.BindingTest.cs

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,43 @@ namespace Chickensoft.LogicBlocks.Tests;
88
public class BlocGlueTests {
99
public static bool WasFinalized { get; set; }
1010

11+
[Fact]
12+
public void DoesNotUpdateIfStateIsSameState() { }
13+
14+
[Fact]
15+
public void DoesNotUpdateIfSelectedDataIsSameObject() {
16+
var block = new FakeLogicBlock();
17+
using var binding = block.Bind();
18+
19+
var count = 0;
20+
var value1Count = 0;
21+
binding.When<FakeLogicBlock.State.StateB>()
22+
.Call(state => count++)
23+
.Use(
24+
data: (state) => state.Value1,
25+
to: (value1) => value1Count++
26+
);
27+
28+
var a = "a";
29+
var b = "b";
30+
block.Input(new FakeLogicBlock.Input.InputTwo(a, b));
31+
block.Input(new FakeLogicBlock.Input.InputTwo(a, "c"));
32+
33+
count.ShouldBe(1);
34+
}
35+
1136
[Fact]
1237
public void UpdatesCorrectly() {
1338
var block = new FakeLogicBlock();
14-
using var glue = block.Bind();
39+
using var binding = block.Bind();
1540

1641
var callA1 = 0;
1742
var callA2 = 0;
1843

1944
var a1 = 3;
2045
var a2 = 4;
2146

22-
glue.When<FakeLogicBlock.State.StateA>()
47+
binding.When<FakeLogicBlock.State.StateA>()
2348
.Use(
2449
data: (state) => state.Value1,
2550
to: (value1) => { callA1++; value1.ShouldBe(a1); })
@@ -63,12 +88,12 @@ public void UpdatesCorrectly() {
6388
[Fact]
6489
public void HandlesEffects() {
6590
var block = new FakeLogicBlock();
66-
using var glue = block.Bind();
91+
using var binding = block.Bind();
6792

6893
var callEffect1 = 0;
6994
var callEffect2 = 0;
7095

71-
glue.Handle<FakeLogicBlock.Output.OutputOne>(
96+
binding.Handle<FakeLogicBlock.Output.OutputOne>(
7297
(effect) => { callEffect1++; effect.Value.ShouldBe(1); }
7398
).Handle<FakeLogicBlock.Output.OutputTwo>(
7499
(effect) => { callEffect2++; effect.Value.ShouldBe("2"); }
@@ -92,15 +117,15 @@ public void CallsSubstateTransitionsOnlyOnce() {
92117
var block = new FakeLogicBlock();
93118
var context = new FakeLogicBlock.Context(block);
94119

95-
using var glue = block.Bind();
120+
using var binding = block.Bind();
96121

97122
var callStateA = 0;
98123
var callStateB = 0;
99124

100-
glue.When<FakeLogicBlock.State.StateA>()
125+
binding.When<FakeLogicBlock.State.StateA>()
101126
.Call((state) => callStateA++);
102127

103-
glue.When<FakeLogicBlock.State.StateB>()
128+
binding.When<FakeLogicBlock.State.StateB>()
104129
.Call((state) => callStateB++);
105130

106131
callStateA.ShouldBe(0);
@@ -145,15 +170,15 @@ public void CleansUpSubscriptions() {
145170
var callSideEffectHandler = 0;
146171

147172
var block = new FakeLogicBlock();
148-
var glue = block.Bind();
173+
var binding = block.Bind();
149174

150-
glue.When<FakeLogicBlock.State.StateA>()
175+
binding.When<FakeLogicBlock.State.StateA>()
151176
.Use(
152177
data: (state) => state.Value1,
153178
to: (value1) => callStateUpdate++
154179
);
155180

156-
glue.Handle<FakeLogicBlock.Output.OutputOne>(
181+
binding.Handle<FakeLogicBlock.Output.OutputOne>(
157182
(effect) => callSideEffectHandler++
158183
);
159184

@@ -162,7 +187,7 @@ public void CleansUpSubscriptions() {
162187
callStateUpdate.ShouldBe(1);
163188
callSideEffectHandler.ShouldBe(1);
164189

165-
glue.Dispose();
190+
binding.Dispose();
166191

167192
block.Input(new FakeLogicBlock.Input.InputOne(5, 6));
168193

@@ -174,10 +199,10 @@ public void CleansUpSubscriptions() {
174199
public void Finalizes() {
175200
// Weak reference has to be created and cleared from a static function
176201
// or else the GC won't ever collect it :P
177-
var weakRef = CreateWeakFakeLogicBlockGlueReference();
202+
var weakRef = CreateWeakFakeLogicBlockBindingReference();
178203
Utils.ClearWeakReference(weakRef);
179204
}
180205

181-
public static WeakReference CreateWeakFakeLogicBlockGlueReference() =>
206+
public static WeakReference CreateWeakFakeLogicBlockBindingReference() =>
182207
new(new FakeLogicBlock().Bind());
183208
}

0 commit comments

Comments
 (0)