This is a proposal: I would love to implement this, but before I do I would like to know if it'd be warranted, and if there'd be any issues with my approach.
I would like to reduce the amount of setup and increase type safety of LogicBlocks and their state. Currently, Get and Set play a big role in its setup, which offer no compile-time warnings.
public class Player
{
public void Setup()
{
Settings = new PlayerLogic.Settings(...);
PlayerLogic = new PlayerLogic();
// Set PlayerLogic data (via blackboard)
PlayerLogic.Set(this as IPlayer);
PlayerLogic.Set(Settings);
PlayerLogic.Set(AppRepo);
PlayerLogic.Set(GameRepo);
PlayerLogic.Save(() => new PlayerLogic.Data());
}
}
public partial class PlayerLogic
{
public partial record State
{
public abstract partial record Alive : State
{
public virtual Transition On(in Input.PhysicsTick input)
{
// Get PlayerLogic data (via blackboard)
// Nothing ensures these objects exist.
var player = Get<IPlayer>();
var settings = Get<Settings>();
var gameRepo = Get<IGameRepo>();
var data = Get<Data>();
}
}
}
}
With an overridable method in LogicBlock, we could specify a factory method for State in which we could provide our objects:
[Meta, Id("player_logic")]
[LogicBlock(typeof(State), Diagram = true)]
public partial class PlayerLogic(
IPlayer player,
PlayerLogic.Settings settings,
IAppRepo appRepo,
IGameRepo gameRepo)
: LogicBlock<PlayerLogic.State>, IPlayerLogic
{
public override Transition GetInitialState() => To<State.Disabled>();
protected override TStateType CreateState<TStateType>()
where TStateType : PlayerLogic.State, new()
=> new TStateType()
{
Player = player,
Settings = settings,
AppRepo = appRepo,
GameRepo = gameRepo
};
[Meta]
public abstract partial record State : StateLogic<State>
{
public required IPlayer Player { get; }
public required PlayerLogic.Settings Settings { get; }
public required IAppRepo AppRepo { get; }
public required IGameRepo GameRepo { get; }
}
}
And in practice, do this:
public class Player
{
public void Setup()
{
Settings = new PlayerLogic.Settings(...);
PlayerLogic = new PlayerLogic(this, Settings, AppRepo, GameRepo);
// Save PlayerLogic data (would love to tackle this one as well someday)
PlayerLogic.Save(() => new PlayerLogic.Data());
}
}
public partial class PlayerLogic
{
public partial record State
{
public abstract partial record Alive : State
{
public virtual Transition On(in Input.PhysicsTick input)
{
// Get objects as they have been initialized.
var player = this.Player;
var settings = this.Settings;
var gameRepo = this.GameRepo;
var data = Get<Data>(); // My arch-nemesis...
}
}
}
}
Of course, you can always do PlayerLogic.GameRepo { get; set; } if you'd like to set them during runtime.
Hidden benefit: using type-checking you can be even more expressive of how you want your state to be created:
protected override TStateType CreateState<TStateType>()
where TStateType : PlayerLogic.State, new()
=> typeof(TStateType) == typeof(PlayerLogic.State.Swimming) ? new() { ... } :
typeof(TStateType) == typeof(PlayerLogic.State.Running) ? new() { ... } :
new() { ... };
This is a proposal: I would love to implement this, but before I do I would like to know if it'd be warranted, and if there'd be any issues with my approach.
I would like to reduce the amount of setup and increase type safety of LogicBlocks and their state. Currently,
GetandSetplay a big role in its setup, which offer no compile-time warnings.With an overridable method in LogicBlock, we could specify a factory method for State in which we could provide our objects:
And in practice, do this:
Of course, you can always do
PlayerLogic.GameRepo { get; set; }if you'd like to set them during runtime.Hidden benefit: using type-checking you can be even more expressive of how you want your state to be created: