News for hackers. Stuff that matters.
Re:Fixed Timestep Without Interpolation by eru on Friday October 18, 2024 @07:51AM (#41878516)
It's somewhat amusing, that someone smart working in a language like Go would come to the same conclusion that Haskell practically forces on you (unless you work to avoid it): by default, your state should be copy-on-write.

> 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?

Re:Fixed Timestep Without Interpolation by pixelpoet on Friday October 18, 2024 @07:54AM (#41878535)
Pretty sure that's Odin, not Go.
Re:Fixed Timestep Without Interpolation by eru on Friday October 18, 2024 @08:00AM (#41878577)
Oh, I used my browser's `inspect` on the source code snipped posted, and they were using Go syntax highlighting, so I assumed the code is written in Go.

Thanks for pointing this out.

Well, I guess my original point applies to Odin instead of Go then.

Re:Fixed Timestep Without Interpolation by meheleventyone on Friday October 18, 2024 @08:56AM (#41879001)
> > 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?

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.

Re:Fixed Timestep Without Interpolation by diggan on Friday October 18, 2024 @09:05AM (#41879074)
Maybe I misunderstand your comment, but seems like you're misunderstanding the contents of the article/the point?

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.

Re:Fixed Timestep Without Interpolation by eru on Saturday October 19, 2024 @04:16AM (#41886440)
In Haskell (and Erlang etc), all data structures 'want' to be copy-on-write by default. That means you don't need to do anything extra to act as if your code is running a against a duplicate: it's behaving like that anyway by default.

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.

Re:Fixed Timestep Without Interpolation by meheleventyone on Saturday October 19, 2024 @06:40AM (#41887017)
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.

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.

Re:Fixed Timestep Without Interpolation by eru on Sunday October 20, 2024 @12:47AM (#41892929)
> 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).

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.)

Re:Fixed Timestep Without Interpolation by diggan on Saturday October 19, 2024 @08:22AM (#41887405)
> In Haskell (and Erlang etc), all data structures 'want' to be copy-on-write by default. That means you don't need to do anything extra to act as if your code is running a against a duplicate: it's behaving like that anyway by default.

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.