# Loop over a list with within X seconds

I have a list of harvested crops. There's a capacity of let's say 100 crops(100 items in a list). Now I want to empty the list within X seconds when max full (2 sec in my case). This should be relative to the max cap. If the list is 100 it should take 2 sec if 50 then 1 sec accordingly. What should be the approach for achieving that?

The reason for this is that I want to iterate over each crop item and animate them towards some point for selling. I have tried for loop but it's not quite giving me the right time as my max cap changes over time when the user upgrades but I want the animation time to be constant to 2 sec when the list is maxed up.

This is what I tried:

 private IEnumerator MoveHarvestToStore()
{
float startTime = Time.time;
animationTimeForSelling = 2.0f * trunkCapacityFilled / trunkTotalCapacity;
Debug.Log("Anim Time = " + animationTimeForSelling);

for (int i = pickablesInHarvester.Count - 1; i >= 0; i--)
{
float animTime = animationTimeForSelling / pickablesInHarvester.Count;
yield return new WaitForSeconds(animTime);
}
float endTime = Time.time;
Debug.Log("Time took = " + (endTime - startTime));
}


The problem with the above code is that this is not consistent in terms of time. Following are the logs with the same trunkCapacityFilled and trunkTotalCapacity every time:

Anim Time = 2, Time took = 2.17

Anim Time = 2, Time took = 2.15

Anim Time = 2, Time took = 2.16

Anim Time = 2, Time took = 2.213

Anim Time = 2, Time took = 2.13

• "I have tried for loop" Show us. Nov 23 '21 at 12:22
• @DMGregory updated Nov 23 '21 at 12:43
• @DMGregory I have updated my findings and logs as well. I need anim time and actual time is taken to be the same. These logs are with trunkTotalCapacity = 150 and trunkCapacityFilled =150 Nov 23 '21 at 13:32
• This looks like what we'd expect, no? Your code asks for the operation to finish in 2*150/150 = 2 seconds, and we find that the end time is 2 seconds plus a small margin due to rounding to the next frame on each iteration. So we've landed quite close to the requested time. You can land closer by adjusting for the rounding on each frame. How does this differ from what you want/expect? Nov 23 '21 at 13:47
• If the capacity goes to 350 then it starts to fluctuate even larger like 2.3s I can accept bit lower fluctuations like 2.05s or something like that but larger values are not what I want. I didn't get what you meant by saying about "rounding to the next frame" Nov 23 '21 at 13:59

Remember that WaitForSeconds waits at least that long. It's only ticked once per frame, so if a frame lands slightly before the waiting deadline, you don't resume until the next frame later - which might be nearly 16 ms later than planned if you're running at 60 FPS. So if you have an average of 8 ms extra delay per item, times 350 items, that could slow down your full iteration by nearly 3 seconds in total!

What you can do is keep track of how long you've actually waited at each step. So if the next frame lands a little later than your desired wake-up time, you subtract the time you overslept from your next alarm, to correct for the error. That way the error does not have an opportunity to accumulate.

float accumulator = 0f;
for (int i = pickablesInHarvester.Count - 1; i >= 0; i--)
{

float animTime = animationTimeForSelling / pickablesInHarvester.Count;
accumulator += animTime;
while (accumulator > 0) {
yield return null;
accumulator -= Time.deltaTime;
}
}

• Where is WaitForSeconds in this? After while loop? Nov 23 '21 at 15:09
• There is no WaitForSeconds at all, as you can see. We yield return null to wait exactly one frame at a time, and update our counter by the amount of time that's passed. This way we can handle iterating lists where the wait between items is actually less than a frame, as well as having good precision even if Time.time is large. Nov 23 '21 at 15:10
• Oh! You did it! Yes, this is exactly what I wanted. Thank you 2000 times! Nov 23 '21 at 15:17

Divide your current amount by max amount and you'll get the percentage amount of items that you have, then multiply this number by you full time and you'll end with the seconds to empty the current bag.

currentAmount / maxAmount * maxTime = x