Will an instance of this struct be allocated on the stack?

I have a struct called Timer:

public struct Timer
{
    private readonly float _duration;

    public float Time { get; private set; }

    public Timer(float duration)
    {
        _duration = duration;
        Time = duration;
    }

    public void Tick(float deltaTime)
    {
        if (Time == 0)
            return;
 
        Time -= deltaTime;

        if (Time < 0)
            Time = 0;
    }

    public void Reset()
    {
        Time = _duration;
    }

}

I create an instance of it inside of a coroutine.

private IEnumerator StartTimer()
{
    var timer = new Timer(5f);

    while (timer.Time > 0)
    {
        timer.Tick(Time.deltaTime);
        yield return null;
    }
    print("Time's up!");
}

I know that value types inside of a method are stored on the stack, but an exception to this is if they are a part of an iterator block. So if I instantiate Timer inside of a coroutine, does that mean that it’s a part of an iterator block and will not be allocated on the stack?

Yes, that’s true.

Structs are value types. This fact does not say anything about where they are stored. value types are directly stored inside the variable that holds the instance. So the data / memory of that struct lives where the variable itself is stored. So local variables inside a method are allocated on the stack and therefore value type instances in such variables are stored on the stack. However if the variable that holds the value type is stored on the heap (which is the case for member variables of a class) the value type is also stored on the heap as part of the housing class / type. The same holds true for arrays of value types. An array in C# is always allocated on the heap.

Since coroutines / iterators are implemented though some compiler magic that creates a hidden class out of your code where all local variables become member variables of that class, your Timer struct is essentially stored on the heap.

Though I don’t quite see why this is relevant. Stack and heap are the same kind of memory, just the management (allocation / deallocation) is different.

Keep in mind that “creating” a value type does never allocate any memory. The memory for the value type has to exist already. Either as part of a class or as part of a stack frame which is allocated when you enter a method and released when you leave the method. Note that creating a value type does not create anything but just initializes the memory for the value type.

Over here I’ve written a coroutine crash course which may help to better understand iterator blocks.

1 Like

Thank you. I think I understand. In your opinion, is there any benefit to making Timer a struct instead of a class?

I think you’re worrying about things too much. :slight_smile: I’ve been using Unity for 8 years now and never once concerned myself with where stuff was being allocated.

Also, if you just need something to happen after some amount of time has passed, keep it simple and keep it generic.

Here’s how I do it:

Includes comment below it showing you how to do pretty much anything with it.

2 Likes

I guess I just wanted a better understanding of how structs work and when to use them. In your opinion, should my Timer be a class or a struct?

Do you understand the implications of passing a struct vs a class into a function that changes the contents of that object (e.g., sets a variable)?

I know that passing by value means that a copy of that value is made in memory, so if you’re passing something by value into a function, that function would only modify a copy of that value, not the original value. Passing by reference means that the memory address is passed into the function, so the original value would be changed in that case. I don’t think I’m going to be passing in the instance of Timer as an argument to any method. I just meant in terms of garbage collection, would it make any difference if Timer was a struct?

Not a useful difference. I don’t imagine you’ll be making / releasing a ton of timers per frame, right??

Yea, I guess you’re right. Anyways, you have a point about passing by value/reference. If my Timer is mutable, then that should imply that it should be a class, not a struct.

2 Likes

Also I just realized that all I did was unnecessarily re-create Unity’s WaitForSeconds. I should either use that, call Invoke, or use your CallAfterDelay script.

3 Likes