Create a rolling 3-second average using a queue but using elapsed time for averaging

Hey everyone, I’ve spent hours on this and I just can’t figure it out - from my research I’ve seen that a queue is the most efficient way of doing this but I can’t quite figure out how to modify my code to create a rolling 3-second average of my variable - power. I think I have too many elements in the queue as my averaging is really slow but I don’t know why I would need to restrict the number of elements in the window if I just gather as many as were there over the 3 second window.

Thanks in advance for your help xxx

private Queue<float> dataQueue = new Queue<float>();
private float currentSum = 0;

public float movingAverage = 0; // Store the calculated moving average

private float timeInterval = 3; // Time interval in seconds for calculating the moving average
private float elapsedTime = 0;

void Update()
{
    AddData((float)speedboi.GetPower());
    {
        elapsedTime += Time.deltaTime;
        if (elapsedTime >= timeInterval)
        {
            CalculateMovingAverage();
            elapsedTime = 0;
        }
    }
}

public void AddData(float newValue)
{
    dataQueue.Enqueue(newValue);
    currentSum += newValue;

    // Remove outdated data from the queue
    while (dataQueue.Count > 0 && Time.time - dataQueue.Peek() > timeInterval)
    {
        currentSum -= dataQueue.Dequeue();
    }
}

public  void CalculateMovingAverage()
{
    if (dataQueue.Count > 0)
    {
        movingAverage = currentSum / dataQueue.Count;
    }
    else
    {
        movingAverage = 0; // No data in the queue, set average to 0
    }
}

public float GetAveragePower()
{
    return movingAverage;
}

So just to make sure i understood correct, what you want to do is - let’s say you have a health stat , you want to be able to have that stat monitored and always be able to get the average health during the last 3 seconds (for example) , right ?

Personally i would just use a good ol’ List and store the data as a pair of <Stat,TimeStamp> , here’s a code example of how i would do it

public class StatAverage
{
    private struct DataTimestampPair
    {
        public float timestamp;
        public float data;
    }

    private float avgTimer;
    private List<DataTimestampPair> timeline;

    public StatAverage(float avgTimer)
    {
        this.avgTimer = avgTimer;
        timeline = new List<DataTimestampPair>();
    }

    // use this method to add entries to the timeline
    // here , it is assumed that we will always add the entries in order , aka first -> last
    public void Add(float data, float timestamp)
    {
        timeline.Add(new DataTimestampPair() { data = data, timestamp = timestamp });
    }

    public float GetAverage(float currentTimestamp)
    {
        int i = 0;

        // since the timeline is assumed be ordered
        // just iterate until you find an element that falls within the "avgTimer" duration
        // this can be more efficient if you go from end -> start , but you get the idea
        for (; i < timeline.Count; ++i)
        {
            // get the different between the entry and the current timestamp
            float diff = currentTimestamp - timeline[i].timestamp;
        
            // as soon as we encounter the first entry that falls within the "timer" interval , we stop
            if (diff <= avgTimer)
                break;
        }

        //  delete the "expired" entries
        {
            timeline.RemoveRange(0, i);
        }

        // here you iterate of entries that fall within the "avgTimer" duration

        if (timeline.Count == 0)
            return 0;

        float sum = 0;
        foreach (var entry in timeline)
        {
            sum += entry.data;
        }

        return sum / avgTimer;
    }
}

NOTE:

The code above is supposed to be a “good enough” start to get things going, you can make it more precise/optimized if you need , one case where this can be inaccurate is a case like this


You can see in this example that the last two value fall within the green “timer” zone , but if we do a naive ( val1 + val2) / 2 , we miss that “rapid rise” that happened at the start of the range but wasn’t accounted for with “simple” avereging, ill leave that kind of polish to you :slight_smile:

Your code looks mostly correct, but the issue was in how you’re adding data to the queue. You should be adding the actual data value (newValue ) to the queue, not the result of (float)speedboi.GetPower() . Ensure that newValue represents the power value you want to average.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RollingAverage : MonoBehaviour
{
    private Queue<float> dataQueue = new Queue<float>();
    private float currentSum = 0;

    public float movingAverage = 0; // Store the calculated moving average

    private float timeInterval = 3; // Time interval in seconds for calculating the moving average
    private float elapsedTime = 0;

    void Update()
    {
        AddData((float)speedboi.GetPower());
        elapsedTime += Time.deltaTime;
        if (elapsedTime >= timeInterval)
        {
            CalculateMovingAverage();
            elapsedTime = 0;
        }
    }

    public void AddData(float newValue)
    {
        dataQueue.Enqueue(newValue);
        currentSum += newValue;

        // Remove outdated data from the queue
        while (dataQueue.Count > 0 && Time.time - dataQueue.Peek() > timeInterval)
        {
            currentSum -= dataQueue.Dequeue();
        }
    }

    public void CalculateMovingAverage()
    {
        if (dataQueue.Count > 0)
        {
            movingAverage = currentSum / dataQueue.Count;
        }
        else
        {
            movingAverage = 0; // No data in the queue, set average to 0
        }
    }

    public float GetAveragePower()
    {
        return movingAverage;
    }
}