A C# coroutine-based Kernel for .Net. If you are one of the many developers using this library, I encourage you provide any feedback and/or fork.
This is Unity-friendly and will work on all versions of Unity after 4.0. Please let me know otherwise.
Current documentation is at GamaSutra but the formatting is a bit screwy.
The original post was on AltDevBlogADay but that site is now lost for the ages.
The tests reside in TestFlow/Editor so they can be used from Unity3d as well.
These tests, along with the GamaSutra article, are the best first sources of documentation.
Using Flow for async REST communications.
private void CreateHeartbeat()
{
New.PeriodicTimer(TimeSpan.FromMinutes(2)).Elapsed += tr =>
{
Get<UserCount>("user/alive").Then(result =>
{
if (result.Succeeded(out var val))
{
_activeUsers.Value = val.Num;
Info($"{val.Num} users online.");
}
});
};
}
This is example code pulled straight for a game I'm quasi-working on:
public void GameLoop()
{
Root.Add(
New.Sequence(
New.Coroutine(StartGame).Named("StartGame"),
New.While(() => !_gameOver),
New.Coroutine(PlayerTurn).Named("Turn").Named("While"),
New.Coroutine(EndGame).Named("EndGame")
).Named("GameLoop")
);
}
Note the .Named("Name")
extensions to the factory methods: these are for debugging and tracing purposes. The library comes with extensive debugging and visualisation support, so you can see in real time as the kernel changes.
The main logic flow for starting a turn is:
private IEnumerator StartGame(IGenerator self)
{
var start = New.Sequence(
New.Barrier(
WhitePlayer.StartGame(),
BlackPlayer.StartGame()
).Named("Init Game"),
New.Barrier(
WhitePlayer.DrawInitialCards(),
BlackPlayer.DrawInitialCards()
).Named("Deal Cards"),
New.Barrier(
New.TimedBarrier(
TimeSpan.FromSeconds(Parameters.MulliganTimer),
WhitePlayer.AcceptCards(),
BlackPlayer.AcceptCards()
).Named("Mulligan"),
New.Sequence(
WhitePlayer.PlaceKing(),
BlackPlayer.PlaceKing()
).Named("Place Kings")
).Named("Preceedings")
).Named("Start Game");
start.Completed += (tr) => Info("StartGame completed");
yield return start;
}
And the relevant IPlayerAgent Method declaractions as being:
ITimer StartGame();
ITimer DrawInitialCards();
ITimedFuture<bool> AcceptCards();
ITimedFuture<PlacePiece> PlaceKing();
ITransient ChangeMaxMana(int i);
ITimedFuture<ICardModel> DrawCard();
ITimedFuture<PlacePiece> PlayCard();
ITimedFuture<MovePiece> MovePiece();
ITimedFuture<Pass> Pass();
This is just a simple example on how the library is tyically used. It's a matter of chaining together sequences of Barriers, Triggers, and Futures to remove the need to keep explicit track of internal state on each Update call.
In this case, I'm using a lot of timed futures because it's a real-time card game and there are time limits.
When using Verbose()
be mindful that the arguments passed into the log will be evaluated even if the verbosity is set lower than would print. Be diligent when using interpolated strings or complex functions in Verbose logging.
e.g.
Verbosity = 10;
Verbose(15, $"Result of complex function: {ComplexFunction()}.");