5 using System.Text.RegularExpressions;
10 public partial class Fiber : IDisposable {
16 public bool Running, Aborted;
24 var node = Queue.Waiting.GetRecycledOrNew();
25 Queue.Reactivation(node);
26 var fiber = node.Item;
35 (context as IDisposable)?.
Dispose();
38 if (resetOnError) onError = Debug.LogError;
44 if (controller == null) {
48 fiber.Update = FirstUpdate;
49 fiber.node.MoveTo(Queue.Update);
50 fiber.disposeOnComplete =
true;
54 private static void FirstUpdate(
Fiber fiber) {
60 public Fiber
Go() =>
Go(NextAction);
64 if (Running)
return this;
65 if (controller == null) {
70 SetAction(actions.Last);
71 node.MoveTo(Queue.Update);
101 private readonly
Fiber fiber;
110 instance.
Scope = scope;
111 instance.onComplete = instance.Fiber.OnComplete;
123 public T
Context<T>() where T :
class => context[typeof(T)].Value as T;
127 (context[typeof(T)].Value as IDisposable)?.
Dispose();
128 context.Add(typeof(T), value);
132 public T
Context<T>(
string name) where T :
class => context[name].Value as T;
136 (context[name].Value as IDisposable)?.
Dispose();
137 context.Add(name, value);
140 private readonly Map context = Map.Instance;
145 public Fiber OnUpdates => AddAction(_ => node.MoveTo(Queue.Update),
"OnUpdates");
154 action = actions.First;
162 fiber.Aborted =
true;
163 fiber.action = fiber.actions.First;
164 node.MoveTo(Queue.Update);
174 #region Blocks and Loops 178 AddAction(_ => blockStack.Push(action?.Previous ?? actions.First),
"Begin");
179 AddAction(NextAction);
185 public Fiber End => AddAction(NextAction).AddAction(_ => blockStack.Pop(),
"End");
188 public Fiber Again => AddAction(_ => action = blockStack.Top).AddAction(NextAction).
End;
196 var begin = blockStack.Top;
197 if ((++counter % count) != 0) action = begin;
202 public Fiber Until(Func<Fiber, bool> isTrue) =>
205 var begin = blockStack.Top;
206 if (!isTrue(
this)) action = begin;
210 public Fiber
BreakIf(Func<Fiber, bool> isBreak) =>
213 if (isBreak(
this))
Break();
218 while ((action?.Previous != null) && (action.Previous.Item.Actor != NextAction)) action = action.Previous;
222 public void Break(
int after) {
228 public void Skip(
int after) {
229 for (
int i = 0; (i < after) && (action != null); i++) action = action.Previous;
235 public Fiber
If(Func<Fiber, bool> isTrue) =>
238 if (!isTrue(
this))
Break();
249 public Fiber Do(
Action nextAction,
string name = null) => AddAction(nextAction, name);
251 #region Fiber Control and Monitoring 255 while (Running) yield
return null;
260 if (anotherFiber == null)
return this;
261 if (anotherFiber.onError == globalOnError) anotherFiber.onError = onError;
262 Do(_ => anotherFiber.
Go());
263 WaitFor(anotherFiber.OnComplete,
"WaitFor(Fiber)");
269 secondsTimeout = seconds;
274 private float secondsTimeout;
275 private Fiber timeoutFiber;
278 public Fiber WaitFor(Func<Fiber, Fiber> getFiber) => AddAction(_ =>
WaitFor(getFiber(
this)));
281 #region Error Management 284 onError = globalOnError = actor;
307 onError(messageLambda(
this));
310 private bool resetOnError;
311 private Action<string> onError = msg => globalOnError(msg);
312 private static Action<string> globalOnError = msg => Debug.LogError($
"onError: {msg}");
313 private bool exitOnError;
318 internal class Queue : LinkedList<Fiber> {
320 internal static Action<Node> Deactivation = (node) => {
321 var fiber = node.Item;
322 fiber.actions.Dispose();
323 fiber.blockStack.Dispose();
324 fiber.OnComplete.Dispose();
325 fiber.CancelOnAborted();
328 internal static void Reactivation(Node node) {
329 var fiber = node.Item;
330 fiber.OnComplete = Emitter.Instance;
331 fiber.actions = Cache<ActionList>.Instance;
332 fiber.AddAction(_ => { },
"Start");
333 fiber.blockStack = Fifo<LinkedList<ActionItem>.Node>.Instance;
334 fiber.Running = fiber.Aborted = fiber.resetOnError = fiber.disposeOnComplete = fiber.exitOnError =
false;
337 internal static readonly Queue Update =
new Queue
338 {Name =
"Update Fibers", DeactivateItem = Deactivation, ReactivateItem = Reactivation};
339 internal static readonly Queue LateUpdate =
new Queue
340 {Name =
"Late Update Fibers", DeactivateItem = Deactivation, ReactivateItem = Reactivation};
341 internal static readonly Queue FixedUpdate =
new Queue
342 {Name =
"Fixed Update Fibers", DeactivateItem = Deactivation, ReactivateItem = Reactivation};
343 internal static readonly Queue Waiting =
new Queue
344 {Name =
"Fiber Waiting Queue", DeactivateItem = Deactivation, ReactivateItem = Reactivation};
349 private static string ActionName(ActionItem actionItem) {
350 var name = actionItem.Name ?? actionItem.Actor.Method.Name;
351 Match match = getRe.Match(name);
352 for (
int i = 0; i < match.Groups.Count; i++) {
353 if (match.Groups[i].Success) name = match.Groups[i].Value;
355 if (name ==
"NextAction") name =
"_";
358 private static readonly Regex getRe =
new Regex(
@"<get_(.*?)>|<.*?>g__(.*)\|\d+|<(.*?)>");
360 private static void NextAction(Fiber fiber) {
361 if (fiber.action?.Previous != null) {
363 fiber.SetAction(fiber.action.Previous).Item.Actor(fiber);
364 }
catch (Exception e) {
365 fiber.onError(e.ToString());
366 if (fiber.exitOnError) fiber.Exit();
370 if (fiber.Debugging) fiber.Log($
"OnComplete: for {fiber.node}");
372 fiber.OnComplete.Fire();
373 fiber.Running =
false;
374 fiber.node.MoveTo(Queue.Waiting);
375 if (fiber.disposeOnComplete) fiber.Dispose();
379 private struct ActionItem {
384 private class ActionList : LinkedList<ActionItem> { }
386 private LinkedList<ActionItem>.Node action;
387 private ActionList actions;
388 private Fifo<LinkedList<ActionItem>.Node> blockStack;
389 private static MonoBehaviour controller;
390 private bool disposeOnComplete;
392 private static int nextId;
393 private LinkedList<Fiber>.Node node;
396 private Fiber AddAction(
Action newAction,
string name = null) {
397 actions.Add(
new ActionItem {Name = name, Actor = newAction});
401 private LinkedList<ActionItem>.Node SetAction(LinkedList<ActionItem>.Node nextAction) {
404 if (
Debugging)
Log($
"Run: {ActionName(action.Item),10} for {this}");
410 #region Debugging Mode 415 public override string ToString() => $
"Id: {id} // Actions: {ActionNames} // Queue: {node?.Owner}";
418 private string ActionNames {
420 var array =
new string[actions.Count];
422 var node = actions.Last;
424 for (var idx = 0; idx < array.Length; node = node.Previous, idx++) {
425 var name = ActionName(node.Item);
426 array[idx] = node == action ? $
"[{name}]" : name;
428 return Csv.ToString(array);
433 public Fiber Log(
string message,
bool warning =
false) {
434 message = $
"{message}\n{this}";
436 Debug.LogWarning(message);
T Context< T >()
Retrieve the context as a class type - null for none or wrong type
void Finish()
Complete a Fiber.Start statement where needed (no action)
Fiber Else
Standard If // Else // Then branch
Fiber OnUpdates
Return Fiber processing to frame Update queue
Fiber Repeat(int count)
Begin/Repeat loop for a specific number of times
Fiber Do(Action nextAction, string name=null)
Business logic activation step
static Fiber Start
Prepare a Fiber and place it on the Update queue
bool Debugging
Displays Do() and action events on Unity console
Interface used by WaitFor(IClosure)
Fiber OnFixedUpdates
Move Fiber processing to FixedUpdate queue
Emitter OnComplete
A reference to closure.Fiber.OnComplete
void Break()
Break a Begin/End/Repeat/Again block
Closure super-class that does all the smarts
lightweight cooperative multi-tasking
override string ToString()
Return Fiber contents and current state
Fiber Log(string message, bool warning=false)
Write to the Unity console (optionally as a warning entry)
static Closure< TS, TTuple > Go(TTuple scope)
Calling this static will fetch a prepared fiber, add scope and run it.
Fiber WaitFor(IClosure closure)
Helper that is the same as fiber.WaitFor(closure.OnComplete)
Fiber OnLateUpdates
Move Fiber processing to LateUpdate queue
Fiber Exit()
Abort fiber processing immediately, cleaning up as we go
Fiber Until(Func< Fiber, bool > isTrue)
Loop until a value function returns true
void Dispose()
Cleans up Fiber before it goes into the recycling
Emitter OnComplete
Emitter that is fired when the fiber completes all actions
Fiber Error(Func< Fiber, string > messageLambda)
//#TBD#//
Fiber Go()
Start a fiber if it is not already running
Fiber WaitFor(Fiber anotherFiber)
Wait for another fiber to complete, starting it if needed
Fiber End
Begin/End block - use Break() to create an if
Fiber Error(string message)
//#TBD#//
Fiber GlobalOnError(Action< string > actor)
Set a global (app-wide) error catch lambda. All fibers without a local override will come here...
Fiber Then
Standard If // Else // Then branch
Fiber Exit(Fiber fiber)
Force another fiber to exit immediately
Fiber ExitOnError
Exceptions in this fiber will cause the fiber to exit
Fiber Timeout(float seconds)
Exit later fiber operations if the time supplied is exceeded
Fiber BreakIf(Func< Fiber, bool > isBreak)
Break out of any block if a value function returns true
Fiber Again
Begin/Again repeating operations. Use Break() or Exit() to leave
abstract void Activities(Fiber fiberToUpdate)
Add all the steps you need to this override. It is called by the constructor.
Fiber Begin
Loops and Blocks - Begin/End, Begin/Again, Begin-Repeat
Cached C# Action instances using the observer pattern
Fiber If(Func< Fiber, bool > isTrue)
Standard If // Else // Then branch
IEnumerator AsCoroutine()
Return an IEnumerator to use with a yield in a Coroutine
static Fiber Instance
Precompile an instance of a fiber command
Disposed does not recycle object immediately
TTuple Scope
Scope is available for 10 frames after OnComplete in case it holds response data
delegate void Action(Fiber fiber)
Method signature for Do(Action) methods
Fiber Go(Action updater)
Start a fiber if it is not already running
Fiber OnError(Action< string > actor)
The catch lambda will be called for any exceptions from this fiber or any fibers called with WaitFor ...