Move routing info into message header, make relay type-agnostic

This commit is contained in:
Christian Semmler 2026-03-08 10:21:36 -07:00
parent a8c3ec7b2f
commit 9e8ecd6d44
No known key found for this signature in database
GPG Key ID: 086DAA1360BEEE5C
6 changed files with 39 additions and 82 deletions

View File

@ -90,7 +90,6 @@ class NetworkManager : public MxCore {
RemotePlayer* CreateAndSpawnPlayer(uint32_t p_peerId, uint8_t p_actorId, uint8_t p_displayActorIndex);
void HandleJoin(const PlayerJoinMsg& p_msg);
void HandleLeave(const PlayerLeaveMsg& p_msg);
void HandleState(const PlayerStateMsg& p_msg);
void HandleHostAssign(const HostAssignMsg& p_msg);

View File

@ -10,8 +10,12 @@ class LegoPathActor;
namespace Multiplayer
{
// Routing target constants for MessageHeader.target
const uint32_t TARGET_BROADCAST = 0; // Broadcast to all except sender
const uint32_t TARGET_HOST = 0xFFFFFFFF; // Send to host only
const uint32_t TARGET_BROADCAST_ALL = 0xFFFFFFFE; // Broadcast to all including sender
enum MessageType : uint8_t {
MSG_JOIN = 1,
MSG_LEAVE = 2,
MSG_STATE = 3,
MSG_HOST_ASSIGN = 4,
@ -74,12 +78,7 @@ struct MessageHeader {
uint8_t type;
uint32_t peerId;
uint32_t sequence;
};
struct PlayerJoinMsg {
MessageHeader header;
uint8_t actorId;
char name[20];
uint32_t target;
};
struct PlayerLeaveMsg {
@ -115,10 +114,9 @@ struct RequestSnapshotMsg {
};
// Host -> specific client: full world state blob (variable length)
// Relay reads targetPeerId at offset 9 and routes to that peer only.
// Relay reads header.target and routes to that peer only.
struct WorldSnapshotMsg {
MessageHeader header;
uint32_t targetPeerId;
uint16_t dataLength;
// Followed by dataLength bytes of serialized plant + building state
};

View File

@ -312,7 +312,7 @@ void NetworkManager::BroadcastLocalState()
}
PlayerStateMsg msg{};
msg.header = {MSG_STATE, m_localPeerId, m_sequence++};
msg.header = {MSG_STATE, m_localPeerId, m_sequence++, TARGET_BROADCAST};
msg.actorId = actorId;
msg.worldId = (int8_t) currentWorld->GetWorldId();
msg.vehicleType = DetectVehicleType(userActor);
@ -397,16 +397,6 @@ void NetworkManager::ProcessIncomingPackets()
}
break;
}
case MSG_JOIN: {
PlayerJoinMsg msg;
if (DeserializeMsg(data, length, msg) && msg.header.type == MSG_JOIN) {
msg.name[sizeof(msg.name) - 1] = '\0';
if (IsValidActorId(msg.actorId)) {
HandleJoin(msg);
}
}
break;
}
case MSG_LEAVE: {
PlayerLeaveMsg msg;
if (DeserializeMsg(data, length, msg) && msg.header.type == MSG_LEAVE) {
@ -494,18 +484,6 @@ RemotePlayer* NetworkManager::CreateAndSpawnPlayer(uint32_t p_peerId, uint8_t p_
return ptr;
}
void NetworkManager::HandleJoin(const PlayerJoinMsg& p_msg)
{
uint32_t peerId = p_msg.header.peerId;
if (m_remotePlayers.count(peerId)) {
return;
}
CreateAndSpawnPlayer(peerId, p_msg.actorId, p_msg.actorId - 1);
NotifyPlayerCountChanged();
}
void NetworkManager::HandleLeave(const PlayerLeaveMsg& p_msg)
{
RemoveRemotePlayer(p_msg.header.peerId);
@ -594,7 +572,7 @@ void NetworkManager::SendEmote(uint8_t p_emoteId)
m_thirdPersonCamera.TriggerEmote(p_emoteId);
EmoteMsg msg{};
msg.header = {MSG_EMOTE, m_localPeerId, m_sequence++};
msg.header = {MSG_EMOTE, m_localPeerId, m_sequence++, TARGET_BROADCAST};
msg.emoteId = p_emoteId;
SendMessage(msg);
}
@ -713,7 +691,7 @@ bool NetworkManager::IsClonedCharacter(const char* p_name) const
void NetworkManager::SendCustomize(uint32_t p_targetPeerId, uint8_t p_changeType, uint8_t p_partIndex)
{
CustomizeMsg msg{};
msg.header = {MSG_CUSTOMIZE, m_localPeerId, m_sequence++};
msg.header = {MSG_CUSTOMIZE, m_localPeerId, m_sequence++, TARGET_BROADCAST_ALL};
msg.targetPeerId = p_targetPeerId;
msg.changeType = p_changeType;
msg.partIndex = p_partIndex;

View File

@ -1,14 +1,12 @@
import {
HEADER_SIZE,
MSG_CUSTOMIZE,
MSG_REQUEST_SNAPSHOT,
MSG_WORLD_EVENT_REQUEST,
MSG_WORLD_SNAPSHOT,
SNAPSHOT_MIN_SIZE,
TARGET_BROADCAST,
TARGET_HOST,
TARGET_BROADCAST_ALL,
createAssignIdMsg,
createHostAssignMsg,
createLeaveMsg,
readTargetPeerId,
readTarget,
stampSender,
} from "./protocol";
import type { Env } from "./relay";
@ -166,30 +164,17 @@ export class GameRoom implements DurableObject {
return;
}
const msgType = data[0];
const stamped = stampSender(data, peerId);
const target = readTarget(stamped);
if (
msgType === MSG_REQUEST_SNAPSHOT ||
msgType === MSG_WORLD_EVENT_REQUEST
) {
if (target === TARGET_BROADCAST) {
this.broadcastExcept(stamped.buffer, peerId);
} else if (target === TARGET_HOST) {
this.sendToHost(stamped);
} else if (
msgType === MSG_WORLD_SNAPSHOT &&
data.length >= SNAPSHOT_MIN_SIZE
) {
const targetId = readTargetPeerId(stamped);
if (targetId === 0) {
this.broadcastExcept(stamped.buffer, peerId);
} else {
this.sendToTarget(stamped);
}
} else if (msgType === MSG_CUSTOMIZE) {
// Broadcast to all including sender so the clicker sees effects
// on the target's clone on their own screen.
} else if (target === TARGET_BROADCAST_ALL) {
this.broadcast(stamped.buffer);
} else {
this.broadcastExcept(stamped.buffer, peerId);
this.sendToTarget(stamped, target);
}
}
@ -200,8 +185,7 @@ export class GameRoom implements DurableObject {
}
}
private sendToTarget(data: Uint8Array): void {
const targetId = readTargetPeerId(data);
private sendToTarget(data: Uint8Array, targetId: number): void {
const targetWs = this.connections.get(targetId);
if (targetWs) {
if (!this.trySend(targetWs, data.buffer)) {

View File

@ -1,30 +1,27 @@
// Protocol constants — must stay in sync with protocol.h
// MessageHeader binary layout: type(1) + peerId(4) + sequence(4) = 9 bytes
export const HEADER_SIZE = 9;
// MessageHeader binary layout: type(1) + peerId(4) + sequence(4) + target(4) = 13 bytes
export const HEADER_SIZE = 13;
export const PEER_ID_OFFSET = 1;
export const SEQUENCE_OFFSET = 5;
export const TARGET_OFFSET = 9;
// Message types the relay inspects for routing decisions.
// All other types are broadcast to every peer in the room.
// Routing target constants
export const TARGET_BROADCAST = 0;
export const TARGET_HOST = 0xffffffff;
export const TARGET_BROADCAST_ALL = 0xfffffffe;
// Message types used by server message constructors only.
export const MSG_LEAVE = 2;
export const MSG_HOST_ASSIGN = 4;
export const MSG_REQUEST_SNAPSHOT = 5;
export const MSG_WORLD_SNAPSHOT = 6;
export const MSG_WORLD_EVENT_REQUEST = 8;
export const MSG_CUSTOMIZE = 10;
export const MSG_ASSIGN_ID = 0xff;
// AssignIdMsg: compact server-only message — type(1) + peerId(4) + maxActors(1)
const ASSIGN_ID_SIZE = 1 + 4 + 1;
// HostAssignMsg: header(9) + hostPeerId(4)
// HostAssignMsg: header(13) + hostPeerId(4)
const HOST_ASSIGN_SIZE = HEADER_SIZE + 4;
// WorldSnapshotMsg: header(9) + targetPeerId(4) + dataLength(2) + data...
export const SNAPSHOT_TARGET_OFFSET = HEADER_SIZE;
export const SNAPSHOT_MIN_SIZE = HEADER_SIZE + 4 + 2;
export function createAssignIdMsg(peerId: number, maxActors: number): ArrayBuffer {
const buf = new ArrayBuffer(ASSIGN_ID_SIZE);
const view = new DataView(buf);
@ -40,6 +37,7 @@ export function createHostAssignMsg(hostPeerId: number): ArrayBuffer {
view.setUint8(0, MSG_HOST_ASSIGN);
view.setUint32(PEER_ID_OFFSET, 0, true);
view.setUint32(SEQUENCE_OFFSET, 0, true);
view.setUint32(TARGET_OFFSET, 0, true);
view.setUint32(HEADER_SIZE, hostPeerId, true);
return buf;
}
@ -50,6 +48,7 @@ export function createLeaveMsg(peerId: number): ArrayBuffer {
view.setUint8(0, MSG_LEAVE);
view.setUint32(PEER_ID_OFFSET, peerId, true);
view.setUint32(SEQUENCE_OFFSET, 0, true);
view.setUint32(TARGET_OFFSET, 0, true);
return buf;
}
@ -61,6 +60,6 @@ export function stampSender(data: Uint8Array, peerId: number): Uint8Array {
return stamped;
}
export function readTargetPeerId(data: Uint8Array): number {
return new DataView(data.buffer).getUint32(SNAPSHOT_TARGET_OFFSET, true);
export function readTarget(data: Uint8Array): number {
return new DataView(data.buffer).getUint32(TARGET_OFFSET, true);
}

View File

@ -232,7 +232,7 @@ MxBool WorldStateSync::HandleSkyLightMutation(uint8_t p_entityType, uint8_t p_ch
void WorldStateSync::SendSnapshotRequest()
{
RequestSnapshotMsg msg{};
msg.header = {MSG_REQUEST_SNAPSHOT, m_localPeerId, m_sequence++};
msg.header = {MSG_REQUEST_SNAPSHOT, m_localPeerId, m_sequence++, TARGET_HOST};
SendMessage(msg);
m_snapshotRequested = true;
@ -270,8 +270,7 @@ void WorldStateSync::SendWorldSnapshot(uint32_t p_targetPeerId)
stateBuffer[dataLength++] = (uint8_t) lightPos;
WorldSnapshotMsg msg{};
msg.header = {MSG_WORLD_SNAPSHOT, m_localPeerId, m_sequence++};
msg.targetPeerId = p_targetPeerId;
msg.header = {MSG_WORLD_SNAPSHOT, m_localPeerId, m_sequence++, p_targetPeerId};
msg.dataLength = (uint16_t) dataLength;
std::vector<uint8_t> msgBuf(sizeof(WorldSnapshotMsg) + dataLength);
@ -284,7 +283,7 @@ void WorldStateSync::SendWorldSnapshot(uint32_t p_targetPeerId)
void WorldStateSync::BroadcastWorldEvent(uint8_t p_entityType, uint8_t p_changeType, uint8_t p_entityIndex)
{
WorldEventMsg msg{};
msg.header = {MSG_WORLD_EVENT, m_localPeerId, m_sequence++};
msg.header = {MSG_WORLD_EVENT, m_localPeerId, m_sequence++, TARGET_BROADCAST};
msg.entityType = p_entityType;
msg.changeType = p_changeType;
msg.entityIndex = p_entityIndex;
@ -294,7 +293,7 @@ void WorldStateSync::BroadcastWorldEvent(uint8_t p_entityType, uint8_t p_changeT
void WorldStateSync::SendWorldEventRequest(uint8_t p_entityType, uint8_t p_changeType, uint8_t p_entityIndex)
{
WorldEventRequestMsg msg{};
msg.header = {MSG_WORLD_EVENT_REQUEST, m_localPeerId, m_sequence++};
msg.header = {MSG_WORLD_EVENT_REQUEST, m_localPeerId, m_sequence++, TARGET_HOST};
msg.entityType = p_entityType;
msg.changeType = p_changeType;
msg.entityIndex = p_entityIndex;