AnnulusGames/LitMotion

Make OnCopmplete, OnCancel LinkedList

Opened this issue · 0 comments

Related to #33
Poolable LinkedList will allow multiple complete/cancel callbacks with state.
Action is large class so, this also reduce GC allocation.
Many codes invoking callbacks or wrapping deletages (e.g. unitask integration) will be smarter.

public struct MotionCallbackData
{
    public byte StateCount;
    public bool IsCallbackRunning;
    public bool CancelOnError;
    public bool SkipValuesDuringDelay;
    public object State1;
    public object State2;
    public object State3;

    public object UpdateAction;
    public UnsafeActionList  OnCompleteAction;
    public UnsafeActionList OnCancelAction;
    
    public void OnComplete(){
        OnCompleteAction.InvokeAndDispose();
    }
    public void OnCancel(){
        OnCancel.InvokeAndDispose();
    }
   
}
public struct UnsafeActionList : IDisposable
{
    ActionList head;

    public void Append([NotNull] Action action)
    {
        ActionList.Append(ref head, action);
    }

    public void Append<TTarget>([NotNull] TTarget target, [NotNull] Action<TTarget> action) where TTarget : class
    {
        ActionList.Append(ref head, target, action);
    }
    public void RemoveTarget(object target)
    {
        ActionList.RemoveTarget(ref head, target);
    }
    public void Remove(object target,object action)
    {
        ActionList.Remove(ref head, target,action);
    }
    public void Invoke()
    {
        if (head == null) return;
        head.Invoke();
        head = null;
    }

    public void InvokeAndDispose()
    {
        if (head == null) return;
        head.InvokeAndDispose();
        head = null;
    }

    public void Dispose()
    {
        if (head == null) return;
        head.Dispose();
        head = null;
    }
}

public sealed class ActionList : IDisposable
{
    static ActionList pool;
    object target;
    object action;
    ActionList nextNode;
    public ActionList LastNode
    {
        get
        {
            var next = this;
            while (next.nextNode != null)
            {
                next = next.nextNode;
            }
            return next;
        }
    }
    ActionList()
    {
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static ActionList CreateOrGet()
    {
        if (pool == null)
        {
            return new ActionList();
        }
        else
        {
            ref var poolRef = ref pool;
            var result = poolRef;
            poolRef = result.nextNode;
            result.nextNode = null;
            return result;
        }
    }

    public static void Append(ref ActionList head, [NotNull] Action action)
    {
        if (action == null) return;
        if (head == null)
        {
            head = CreateOrGet(null, action);
        }
        else
        {
            head.LastNode.nextNode = CreateOrGet(null, action);
        }
    }

    public static void Append<TTarget>(ref ActionList head, [NotNull] TTarget target, [NotNull] Action<TTarget> action)
        where TTarget : class
    {
        if (target == null)
        {
            Debug.LogError("target is null");
            return;
        }

        if (action == null) return;
        if (head == null)
        {
            head = CreateOrGet(target, action);
        }
        else
        {
            head.LastNode.nextNode = CreateOrGet(target, action);
        }
    }
    public static void RemoveTarget(ref ActionList head, object target)
    {
        if (head == null) return;
        if (head.target == target)
        {
            head = head.nextNode;
            return;
        }
        var next = head;
        while (next.nextNode != null)
        {
            if (next.nextNode.target == target)
            {
                next.nextNode = next.nextNode.nextNode;
            }
            next = next.nextNode;
        }
    }
    public static void Remove(ref ActionList head, object target,object action)
    {
        if (head == null) return;
        if (head.target == target&&head.action==action)
        {
            head = head.nextNode;
            return;
        }
        var next = head;
        while (next.nextNode != null)
        {
            if (next.nextNode.target == target&&next.nextNode.action==action)
            {
                next.nextNode = next.nextNode.nextNode;
            }
            next = next.nextNode;
        }
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ActionList CreateOrGet(object target, object action)
    {
        var result = CreateOrGet();
        result.target = target;
        result.action = action;
        return result;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Invoke()
    {
        var next = this;
        InvokeLabel:
        try
        {
            if (next.target != null)
                UnsafeUtility.As<object, Action<object>>(ref next.action)(next.target);
            else UnsafeUtility.As<object, Action>(ref next.action)();
        }
        catch (Exception ex)
        {
            Debug.LogException(ex);
        }

        var nextNext = next.nextNode;
        if (nextNext != null)
        {
            next = nextNext;
            goto InvokeLabel;
        }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void InvokeAndDispose()
    {
        var next = this;
        InvokeLabel:
        try
        {
            if (next.target != null)
                UnsafeUtility.As<object, Action<object>>(ref next.action)(next.target);
            else UnsafeUtility.As<object, Action>(ref next.action)();
        }
        catch (Exception ex)
        {
            MotionDispatcher.GetUnhandledExceptionHandler()?.Invoke(ex);
        }

        next.action = null;
        next.target = null;
        var nextNext = next.nextNode;
        if (nextNext != null)
        {
            next = nextNext;
            goto InvokeLabel;
        }

        next.nextNode = pool;
        pool = this;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void Dispose()
    {
        var next = this;
        InvokeLabel:
        next.action = null;
        next.target = null;
        var nextNext = next.nextNode;
        if (nextNext != null)
        {
            next = nextNext;
            goto InvokeLabel;
        }

        next.nextNode = pool;
        pool = next;
    }
}