Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question Best practices for determining first time ghost creation

Discussion in 'NetCode for ECS' started by tertle, Feb 22, 2023.

  1. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,647
    This is something I've seen crop up a few times but I've never seen a really solid solution.

    Basically, from the clients perspective a new ghost being created by server and spawning on client and a player walking into relevancy of an existing ghost appears to be the same thing.

    However a client might want to treat these cases quite differently - for example, on a first spawn you might want to play a sound effect etc.

    I've only seen really two solutions here
    - attaching a created game time and on spawn on client checking if it was created in say the last 1 second of game time. To me this has always seen a bit flimsy or hacky though.
    - have the server fire a separate event linked to the entity on first creation. We've done something like this previously at work but I'm not sure I have a good way of implementing a similar system at the moment with netcode though. I'd need to guarantee the ghost either spawned first or at the same time as the separate event.

    So I'm wondering are there any alternative patterns for handling this that people have come up with?
     
  2. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    677
    I've barely touched netcode stuff, but a common way to achieve that in any framework is to go with option two (having a separate event linked to the entity) and on the client-side you check if all the dependencies (linked entity) for the event exist and, if not, add the event to a queue so that it gets re-evaluated again after the dependencies are ready.
     
  3. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,647
    Yeah queuing it makes sense. I'm still not entirely sure how I'd handle this in netcode though. RPCs are quite limited in quantity so I can't really send 1 per event as there is certainly a high chance I could send a lot in a short period (batch building or something.)

    I could probably throw together a system that collects all events on server and partitions them to each player for their relevance and send them together though.
     
  4. NikiWalker

    NikiWalker

    Unity Technologies

    Joined:
    May 18, 2021
    Posts:
    241
    Hey tertle. Ah, yeah, good question and use-case.

    The underlying problem is that you need this boundary to be "flimsy", due to the nature of "eventual consistency + relevancy radius". Consider the following 3 cases:
    • A ghost spawned two minutes ago far away, and you've walked over to it. This is the first time your client has seen it. In your example, this should not play a "spawned right now" SFX.
    • A ghost spawned 0.5 seconds ago on the server, within your relevancy radius, and was replicated to you. This should play the SFX.
    • A ghost spawned 20 seconds ago on the server, within your relevancy radius, but due to some unluckiness, you only receive it for the first time on this frame (20s later). Should this play "first spawn" SFX?

    Your answer to this Q changes how you'd build it:
    • If you ONLY care about spawns within a time threshold: By far the best approach (which won't require any additional GhostFields or data) is to compare the GhostComponent.spawnTick with the current NetworkTick.ServerTick. The GhostComponent is stable between relevancy boundaries. Have a threshold very low. To account for spawns near the edge of the relevancy boundary:
      • Either extend the relevancy border size (so that a newly spawned relevant ghost cannot become irrelevant and then relevant within that time window).
      • Or store a list collection of "recent spawns that my client has already seen", and remove entities from that list after they exceed the time threshold. Query that before playing the SFX.
    • If you ALWAYS care about new spawns (regardless of eventual consistency latency): You need additional data from the server. I.e. The data of "Was this spawned within my relevancy radius, and is this the first time I've seen it?" Thus, the RPC approach described above is required. I.e. An RPC that adds a ghost to a queue (which is then dequeued when the ghost is replicated) probably makes sense. You'd periodically need to cull this queue though, as there is a chance you never see it (as it could be destroyed before it is replicated to you).
     
  5. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,647
    The other issue I have is I forgot to mention, save & loading games.

    This would reset all the GhostComponent.spawnTick causing everything to re-trigger. I would need a separate component that tracks my own spawn tick that is persistent across games I think?
     
  6. NikiWalker

    NikiWalker

    Unity Technologies

    Joined:
    May 18, 2021
    Posts:
    241
    Alternatively: While loading a save (either a local save, or a server-triggered "everyone-load-from-save"), have some state representing "I am loading in right now, and syncing ghosts, please wait...." (you probably need this anyway to ensure your character controller isn't affected by the world before they can react), and then have the server detect when you've received all ghosts, and change your state to "ready". While in the loading state, you receive zero sound notifications.

    It may cause you to lose a little bit of info (e.g. new spawns while you're loading in), but it's not horrible.

    EDIT: Actually, if you're loading from save (or if everyone is loading from a server-save), the game is likely paused anyway (for a few seconds), so no big deal.

    EDIT2: Unless you mean like chunked, streaming save + load. In those cases, I'm actually not sure what happens to the GhostComponent. The simplest solution is to keep those entities around on the server. Checking...

    EDIT3: We don't actually have a sample showing a version of streaming where ghosts are saved, then despawned on the server as well. This would be implementation specific.
     
  7. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,647
    Oh yeah that totally makes sense, I was over thinking this.

    Loading a save for me is re-creating all prefabs from scratch like they were fresh then applying the save data to them. (I have this whole [Save] attribute based saving system for specifying what components save and the whole thing is automatic https://github.com/tertle/com.bovinelabs.saving.free/).

    So while they would appear as fresh spawns and a host might load too quickly, I already have a whole setup that stops world ticking while loading required subscenes, saves, navmesh generation etc so I should not really have an issue to simply tick for a tiny bit or have client ignore events during loading etc.

    Going to give it a shot and see how it feels,

    Thanks for the discussion brunocoimbra & NikiWalker
     
    brunocoimbra and NikiWalker like this.
  8. NikiWalker

    NikiWalker

    Unity Technologies

    Joined:
    May 18, 2021
    Posts:
    241
    Nice, and no problem. This Save system looks sick! I've never explored it, but I'm assuming you get pretty insane performance characteristics with this approach?
     
  9. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,647
    I discussed a bunch about my motivation for this system and my design for it over here: https://forum.unity.com/threads/dots-0-5-and-serialization.1258140/#post-8027630

    TLDR: worked with different 2 save systems at work, wanted to build something that solved all the problems I had with them.

    Objectives
    - Serialization needs to be fast (deserialization less important as it can usually be hidden behind a load screen. that said it can still do real time deserialization fine)
    - Data not object focused serialization
    - Attributes based for convenience, but can use custom alternatives if required
    - Easy migration of old data (not included in above library yet but works great)

    Also I had the side objective of
    - Subscene state saving - it can handle save & closing subscenes, then loading & applying the save state to them seamlessly

    I wanted to be able to serialize in the same frame without any spikes or the need for copying data to a separate world.

    It's fast enough I can run it for fun as a real time rollback solution for 6 digit entities (extremely impracticable for rollback as it's not designed for that, you'll use gigs of memory in a minute, but it's a nice test for real time serialization/de-serialization)



    (The lag in the video is just my slow GPU (1060) struggling to render this much)
     
  10. NikiWalker

    NikiWalker

    Unity Technologies

    Joined:
    May 18, 2021
    Posts:
    241
    Insane! Thanks for the follow up.