Search Unity

Question Server World Serialization

Discussion in 'NetCode for ECS' started by SchnozzleCat, Jul 5, 2021.

  1. SchnozzleCat

    SchnozzleCat

    Joined:
    Oct 23, 2013
    Posts:
    42
    What is the recommended way to serialize server worlds (to e.g. save the world state, shut down the server, and then resume later)?

    I tried the simple solution of moving all (non-prefab) ghosts to a separate world, serializing them and then moving them back, but got the following error:

    ArgumentException: Tried to serialized an Entity reference however entity reference serialization has been explicitly disabled.
    Unity.Entities.Serialization.SerializeUtility+ManagedObjectWriterAdapter.Unity.Serialization.Binary.Adapters.IBinaryAdapter<Unity.Entities.Entity>.Serialize (Unity.Collections.LowLevel.Unsafe.UnsafeAppendBuffer* writer, Unity.Entities.Entity value) (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/Serialization/SerializeUtility.cs:77)

    This seems to have to do with livelink, as not using the subscene workflow avoids this error. (I assume the issue is the entity reference in the SceneTag component, which is a shared component)

    Without livelink, I can serialize my ghosts. Is there anything else I need to do besides tell the GhostSendSystem about my loaded ghostIds so they aren't reused? I did this using GhostSendSystem.SetAllocatedGhostId(int), which seems to work fine. Since the function returns false if the ID is already used, this could also be used to make sure that loaded ghostIds are valid before even moving them to the server world (if e.g. loading into a world that already contains some ghosts for some reason).

    With the above both the loaded ghost and the newly created one when the client connects are being properly spawned on the client, and values are being correctly (de)serialized (I checked with a simple HealthComponent).

    Is there anything wrong with the above approach? I don't need the livelink workflow / subscenes in my usecase so just creating the ghostcollection in the server and client world with GameObjectConversionUtility.ConvertGameObjectHierarchy is fine for me.

    Any feedback is appreciated, thanks!
     
  2. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    896
    Hi Roamer,
    Using runtime conversion is something we discourage in general. ConvertClientServer is now deprecated (the component is still there for some niche cases). The suggested and desired workflow is to use subcene.

    The problem you had using the world serialisation utilities is not livelink but in general the fact that having a shared component that contains Entity references is not allowed.

    Unless you are using pre-spawned ghosts (ghost instances present in the subscene) no SceneTag or SceneSection or SceneReference component are added to instantiated ghosts at runtime (I don't remember such a thing).
    The SceneTag is usually added automatically to sub-scene entities when the subscene is loaded at runtime.
    Do you have any shared component with entity reference? Or are you copying something that is not a ghost (by mistake)?.
    For pre-spawned ghosts you may want to remove the SceneTag shared components in the temp world where you copied the ghost. That would prevent that problem.

    GhostSendSystem.SetAllocatedGhostId does not tell you if the id is used or not. It is just setting the next id that should be used to spawn a new ghost. Is returning true or false if the value is updated.
    Using it to restore what should be the next id after the state has been loaded works fine, as long as you call it for all ghosts or just use the largest restored ghost id.

    For. restoring the state though you probably want to load that into a temp world and then copy the entities back. However, for pre-spawned ghosts you can't just copy/move them back.

    Prespawend ghosts are already present in scene and their ghost id are invariant (are always the same everytime the scene is loaded).
    A manual process is necessary to transfer the component data to the matching ghost (that already exist in the server scene).
    I don't think there is anything in Entities right now (at least public) that let you patch data like that. A simple solution is to just walk through all the component types, find the matching one on the other entity and memcpy the data.
    Is not super efficient though.
     
  3. SchnozzleCat

    SchnozzleCat

    Joined:
    Oct 23, 2013
    Posts:
    42
    Thanks as always for the quick reply, I really appreciate it! :)

    Alright, I will try to work towards getting it working for subscenes then

    There are no ghosts present in the scene when I start the game. The only thing in the subscene is the ghostcollection which contains references to my ghost prefab.



    When the game starts, these are the prefabs spawned by (I assume) the ghost collection (I did not manually spawn these prefabs!):



    As you can see it has a SceneTag, SceneSection component, and is a prefab. I'm not sure if this is an error on my side or I am misunderstanding the ghostcollection workflow.

    When spawning a runtime ghost, I get the singleton GhostPrefabCollectionComponent and then the GhostPrefabBuffer, and find the prefab I want to spawn there. (This is based off of the approach in the repository samples)

    Since the prefab I am spawning has the SceneTag component (above) it it makes sense that the runtime version also has the SceneTag. I guess I could remove it during spawning to avoid this (like you mentioned), but I thought I would mention it in case this is not intended behavior on your side.

    Yes my idea was along the lines of calling GhostSendSystem.SetAllocatedGhostId with increasing IDs until it returns true, meaning the ID would be valid. Or something along those lines, but it seems to solve my problem.

    My current project does not use any pre-spawned ghosts, all ghosts are created during runtime (or loaded from a savefile during runtime). So luckily I don't need to deal with this problem (for now), but the insight is nonetheless appreciated and definitely useful for others. I feel like it should be possible to run some self-made system to match loaded ghosts to scene ghosts, like you mentioned.