-
-
Notifications
You must be signed in to change notification settings - Fork 478
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Synchronize simulations #75
Comments
Hi! I assume this is for networking? The waves are mostly deterministic - the shaders current use _Time which unity sets to Time.time. ShapeGerstnerBatch.shader is responsible for adding gerstner waves to create the ocean surface shape. I've done a small amount of networking so i get the basic overview, but I've not done it in Unity so i dont know if Time.time is sync'd or not, or what is required? The dynamic wave simulation is not deterministic, and not trivially cloned across a network - these are used to create boat wakes and other interaction effects. These are copied on top of the gerstner waves and are then read back to the CPU for physics. It would be worth just letting these simulate independently on clients and seeing how bad they diverge in practice and how badly this affects your game. In my extremely brief career as a network programmer i was actually working on networking boats and it was ok for them to be in slightly different positions. we networked boat control inputs and then the clone(s) could stray slightly but were nudged to match the master boats position with a force, and seemed like it was good enough for our game. however the networking part of this game did not ship and I'm not experienced enough to know if what i've described is a good solution or can generalise to other games. if the waves do need to exactly match, an option would be to copy the waves to the CPU before dynamic waves are added on top, or to simulate in the 1 or 2 of the most detailed lods only and use the higher lods for the physics. We could discuss these more if useful.. |
Ya I wasn't even going to attempt to sync the dynamic waves. Basically the generation has to be consistent over time, not per method call. The latter won't be consistent across clients. So anything you are adding/multiplaying needs to be scaled by the same time source on all clients. So you start with a configurable time source that C# and the shader both use. So when you get an external time update you just increment that with deltatime. So you only have to pass the time to the shader every few seconds or so at most. As for GenerateWaveData , it's not deterministic due to being called in Update. There are several variations I think of the same core approach of making it consistent over time. You could generate the data at a fixed rate and then lerp over it using deltatime to smooth it out in Update. Or do the same for just the random values. Or precompute the random values, basically one big curve with the values being the points on the curve, then lerp through those. |
Oh no I guess i havent been clear - the animated waves are stateless. If we're not considering the dynamic waves, then we don't have to worry about simulation states being progressed forward at the same delta time or anything like that. To be explicit: You tell it time=55.3s and what the wave conditions look like (the power spectrum), and it will generate the ocean surface at that time. As I mentioned above though its currently hardcoded to use _Time which is Time.time, but thats easy to change to whatever your network time is. It just occurred to me though that the collision for physics etc is read back from the GPU and this comes with a couple of frames latency. I've not got my head around what this means for networking, but I'm not sure its a good thing. This has been the best path for Crest to support the crazy range of shapes and dynamic waves that are possible. If only gerstner waves are being used then it would be possible to just compute the gerstner waves in C#, thats the get out of jail card here. I believe this is a workable solution. There is code for this - i think i cleaned it out recently but it could be brought back. |
Oh just read your reply again - generate wave data doesn't need to be called each frame, it's only for convenience to regenerate based on wave spectra on the fly. it generates time independent data like amplitudes and phases and directions. If it was time dependent we'd need to generate it with network time - it's also stateless so we don't need to worry about dts. |
Ah ok so not the issue I thought it was. Collisions, some might want to do full physics syncs with boats, my experience is that doesn't work so well. By the time you factor in buoyancy and ship/ship collisions, if you try to do a full physics sync it's going to look bad due to all the corrections. If you relax it a bit, which is usually fine because you don't need the precision like you do for characters, it looks and feels better. |
I put up a gist with a time provider you can use for networked time. Verified to work with multiple clients. https://gist.github.com/gamemachine/209676c14d383eb6c155e082825dca92 ShapeGerstnerBatched.cs should create an instance of CrestTimeProvider, and call GetWaterTime every update. Then just add a new uniform _WaterTime float to the shader and use that in place of _Time.y. Update that in UpdateBatch where it sets the rest of the material vars. CrestTimeProvider should be used as the source on both client and server. I use StopWatch so that the implementation is consistent on both ends and regardless of whether you use Unity on the server or not. The constructor takes a bool to say whether to use network time. If false it will provide the local stopwatch time. This doesn't have logic for when server resets it's time. Simple enough just if diff > a minute or whatever, reset. Server times have to be reset about once a day or so, otherwise you would start to lose the precision needed for smooth waves. |
Thanks for sharing! Interesting. I'll probably add a field where a time provider can be specified, and let the user define time as required. That way it's flexible and I don't have networking code in core crest that I don't have resources or equipment to test and maintain. Is this something you could use right now? Just thinking about priorities. |
I'm good regardless since I already have this integrated. But ya a float time field you can set, and Crest uses it if it's > 0 otherwise uses time since level loaded is the simplest integration I can think of. |
Finally got back to this. See PR #88 and let me know what you think. |
PR merged. Good stuff - this opens the door to automated testing which is something id like to look at in the near future.. |
Apparently System.Random is not deterministic across platforms. There is a PR for a deterministic RNG: #224 |
Just considering to add a good water system to a multiplayer project, and I came across this precious site. Regarding syncing different clients on network, I would like to ask the logic behind. I don't know the crest script itself, but is not this kind of ocean re-creations a result of some Fourier transformations, which creates a real time simulation from a frequency based distribution? In that case, form different wavelengths and frequencies into the time domain? If so, I could not get the logic how syncing the time would help to solve this issue. Even with the same parameters and same time, an infinite random outcomes are possible. Or is not it the case for Crest? |
The wave generation has a parameter for time. Knowing the time is enough to fully recreate the waves. This is true of approaches based on Fourier or any other approach using animated waves (we just add gerstner waves together instead of running an FFT, but it doesnt matter for this question). If one knows the time then one can generate the waves, for example using a script like the above. Does this make sense? |
Thoughts on the best approach? I was looking for the simplest approach requiring the least number of changes.
My initial thought is precompute the random bits, enough for say 5 minutes. Then lerp through those using a configurable float time source. Have the lerp just wrap around when it reaches the end of the data.
Any reason that wouldn't work you can think of?
The text was updated successfully, but these errors were encountered: