- Home /
Execution order of static variable value change in update
A student in a game design class "hacked" a plate stacking function ingeniously: using a third person controller with a trigger box attached in front, he can collect "plates" staggered on a plane "On trigger enter" by pressing a button (position of plate object goes off screen) and drop all collected plates by pressing another button (position of plates goes to trigger box position), at first the plates would spawn overlapping, and here is the hack: He used a static variable "Object Counter" on a script attached to each plate, it increases on each pick, and decreases on dropping. He passed the value of the counter to the Y position of the object (x & z are determined by trigger box position) and the plates are dropped in a stack! I was surprised that each plate always had a unique value for counter, even if all are dropped on the same Update call. I've seen threads about order within an Update cycle being undetermined, but is this problematic in a meaningful way? He should probably do a for loop, and have an array of picked plates, but I was impressed how one line of code with a static counter in multiple GOs updates worked so smooth. I believe the order is out of his control, but are there fundamental flaws to this approach?
Answer by Fragsteel · Mar 09, 2020 at 10:18 PM
If the object counter variable is being incremented/decremented immediately after each put/drop, before another can be put/dropped, I don't see how order can make a difference.
You are correct that the order that Update() is called between GameObjects is generally arbitrary (assuming he didn't change the script execution order). But if he sets a static variable, it will be applied instantly, and will therefore be changed before the next Update() is called.
If I can make some assumptions about how the code is written, that static function can basically serve as the first available "slot" in a stack of plates, where each "slot" is a Y position. For simplicity's sake, I'll say that each plate is 1m tall (even though that's almost certainly not true) just so that it gets incremented by 1 each time.
Before any plates are dropped, the number of dropped plates is 0, so the first available "slot" is Y=0. Then if a plate's Update() tells it to be dropped, it will appear at Y=0, and immediately set that "slot" to 1 before any other Update() functions get to be called. So then when the next plate hits its Update() (no matter which plate) the "slot" will be 1, and it'll appear at Y=1, on top of the first plate, and set the slot to 2, etc.
I have a slight feeling that it's not 100% understood what a static variable does. Each plate never in fact "had a unique value for counter" because that's impossible for a static value; all instances of a class share the same value for a given static value, as static means it's not tied to an object/instantiation, but the class itself. It's just that each time a plate drops, the value that all plates share gets changed before another plate uses it.
I do think it's a clever solution. The only thing I don't like about it is that it would not work if there were multiple trigger boxes, as dropping a plate on one box would cause plates dropped on another trigger box to appear elevated. I'd instead have that value as a non-static value on a script on the trigger box. I'd also personally want to move the logic that makes the plates appear out of Update(), instead triggered by events, but that's just me.
Thanks for the fast reply, the slot analogy does indeed work. What surprised me was that I was under the assumption that an update call on multiple game objects, each accessing the same static variable in the same Update frame, would have received the same value for that variable, even if within that update cycle the variable gets changed. I guess it's not actually a parallel operation, unity must somehow cycle all gameobjects and call update in a sequence. If I recall, this is deter$$anonymous$$ed by the location of the gameobject on the memory "stack"... As this was a game design class, not program$$anonymous$$g, the gameplay loop rather than the code take priority, and the student came up with a "simple" solution!
Yup! Pretty much any $$anonymous$$onobehaviour function you can work with (Start, Update, etc.) is called on the main thread. Generally, nothing's really parallel in Unity unless it's something more in the background or separate from your scripts (like a lot of rendering stuff on the GPU), you explicitly called an async Unity function (like Jobs stuff), or if you started a separate thread yourself (via normal C# threading stuff).
Even coroutines, which feel asynchronous, are actually on the main thread, and just run before most logic each frame, and basically just pause for a frame when you call yield return
.
This diagram is quite helpful: https://docs.unity3d.com/$$anonymous$$anual/ExecutionOrder.html
So generally, until you're pretty advanced, you don't have to think about asynchronous code unless you want to implement something specific yourself.
It's still important that you recognize that the order between GameObjects having their $$anonymous$$onobehaviour event functions called is arbitrary. That can come into play if a method in one object assumes that a method in another object has already gone off. This usually causes issues in Start() and Awake(), less so Update().
For example, say you have Object A and Object B with different scripts. Object A has some value called "target", and in Start() it calls something like`ShootAt(target);`. But say you don't define what its target is in the Inspector. Object B's Start() has something like FindObjectOfType<ObjectA>().target = this;
so that as soon as its Start() goes off, Object A knows what to shoot at. This might cause problems, but might not. If Object B's Start() goes off first, Object A will know what to shoot at when its Start() goes off. But if Object A's Start() happens first, you'll get a null reference exception since target wasn't set yet.
recreated the effect for testing, add this to multiple identical game objects.
public class staticCounter : $$anonymous$$onoBehaviour
{
public static int counter = 0;
public int myCounter = 0;
void Update()
{
if (Input.GetButtonDown("Fire2"))
{
myCounter = counter;
Debug.Log(gameObject.name + "has counter of" + counter);
counter--;
}
}
void On$$anonymous$$ouseDown()
{
counter++;
Debug.Log(gameObject.name + "has counter of" + counter);
}
}
Your answer
