Can't synch instantiated nodes?

Question

Hello! I have a RigidBody2 ball that is being deleted and instantiated whenever it touches a specific object. We are currently using the MultiplayerSynchronizer node to sync all of our scenes with online multiplayer. Everything seems to be working and synching, except when the ball gets deleted and instantiated, we get the following error:

E 0:00:13:0037 get_cached_object: ID 3 not found in cache of peer 1.
<C++ Error> Parameter “oid” is null.
<C++ Source> modules/multiplayer/scene_cache_interface.cpp:278 @ get_cached_object()

After this, the clients completely desync.

We thought maybe both the server and the peer were trying to delete the ball, and one deletes it and the other can’t find the object to delete. So, we made only the server handle the deletion and instantiation of the ball, but then once the ball is instantiated, the peer player can no longer interact with the ball at all.

How the heck are we supposed to handle synching and interacting with objects that regular get instantiated?

Are you using a MultiplayerSpawner too?

We… we are not. I assumed the MultiplayerSpawner was for spawning player objects. How would MultiplayerSpawner be used for this instance?

MultiplayerSpawner is for synchronizing the spawning of objects. When the authority adds a child to the designated node, or uses the $MultiplayerSpawner.spawn() function it will transmit to all connected clients.

We added a MultiplayerSpawner, set the path to the main game object, and then the ball is selected in the auto spawn list.

This doesn’t seem to have changed anything, but based on what I read in the article that should be all that needs to be done to use MultiplayerSpawners.

It seems the errors are actually coming about when the object is removed using queue_free(). The object being instantiated again doesn’t seem to fix these issues. Is there an issue with using queue_free() on nodes that are being synced?

Yeah the client should not free objects on their end, the MultiplayerSpawner should handle that too. You may also need to change your ball to be instantiate in-game instead of in-editor.

Okay yeah, only the server is freeing the ball (now). Should we not use MultiplayerSynchronizer for the ball itself? I’m starting to think maybe we should only use the MultiplayerSynchronizer for player actions, and let the client calculate how that influences the ball, but that sounds like it could cause some whacky situations.

Not sure on the synchronizer, physics objects are always tricky. The demo I linked syncrhonizes a physics ball velocities and transforms, the interactions are strange though. Playing it the host can push one ball, and the client can push the other, but not vice-versa, not sure how or why :confused:

Well, at least that makes me feel better! lol

The MultiplayerSpawner is definitely a huge step for us in the right direction though! Thanks for the information!

1 Like

It certainly does a lot! Only other tip I can impart is to avoid change_scene_to_file, it can work for connected clients, but any late-joining friends will be desynced.

Sorry about the physics object, I would assume move_and_slide() needs to be called on the host, but seems like there is more going on I don’t understand.

I have had the same issue and in my case it is a scene instantiated on all peers through a MultiplayerSpawner and kept in sync through a MultiplayerSynchronizer. When I free the scene, I got this exact error.

It might relate to this bug: RPC function breaks when using it right after giving visibility to a peer on a MultiplayerSynchronizer · Issue #93473 · godotengine/godot · GitHub

What I did to resolve this is disable the Synchronizer on all peers, wait for one frame and then remove the object. Here is a snippet:

private async Task RemoveEntity(EntityView entityView)
{
    if (entityView.Synchronizer != null)
    {
        // not all entityView nodes have Synchronizers
        Rpc(MethodName.RpcOnAny_RemoveVisibility, entityView.GetPath());
        await ToSignal(GetTree(), "process_frame");    
    }
    entityView.QueueFree();
}

[Rpc(CallLocal = true)]
private void RpcOnAny_RemoveVisibility(string nodePath)
{
    var entityView = GetNode<EntityView>(nodePath);
    // 0 as peerId sets it for all peers
    entityView.Synchronizer.SetVisibilityFor(0, false);
}