Search code examples
c#unity-game-enginecollections

Which collection should I use for time-based game events in C#


What is the best collection for scheduling time-based events in games? I want to sort elements by time and check only first one

while (collection[0].TimeToTrigger <= time)
{
    collection[0].Trigger();
    collection.RemoveAt(0);
}

Whats wrong with basic collections:

  • List - moving elements on remove/insert
  • Queue - doesnt have insert

Solution

  • Pretty interesting and deep question. Don't get why it got dislikes. Probably because there is not enough details. For example, what kind of objects you gonna keep in this collection. The decision of which collection to use should be based on your estimation of how it should be used(that you also hadn't mentioned) and collection's operations O(n) complexity.

    But still, I daresay, I've got your problem, and you want to create a system for managing callbacks across the time. I don't know which class do you create for these purposes, but I'd use the collection like SortedDictionary<long, List<Action>>. Where long is a timestamp(it could be float or any else), and a list of the callbacks assigned to this time. Sorted dictionary has O(log(n)) complexity for elements adding and retrieving, and O(1) complexity to get first element. Also, the first element will always have the lowest timestamp.

    So far, you can add callback with a method like this:

    private readonly SortedDictionary<long, List<Action>> _callbacks = new();
    
    public void AddCallback(long timestamp, Action callback)
    {
        if (callback == null)
            return;
        
        Debug.Log($"Delayed callback submitted: {callback.Method.Name} with time = {new DateTime(timestamp)}");
        
        if (timestamp < _currentTime) // case handling for passed callback submission could be helpful, but not necessary.
        {
            Debug.Log($"Invoke delayed callback: {callback.Method.Name}");
            callback.Invoke();
            return;
        }
        
        if (!_callbacks.ContainsKey(timestamp))
        {
            _callbacks.Add(timestamp, new List<Action>());
        }
        _callbacks[timestamp].Add(callback);
    }
    

    And callback execution could be placed in Update method like this:

    private void Update()
    {
        _currentTime = // get current time the way you like.
        if (_callbacks.Any())
        {
            KeyValuePair<long, List<Action>> kvp;
            while (_callbacks.Any() && (kvp = _callbacks.First()).Key < currentTimeStamp)
            {
                // copy events before execution cause it could be modified while execution
                var callbacksCollection = new Action[kvp.Value.Count];
                kvp.Value.CopyTo(callbacksCollection);
                foreach (var callback in callbacksCollection)
                {
                    Debug.Log($"Invoke delayed callback: {callback.Method.Name}");
                    callback.Invoke();
                }
    
                _callbacks.Remove(kvp.Key);
            }
        }
    }
    

    Therefore, I'd also think SortedList is suitable for this issue. It has worse time complexity for its operations, but takes less memory. Also, there are a bunch of optimizations you can do with this code, like collection recreation if it becomes too big (Don't forget all collections in C# are not becomes smaller in memory, even when you remove all elements) or have some kind of pool for large callback sequences and so on. But, I'd like to say again, there is no single answer for questions like this. Moreover, you describe your problem so poor.

    PS. You can check this article to get brief review of sorted collections.