"The ghost collection contains a ghost which does not have a valid prefab on the client!"

I’m experiencing the following error in Unity 6.0, when I join a hosted game (the hosting instance of the game is just fine) when loading the game scene from another menu scene:

The ghost collection contains a ghost which does not have a valid prefab on the client! Ghost: ‘’ (‘ENTITY_NOT_FOUND’).

My game scene is dead simple:

‘GameSetup’ is the Authoring part of the FPS character controller netcode tutorial.

When using the automatic ClientServerBootstrap, and when hosting a game (Client + Server Worlds running in one game instance), the character and player entities spawn and I can move around the game world successfully, both in editor and in a build.

But when I arrive into the game scene from my Main Menu scene, as a client, I get those Ghost Collection errors:

I’m guessing that the two entities not found are the FirstPersonCharacter and FirstPersonPlayer entities the authoring script ought to handle. I guess that because those are the only two entities in the game, I don’t think it can be anything else.

Why is it failing on the client? Why does the Ghost Collection not have valid prefabs when coming in from the other scene?

My scripts for joining/hosting the game are also straight from the Entities For Netcode tutorial:

    // In both HostGame and JoinGame address is an endpoint like "192.168.1.1"
public static void HostGame(string address) {
	DestroyLocalSimulationWorld();
	var server = FrontendBootstrap.CreateServerWorld("ServerWorld");
	var client = FrontendBootstrap.CreateClientWorld("ClientWorld");
	
	if (World.DefaultGameObjectInjectionWorld == null)
		World.DefaultGameObjectInjectionWorld = server;
	SceneManager.LoadSceneAsync("Alt", LoadSceneMode.Single);

	NetworkEndpoint.TryParse(address, 13131, out NetworkEndpoint e);
	
	{
		using var query = server.EntityManager.CreateEntityQuery(ComponentType.ReadWrite<NetworkStreamDriver>());
		query.GetSingletonRW<NetworkStreamDriver>().ValueRW.Listen(e);
	}

	{
		using var query = client.EntityManager.CreateEntityQuery(ComponentType.ReadWrite<NetworkStreamDriver>());
		query.GetSingletonRW<NetworkStreamDriver>().ValueRW.Connect(client.EntityManager, e);
	}

}

public static void JoinGame(string address) {
	DestroyLocalSimulationWorld();
	var client = FrontendBootstrap.CreateClientWorld("ClientWorld");
	if (World.DefaultGameObjectInjectionWorld == null)
		World.DefaultGameObjectInjectionWorld = client;
	
	SceneManager.LoadSceneAsync("Alt", LoadSceneMode.Single);

	NetworkEndpoint.TryParse(address, 13131, out NetworkEndpoint e);
	{
		using var query = client.EntityManager.CreateEntityQuery(ComponentType.ReadWrite<NetworkStreamDriver>());
		query.GetSingletonRW<NetworkStreamDriver>().ValueRW.Connect(client.EntityManager, e);
	}
}

Nothing exotic there, I’m sure you’ll agree.

What’s going on here?

1 Like

I have the same behaviour but only on the Virtual Player with Multiplayer Play Mode

I took some time today to submit a bug report:

IN-90033

It contains a stripped-down project that clearly shows the error, reproduced.

Could one of the Netcode For Entities team please take a look?

The issue occurs because you are making on the server the game client “In Game” immediately after you receive the connection but on the client side, the sub-scene hasn’t been loaded and processed yet by the GhostCollectionSystem.

The server send to the client initially a list of “prefab ghost” that should be loaded and that the client needs to ack.

Normally, for simple case scenarios the client load the same sub-scene the server does before it receives the first snapshot, so the same prefabs are already present and processed at that point and the GhostCollectionSystem is able to assign to the GhostCollectionPrefab the right mapping.

But in case the server will set the connection to be in Game and the client load the scene later on, and meanwhile he receive the first snapshot, some actions need to be taken.?

It is rensposibility of the devs to check of any newly and pending assignment in the GhostCollectionPrefab list (any ghost that has the prefab field set to null or not existing) and mark these entry by setting the Loading state to LoadingState.Active.

This should be done each frame, until the resource (prefab) has been loaded and therefore the mapping assigned.

Thanks CMarastoni, I’ve got something working - is this the sort of flow you’d expect:

  1. On Main Menu Scene’s button click: start loading Game Scene.
  2. Wait for Game Scene to finish loading.
  3. With netcode-for-entities + entities physics Subscene within the Game scene set to Auto-Load, wait for the subscene to finish loading.
  4. Only then does the Client connect to the server.
  5. On the server, on client connection, create the player character as before except don’t add NetworkStreamInGame to the connection at this point.
  6. The client sends an RPC command and…
  7. …when the server receives it, the server makes the client ‘Go In Game’ by adding NetworkStreamInGame to that connection.

And that’s all fine, that works. No more GhostCollectionPrefab errors.

My concern is that adding the whole RPC sending/receiving roundtrip seems a bit heavy-duty, a bit over-engineered, especially since the client instance only ever starts a connection to the server network endpoint after the scene-and-subscene have reported that they have finished loading. I would have thought the client would be ready for a snapshot by then. So perhaps I’m missing something?

(Another thing: I also noticed that, when I had the flow setup the way I described in my initial post, if I added a Debug.Log statement or two, I’d occasionally (but only sometimes) get a client receiving snapshots quite happily, as if the presence of the Debug.Log added an extra millisecond to the processing time, such that the client would have all the prefabs it needed. Very heisenbuggish.)

Great!

We are considering simplifying some of these flows. Definitively we are going to add more documentation

2 Likes

Does anyone know if there was any improvement on that side?

I got that issue today, and the most reliable way to fix it was to wait for all sub-scenes to get fully loaded before proceeding to next steps:

[BurstCompile]
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation | WorldSystemFilterFlags.ThinClientSimulation)]
public partial struct ClientRequestGameEntrySystem : ISystem
{
    private EntityQuery _pendingNetworkIdQuery;

    [BurstCompile]
    public void OnCreate(ref SystemState state)
    {
        var builder = new EntityQueryBuilder(Allocator.Temp).WithAll<NetworkId>().WithNone<NetworkStreamInGame>();
        _pendingNetworkIdQuery = state.GetEntityQuery(builder);
        state.RequireForUpdate(_pendingNetworkIdQuery);
        state.RequireForUpdate<ClientGameEntryRequest>();
    }

    public void OnUpdate(ref SystemState state)
    {
        var world = state.World;

        // Wait for the SubScenes to be fully loaded
        // to avoid the "race condition" where the Client received Ghost Snapshots from the Server
        // before it had fully initialized its local Ghost Prefabs. This caused the 'ENTITY_NOT_FOUND' crash.
        if (AnySubSceneLoading(world))
        {
            return;
        }

        var request = SystemAPI.GetSingleton<ClientGameEntryRequest>().Value;
        using var ecb = new EntityCommandBuffer(Allocator.Temp);
        using var pendingNetworkIds = _pendingNetworkIdQuery.ToEntityArray(Allocator.Temp);

        foreach (var pendingNetworkId in pendingNetworkIds)
        {
            ecb.AddComponent<NetworkStreamInGame>(pendingNetworkId);
            var requestEntity = ecb.CreateEntity();
            ecb.AddComponent(requestEntity, new GameEntryRequest { Value = request });
            ecb.AddComponent(requestEntity, new SendRpcCommandRequest { TargetConnection = pendingNetworkId });
        }

        ecb.Playback(state.EntityManager);
    }

    private static bool AnySubSceneLoading(World world)
    {
        var subSceneQuery = new EntityQueryBuilder(Allocator.Temp)
            .WithAll<RequestSceneLoaded, SceneReference>()
            .Build(world.EntityManager);

        using var pendingSubScenes = subSceneQuery.ToEntityArray(Allocator.Persistent);
        return pendingSubScenes.Any(subScene => !SceneSystem.IsSceneLoaded(world.Unmanaged, subScene));
    }
}