Voxel based world, best practice for sending information

Started by
20 comments, last by hplus0603 11 years, 6 months ago

You could use a separate seed for placing resources, so you send the normal terrain seed, and then send the visible resources individually (not by sending the seed)


That is a very interesting concept... separate them out and never share that seed with the client. Send resources to the client as they get near the chunk... very interesting indeed.
Advertisement

We have a very massive world that is created with a perlin noise algorithm.

First: Is the algorithm deterministic?

No. Floating point arithmetic is not really deterministic. It is highly dependent on compiler settings, compiler mood (storing an intermediary value in memory causes a rounding while no rounding is done if compiler choosed/managed to use/reuse a register instead) and current FPU state. If you have any code in form of "if(noise_float < 0.5) spawn_block()" then sooner or later it will give different results if not the exact same binary is running.

Playing with fire.
No. Floating point arithmetic is not really deterministic.[/quote]

I'd like to correct this statement on two parts:

First, not all algorithms need floating point.

Second, floating point is fully deterministic within the restrictions of a particular instruction set architecture.
If all CPUs run the same code, they will all come to the EXACT same result.

If some CPUs run an SSE2 path, and others run a x87 path, then no, they will not be deterministic. So don't do that.
If some CPUs run an x86 path, and others run a x86_64 version, then no, they will not be deterministic. So don't do that, either.
If some CPUs run with 80 bits internal precision, and others run with 64 bits internal precision, then on, they will not be deterministic. You have to avoid that, too. Set the floating point control word (rounding, precision) to a known value before you start computation to control for this.

Once all that's done, you can use floating point just as deterministically as any other user-level instruction set architecture feature.
enum Bool { True, False, FileNotFound };

No. Floating point arithmetic is not really deterministic.


I'd like to correct this statement on two parts:

First, not all algorithms need floating point.

Second, floating point is fully deterministic within the restrictions of a particular instruction set architecture.
If all CPUs run the same code, they will all come to the EXACT same result.

If some CPUs run an SSE2 path, and others run a x87 path, then no, they will not be deterministic. So don't do that.
If some CPUs run an x86 path, and others run a x86_64 version, then no, they will not be deterministic. So don't do that, either.
If some CPUs run with 80 bits internal precision, and others run with 64 bits internal precision, then on, they will not be deterministic. You have to avoid that, too. Set the floating point control word (rounding, precision) to a known value before you start computation to control for this.

Once all that's done, you can use floating point just as deterministically as any other user-level instruction set architecture feature.
[/quote]

um, would floats be deterministic in a C# program compiled for "any cpu"?
would floats be deterministic in a C# program compiled for "any cpu"? [/quote]

I don't know; you have to check what guarantees the CLR makes regarding that. My guess would be "no."

My point is that the CPU itself and instruction set architecture is deterministic. The only non-deterministic parts come in simplifying assumptions sometimes made by software developers, but you don't *have* to make those assumptions, and those the CPUs (with floating point) *can* be deterministic.

enum Bool { True, False, FileNotFound };

No. Floating point arithmetic is not really deterministic.

I'd like to correct this statement on two parts:

First, not all algorithms need floating point.
[/quote]
Erm, if it is not using floating point then it is not floating point arithmetic. What on earth are you correcting? Some form of straw-man?

PS. OP specifically mentioned Perlin noise, whose implementations almost exclusively use floating point as fixed point is just too slow there (coincidentally, some time ago i explored options to drop floating point in regards with simplex/perlin noise, but gave it up as it is just way too slow). Of course, whether or not OP uses floating point does not invalidate what i said either way.


Second, floating point is fully deterministic within the restrictions of a particular instruction set architecture.
If all CPUs run the same code, they will all come to the EXACT same result.
... then sooner or later it will give different results if not the exact same binary is running.

Which is what i said :/. So, no complaints here either.


Once all that's done, you can use floating point just as deterministically as any other user-level instruction set architecture feature.

Your list omitted the primary source of inconsistencies (which is odd as i specifically mentioned it in my post): stack vs register usage. Compiler decides when and where stack is used when it runs out of full precision registers. Even with VC "precise" compiler flag - platform / OS limitations remain:
* Different OS's have different FPU precision defaults (Win x86 - varying, Win x64 - 64bit, Unix/linux usually - 80bit). Also, VC "precise" allows compiler to ignore _controlfp.
* Even with VC "precise" (which requires extra rounding code to enforce specific precision in addition to other more restrictive/slower semantics) - rounding behavior is CPU architecture specific and differs between x86, ia64 and amd64 (Where x86 and amd64 are specifically mentioned as "This particular semantic is subject to change." Ie. you are essentially in the "unspecified" land).

Playing with fire. Might help roast beef - or roast you. I am not stopping anyone - just warning "if exact repeatability is required - stay away from floating point".
To avoid unnecessary updates one could also cache chunk data on the client side and essentially version the chunks. So before resending a whole new chunk (after the player walked away and back) you can first check if the "version" on the server has changed. Most of the other things like only sending deltas etc has already been said.

Erm, if it is not using floating point then it is not floating point arithmetic. What on earth are you correcting? Some form of straw-man?


My answer was:



To which you answered:

[quote name='tanzanite7']No. Floating point arithmetic is not really deterministic.


So, it seemed to me that you were jumping to conclusions, and/or attacking a straw man, hence I aclled that out.
Also, the statement you made as quoted right here is clearly not factual.

PS. OP specifically mentioned Perlin noise, whose implementations almost exclusively use floating point as fixed point is just too slow there[/quote]

That's funny, given that the original Perlin noise was specified in terms of byte-size integer values, and it has been implemented on GPUs with 8 bits per component fixed-point colors. It can also be implemented with MMX or plain integer registers in fixed point with good performance. Thus, I have to also say that I don't agree that fixed-point implementations of Perlin noise are slower than floating point.



No, I explicitly included it:

[quote name='hplus0603']If some CPUs run with 80 bits internal precision, and others run with 64 bits internal precision, then on, they will not be deterministic. You have to avoid that, too. Set the floating point control word (rounding, precision) to a known value before you start computation to control for this.


[quote name='tanzanite7']I am not stopping anyone - just warning "if exact repeatability is required - stay away from floating point".
[/quote]

You were saying that floating point arithmetic is not deterministic, and I disagree, and I think I have literature and facts to back me up. If you do it right, it is totally deterministic. Many shipping games rely on this fact, including some I've worked on.

I think we can both agree that it's not for beginners. It seems like riuthamus is not a beginner, though, and thus may benefit from the actual facts to make an informed decision.
enum Bool { True, False, FileNotFound };
Thank you all, what you have provided is some very valuable insight. It has, at the very least, helped to affirm some of the methods we have chosen to take. I will update you guys with any further questions and progress we obtain. This should help us to get the multiplayer alpha test started.
In general it's best to stay away from floating points if you need determinism, although it can be made deterministic. Some algorithms can work fine off fixed point maths, which will be deterministic, but limited in precision. You can certainly use pseudo-random generators to build your world that way.

I've used two deterministic system based on floating point vector maths. One for a replay system, one for a network FPS. Both involved rather complex collision systems, one rigid body physics, so it is possible. None were cross platform, one was on PC supporting AMD and Intel architectures.

Once you start relying on determinism, you need to be very careful and provide debugging tools to detect determinism breaking up as soon as possible. Once it goes wrong, it stays wrong.

Everything is better with Metal.

This topic is closed to new replies.

Advertisement