1
0

packet generation experiment

This commit is contained in:
Sky Johnson 2025-09-08 13:46:30 -05:00
parent cc6c92a005
commit 2cc96422a2
5 changed files with 337 additions and 40 deletions

View File

@ -20,4 +20,7 @@ clean:
run: $(TARGET) run: $(TARGET)
./$(TARGET) ./$(TARGET)
.PHONY: all clean run packets:
cd .. && lua generate_packets.lua
.PHONY: all clean run packets

View File

@ -31,9 +31,9 @@ void NetworkManager::startReceive() {
void NetworkManager::processMessage(const uint8_t* data, std::size_t size) { void NetworkManager::processMessage(const uint8_t* data, std::size_t size) {
if (size == 0) return; if (size == 0) return;
auto msgType = static_cast<MessageType>(data[0]); auto msgType = static_cast<MessageType>(data[0]);
switch (msgType) { switch (msgType) {
case MessageType::Spawn: case MessageType::Spawn:
handleSpawn(data, size); handleSpawn(data, size);
@ -61,19 +61,19 @@ void NetworkManager::processMessage(const uint8_t* data, std::size_t size) {
void NetworkManager::handleSpawn(const uint8_t* data, std::size_t size) { void NetworkManager::handleSpawn(const uint8_t* data, std::size_t size) {
// Message format: [type(1)][id(4)][x(4)][y(4)][z(4)][colorLen(1)][color(colorLen)] // Message format: [type(1)][id(4)][x(4)][y(4)][z(4)][colorLen(1)][color(colorLen)]
if (size < 18) return; if (size < 18) return;
uint32_t id; uint32_t id;
float x, y, z; float x, y, z;
std::memcpy(&id, &data[1], sizeof(id)); std::memcpy(&id, &data[1], sizeof(id));
std::memcpy(&x, &data[5], sizeof(x)); std::memcpy(&x, &data[5], sizeof(x));
std::memcpy(&y, &data[9], sizeof(y)); std::memcpy(&y, &data[9], sizeof(y));
std::memcpy(&z, &data[13], sizeof(z)); std::memcpy(&z, &data[13], sizeof(z));
uint8_t colorLen = data[17]; uint8_t colorLen = data[17];
if (size >= 18 + colorLen) { if (size >= 18 + colorLen) {
playerColor = std::string(reinterpret_cast<const char*>(&data[18]), colorLen); playerColor = std::string(reinterpret_cast<const char*>(&data[18]), colorLen);
} }
playerID = id; playerID = id;
{ {
std::lock_guard lock(positionMutex); std::lock_guard lock(positionMutex);
@ -85,14 +85,14 @@ void NetworkManager::handleSpawn(const uint8_t* data, std::size_t size) {
void NetworkManager::handleUpdate(const uint8_t* data, std::size_t size) { void NetworkManager::handleUpdate(const uint8_t* data, std::size_t size) {
if (size < 17) return; if (size < 17) return;
uint32_t id; uint32_t id;
float x, y, z; float x, y, z;
std::memcpy(&id, &data[1], sizeof(id)); std::memcpy(&id, &data[1], sizeof(id));
std::memcpy(&x, &data[5], sizeof(x)); std::memcpy(&x, &data[5], sizeof(x));
std::memcpy(&y, &data[9], sizeof(y)); std::memcpy(&y, &data[9], sizeof(y));
std::memcpy(&z, &data[13], sizeof(z)); std::memcpy(&z, &data[13], sizeof(z));
if (id == playerID) { if (id == playerID) {
std::lock_guard lock(positionMutex); std::lock_guard lock(positionMutex);
serverPosition = {x, y, z}; serverPosition = {x, y, z};
@ -108,20 +108,20 @@ void NetworkManager::handleUpdate(const uint8_t* data, std::size_t size) {
void NetworkManager::handlePlayerJoined(const uint8_t* data, std::size_t size) { void NetworkManager::handlePlayerJoined(const uint8_t* data, std::size_t size) {
// Message format: [type(1)][id(4)][x(4)][y(4)][z(4)][colorLen(1)][color(colorLen)] // Message format: [type(1)][id(4)][x(4)][y(4)][z(4)][colorLen(1)][color(colorLen)]
if (size < 18) return; if (size < 18) return;
uint32_t id; uint32_t id;
float x, y, z; float x, y, z;
std::memcpy(&id, &data[1], sizeof(id)); std::memcpy(&id, &data[1], sizeof(id));
std::memcpy(&x, &data[5], sizeof(x)); std::memcpy(&x, &data[5], sizeof(x));
std::memcpy(&y, &data[9], sizeof(y)); std::memcpy(&y, &data[9], sizeof(y));
std::memcpy(&z, &data[13], sizeof(z)); std::memcpy(&z, &data[13], sizeof(z));
uint8_t colorLen = data[17]; uint8_t colorLen = data[17];
std::string color = "red"; std::string color = "red";
if (size >= 18 + colorLen) { if (size >= 18 + colorLen) {
color = std::string(reinterpret_cast<const char*>(&data[18]), colorLen); color = std::string(reinterpret_cast<const char*>(&data[18]), colorLen);
} }
if (id != playerID) { if (id != playerID) {
std::lock_guard lock(remotePlayersMutex); std::lock_guard lock(remotePlayersMutex);
remotePlayers[id] = {id, {x, y, z}, color, static_cast<float>(GetTime())}; remotePlayers[id] = {id, {x, y, z}, color, static_cast<float>(GetTime())};
@ -131,10 +131,10 @@ void NetworkManager::handlePlayerJoined(const uint8_t* data, std::size_t size) {
void NetworkManager::handlePlayerLeft(const uint8_t* data, std::size_t size) { void NetworkManager::handlePlayerLeft(const uint8_t* data, std::size_t size) {
if (size < 5) return; if (size < 5) return;
uint32_t id; uint32_t id;
std::memcpy(&id, &data[1], sizeof(id)); std::memcpy(&id, &data[1], sizeof(id));
std::lock_guard lock(remotePlayersMutex); std::lock_guard lock(remotePlayersMutex);
remotePlayers.erase(id); remotePlayers.erase(id);
std::cout << "Player " << id << " left\n"; std::cout << "Player " << id << " left\n";
@ -142,13 +142,13 @@ void NetworkManager::handlePlayerLeft(const uint8_t* data, std::size_t size) {
void NetworkManager::handlePlayerList(const uint8_t* data, std::size_t size) { void NetworkManager::handlePlayerList(const uint8_t* data, std::size_t size) {
if (size < 2) return; if (size < 2) return;
uint8_t count = data[1]; uint8_t count = data[1];
size_t offset = 2; size_t offset = 2;
std::lock_guard lock(remotePlayersMutex); std::lock_guard lock(remotePlayersMutex);
remotePlayers.clear(); remotePlayers.clear();
for (uint8_t i = 0; i < count && offset + 17 < size; i++) { for (uint8_t i = 0; i < count && offset + 17 < size; i++) {
uint32_t id; uint32_t id;
float x, y, z; float x, y, z;
@ -156,21 +156,21 @@ void NetworkManager::handlePlayerList(const uint8_t* data, std::size_t size) {
std::memcpy(&x, &data[offset + 4], sizeof(x)); std::memcpy(&x, &data[offset + 4], sizeof(x));
std::memcpy(&y, &data[offset + 8], sizeof(y)); std::memcpy(&y, &data[offset + 8], sizeof(y));
std::memcpy(&z, &data[offset + 12], sizeof(z)); std::memcpy(&z, &data[offset + 12], sizeof(z));
uint8_t colorLen = data[offset + 16]; uint8_t colorLen = data[offset + 16];
std::string color = "red"; std::string color = "red";
if (offset + 17 + colorLen <= size) { if (offset + 17 + colorLen <= size) {
color = std::string(reinterpret_cast<const char*>(&data[offset + 17]), colorLen); color = std::string(reinterpret_cast<const char*>(&data[offset + 17]), colorLen);
} }
offset += 17 + colorLen; offset += 17 + colorLen;
if (id != playerID) { if (id != playerID) {
remotePlayers[id] = {id, {x, y, z}, color, static_cast<float>(GetTime())}; remotePlayers[id] = {id, {x, y, z}, color, static_cast<float>(GetTime())};
} }
} }
std::cout << "Received list of " << (int)count << " players\n"; std::cout << "Received list of " << (int)count << " players\n";
} }
@ -181,30 +181,30 @@ void NetworkManager::sendLogin() {
void NetworkManager::sendMove(float dx, float dy, float dz) { void NetworkManager::sendMove(float dx, float dy, float dz) {
if (!connected) return; if (!connected) return;
std::array<uint8_t, 17> msg{}; std::array<uint8_t, 17> msg{};
msg[0] = static_cast<uint8_t>(MessageType::Move); msg[0] = static_cast<uint8_t>(MessageType::Move);
uint32_t id = playerID; uint32_t id = playerID;
std::memcpy(&msg[1], &id, sizeof(id)); std::memcpy(&msg[1], &id, sizeof(id));
std::memcpy(&msg[5], &dx, sizeof(dx)); std::memcpy(&msg[5], &dx, sizeof(dx));
std::memcpy(&msg[9], &dy, sizeof(dy)); std::memcpy(&msg[9], &dy, sizeof(dy));
std::memcpy(&msg[13], &dz, sizeof(dz)); std::memcpy(&msg[13], &dz, sizeof(dz));
socket.send_to(buffer(msg), serverEndpoint); socket.send_to(buffer(msg), serverEndpoint);
} }
void NetworkManager::sendColorChange(const std::string& newColor) { void NetworkManager::sendColorChange(const std::string& newColor) {
if (!connected) return; if (!connected) return;
std::vector<uint8_t> msg(6 + newColor.size()); std::vector<uint8_t> msg(6 + newColor.size());
msg[0] = static_cast<uint8_t>(MessageType::ChangeColor); msg[0] = static_cast<uint8_t>(MessageType::ChangeColor);
uint32_t id = playerID; uint32_t id = playerID;
std::memcpy(&msg[1], &id, sizeof(id)); std::memcpy(&msg[1], &id, sizeof(id));
msg[5] = static_cast<uint8_t>(newColor.size()); msg[5] = static_cast<uint8_t>(newColor.size());
std::memcpy(&msg[6], newColor.data(), newColor.size()); std::memcpy(&msg[6], newColor.data(), newColor.size());
socket.send_to(buffer(msg), serverEndpoint); socket.send_to(buffer(msg), serverEndpoint);
} }
@ -216,14 +216,14 @@ Vector3 NetworkManager::getPosition() {
void NetworkManager::handleColorChanged(const uint8_t* data, std::size_t size) { void NetworkManager::handleColorChanged(const uint8_t* data, std::size_t size) {
// Message format: [type(1)][id(4)][colorLen(1)][color(colorLen)] // Message format: [type(1)][id(4)][colorLen(1)][color(colorLen)]
if (size < 6) return; if (size < 6) return;
uint32_t id; uint32_t id;
std::memcpy(&id, &data[1], sizeof(id)); std::memcpy(&id, &data[1], sizeof(id));
uint8_t colorLen = data[5]; uint8_t colorLen = data[5];
if (size >= 6 + colorLen) { if (size >= 6 + colorLen) {
std::string newColor(reinterpret_cast<const char*>(&data[6]), colorLen); std::string newColor(reinterpret_cast<const char*>(&data[6]), colorLen);
if (id == playerID) { if (id == playerID) {
playerColor = newColor; playerColor = newColor;
std::cout << "Your color changed to " << newColor << "\n"; std::cout << "Your color changed to " << newColor << "\n";
@ -240,4 +240,4 @@ void NetworkManager::handleColorChanged(const uint8_t* data, std::size_t size) {
std::unordered_map<uint32_t, RemotePlayer> NetworkManager::getRemotePlayers() { std::unordered_map<uint32_t, RemotePlayer> NetworkManager::getRemotePlayers() {
std::lock_guard lock(remotePlayersMutex); std::lock_guard lock(remotePlayersMutex);
return remotePlayers; return remotePlayers;
} }

View File

@ -36,37 +36,37 @@ class NetworkManager {
public: public:
NetworkManager(); NetworkManager();
~NetworkManager(); ~NetworkManager();
void sendLogin(); void sendLogin();
void sendMove(float dx, float dy, float dz); void sendMove(float dx, float dy, float dz);
void sendColorChange(const std::string& newColor); void sendColorChange(const std::string& newColor);
Vector3 getPosition(); Vector3 getPosition();
bool isConnected() const { return connected; } bool isConnected() const { return connected; }
uint32_t getPlayerID() const { return playerID; } uint32_t getPlayerID() const { return playerID; }
std::string getPlayerColor() const { return playerColor; } std::string getPlayerColor() const { return playerColor; }
std::unordered_map<uint32_t, RemotePlayer> getRemotePlayers(); std::unordered_map<uint32_t, RemotePlayer> getRemotePlayers();
// Available colors for cycling // Available colors for cycling
static const std::vector<std::string> AVAILABLE_COLORS; static const std::vector<std::string> AVAILABLE_COLORS;
private: private:
io_context ioContext; io_context ioContext;
udp::socket socket{ioContext}; udp::socket socket{ioContext};
udp::endpoint serverEndpoint; udp::endpoint serverEndpoint;
std::thread ioThread; std::thread ioThread;
std::array<uint8_t, 1024> recvBuffer; std::array<uint8_t, 1024> recvBuffer;
std::atomic<uint32_t> playerID{0}; std::atomic<uint32_t> playerID{0};
std::string playerColor{"red"}; std::string playerColor{"red"};
std::mutex positionMutex; std::mutex positionMutex;
Vector3 serverPosition{0, 0, 0}; Vector3 serverPosition{0, 0, 0};
std::atomic<bool> connected{false}; std::atomic<bool> connected{false};
std::mutex remotePlayersMutex; std::mutex remotePlayersMutex;
std::unordered_map<uint32_t, RemotePlayer> remotePlayers; std::unordered_map<uint32_t, RemotePlayer> remotePlayers;
void startReceive(); void startReceive();
void processMessage(const uint8_t* data, std::size_t size); void processMessage(const uint8_t* data, std::size_t size);
void handleSpawn(const uint8_t* data, std::size_t size); void handleSpawn(const uint8_t* data, std::size_t size);
@ -75,4 +75,4 @@ private:
void handlePlayerLeft(const uint8_t* data, std::size_t size); void handlePlayerLeft(const uint8_t* data, std::size_t size);
void handlePlayerList(const uint8_t* data, std::size_t size); void handlePlayerList(const uint8_t* data, std::size_t size);
void handleColorChanged(const uint8_t* data, std::size_t size); void handleColorChanged(const uint8_t* data, std::size_t size);
}; };

198
generate_packets.php Executable file
View File

@ -0,0 +1,198 @@
#!/usr/bin/env php
<?php
/**
* Generate packet definitions for C++ client and Go server from packets.json
*/
/**
* Load packet definitions from JSON file
*/
function load_packet_definitions($filename = 'packets.json') {
if (!file_exists($filename)) {
throw new Exception("Could not open $filename");
}
$content = file_get_contents($filename);
$packets = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception("JSON decode error: " . json_last_error_msg());
}
return $packets;
}
/**
* Convert PascalCase to SNAKE_CASE
*/
function to_snake_case($str) {
$result = preg_replace('/([A-Z])/', '_$1', $str);
return strtoupper(ltrim($result, '_'));
}
/**
* Generate C++ header file with packet definitions
*/
function generate_cpp_header($packets) {
$header = <<<CPP
// Auto-generated packet definitions from packets.json
// DO NOT EDIT MANUALLY
#pragma once
#include <cstdint>
#include <string>
CPP;
// Generate enum
$header .= "enum class MessageType : uint8_t {\n";
// Sort keys for consistent output
$names = array_keys($packets['opcodes']);
sort($names);
foreach ($names as $i => $name) {
$packet = $packets['opcodes'][$name];
$header .= sprintf(" %s = %s", $name, $packet['id']);
if ($i < count($names) - 1) {
$header .= ",";
}
$header .= "\n";
}
$header .= "};\n\n";
// Generate packet structures
$header .= "// Packet structure definitions\n";
$header .= "namespace Packets {\n\n";
foreach ($names as $name) {
$packet = $packets['opcodes'][$name];
if (!empty($packet['fields'])) {
$header .= sprintf("struct %s {\n", $name);
$header .= sprintf(" static constexpr MessageType TYPE = MessageType::%s;\n", $name);
foreach ($packet['fields'] as $field) {
$cpp_type = '';
switch ($field['type']) {
case 'string':
$cpp_type = 'std::string';
break;
case 'uint8':
$cpp_type = 'uint8_t';
break;
case 'uint32':
$cpp_type = 'uint32_t';
break;
case 'float32':
$cpp_type = 'float';
break;
case 'array':
$header .= sprintf(" // Array field: %s - requires custom handling\n", $field['name']);
continue 2;
}
$header .= sprintf(" %s %s;\n", $cpp_type, $field['name']);
}
$header .= "};\n\n";
}
}
$header .= "} // namespace Packets\n";
return $header;
}
/**
* Generate Go file with packet definitions
*/
function generate_go_file($packets) {
$go_file = <<<GO
// Auto-generated packet definitions from packets.json
// DO NOT EDIT MANUALLY
package main
// Message type constants
const (
GO;
$go_file .= "\n";
// Sort keys for consistent output
$names = array_keys($packets['opcodes']);
sort($names);
// Generate constants
foreach ($names as $name) {
$packet = $packets['opcodes'][$name];
$const_name = 'MSG_' . to_snake_case($name);
$go_file .= sprintf("\t%s = %s\n", $const_name, $packet['id']);
}
$go_file .= ")\n\n";
// Generate packet structures
$go_file .= "// Packet structure definitions\n\n";
foreach ($names as $name) {
$packet = $packets['opcodes'][$name];
if (!empty($packet['fields'])) {
$struct_name = "Packet $name";
$go_file .= sprintf("type %s struct {\n", $struct_name);
foreach ($packet['fields'] as $field) {
$field_name = ucfirst($field['name']);
$go_type = '';
switch ($field['type']) {
case 'string':
$go_type = 'string';
break;
case 'uint8':
$go_type = 'uint8';
break;
case 'uint32':
$go_type = 'uint32';
break;
case 'float32':
$go_type = 'float32';
break;
case 'array':
$go_file .= sprintf("\t// Array field: %s - requires custom handling\n", $field_name);
continue 2;
}
$go_file .= sprintf("\t%s %s\n", $field_name, $go_type);
}
$go_file .= "}\n\n";
}
}
return $go_file;
}
try {
// Load packet definitions
$packets = load_packet_definitions();
// Generate C++ header
$cpp_header = generate_cpp_header($packets);
$cpp_path = 'client/net/PacketDefinitions.hpp';
file_put_contents($cpp_path, $cpp_header);
echo "Generated $cpp_path\n";
// Generate Go file
$go_file = generate_go_file($packets);
$go_path = 'server/packet_definitions.go';
file_put_contents($go_path, $go_file);
echo "Generated $go_path\n";
echo sprintf("Successfully generated packet definitions for protocol version %s\n", $packets['version']);
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
exit(1);
}

96
packets.json Normal file
View File

@ -0,0 +1,96 @@
{
"version": "1.0.0",
"opcodes": {
"Login": {
"id": "0x01",
"fields": []
},
"Position": {
"id": "0x02",
"fields": [
{"name": "x", "type": "float32"},
{"name": "y", "type": "float32"},
{"name": "z", "type": "float32"}
]
},
"Spawn": {
"id": "0x03",
"fields": [
{"name": "playerId", "type": "uint32"},
{"name": "x", "type": "float32"},
{"name": "y", "type": "float32"},
{"name": "z", "type": "float32"},
{"name": "color", "type": "string"}
]
},
"Move": {
"id": "0x04",
"fields": [
{"name": "playerId", "type": "uint32"},
{"name": "dx", "type": "float32"},
{"name": "dy", "type": "float32"},
{"name": "dz", "type": "float32"}
]
},
"Update": {
"id": "0x05",
"fields": [
{"name": "playerId", "type": "uint32"},
{"name": "x", "type": "float32"},
{"name": "y", "type": "float32"},
{"name": "z", "type": "float32"}
]
},
"PlayerJoined": {
"id": "0x06",
"fields": [
{"name": "playerId", "type": "uint32"},
{"name": "x", "type": "float32"},
{"name": "y", "type": "float32"},
{"name": "z", "type": "float32"},
{"name": "color", "type": "string"}
]
},
"PlayerLeft": {
"id": "0x07",
"fields": [
{"name": "playerId", "type": "uint32"}
]
},
"PlayerList": {
"id": "0x08",
"fields": [
{"name": "count", "type": "uint8"},
{"name": "players", "type": "array", "itemType": {
"fields": [
{"name": "playerId", "type": "uint32"},
{"name": "x", "type": "float32"},
{"name": "y", "type": "float32"},
{"name": "z", "type": "float32"},
{"name": "color", "type": "string"}
]
}}
]
},
"ChangeColor": {
"id": "0x09",
"fields": [
{"name": "playerId", "type": "uint32"},
{"name": "color", "type": "string"}
]
},
"ColorChanged": {
"id": "0x0A",
"fields": [
{"name": "playerId", "type": "uint32"},
{"name": "color", "type": "string"}
]
}
},
"types": {
"uint8": {"size": 1, "encoding": "little-endian"},
"uint32": {"size": 4, "encoding": "little-endian"},
"float32": {"size": 4, "encoding": "little-endian"},
"string": {"encoding": "length-prefixed", "lengthType": "uint8"}
}
}