Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions Assets/Examples/Infrastructure/Reflex/DiceInstaller.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Examples.States;
using Reflex.Attributes;
using Reflex.Core;
using Reflex.Enums;
using UniState;
using UnityEngine;

Expand All @@ -13,20 +14,23 @@ public class DiceInstaller : MonoBehaviour, IInstaller

public void InstallBindings(ContainerBuilder builder)
{
builder.AddStateMachine(typeof(StateMachine), typeof(IStateMachine));
builder.RegisterStateMachine(typeof(StateMachine), typeof(IStateMachine));

builder.AddState(typeof(LostState));
builder.AddState(typeof(RollDiceState));
builder.AddState(typeof(StartGameState));
builder.AddState(typeof(WinState));
builder.RegisterState(typeof(LostState));
builder.RegisterState(typeof(RollDiceState));
builder.RegisterState(typeof(StartGameState));
builder.RegisterState(typeof(WinState));

builder.AddSingleton(container =>
{
DiceEntryPoint entryPoint = new(container.Resolve<IStateMachine>());
entryPoint.Start();
builder.RegisterFactory<DiceEntryPoint>(
container =>
{
DiceEntryPoint entryPoint = new(container.Resolve<IStateMachine>());
entryPoint.Start();

return entryPoint;
});
return entryPoint;
},
Lifetime.Singleton,
global::Reflex.Enums.Resolution.Lazy);
}
}
}
}
224 changes: 107 additions & 117 deletions Assets/UniState/Runtime/Integrations/Reflex/ReflexBuildExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,77 +1,112 @@
#if UNISTATE_REFLEX_SUPPORT

using System;
using System.Collections.Generic;
using Reflex.Core;
using Reflex.Enums;
using Reflex.Resolvers;

namespace UniState
{
namespace UniState
{
public static class ReflexBuildExtensions
{
public static void AddStateMachine(
public static void RegisterStateMachine(
this ContainerBuilder builder,
Type stateMachineImplementation,
Type stateMachineContract)
{
AddStateMachineInternal(builder, stateMachineImplementation, stateMachineContract, Lifetime.Transient);
RegisterStateMachine(builder, stateMachineImplementation, stateMachineContract, Lifetime.Transient);
}
public static void AddSingletonStateMachine(

public static void RegisterStateMachine(
this ContainerBuilder builder,
Type stateMachineImplementation,
Type stateMachineContract)
Type stateMachineContract,
Lifetime lifetime)
{
AddStateMachineInternal(builder, stateMachineImplementation, stateMachineContract, Lifetime.Singleton);
RegisterStateMachineInternal(builder, stateMachineImplementation, stateMachineContract, lifetime);
}

public static void AddState(this ContainerBuilder builder, Type state)

public static void RegisterState(this ContainerBuilder builder, Type state)
{
RegisterState(builder, state, Lifetime.Transient);
}

public static void RegisterState(this ContainerBuilder builder, Type state, Lifetime lifetime)
{
ValidateStateBindingInput(state);

builder.AddTransient(state, GetStateContracts(state));
builder.RegisterType(state, GetStateContracts(state), lifetime, Resolution.Lazy);
}

public static void AddState(this ContainerBuilder builder, Type stateImplementation, Type stateContract)
{
ValidateStateBindingInput(stateImplementation, stateContract);

builder.AddTransient(stateImplementation, stateContract);
}

public static void AddSingletonState(this ContainerBuilder builder, Type state)

public static void RegisterState(this ContainerBuilder builder, Type stateImplementation, Type stateContract)
{
ValidateStateBindingInput(state);
RegisterState(builder, stateImplementation, stateContract, Lifetime.Transient);
}

public static void RegisterState(
this ContainerBuilder builder,
Type stateImplementation,
Type stateContract,
Lifetime lifetime)
{
ValidateStateBindingInput(stateImplementation, stateContract);

builder.AddSingleton(state, GetStateContracts(state));
builder.RegisterType(
stateImplementation,
new[] { stateContract },
lifetime,
Resolution.Lazy);
}

public static void AddSingletonState(this ContainerBuilder builder, Type stateImplementation,
Type stateContract)
{
ValidateStateBindingInput(stateImplementation, stateContract);

builder.AddSingleton(stateImplementation, stateContract);
}

[Obsolete("Use RegisterStateMachine(builder, implementation, contract) or the overload with Lifetime.")]
public static void AddStateMachine(
this ContainerBuilder builder,
Type stateMachineImplementation,
Type stateMachineContract) =>
RegisterStateMachine(builder, stateMachineImplementation, stateMachineContract);

[Obsolete("Use RegisterStateMachine(builder, implementation, contract, Lifetime.Singleton) instead.")]
public static void AddSingletonStateMachine(
this ContainerBuilder builder,
Type stateMachineImplementation,
Type stateMachineContract) =>
RegisterStateMachine(builder, stateMachineImplementation, stateMachineContract, Lifetime.Singleton);

[Obsolete("Use RegisterState(builder, state) or the overload with Lifetime.")]
public static void AddState(this ContainerBuilder builder, Type state) =>
RegisterState(builder, state);

[Obsolete("Use RegisterState(builder, implementation, contract) or the overload with Lifetime.")]
public static void AddState(this ContainerBuilder builder, Type stateImplementation, Type stateContract) =>
RegisterState(builder, stateImplementation, stateContract);

[Obsolete("Use RegisterState(builder, state, Lifetime.Singleton) instead.")]
public static void AddSingletonState(this ContainerBuilder builder, Type state) =>
RegisterState(builder, state, Lifetime.Singleton);

[Obsolete("Use RegisterState(builder, implementation, contract, Lifetime.Singleton) instead.")]
public static void AddSingletonState(
this ContainerBuilder builder,
Type stateImplementation,
Type stateContract) =>
RegisterState(builder, stateImplementation, stateContract, Lifetime.Singleton);

private static void ValidateStateBindingInput(Type stateImplementation, Type stateContract)
{
ValidateStateBindingInput(stateImplementation);

if (!stateContract.IsAssignableFrom(stateImplementation))
{
throw new ArgumentException(
$"AddState({stateImplementation.Name}): Type parameter state must implement {stateContract.Name}.");
}
}
if (!stateContract.IsAssignableFrom(stateImplementation))
{
throw new ArgumentException(
$"RegisterState({stateImplementation.Name}): Type parameter state must implement {stateContract.Name}.");
}
}

private static void ValidateStateBindingInput(Type state)
{
if (!typeof(IExecutableState).IsAssignableFrom(state))
{
throw new ArgumentException(
$"AddState({state.Name}): Type parameter state must implement IState<TPayload>");
$"RegisterState({state.Name}): Type parameter state must implement IState<TPayload>");
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ValidateStateBindingInput(Type state) checks IExecutableState, but the thrown message says the type must implement IState<TPayload>. Since non-IState<TPayload> types can still be valid IExecutableState (e.g., sub-state containers), the message is misleading—update it to reference IExecutableState (or whatever the actual required contract is).

Suggested change
$"RegisterState({state.Name}): Type parameter state must implement IState<TPayload>");
$"RegisterState({state.Name}): Type parameter state must implement IExecutableState.");

Copilot uses AI. Check for mistakes.
}
}

Expand All @@ -91,93 +126,48 @@ private static Type[] GetStateContracts(Type state)

private static void ValidateStateMachineBindingInput(Type stateMachineImplementation, Type stateMachineContract)
{
if (stateMachineImplementation == stateMachineContract)
{
throw new ArgumentException(
$"AddStateMachine<{stateMachineImplementation.Name}>: Type parameters must differ : " +
"use AddStateMachine() where stateMachineImplementation implements stateMachineContract.\");");
}

if (!stateMachineContract.IsAssignableFrom(stateMachineImplementation))
{
throw new ArgumentException(
$"AddStateMachine: Type {stateMachineImplementation.Name} " +
$"must implement {stateMachineContract.Name}.");
}

if (!typeof(IStateMachine).IsAssignableFrom(stateMachineContract))
{
throw new ArgumentException(
$"AddStateMachine: Type {stateMachineContract.Name} " +
$"must implement IStateMachine.");
}
}

private static void AddStateMachineInternal(
ContainerBuilder builder,
Type stateMachineImplementation,
Type stateMachineContract,
Lifetime lifetime)
{
ValidateStateMachineBindingInput(stateMachineImplementation, stateMachineContract);

builder.Bindings.Add(Binding.Validated(
new ReflexStateMachineResolver(stateMachineImplementation, lifetime),
stateMachineImplementation,
stateMachineImplementation,
stateMachineContract));
}

private sealed class ReflexStateMachineResolver : IResolver
{
private readonly Type _stateMachineImplementation;
private readonly Lifetime _lifetime;
private readonly List<IDisposable> _disposables = new();

private object _instance;

public Lifetime Lifetime => _lifetime;

public ReflexStateMachineResolver(Type stateMachineImplementation, Lifetime lifetime)
if (stateMachineImplementation == stateMachineContract)
{
_stateMachineImplementation = stateMachineImplementation;
_lifetime = lifetime;
throw new ArgumentException(
$"RegisterStateMachine<{stateMachineImplementation.Name}>: Type parameters must differ : " +
"use RegisterStateMachine() where stateMachineImplementation implements stateMachineContract.\");");
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ArgumentException message for the stateMachineImplementation == stateMachineContract case contains stray escaped characters (\");");) that will show up in the runtime error text and make it confusing. Clean up the string literal so the message reads as intended (and doesn’t include leftover quotes/parentheses/semicolons).

Suggested change
"use RegisterStateMachine() where stateMachineImplementation implements stateMachineContract.\");");
"use RegisterStateMachine() where stateMachineImplementation implements stateMachineContract.");

Copilot uses AI. Check for mistakes.
}

public object Resolve(Container container)
if (!stateMachineContract.IsAssignableFrom(stateMachineImplementation))
{
if (_lifetime == Lifetime.Singleton && _instance != null)
{
return _instance;
}

var stateMachine = (IStateMachine)container.Construct(_stateMachineImplementation);
stateMachine.SetResolver(container.ToTypeResolver());

if (_lifetime == Lifetime.Singleton)
{
_instance = stateMachine;
}

if (stateMachine is IDisposable disposable)
{
_disposables.Add(disposable);
}

return stateMachine;
throw new ArgumentException(
$"RegisterStateMachine: Type {stateMachineImplementation.Name} " +
$"must implement {stateMachineContract.Name}.");
}

public void Dispose()
if (!typeof(IStateMachine).IsAssignableFrom(stateMachineContract))
{
for (var i = _disposables.Count - 1; i >= 0; i--)
{
_disposables[i].Dispose();
}

_disposables.Clear();
_instance = null;
throw new ArgumentException(
$"RegisterStateMachine: Type {stateMachineContract.Name} " +
$"must implement IStateMachine.");
}
}

private static void RegisterStateMachineInternal(
ContainerBuilder builder,
Type stateMachineImplementation,
Type stateMachineContract,
Lifetime lifetime)
{
ValidateStateMachineBindingInput(stateMachineImplementation, stateMachineContract);

builder.RegisterFactory(
container =>
{
var stateMachine = (IStateMachine)container.Construct(stateMachineImplementation);
stateMachine.SetResolver(container.ToTypeResolver());
return stateMachine;
},
stateMachineImplementation,
new[] { stateMachineImplementation, stateMachineContract },
lifetime,
Resolution.Lazy);
}
}
}

Expand Down
5 changes: 3 additions & 2 deletions Assets/UniStateTests/Common/TestFixture/ReflexTestsBase.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Cysharp.Threading.Tasks;
using Reflex.Core;
using Reflex.Enums;
using UniState;

namespace UniStateTests.Common
Expand Down Expand Up @@ -37,7 +38,7 @@ await StateMachineTestHelper.RunAndVerify<TStateMachine, TState>(Container.ToTyp

protected virtual void SetupBindings(ContainerBuilder builder)
{
builder.AddSingleton(typeof(ExecutionLogger));
builder.RegisterType(typeof(ExecutionLogger), Lifetime.Singleton, Resolution.Lazy);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Cysharp.Threading.Tasks;
using NUnit.Framework;
using Reflex.Core;
using Reflex.Enums;
using UniState;
using UniStateTests.Common;
using UniStateTests.PlayMode.StateBehaviorAttributeTests.Infrastructure;
Expand All @@ -22,11 +23,11 @@ protected override void SetupBindings(ContainerBuilder builder)
{
base.SetupBindings(builder);

builder.AddStateMachine(typeof(StateMachineBehaviourAttribute), typeof(IVerifiableStateMachine));
builder.AddState(typeof(FirstState));
builder.AddState(typeof(NoReturnState));
builder.AddState(typeof(FastInitializeState));
builder.AddSingleton(typeof(BehaviourAttributeTestHelper));
builder.RegisterStateMachine(typeof(StateMachineBehaviourAttribute), typeof(IVerifiableStateMachine));
builder.RegisterState(typeof(FirstState));
builder.RegisterState(typeof(NoReturnState));
builder.RegisterState(typeof(FastInitializeState));
builder.RegisterType(typeof(BehaviourAttributeTestHelper), Lifetime.Singleton, Resolution.Lazy);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Cysharp.Threading.Tasks;
using NUnit.Framework;
using Reflex.Core;
using Reflex.Enums;
using UniState;
using UniStateTests.Common;
using UniStateTests.PlayMode.Execution.Infrastructure;
Expand Down Expand Up @@ -38,12 +39,12 @@ protected override void SetupBindings(ContainerBuilder builder)
{
base.SetupBindings(builder);

builder.AddSingleton(typeof(ExecutionTestHelper));
builder.AddStateMachine(typeof(ExecutionStateMachine), typeof(IVerifiableStateMachine));
builder.AddState(typeof(FirstState));
builder.AddState(typeof(SecondState));
builder.AddState(typeof(SecondStateWithException));
builder.AddState(typeof(SecondStateWithWrongDependency));
builder.RegisterType(typeof(ExecutionTestHelper), Lifetime.Singleton, Resolution.Lazy);
builder.RegisterStateMachine(typeof(ExecutionStateMachine), typeof(IVerifiableStateMachine));
builder.RegisterState(typeof(FirstState));
builder.RegisterState(typeof(SecondState));
builder.RegisterState(typeof(SecondStateWithException));
builder.RegisterState(typeof(SecondStateWithWrongDependency));
}
}
}
Loading
Loading