I solved this issue by having a custom RPC wrappers that send messages to clients, with manually managing Differences and serialization
Would probably make it into a package later when i find time
I utilize Roslyn and look for ALL types that have ‘AttachTo’ (custom attribute) to automatically generate the necessary code, kind of like with the MLAPI / Mirror / netcode for GameGbjects
using Entities 1.0.10 Unity 2022.3.14
While not pretty, does allow for runtime adding of components (which i had due to my design to have Mods and each dll adds more functionality)
// pseudo code
[GhostComponent(OwnerSendType = SendToOwnerType.SendToOwner)]
[AttachTo(typeof(Player))]
public struct Shipyard : IComponentData
{
public int dockedShips;
}
And then with the respective RPC receiver and Sender
// client side, On spawn - Send request
var messageArchetype = EntityManager.CreateArchetype(ComponentType.ReadWrite<Shipyard_DetailsRequest>(), ComponentType.ReadWrite<SendRpcCommandRequest>());
Entities.WithNone<RequestedDetails>().ForEach((Entity entity, GhostInstance instance) =>
{
var message = new Shipyard_DetailsSubscription { target = entity };
Send(ref message, ref buffer, ref messageArchetype);
buffer.AddComponent<RequestedDetails>(entity);
}).Run()
buffer.Playback(EntityManager)
//serverside
// Catch the Message (keep in mind, MANY can be at the same time. If you need to process many - use buffers to store them
var messageArchetype = EntityManager.CreateArchetype(ComponentType.ReadWrite<Shipyard_DetailsResponse>(), ComponentType.ReadWrite<SendRpcCommandRequest>());
var lookup = this.GetComponentLookup<Shipyard>();
Entities.ForEach((Entity messageEntity, in Shipyard_DetailsSubscription cmd, in ReceiveRpcCommandRequest req) =>
{
var response = new Shipyard_DetailsResponse{target = entity, data = lookup[cmd.target]}
var player = req.SourceConnection;
Send(ref response, ref player, ref buffer, ref messageArchetype);
buffer.DestroyEntity(messageEntity);
}).Run()
buffer.Playback(EntityManager)
and then the last piece is to receive it on the Client side
// client side
// catch the messages and apply them to the desired entity
Entities.ForEach((Entity messageEntity, in Shipyard_DetailsResponse cmd, in ReceiveRpcCommandRequest req) =>
{
buffer.SetComponent(cmd.target, cmd.data);
buffer.DestroyEntity(messageEntity)
}).Run()
buffer.Playback(EntityManager)
Shipyard_DetailsResponse : IRpcCommand
{
public Entity target;
public Shipyard data;
}
Shipyard_DetailsRequest : IRpcCommand
{
public Entity target;
}
Send methods are
[BurstCompile]
public static void Send(ref Shipyard_DetailsRequest message, ref Entity target, ref EntityCommandBuffer ecb, ref EntityArchetype entityArchetype)
{
var commandEntity = ecb.CreateEntity(entityArchetype);
ecb.SetComponent(commandEntity, message);
}
[BurstCompile]
public static void Send(ref Shipyard_DetailsResponse message, ref Entity player, ref EntityCommandBuffer ecb, ref EntityArchetype entityArchetype)
{
var targetConnection = new SendRpcCommandRequest
{
TargetConnection = player
};
var commandEntity = ecb.CreateEntity(entityArchetype);
ecb.SetComponent(commandEntity, message);
ecb.SetComponent(commandEntity, targetConnection);
}
And then with some code-gen magic it goes generic
and with some ‘Change detection’ allows for a nice Subscriber - Publisher pattern
I got also buffers to be send in the same manner, with pagination. Tested with 50k (sending small pages of 128 items) so ~ 390 messages and it was ~(300-900ms) (No Burst enabled) on localhost until fully received, server was working fine, just the message queue was swamped
(bandwidth might be a bigger issue if you want to send 50k items every frame )
With Burst it took less than 10ms for the whole thing (6 frames) (6x server, 6x client), sending 8192 items per frame
in my test a message has ~12 + 4 * 128 bytes so 524b / message ~ 200kb total + (?) overhead from netcode
The way i have split things up is:
Components that need to be sent every frame - use the GhostComponent
Components that need to be sent ‘eventually’ - use RPC
Hopefully i do not run into a “Max number of RPC” issue
Edit: if (rpcIndex == ushort.MaxValue) => v .10 uses 65,535 max rpc, so no fear
Edit: added the send method; archetypes for completeness