IRpcCommand not working on build

Hello,

I have Team system that sends invitation request to the desired player to join the team, I’m using RPCs sending the message from server to client, the system works well when testing it between Editor (Client & Server) and prebuild client and works also between prebuild client and editor (client), but when test the system between 2 prebuild clients the server sends the message but not received at all on client.

Code
RPC:
public struct TeamInvitationRpc : IRpcCommand
{
    public FixedString32Bytes SenderName;
    public ushort MasterId;
    public TeamType Type;
}

Server System:
for (int x = 0; x < clientChunks.Length; ++x)
{
    NativeArray<Entity> entityClient = clientChunks[x].GetNativeArray(entityType);
    NativeArray<NetworkId> clientId = clientChunks[x].GetNativeArray(ref networkId);

    for (int z = 0; z < entityClient .Length; ++z)
    {
        if (clientId[z].Value == targetIdComponent[c].NetworkId)
        {
            var RpcMessage = ECB.CreateEntity();
            ECB.AddComponent(RpcMessage, new TeamInvitationRpc { SenderName = playerNameComponent[i].Name, MasterId = masterId, Type = partyInput.TeamType });
            ECB.AddComponent(RpcMessage, new SendRpcCommandRequest { TargetConnection = entityClient[z] });

            UnityEngine.Debug.Log("RPC REQUEST SENT");

            break;
        }
    }
}

Client Reciever:
var ECB = new EntityCommandBuffer(Allocator.Temp);

foreach (var (invt, receiveCommand, message) in SystemAPI.Query<RefRO<TeamInvitationRpc>, RefRO<ReceiveRpcCommandRequest>>().WithEntityAccess())
{
    ECB.DestroyEntity(message);

   Generating UI Message....
    UnityEngine.Debug.Log($"We received your Team Invitation");
}

ECB.Playback(state.EntityManager);
ECB.Dispose();

Thanks in advance.

At a glance, your RPC setup looks ok. If this works with an editor and there’s a difference with builds, you might have world setup issues. Builds will have different bootstrapping and playmode behaviour than the editor. If I were you, I’d do a quick sanity check whether you have the expected worlds in your builds and whether they are connected and/or listening the way you expect.
Can you list the network IDs you have server side and the network ID for each client too? Then log which request is sent to which ID?

Usually when debugging these types of issues, you want to sanity check every single step between each nodes in your topology and try to zero in on the step that fails. So “is client 1 connected”, “is server listening”, “is client 2 connected”, “does the server see all clients”, “does the server receive the RPC”, “does client 2 see the RPC”, etc

1 Like

Yes, there are different bootstrapping scripts but with the same implementation, because I’m separating Client, Server and Editor using assemblies, maybe am I setting up them wrong from beginning? But other systems that use RPCs work well on both editor client and prebuild client (requesting the server to send to the client itself back RPC i.e. pick an item).

I checked the player.log and I found the clients get continuous large server tick exceptions (Large serverTick prediction error. Server tick rollback to Unity.NetCode.NetworkTick delta).

Editor Bootstrap
[UnityEngine.Scripting.Preserve]
public class BootstrapEditor : ClientServerBootstrap
{
    public override bool Initialize(string defaultWorldName)
    {
        
#if UNITY_EDITOR

        AutoConnectPort = 0;
        NetworkStreamReceiveSystem.DriverConstructor = new SecureDriverConstructorEditor();
        CreateLocalWorld(defaultWorldName);

#endif
        return true;
    }
}
Client Bootstrap
Manual connect using UI.

#if CLIENT_BUILD

    [UnityEngine.Scripting.Preserve]
    public class BootstrapClient : ClientServerBootstrap
    {
        public override bool Initialize(string defaultWorldName)
        {
            NetworkStreamReceiveSystem.DriverConstructor = new SecureDriverConstructorClientSide();
            CreateLocalWorld(defaultWorldName);

            return true;
        }
    }

#endif
Server Bootstrap
#if UNITY_SERVER

    [UnityEngine.Scripting.Preserve]
    public class BootstrapServer : ClientServerBootstrap
    {
        const ushort NetworkPort = 7979;
        internal static string OldFrontendWorldName = string.Empty;

        public override bool Initialize(string defaultWorldName)
        {
            StartServer();

            return true;
        }

        private void StartServer()
        {
            if (RequestedPlayType != PlayType.Server)
            {
                Debug.LogError($"Creating server worlds is not allowed if playmode is set to {RequestedPlayType}");
                return;
            }

            NetworkStreamReceiveSystem.DriverConstructor = new SecureDriverConstructorServerSide();

            var server = CreateServerWorld("ServerWorld");

            SceneManager.LoadScene("World");

            //Destroy the local simulation world to avoid the game scene to be loaded into it
            //This prevent rendering (rendering from multiple world with presentation is not greatly supported)
            //and other issues.
            DestroyLocalSimulationWorld();

            if (World.DefaultGameObjectInjectionWorld == null)
                World.DefaultGameObjectInjectionWorld = server;

            var port = ParsePortOrDefault(NetworkPort.ToString());

            NetworkEndpoint Ep = NetworkEndpoint.AnyIpv4.WithPort(port);
            {
                using var drvQuery = server.EntityManager.CreateEntityQuery(ComponentType.ReadWrite<NetworkStreamDriver>());
                drvQuery.GetSingletonRW<NetworkStreamDriver>().ValueRW.Listen(Ep);
            }
        }

        private void DestroyLocalSimulationWorld()
        {
            foreach (var world in World.All)
            {
                if (world.Flags == WorldFlags.Game)
                {
                    OldFrontendWorldName = world.Name;
                    world.Dispose();
                    break;
                }
            }
        }

        private UInt16 ParsePortOrDefault(string s)
        {
            if (!UInt16.TryParse(s, out var port))
            {
                Debug.LogWarning($"Unable to parse port, using default port {NetworkPort}");
                return NetworkPort;
            }

            return port;
        }
    }

#endif
ForntEnd UI Bootstrap
public class BootstrapFrontend : MonoBehaviour
{
    const ushort NetworkPort = 7979;
    const string NetworkIPAddress = "127.0.0.1";

    /// <summary>
    /// Stores the old name of the local world (create by initial bootstrap).
    /// It is reused later when the local world is created when coming back from game to the menu.
    /// </summary>
    internal static string OldFrontendWorldName = string.Empty;

#if UNITY_EDITOR || CLIENT_BUILD
    public void ConnectToServer()
    {
        if (SceneManager.GetActiveScene().name == "Login")
        {
           InstantiateClientWorld();
        }
    }
#endif

#if UNITY_EDITOR
    public void StartClientAndServer()
    {
        if (ClientServerBootstrap.RequestedPlayType != ClientServerBootstrap.PlayType.ClientAndServer)
        {
            Debug.LogError($"Creating client/server worlds is not allowed if playmode is set to {ClientServerBootstrap.RequestedPlayType}");
            return;
        }

        var server = ClientServerBootstrap.CreateServerWorld("ServerWorld");
        var client = ClientServerBootstrap.CreateClientWorld("ClientWorld");

        SceneManager.LoadScene("World");

        //Destroy the local simulation world to avoid the game scene to be loaded into it
        //This prevent rendering (rendering from multiple world with presentation is not greatly supported)
        //and other issues.
        DestroyLocalSimulationWorld();

        if (World.DefaultGameObjectInjectionWorld == null)
            World.DefaultGameObjectInjectionWorld = server;


        var port = ParsePortOrDefault(NetworkPort.ToString());

        NetworkEndpoint Ep = NetworkEndpoint.AnyIpv4.WithPort(port);
        {
            using var drvQuery = server.EntityManager.CreateEntityQuery(ComponentType.ReadWrite<NetworkStreamDriver>());
            drvQuery.GetSingletonRW<NetworkStreamDriver>().ValueRW.Listen(Ep);
        }

        Ep = NetworkEndpoint.LoopbackIpv4.WithPort(port);
        {
            using var drvQuery = client.EntityManager.CreateEntityQuery(ComponentType.ReadWrite<NetworkStreamDriver>());
            drvQuery.GetSingletonRW<NetworkStreamDriver>().ValueRW.Connect(client.EntityManager, Ep);
        }
    }
#endif

#if UNITY_EDITOR
    public void StartServer()
    {
        if (ClientServerBootstrap.RequestedPlayType != ClientServerBootstrap.PlayType.Server)
        {
            Debug.LogError($"Creating server worlds is not allowed if playmode is set to {ClientServerBootstrap.RequestedPlayType}");
            return;
        }

        var server = ClientServerBootstrap.CreateServerWorld("ServerWorld");


        SceneManager.LoadScene("World");

        //Destroy the local simulation world to avoid the game scene to be loaded into it
        //This prevent rendering (rendering from multiple world with presentation is not greatly supported)
        //and other issues.
        DestroyLocalSimulationWorld();

        if (World.DefaultGameObjectInjectionWorld == null)
            World.DefaultGameObjectInjectionWorld = server;


        var port = ParsePortOrDefault(NetworkPort.ToString());

        NetworkEndpoint Ep = NetworkEndpoint.AnyIpv4.WithPort(port);
        {
            using var drvQuery = server.EntityManager.CreateEntityQuery(ComponentType.ReadWrite<NetworkStreamDriver>());
            drvQuery.GetSingletonRW<NetworkStreamDriver>().ValueRW.Listen(Ep);
        }
    }
#endif

    private void InstantiateClientWorld()
    {
        if (ClientServerBootstrap.RequestedPlayType != ClientServerBootstrap.PlayType.Client)
        {
            Debug.LogError($"Creating client worlds is not allowed if playmode is set to {ClientServerBootstrap.RequestedPlayType}");
            return;
        }

        var client = ClientServerBootstrap.CreateClientWorld("ClientWorld");

        //Destroy the local simulation world to avoid the game scene to be loaded into it
        //This prevent rendering (rendering from multiple world with presentation is not greatly supported)
        //and other issues.
        DestroyLocalSimulationWorld();

        if (World.DefaultGameObjectInjectionWorld == null)
            World.DefaultGameObjectInjectionWorld = client;

        var Ep = NetworkEndpoint.Parse(NetworkIPAddress, ParsePortOrDefault(NetworkPort.ToString()));
        {
            using var drvQuery = client.EntityManager.CreateEntityQuery(ComponentType.ReadWrite<NetworkStreamDriver>());
            drvQuery.GetSingletonRW<NetworkStreamDriver>().ValueRW.Connect(client.EntityManager, Ep);
        }

        SceneManager.LoadScene("World");
    }

    private void DestroyLocalSimulationWorld()
    {
        foreach (var world in World.All)
        {
            if (world.Flags == WorldFlags.Game)
            {
                OldFrontendWorldName = world.Name;
                world.Dispose();
                break;
            }
        }
    }

    private UInt16 ParsePortOrDefault(string s)
    {
        if (!UInt16.TryParse(s, out var port))
        {
            Debug.LogWarning($"Unable to parse port, using default port {NetworkPort}");
            return NetworkPort;
        }

        return port;
    }
}

The following images will illustrate the connection Id on both client and server:

Clients Ids on server side


Clients Ids on client side 'Player Log'

Screenshot 2024-07-31 115938

The Player.log below indicates that the RPC message is received, but it doesn’t invoke the UI window:

When Sending RPC request

I changed the server code to be able to send team request to myself and surprisingly the Team Invitation UI window generated correctly, why only generated on same client and not others and both messages come from same server?

Thanks in advance.

I recently upgraded from Unity 2022.3.38f1 to 2022.3.40.f1 and the issue I was experiencing is resolved. Could it be that the older editor version was building a different client?