Skip to content

Commit 2c0fa51

Browse files
authored
Merge pull request #111 from bazyleu/feature/reflex-support
Reflex support
2 parents f97b0d1 + ba486d2 commit 2c0fa51

21 files changed

+504
-75
lines changed
Lines changed: 135 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,184 @@
1-
#if UNISTATE_REFLEX_SUPPORT
2-
1+
#if UNISTATE_REFLEX_SUPPORT
2+
33
using System;
4+
using System.Collections.Generic;
45
using Reflex.Core;
5-
6-
namespace UniState
7-
{
6+
using Reflex.Enums;
7+
using Reflex.Resolvers;
8+
9+
namespace UniState
10+
{
811
public static class ReflexBuildExtensions
912
{
1013
public static void AddStateMachine(
1114
this ContainerBuilder builder,
1215
Type stateMachineImplementation,
1316
Type stateMachineContract)
1417
{
15-
ValidateStateMachineBindingInput(stateMachineImplementation, stateMachineContract);
16-
17-
builder.AddTransient(stateMachineImplementation);
18-
builder.AddTransient(container =>
19-
{
20-
var stateMachine = (IStateMachine)container.Resolve(stateMachineImplementation);
21-
stateMachine.SetResolver(container.ToTypeResolver());
22-
23-
return stateMachine;
24-
}, stateMachineContract);
18+
AddStateMachineInternal(builder, stateMachineImplementation, stateMachineContract, Lifetime.Transient);
2519
}
26-
20+
2721
public static void AddSingletonStateMachine(
2822
this ContainerBuilder builder,
2923
Type stateMachineImplementation,
3024
Type stateMachineContract)
3125
{
32-
ValidateStateMachineBindingInput(stateMachineImplementation, stateMachineContract);
33-
34-
builder.AddSingleton(stateMachineImplementation);
35-
builder.AddSingleton(container =>
36-
{
37-
var stateMachine = (IStateMachine)container.Resolve(stateMachineImplementation);
38-
stateMachine.SetResolver(container.ToTypeResolver());
39-
40-
return stateMachine;
41-
}, stateMachineContract);
26+
AddStateMachineInternal(builder, stateMachineImplementation, stateMachineContract, Lifetime.Singleton);
4227
}
43-
28+
4429
public static void AddState(this ContainerBuilder builder, Type state)
4530
{
4631
ValidateStateBindingInput(state);
4732

48-
builder.AddTransient(state);
33+
builder.AddTransient(state, GetStateContracts(state));
4934
}
50-
51-
public static void AddState(this ContainerBuilder builder, Type stateImplementation, Type stateContract)
52-
{
53-
ValidateStateBindingInput(stateImplementation, stateContract);
54-
55-
builder.AddTransient(stateImplementation, stateContract);
56-
}
57-
35+
36+
public static void AddState(this ContainerBuilder builder, Type stateImplementation, Type stateContract)
37+
{
38+
ValidateStateBindingInput(stateImplementation, stateContract);
39+
40+
builder.AddTransient(stateImplementation, stateContract);
41+
}
42+
5843
public static void AddSingletonState(this ContainerBuilder builder, Type state)
5944
{
6045
ValidateStateBindingInput(state);
6146

62-
builder.AddSingleton(state);
47+
builder.AddSingleton(state, GetStateContracts(state));
6348
}
64-
65-
public static void AddSingletonState(this ContainerBuilder builder, Type stateImplementation,
66-
Type stateContract)
67-
{
68-
ValidateStateBindingInput(stateImplementation, stateContract);
69-
70-
builder.AddSingleton(stateImplementation, stateContract);
71-
}
72-
73-
private static void ValidateStateBindingInput(Type stateImplementation, Type stateContract)
49+
50+
public static void AddSingletonState(this ContainerBuilder builder, Type stateImplementation,
51+
Type stateContract)
52+
{
53+
ValidateStateBindingInput(stateImplementation, stateContract);
54+
55+
builder.AddSingleton(stateImplementation, stateContract);
56+
}
57+
58+
private static void ValidateStateBindingInput(Type stateImplementation, Type stateContract)
59+
{
60+
ValidateStateBindingInput(stateImplementation);
61+
62+
if (!stateContract.IsAssignableFrom(stateImplementation))
63+
{
64+
throw new ArgumentException(
65+
$"AddState({stateImplementation.Name}): Type parameter state must implement {stateContract.Name}.");
66+
}
67+
}
68+
69+
private static void ValidateStateBindingInput(Type state)
7470
{
75-
ValidateStateBindingInput(stateImplementation);
76-
77-
if (!stateContract.IsAssignableFrom(stateImplementation))
71+
if (!typeof(IExecutableState).IsAssignableFrom(state))
7872
{
7973
throw new ArgumentException(
80-
$"AddState({stateImplementation.Name}): Type parameter state must implement {stateContract.Name}.");
74+
$"AddState({state.Name}): Type parameter state must implement IState<TPayload>");
8175
}
8276
}
8377

84-
private static void ValidateStateBindingInput(Type state)
78+
private static Type[] GetStateContracts(Type state)
8579
{
86-
if (!typeof(IExecutableState).IsAssignableFrom(state))
80+
var interfaces = state.GetInterfaces();
81+
var contracts = new Type[interfaces.Length + 1];
82+
contracts[0] = state;
83+
84+
for (var i = 0; i < interfaces.Length; i++)
8785
{
88-
throw new ArgumentException(
89-
$"AddState({state.Name}): Type parameter state must implement IState<TPayload>");
86+
contracts[i + 1] = interfaces[i];
9087
}
88+
89+
return contracts;
9190
}
91+
92+
private static void ValidateStateMachineBindingInput(Type stateMachineImplementation, Type stateMachineContract)
93+
{
94+
if (stateMachineImplementation == stateMachineContract)
95+
{
96+
throw new ArgumentException(
97+
$"AddStateMachine<{stateMachineImplementation.Name}>: Type parameters must differ : " +
98+
"use AddStateMachine() where stateMachineImplementation implements stateMachineContract.\");");
99+
}
100+
101+
if (!stateMachineContract.IsAssignableFrom(stateMachineImplementation))
102+
{
103+
throw new ArgumentException(
104+
$"AddStateMachine: Type {stateMachineImplementation.Name} " +
105+
$"must implement {stateMachineContract.Name}.");
106+
}
107+
108+
if (!typeof(IStateMachine).IsAssignableFrom(stateMachineContract))
109+
{
110+
throw new ArgumentException(
111+
$"AddStateMachine: Type {stateMachineContract.Name} " +
112+
$"must implement IStateMachine.");
113+
}
114+
}
115+
116+
private static void AddStateMachineInternal(
117+
ContainerBuilder builder,
118+
Type stateMachineImplementation,
119+
Type stateMachineContract,
120+
Lifetime lifetime)
121+
{
122+
ValidateStateMachineBindingInput(stateMachineImplementation, stateMachineContract);
92123

93-
private static void ValidateStateMachineBindingInput(Type stateMachineImplementation, Type stateMachineContract)
124+
builder.Bindings.Add(Binding.Validated(
125+
new ReflexStateMachineResolver(stateMachineImplementation, lifetime),
126+
stateMachineImplementation,
127+
stateMachineImplementation,
128+
stateMachineContract));
129+
}
130+
131+
private sealed class ReflexStateMachineResolver : IResolver
94132
{
95-
if (stateMachineImplementation == stateMachineContract)
133+
private readonly Type _stateMachineImplementation;
134+
private readonly Lifetime _lifetime;
135+
private readonly List<IDisposable> _disposables = new();
136+
137+
private object _instance;
138+
139+
public Lifetime Lifetime => _lifetime;
140+
141+
public ReflexStateMachineResolver(Type stateMachineImplementation, Lifetime lifetime)
96142
{
97-
throw new ArgumentException(
98-
$"AddStateMachine<{stateMachineImplementation.Name}>: Type parameters must differ : " +
99-
"use AddStateMachine() where stateMachineImplementation implements stateMachineContract.\");");
143+
_stateMachineImplementation = stateMachineImplementation;
144+
_lifetime = lifetime;
100145
}
101146

102-
if (!stateMachineContract.IsAssignableFrom(stateMachineImplementation))
147+
public object Resolve(Container container)
103148
{
104-
throw new ArgumentException(
105-
$"AddStateMachine: Type {stateMachineImplementation.Name} " +
106-
$"must implement {stateMachineContract.Name}.");
149+
if (_lifetime == Lifetime.Singleton && _instance != null)
150+
{
151+
return _instance;
152+
}
153+
154+
var stateMachine = (IStateMachine)container.Construct(_stateMachineImplementation);
155+
stateMachine.SetResolver(container.ToTypeResolver());
156+
157+
if (_lifetime == Lifetime.Singleton)
158+
{
159+
_instance = stateMachine;
160+
}
161+
162+
if (stateMachine is IDisposable disposable)
163+
{
164+
_disposables.Add(disposable);
165+
}
166+
167+
return stateMachine;
107168
}
108169

109-
if (!typeof(IStateMachine).IsAssignableFrom(stateMachineContract))
170+
public void Dispose()
110171
{
111-
throw new ArgumentException(
112-
$"AddStateMachine: Type {stateMachineContract.Name} " +
113-
$"must implement IStateMachine.");
172+
for (var i = _disposables.Count - 1; i >= 0; i--)
173+
{
174+
_disposables[i].Dispose();
175+
}
176+
177+
_disposables.Clear();
178+
_instance = null;
114179
}
115180
}
116181
}
117182
}
118-
119-
#endif
183+
184+
#endif
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using Cysharp.Threading.Tasks;
2+
using Reflex.Core;
3+
using UniState;
4+
5+
namespace UniStateTests.Common
6+
{
7+
public abstract class ReflexTestsBase : TestsBase
8+
{
9+
private Container _container;
10+
11+
protected Container Container => _container;
12+
13+
public override void Setup()
14+
{
15+
base.Setup();
16+
17+
var builder = new ContainerBuilder();
18+
SetupBindings(builder);
19+
_container = builder.Build();
20+
}
21+
22+
public override void TearDown()
23+
{
24+
base.TearDown();
25+
26+
_container?.Dispose();
27+
_container = null;
28+
}
29+
30+
protected async UniTask RunAndVerify<TStateMachine, TState>()
31+
where TStateMachine : class, IStateMachine, IVerifiableStateMachine
32+
where TState : class, IState<EmptyPayload>
33+
{
34+
await StateMachineTestHelper.RunAndVerify<TStateMachine, TState>(Container.ToTypeResolver(),
35+
GetTimeoutToken());
36+
}
37+
38+
protected virtual void SetupBindings(ContainerBuilder builder)
39+
{
40+
builder.AddSingleton(typeof(ExecutionLogger));
41+
}
42+
}
43+
}

Assets/UniStateTests/Common/TestFixture/ReflexTestsBase.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.Collections;
2+
using Cysharp.Threading.Tasks;
3+
using NUnit.Framework;
4+
using Reflex.Core;
5+
using UniState;
6+
using UniStateTests.Common;
7+
using UniStateTests.PlayMode.StateBehaviorAttributeTests.Infrastructure;
8+
using UnityEngine.TestTools;
9+
10+
namespace UniStateTests.PlayMode.StateBehaviorAttributeTests
11+
{
12+
[TestFixture]
13+
public class BehaviorAttributeReflexTests : ReflexTestsBase
14+
{
15+
[UnityTest]
16+
public IEnumerator RunChaneOfStateWithAttributes_ExitFromChain_ChainExecutedCorrectly() => UniTask.ToCoroutine(async () =>
17+
{
18+
await RunAndVerify<IVerifiableStateMachine, FirstState>();
19+
});
20+
21+
protected override void SetupBindings(ContainerBuilder builder)
22+
{
23+
base.SetupBindings(builder);
24+
25+
builder.AddStateMachine(typeof(StateMachineBehaviourAttribute), typeof(IVerifiableStateMachine));
26+
builder.AddState(typeof(FirstState));
27+
builder.AddState(typeof(NoReturnState));
28+
builder.AddState(typeof(FastInitializeState));
29+
builder.AddSingleton(typeof(BehaviourAttributeTestHelper));
30+
}
31+
}
32+
}

Assets/UniStateTests/PlayMode/BehaviorAttributeTests/BehaviorAttributeReflexTests.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System.Collections;
2+
using Cysharp.Threading.Tasks;
3+
using NUnit.Framework;
4+
using Reflex.Core;
5+
using UniState;
6+
using UniStateTests.Common;
7+
using UniStateTests.PlayMode.Execution.Infrastructure;
8+
using UnityEngine.TestTools;
9+
using FirstState = UniStateTests.PlayMode.Execution.Infrastructure.FirstState;
10+
11+
namespace UniStateTests.PlayMode.Execution
12+
{
13+
[TestFixture]
14+
public class ExecutionReflexTests : ReflexTestsBase
15+
{
16+
[UnityTest]
17+
public IEnumerator RunStateMachineSeveralTime_EndExecution_ExecutionStatusValid() => UniTask.ToCoroutine(
18+
async () =>
19+
{
20+
var testHelper = Container.Resolve<ExecutionTestHelper>();
21+
testHelper.SetPath(StateMachineExecutionType.Default);
22+
23+
await RunAndVerify<IVerifiableStateMachine, FirstState>();
24+
Assert.False(testHelper.CurrentStateMachine.IsExecuting);
25+
26+
testHelper.SetPath(StateMachineExecutionType.WrongDependency);
27+
28+
await RunAndVerify<IVerifiableStateMachine, FirstState>();
29+
Assert.False(testHelper.CurrentStateMachine.IsExecuting);
30+
31+
testHelper.SetPath(StateMachineExecutionType.Exception);
32+
33+
await RunAndVerify<IVerifiableStateMachine, FirstState>();
34+
Assert.False(testHelper.CurrentStateMachine.IsExecuting);
35+
});
36+
37+
protected override void SetupBindings(ContainerBuilder builder)
38+
{
39+
base.SetupBindings(builder);
40+
41+
builder.AddSingleton(typeof(ExecutionTestHelper));
42+
builder.AddStateMachine(typeof(ExecutionStateMachine), typeof(IVerifiableStateMachine));
43+
builder.AddState(typeof(FirstState));
44+
builder.AddState(typeof(SecondState));
45+
builder.AddState(typeof(SecondStateWithException));
46+
builder.AddState(typeof(SecondStateWithWrongDependency));
47+
}
48+
}
49+
}

Assets/UniStateTests/PlayMode/ExecutionTests/ExecutionReflexTests.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)