Same [Rpc(SendTo.Owner)] do not triggers all the time

Hi,
I have a script with a test RPC called “testRPC” that sends to the Owner of the NetworkObject. This RPC correctly triggers on the Owner if it’s called on OnNetworkSpawn, but not if it’s called by its StartInteractionServer function. How do I fix this ?

Here’s the code:

And the call stack of the StartInteractionServer function :

This does look strange. Where in your script are you calling the StartInteractionServer function?

Here’s the complete sequence of code :








(OnPressInteract is added to the promptsByInputs dictionnary elsewhere)


You will want to make this adjustment if you want non-owners to invoke an RPC on a spawned instance where the server/host is the owner:

[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
private void SetAbilityInput_ServerRpc(AbilityButtonInputType input, bool hold)
{
    //...etc
}

Let me know if this resolves your issue?

Just a heads up, screenshots of code are very hard to work with, though these are nice and clear snapshots. It’s always best practice to embed the code into the post. You can do that by using ``` on a new line, and close the code block the same way.

In these screenshots, I don’t see where you are calling the function StartInteractionServer

Unfortunately, it doesn’t. That part of the code gets triggered properly because it gets called by the owner. Everything gets triggered properly until the StartInteractionServer() method, after that the testClientRPC() method doesn’t trigger.
Your comment did make me realize I was using the version 2.3.2 of NGO, so I updated it to 2.7.0, but the bug persist).

Here’s the full code in text form.

    public class ButtonAbilityInput
    {
        public delegate void ButtonAbilityInputEvent(AbilityButtonInputType input, bool hold);

        private AbilityButtonInputType input;

        public event ButtonAbilityInputEvent OnUpdateButtonHeld;

        public ButtonAbilityInput(AbilityButtonInputType input, InputAction inputAction)
        {
            this.input = input;
            inputAction.performed += InputAction_performed;
            inputAction.canceled += InputAction_canceled;
        }

        private void InputAction_performed(InputAction.CallbackContext obj)
        {
            OnUpdateButtonHeld?.Invoke(input, true);
        }
        private void InputAction_canceled(InputAction.CallbackContext obj)
        {
            OnUpdateButtonHeld?.Invoke(input, false);
        }
    }

LocalInputs.cs (implements a singleton pattern)

public event ButtonAbilityInput.ButtonAbilityInputEvent OnAbilityInput;

protected void Awake()
{
for (int i = 0; i < inputActionNameByAbilityInputs.Length; i++)
{
    var current = inputActionNameByAbilityInputs[i];
    InputAction inputAction = inputActions[current.value];

    var buttonInput = new ButtonAbilityInput(current.key, inputAction);
    buttonInput.OnUpdateButtonHeld += ButtonInput_OnUpdateButtonHeld;
}
}

private void ButtonInput_OnUpdateButtonHeld(AbilityButtonInputType input, bool hold)
=> OnAbilityInput?.Invoke(input, hold);

AvatarInputs.cs

        public override void OnNetworkSpawn()
        {
            base.OnNetworkSpawn();

            if (!IsOwner) return;

            LocalInputs localInputs = LocalInputs.Instance;
            localInputs.OnAbilityInput += LocalInputs_OnAbilityInput;
        }

        private void LocalInputs_OnAbilityInput(AbilityButtonInputType input, bool hold)
        {
            SetAbilityInput_ServerRpc(input, hold);
        }

        [Rpc(SendTo.Server)]
        private void SetAbilityInput_ServerRpc(AbilityButtonInputType input, bool hold)
        {
            SetAbilityInput(input, hold);
        }

        private void SetAbilityInput(AbilityButtonInputType input, bool hold)
        {
            Debug.Assert(IsServer);

            if (promptActionByInputs.TryGetValue(input, out Action onPress))
            {
                if (hold) onPress?.Invoke();
                return;
            }
        }
        public void AddPrompt(PromptInfo prompt, Action onPress)
        {
            Debug.Assert(IsServer);
            Log($"Add prompt {prompt} {this}");
            if (!TryAddPromptInput(prompt.Button, onPress)) return;
        }

        public bool TryAddPromptInput(AbilityButtonInputType button, Action onPress)
        {
            Debug.Assert(IsServer);
            if (promptActionByInputs.ContainsKey(button))
            {
                Debug.LogError($"There is already a prompt for input {button} !");
                return false;
            }

            promptActionByInputs.Add(button, onPress);
            return true;
        }

AvatarInteract.cs

        private void SetCurrentInteractable(Interactable newInteractable)
        {
            currentInteractable = newInteractable;

            if (currentInteractable != null)
            {
                currentPromptInfo = currentInteractable.GetInteractPrompt();
                avatarInputs.AddPrompt(currentPromptInfo, OnPressInteract);
            }
        }

        private void OnPressInteract()
        {
            currentInteractable.Interact(this, networkAvatar);
        }

Interactable.cs

        public abstract void Interact(AvatarInteract avatarInteract, NetworkAvatar networkAvatar);

CrowdHumanInteractableFan.cs

public class CrowdHumanInteractableFan : Interactable
{
    public override void Interact(AvatarInteract avatarInteract, NetworkAvatar avatar)
    {
        if (!TryGetPossesser(out CrowdPossesser possesser)) return;
        avatar.AvatarStar.AvatarStarFan.StartInteraction(possesser);
    }
}

AvatarStarFan.cs

        public void StartInteraction(CrowdPossesser possesser)
        {
            avatarStarFanInteraction.StartInteractionServer(possesser);
        }

AvatarStarFanInteraction.cs

public class AvatarStarFanInteraction : NetworkBehaviour
{
    public override void OnNetworkSpawn()
    {
        base.OnNetworkSpawn();
        if (IsServer)
        {
            Debug.Log("start server");//This log "start server" on server
            testClientRPC(false);//This correctly triggers on client
        }
    }

    [Rpc(SendTo.Owner)]
    private void testClientRPC(bool interaction)
    {
        Debug.Log($"test client {interaction}");
    }

    public void StartInteractionServer(CrowdPossesser possesser)
    {
        Debug.Assert(IsServer);

        //This log "start interaction True 0 1" on server
        Debug.Log($"start interaction {IsSpawned} {NetworkManager.LocalClientId} {OwnerClientId}");

        testClientRPC(true);//This does not triggers on client
    }
}

And here are how the scripts are placed on the gameobjects




Is there anything you see that could go wrong ? This is making me crazy.

Looks like you might have transitioned from the legacy ServerRpc to the newer “universal” RPC?

Just for a “sanity” check, try renaming it where the postfix is just “Rpc”:

        [Rpc(SendTo.Server)]
        private void SetAbilityInputRpc(AbilityButtonInputType input, bool hold)
        {
            SetAbilityInput(input, hold);
        }

Let me know if this works?

No, sadly, it doesn’t change anything.

@Fledered1

Ok, that is fine. Most likely it is an order of operations related issue. Typically when I run into potential OOO related issues I will start peppering the logical paths with debug logging information to see where the disconnect is happening.

So, if you could make the following modifications to the following areas of your script:

CrowdHumanInteractableFan.cs

    public class CrowdHumanInteractableFan : Interactable
    {
        public override void Interact(AvatarInteract avatarInteract, NetworkAvatar avatar)
        {
            var localClientId = NetworkManager.Singleton.LocalClientId; 
            if (!TryGetPossesser(out CrowdPossesser possesser))
            {
                Debug.LogWarning($"[{avatar.name}][Client-{localClientId}] No prossesser found for interaction!");
                return;
            }
            Debug.Log($"[{avatar.name}][Client-{localClientId}][Owner: Client-{avatar.OwnerClientId}] Starting interaction...");
            avatar.AvatarStar.AvatarStarFan.StartInteraction(possesser);
        }
    }

AvatarStarFanInteraction.cs

Note:
I renamed your RPC method to TestOwnerRpc since ClientRpc is the postfix for the legacy RPC system.

If you think of any other places that could shed more light on the logical flow feel free to add more log information there too. Then if you could replicate the steps and provide the logs for both client and server it will help us figure out where this bugger is hiding.

Also, try turning the LogLevel field on the NetworkManager to Developer. This will log any extra information from within the Netcode system that might be happening.

I found the solution ! Here’s my thought process in case anyone get stuck on this too.

Here was my code:

        public override void OnNetworkSpawn()
        {
            base.OnNetworkSpawn();

            Debug.Log("spawned");
            if(IsServer) TestOwnerRpc(false);
        }
        public override void OnNetworkDespawn()
        {
            base.OnNetworkDespawn();
            Debug.Log("despawn");
        }

        private void Update()
        {
            if (!IsServer) return;

            if (Keyboard.current.lKey.wasPressedThisFrame)
            {
                Debug.Log("debug key");
                TestOwnerRpc(false);
            }
        }

        public void StartInteractionServer(CrowdPossesser target)
        {
            if(isInteracting)
            {
                Debug.LogError("Already interacting !", gameObject);
                return;
            }

            Debug.Assert(IsServer);
            Debug.Log($"[{name}][IsSpawned: {IsSpawned}][Owner: {OwnerClientId}][On Client-{NetworkManager.LocalClientId}] Start interaction.");

            TestOwnerRpc(true);
        }

        [Rpc(SendTo.Owner)]
        private void TestOwnerRpc(bool interaction, RpcParams rpcParams = default)
        {
            Debug.Log($"[Sender: Client-{rpcParams.Receive.SenderClientId}][Owner: Client-{OwnerClientId}] Interaction: {interaction}");
        
        }

When I tested the game, pressed the L key on the server, and tried to interact on the client, here were the logs :

Server side :
spawned
debug key
[AvatarStar(Clone)][IsSpawned: True][Owner: 1][On Client-0] Start interaction.

Client side :
spawned
[Sender: Client-0][Owner: Client-1] Interaction: False

So it still didn’t work. What helped me was using the Developer log level on the NetworkManager (just like emmcmill said), which gave me some interesting warnings :

On Server :
[Netcode-Server Sender=1] [Client-1][FlederedCreate_0(Player)_0] Server - client scene mismatch detected! Client-side scene handle (Unity.Netcode.NetworkSceneHandle) for scene (Apartment)has no associated server side (network) scene handle!
[Netcode-Server Sender=1] [Client-1][FlederedCreate_1(Player)_1 [LOCAL]] Server - client scene mismatch detected! Client-side scene handle (Unity.Netcode.NetworkSceneHandle) for scene (Apartment)has no associated server side (network) scene handle!

On Client :
[Netcode] [Client-1][FlederedCreate_0(Player)_0] Server - client scene mismatch detected! Client-side scene handle (Unity.Netcode.NetworkSceneHandle) for scene (Apartment)has no associated server side (network) scene handle!
[Netcode] [Client-1][FlederedCreate_1(Player)_1 [LOCAL]] Server - client scene mismatch detected! Client-side scene handle (Unity.Netcode.NetworkSceneHandle) for scene (Apartment)has no associated server side (network) scene handle!
[Netcode] NetworkBehaviour-AvatarStar(Clone) is being destroyed while NetworkObject-AvatarStar(Clone) is still spawned! (could break state synchronization)
[Netcode] Trying to get the transport client ID map for the NGO client ID (1) but did not find the map entry! Returning default transport ID value.

I then realized the NetworkObject of my avatar was spawning on the client before the scene could finish loading. I first thought that this is what was causing the issue but the problem persisted after I fixed that.
After further inspection, I realized I was destroying the AvatarStarFan.cs script on the client (since its content is only used on the server) and this is why the script wasn’t working.

The “Server - client scene mismatch detected” warning persists, but now the rest of the code works correctly.
Thank you for helping me and sorry for wasting your time !

@Fledered1

Glad you were able to get past this point.

Two things to contemplate:

  • You also might think about using scene validation to exclude any server-side scenes you might not want to be synchronized.
  • NGO v2.11.2 includes and update for ChildNetworkBehaviours which If you are going to be removing components then you will most likely want to be on this version (or later).

However, removing a NetworkBehaviour component from a prefab instance on only clients or only the server will most definitely break any netcode related functionality because one side won’t process messages targeting that component (i.e. any RPC messages or NetworkVariable changes).