Code/BehaviorTree/Task/Action.cs
using Sandbox.Diagnostics;

namespace NPBehave
{
    public class Action : Task
    {
        public enum Result
        {
            Success,
            Failed,
            Blocked,
            Progress
        }

        public enum Request
        {
            Start,
            Update,
            Cancel,
        }

        private System.Func<bool> _singleFrameFunc = null;
        private System.Func<bool, Result> _multiFrameFunc = null;
        private System.Func<Request, Result> _multiFrameFunc2 = null;
        private System.Action _action = null;
        private bool _bWasBlocked = false;

        public Action(System.Action action) : base("Action")
        {
            _action = action;
        }

        public Action(System.Func<bool, Result> multiframeFunc) : base("Action")
        {
            _multiFrameFunc = multiframeFunc;
        }

        public Action(System.Func<Request, Result> multiframeFunc2) : base("Action")
        {
            _multiFrameFunc2 = multiframeFunc2;
        }


        public Action(System.Func<bool> singleFrameFunc) : base("Action")
        {
            _singleFrameFunc = singleFrameFunc;
        }

        protected override void DoStart()
        {
            if (_action != null)
            {
                _action.Invoke();
                Stopped(true);
            }
            else if (_multiFrameFunc != null)
            {
                Result result = _multiFrameFunc.Invoke(false);
                if ( result == Result.Progress )
                {
                    RootNode.Clock.AddUpdateObserver( OnUpdateFunc );
                }
                else if ( result == Result.Blocked )
                {
                    _bWasBlocked = true;
                    RootNode.Clock.AddUpdateObserver( OnUpdateFunc );
                }
                else
                {
                    Stopped(result == Result.Success);
                }
            }
            else if (_multiFrameFunc2 != null)
            {
                Result result = _multiFrameFunc2.Invoke(Request.Start);
                if (result == Result.Progress)
                {
                    RootNode.Clock.AddUpdateObserver(OnUpdateFunc2);
                }
                else if ( result == Result.Blocked )
                {
                    _bWasBlocked = true;
                    RootNode.Clock.AddUpdateObserver( OnUpdateFunc2 );
                }
                else
                {
                    Stopped(result == Result.Success);
                }
            }
            else if (_singleFrameFunc != null)
            {
                Stopped(_singleFrameFunc.Invoke());
            }
        }

        private void OnUpdateFunc()
        {
            Result result = _multiFrameFunc.Invoke(false);
            if (result != Result.Progress && result != Result.Blocked)
            {
                RootNode.Clock.RemoveUpdateObserver(OnUpdateFunc);
                Stopped(result == Result.Success);
            }
        }

        private void OnUpdateFunc2()
        {
            Result result = _multiFrameFunc2.Invoke( _bWasBlocked ? Request.Start : Request.Update);

            if ( result == Result.Blocked )
            {
                _bWasBlocked = true;
            }
            else if ( result == Result.Progress )
            {
                _bWasBlocked = false;
            }
            else
            {
                RootNode.Clock.RemoveUpdateObserver( OnUpdateFunc2 );
                Stopped( result == Result.Success );
            }
        }

        protected override void DoStop()
        {
            if (_multiFrameFunc != null)
            {
                Result result = _multiFrameFunc.Invoke(true);
                Assert.AreNotEqual(result, Result.Progress, "The Task has to return Result.SUCCESS, Result.FAILED/BLOCKED after beeing cancelled!");
                RootNode.Clock.RemoveUpdateObserver(OnUpdateFunc);
                Stopped(result == Result.Success);
            }
            else if (_multiFrameFunc2 != null)
            {
                Result result = _multiFrameFunc2.Invoke(Request.Cancel);
                Assert.AreNotEqual(result, Result.Progress, "The Task has to return Result.SUCCESS or Result.FAILED/BLOCKED after beeing cancelled!");
                RootNode.Clock.RemoveUpdateObserver(OnUpdateFunc2);
                Stopped(result == Result.Success);
            }
            else
            {
                Assert.True(false, $"DoStop called for a single frame action on {this}" );
            }
        }
    }
}