Interlock Helper class

The Interlock helper class can ease the locking of resources to prevent multiple requests to modify it. Usage example:

using RestServer;
using RestServer.Helper;
using UnityEngine;

public class TOMover : MonoBehaviour {
    public RestServer.RestServer server;
    
    private SimpleInterlock _lock = new SimpleInterlock();

    void Start() {
        server.EndpointCollection.RegisterEndpoint(HttpMethod.GET, "/move", MoveHandler);
    }

    public void MoveHandler(RestRequest request) {
        if (_lock.isRunning) {
            request.SendAsyncErrorResponse(423, "Animation is still running.");
        } else {
            ThreadingHelper.Instance.ExecuteAsyncCoroutine(DoAnimation);
            request.SendAsyncOkResponse();
        }
    }

    public IEnumerator DoAnimation() {
        using (var interlock = _lock.DoWork()) {
            // do animation
        }
    }
}

Interlock code example

This code example shows how to protect a unity animation or any long running process from being triggered twice by incoming requests. It handles requests, while the animation is running, gracefully.

Notes:

  • ReaderWriteLockSlim is used in favor of lock or other lock classes, so there is an easy and fast way to check if the animation is running
  • The if (_animationRunning == newState) { after entering the _lock.EnterWriteLock(); is mandatory and no mistake. There could be a second request that is handed out the write lock before we have aquired it and would result in starting the animation twice.
using System.Collections;
using System.Threading;
using RestServer;
using UnityEngine;

public class ExampleAnimation : MonoBehaviour {
    private RestServer.RestServer server;
    private readonly ReaderWriterLockSlim _lock = new();
    private bool _animationRunning;

    void Start() {
        server.EndpointCollection.RegisterEndpoint(HttpMethod.POST, "/animation/start", request => {
            if (IsAnimationRunning()) {
                // Quick and save check if the animation is running
                request.SendAsyncErrorResponse(423, "Animation is still running");
            }
            
            // Start the animation in the main rendering thread
            var animationStarted = ThreadingHelper.Instance.ExecuteSync(StartAnimation);

            if (animationStarted) {
                request.SendAsyncGetResponse("OK");
            } else {
                request.SendAsyncErrorResponse(423, "Animation is still running");
            }
        });
    }

    /// <summary>
    /// Start the animation and report the result
    /// </summary>
    /// <returns>False if animation is still running; true if it was started with this call.</returns>
    public bool StartAnimation() {
        if (!MarkAnimation(true)) {
            // animation already running
            return false;
        }
        
        // Animation can be started
        StartCoroutine(DoAnimation());

        return true;
    }

    private IEnumerator DoAnimation() {
        // Animation Step 1
        yield return new WaitForSeconds(1.0f);

        // Animation Step 2
        yield return new WaitForSeconds(1.0f);

        // Animation Finished
        MarkAnimation(false /* finished */);

        yield return null;
    }

    private bool IsAnimationRunning() {
        try {
            _lock.EnterReadLock();
            return _animationRunning;
        }
        finally {
            _lock.ExitReadLock();
        }
    }

    private bool MarkAnimation(bool newState) {
        try {
            _lock.EnterWriteLock();
            if (_animationRunning == newState) {
                return false;
            }

            _animationRunning = newState;
        }
        finally {
            _lock.ExitWriteLock();
        }

        return true;
    }
}