Thanks for pointing this out.
Well, I guess my original point applies to Odin instead of Go then.
> > You need to make sure entities are always updated in the same order. This means deterministic O(1) datastructures like pools are your friend.
> That's not actually required. But I guess Go makes this the least painful avenue forward?
The formulation isn't complete but they're correct for what they're getting at. It should be:
"You need to make sure entities are always updated in the same order iff the order matters to the end result."
For example if you move entity A before entity B and collision check on move to make sure it's valid if A and B would intersect after moving without collision checking you will end up with different positions of A and B depending on which goes first.
Your choices then are to make sure entity update order is deterministic or rework the problem so it doesn't have the ordering dependency. The latter can be quite hard to do and the conditions where there is an ordering dependency can be subtle and unintuitive making it hard to cover all cases.
They're doing a duplicate of the state not because it's required in the language or whatever, but because they want to predict something without affecting the actual state. The only way of doing that is running the actual code against a new state duplicate from the existing, actual state.
Not sure what the language has to do with it, you'd have to do the same in any language, explicitly or implicitly.
Git and bcachefs work the same way, btw: you never really change to anything, you just make a new version (that shares most of its storage with the previous version), and then at the end update a pointer.
Making a 'copy' in these systems is basically O(1), but making updates might cost you O(log n) (details depend on the data structure in question, some can do O(1)), and in general you want some form of garbage collection, or some type system that really cares about tracking these kinds of thing statically; think something like Rust's borrow checker on steroids.
> Not sure what the language has to do with it, you'd have to do the same in any language, explicitly or implicitly.
If instead your language or system encourages mutation, then making a complete O(n) copy of your state is the easiest way to get the desired behaviour.
As a random tangent, whilst memcpy in the general case is going to be O(n) given that in this program the n is a fixed size the operation in context is always going to be O(1). Fundamentally though it's not a very good way to compare the performance profiles of the approaches to this problem on real hardware.
Well, with a fixed n, every operation trivially becomes O(1).
> This isn't really trying to emulate copy-on-write though. It's intentionally making a copy temporarily to mutate it further whilst keeping the original ready to be mutated on the next tick. They share superficial similarity but that's about it.
Having to copy everything is exactly what you need to achieve this effect, when you don't have copy-on-write. You need to make a copy somewhere. (Or otherwise, explicitly roll-back any changes you made.)
Right, but still, that's an implementation detail. It could have been implicit, or it can (like in the authors case be explicit), how is that important?
I'm well aware of languages that does this implicitly, as I'm mostly writing Clojure code which is all about immutability and persistent data structures. I still don't understand why you want to highlight that this could be implicit instead of explicit, what the author had to do wouldn't change really.
> For this reason we duplicate the entire game state into a new, temporary render-only game state, and simulate the tick on this one. That way the fixed timestep game state stays untouched.
Of course, because it's Go, they have to duplicate everything explicitly. A poor-man's copy-on-write.
> Tangent: determinism and replay
> You need to make sure entities are always updated in the same order. This means deterministic O(1) datastructures like pools are your friend.
That's not actually required. But I guess Go makes this the least painful avenue forward?