This commit is contained in:
Martin Slachta
2026-07-18 14:31:15 +02:00
commit a04f0dc262
3343 changed files with 1140208 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
project(tw_protocol)
file(GLOB FILES
./src/messages/*.cpp
./src/*.cc
)
file(GLOB HEADERS
./src/messages/*.hpp
./src/*.pb.h
./src/messages/*.h
)
file(GLOB PROTO_FILES
${CMAKE_CURRENT_LIST_DIR}/proto/*.proto
)
set(PROTO_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
add_library(${PROJECT_NAME} OBJECT ./src/Protocol.cpp ${PROTO_FILES})
add_library(tw::protocol ALIAS ${PROJECT_NAME})
target_link_libraries(${PROJECT_NAME}
PUBLIC
glm::glm
protobuf::libprotobuf
)
target_include_directories(${PROJECT_NAME}
PUBLIC
"$<BUILD_INTERFACE:${PROTO_BINARY_DIR}>"
${CMAKE_CURRENT_BINARY_DIR}/generated
./src/
)
file(MAKE_DIRECTORY "${PROTO_BINARY_DIR}")
include(FindProtobuf)
find_package(Protobuf CONFIG REQUIRED)
protobuf_generate(
TARGET ${PROJECT_NAME}
IMPORT_DIRS "${CMAKE_CURRENT_LIST_DIR}/proto"
PROTOC_OUT_DIR "${PROTO_BINARY_DIR}")
#set_target_properties(${PROJECT_NAME} PROPERTIES
# POSITION_INDEPENDENT_CODE 1
#)
#set_source_files_properties(PROTO_SRC PROTO_HDRS PROPERTIES GENERATED TRUE)
target_sources(${PROJECT_NAME}
PUBLIC FILE_SET HEADERS
BASE_DIRS ${CMAKE_CURRENT_LIST_DIR}
FILES ${HEADERS} ${PROTO_HDRS})
+47
View File
@@ -0,0 +1,47 @@
edition = "2023";
package mmo.chat;
message SendChatMessageRequest {
uint64 channel_id = 1;
string message = 2;
}
message JoinChannelRequest {
uint64 channel_id = 1;
}
message LeaveChannelRequest {
uint64 channel_id = 1;
}
message ChatMessageBroadcastRequest {
uint64 channel_id = 1;
uint64 sender_id = 2;
string message = 3;
}
// Server -> Client responses
enum ChatErrorCode {
CHAT_ERROR_CODE_OK = 0;
CHAT_ERROR_CODE_UNAUTHORIZED = 1;
CHAT_ERROR_CODE_CHANNEL_NOT_FOUND = 2;
CHAT_ERROR_CODE_ALREADY_IN_CHANNEL = 3;
CHAT_ERROR_CODE_NOT_IN_CHANNEL = 4;
}
message JoinChannelResponse {
uint64 channel_id = 1;
ChatErrorCode error = 2;
}
message LeaveChannelResponse {
uint64 channel_id = 1;
ChatErrorCode error = 2;
}
message SendChatMessageResponse {
uint64 channel_id = 1;
ChatErrorCode error = 2;
}
+30
View File
@@ -0,0 +1,30 @@
edition = "2023";
package mmo.cluster;
// XZ-plane area owned by a zone (vec2.y maps to world Z).
message ZoneBounds {
float min_x = 1;
float min_z = 2;
float max_x = 3;
float max_z = 4;
}
// Opaque zone identity assigned by ZoneCoordinator.
// Carries bounds so receivers can build their routing table without a separate lookup.
message ZoneId {
uint32 id = 1;
ZoneBounds bounds = 2;
}
// Sent by a zone server to every peer it discovers from the coordinator.
// Establishes the sender's identity on the cluster link.
message ZoneHello {
ZoneId zone = 1;
}
// Sent by a zone server just before it shuts down.
// Receivers must remove the named zone from their peer tables.
message ZoneBye {
ZoneId zone = 1;
}
+9
View File
@@ -0,0 +1,9 @@
syntax = "proto3";
package mmo;
message Pos3 {
float x = 1;
float y = 2;
float z = 3;
}
+13
View File
@@ -0,0 +1,13 @@
syntax = "proto3";
package mmo;
message EntitySpawnMessage {
uint32 entity_id = 1;
bool is_player = 2;
string name = 3;
}
message EntityDespawnMessage {
uint32 entity_id = 1;
}
+14
View File
@@ -0,0 +1,14 @@
edition = "2023";
package mmo;
message LoginRequest {
string username = 1;
string password = 2;
}
message LoginResponse {
bool success = 1;
string message = 2;
int32 entity_id = 3;
}
+30
View File
@@ -0,0 +1,30 @@
syntax = "proto3";
package mmo.peer;
message PeerInput {
float x = 1;
float y = 2;
float z = 3;
}
message PeerHello {
uint32 peer_id = 1;
}
message PeerAction {
uint32 frame_idx = 1;
PeerInput input = 2;
}
// All unacked history frames for one sender, plus a piggybacked ack.
// Sent as a single datagram / framed TCP message per tick.
message PeerActionBatch {
uint32 peer_id = 1;
uint32 ack_frame = 2; // sender's committed_frame
repeated PeerAction actions = 3;
}
message PeerBye {
uint32 peer_id = 1;
}
+15
View File
@@ -0,0 +1,15 @@
syntax = "proto3";
package mmo;
message PlayerInput {
float x = 1;
float y = 2;
float z = 3;
}
message PlayerMoveMessage {
int32 frame_idx = 1;
PlayerInput input = 2;
}
+16
View File
@@ -0,0 +1,16 @@
struct Vec3 {
x: float;
y: float;
z: float;
}
table PlayerInfo {
id:int;
pos:Vec3;
}
table WorldState {
players:[PlayerInfo];
}
root_type WorldState;
+28
View File
@@ -0,0 +1,28 @@
syntax = "proto3";
package mmo;
message EntityPosition {
int32 id = 1;
float x = 2;
float y = 3;
float z = 4;
}
message SpawnEntity {
int32 id = 1;
}
message DespawnEntity {
int32 id = 1;
}
message WorldStateMessage {
uint32 frame_idx = 1;
repeated SpawnEntity spawns = 2;
repeated EntityPosition entities = 3;
repeated DespawnEntity despawns = 4;
}
+162
View File
@@ -0,0 +1,162 @@
#pragma once
#include "WorldState.pb.h"
#include "Chat.pb.h"
#include "Cluster.pb.h"
#include "Login.pb.h"
#include "PlayerMove.pb.h"
#include "Entity.pb.h"
#include <spdlog/fmt/fmt.h>
#include <string_view>
enum PacketType {
MESSAGE_PACKET,
LOGIN_REQUEST_MSG,
LOGIN_RESPONSE_PACKET,
WORLD_STATE_PACKET,
PLAYER_UPDATE_MSG,
PLAYER_UPDATE_PACKET,
ENTITY_SPAWN_MSG,
ENTITY_DESPAWN_MSG,
ENTITY_MOVE_PACKET,
PLAYER_JOIN_REQUEST_PACKET,
PLAYER_JOIN_RESPONSE_PACKET,
CHAT_SEND_MESSAGE_REQUEST,
CHAT_SEND_MESSAGE_RESPONSE,
CHAT_JOIN_CHANNEL_REQUEST,
CHAT_JOIN_CHANNEL_RESPONSE,
CHAT_LEAVE_CHANNEL_REQUEST,
CHAT_LEAVE_CHANNEL_RESPONSE,
CHAT_MESSAGE_BROADCAST_REQUEST,
CLUSTER_ZONE_HELLO,
CLUSTER_ZONE_BYE,
};
namespace tw {
template<typename Msg>
class Message;
template<>
class Message<mmo::LoginRequest> {
public:
static constexpr PacketType value = LOGIN_REQUEST_MSG;
};
template<>
class Message<mmo::LoginResponse> {
public:
static constexpr PacketType value = LOGIN_RESPONSE_PACKET;
};
template<>
class Message<mmo::WorldStateMessage> {
public:
static constexpr PacketType value = WORLD_STATE_PACKET;
};
template<>
class Message<mmo::PlayerMoveMessage> {
public:
static constexpr PacketType value = PLAYER_UPDATE_MSG;
};
template<>
class Message<mmo::EntitySpawnMessage> {
public:
static constexpr PacketType value = ENTITY_SPAWN_MSG;
};
template<>
class Message<mmo::EntityDespawnMessage> {
public:
static constexpr PacketType value = ENTITY_DESPAWN_MSG;
};
template<>
class Message<mmo::chat::SendChatMessageRequest> {
public:
static constexpr PacketType value = CHAT_SEND_MESSAGE_REQUEST;
};
template<>
class Message<mmo::chat::SendChatMessageResponse> {
public:
static constexpr PacketType value = CHAT_SEND_MESSAGE_RESPONSE;
};
template<>
class Message<mmo::chat::JoinChannelRequest> {
public:
static constexpr PacketType value = CHAT_JOIN_CHANNEL_REQUEST;
};
template<>
class Message<mmo::chat::JoinChannelResponse> {
public:
static constexpr PacketType value = CHAT_JOIN_CHANNEL_RESPONSE;
};
template<>
class Message<mmo::chat::LeaveChannelRequest> {
public:
static constexpr PacketType value = CHAT_LEAVE_CHANNEL_REQUEST;
};
template<>
class Message<mmo::chat::LeaveChannelResponse> {
public:
static constexpr PacketType value = CHAT_LEAVE_CHANNEL_RESPONSE;
};
template<>
class Message<mmo::chat::ChatMessageBroadcastRequest> {
public:
static constexpr PacketType value = CHAT_MESSAGE_BROADCAST_REQUEST;
};
template<>
class Message<mmo::cluster::ZoneHello> {
public:
static constexpr PacketType value = CLUSTER_ZONE_HELLO;
};
template<>
class Message<mmo::cluster::ZoneBye> {
public:
static constexpr PacketType value = CLUSTER_ZONE_BYE;
};
}
template<>
struct fmt::formatter<PacketType> : fmt::formatter<std::string_view> {
auto format(PacketType type, fmt::format_context& ctx) const {
std::string_view name;
switch (type) {
case MESSAGE_PACKET: name = "MESSAGE_PACKET"; break;
case LOGIN_REQUEST_MSG: name = "LOGIN_REQUEST_MSG"; break;
case LOGIN_RESPONSE_PACKET: name = "LOGIN_RESPONSE_PACKET"; break;
case WORLD_STATE_PACKET: name = "WORLD_STATE_PACKET"; break;
case PLAYER_UPDATE_MSG: name = "PLAYER_UPDATE_MSG"; break;
case PLAYER_UPDATE_PACKET: name = "PLAYER_UPDATE_PACKET"; break;
case ENTITY_SPAWN_MSG: name = "ENTITY_SPAWN_MSG"; break;
case ENTITY_DESPAWN_MSG: name = "ENTITY_DESPAWN_MSG"; break;
case ENTITY_MOVE_PACKET: name = "ENTITY_MOVE_PACKET"; break;
case PLAYER_JOIN_REQUEST_PACKET: name = "PLAYER_JOIN_REQUEST_PACKET"; break;
case PLAYER_JOIN_RESPONSE_PACKET: name = "PLAYER_JOIN_RESPONSE_PACKET"; break;
case CHAT_SEND_MESSAGE_REQUEST: name = "CHAT_SEND_MESSAGE"; break;
case CHAT_JOIN_CHANNEL_REQUEST: name = "CHAT_JOIN_CHANNEL"; break;
case CHAT_LEAVE_CHANNEL_REQUEST: name = "CHAT_LEAVE_CHANNEL"; break;
case CHAT_MESSAGE_BROADCAST_REQUEST: name = "CHAT_MESSAGE_BROADCAST"; break;
case CLUSTER_ZONE_HELLO: name = "CLUSTER_ZONE_HELLO"; break;
case CLUSTER_ZONE_BYE: name = "CLUSTER_ZONE_BYE"; break;
default: name = "UNKNOWN"; break;
}
return fmt::formatter<std::string_view>::format(name, ctx);
}
};
+3
View File
@@ -0,0 +1,3 @@
int protocol() {
return 0;
}
@@ -0,0 +1,16 @@
#pragma once
#include <glm/glm.hpp>
#include <string>
#include "Serializers.hpp"
template<>
class tw::net::Serializer<glm::vec3> {
public:
static bool serialize(Serialization& buffer, glm::vec3& value) {
buffer.serialize(&value.x);
buffer.serialize(&value.y);
buffer.serialize(&value.z);
return true;
}
};
@@ -0,0 +1,143 @@
#pragma once
#include <nlohmann/json.hpp>
#include "Serialization.hpp"
#include "packets/Packet.hpp"
#include "Serializers.hpp"
#include "GlmSerializers.hpp"
#include <glm/glm.hpp>
#include <vector>
// MESSAGE(PlayerInputMessage, PLAYER_UPDATE_MSG) {
// public:
// uint32_t frame_idx;
// uint32_t entity_id;
// glm::vec3 inputs;
// };
// MESSAGE(PlayerJoinRequestMessage, PLAYER_JOIN_REQUEST_PACKET) {
// public:
// std::string name;
// };
// MESSAGE(PlayerJoinResponseMessage, LOGIN_RESPONSE_PACKET) {
// public:
// bool is_valid;
// uint32_t entity_id;
// glm::vec3 position;
// };
// MESSAGE(EntitySpawnMessage, ENTITY_SPAWN_MSG) {
// public:
// uint32_t entity_id;
// bool is_player; // could determine what commands should the entity get
// std::string name;
// glm::vec3 position;
// };
// MESSAGE(EntityDespawnMessage, ENTITY_DESPAWN_MSG) {
// public:
// uint32_t entity_id;
// bool is_player; // could determine what commands should the entity get
// };
// struct EntityPosition {
// uint32_t entity_id;
// glm::vec3 position;
// };
// MESSAGE(EntityMoveMessage, ENTITY_MOVE_PACKET) {
// public:
// uint32_t frame_idx;
// std::vector<EntityPosition> entities;
// };
// template<>
// class tw::net::Serializer<PlayerInputMessage> {
// public:
// static bool serialize(Serialization& buffer, PlayerInputMessage& message) {
// buffer.serialize(&message.frame_idx);
// buffer.serialize(&message.entity_id);
// buffer.serialize(message.inputs);
// return true;
// }
// };
// // inline void to_json(json& j, const PlayerInputMessage& value) {
// // j = json{
// // {"frame_idx", value.frame_idx},
// // {"entity_id", value.entity_id}
// // };
// // }
// template<>
// class tw::net::Serializer<EntitySpawnMessage> {
// public:
// static bool serialize(Serialization& buffer, EntitySpawnMessage& message) {
// buffer.serialize(&message.entity_id);
// buffer.serialize(&message.is_player);
// buffer.serialize(message.name);
// // buffer.serialize(message.position);
// return true;
// }
// };
// template<>
// class tw::net::Serializer<EntityDespawnMessage> {
// public:
// static bool serialize(Serialization& buffer, EntityDespawnMessage& message) {
// buffer.serialize(&message.entity_id);
// buffer.serialize(&message.is_player);
// return true;
// }
// };
// template<>
// class tw::net::Serializer<EntityPosition> {
// public:
// static bool serialize(Serialization& buffer, EntityPosition& message) {
// buffer.serialize(&message.entity_id);
// // buffer.serialize(message.position);
// return true;
// }
// };
// template<>
// class tw::net::Serializer<EntityMoveMessage> {
// public:
// static bool serialize(Serialization& buffer, EntityMoveMessage& message) {
// buffer.serialize(&message.frame_idx);
// VectorSerializer<EntityPosition>::serialize(buffer, message.entities);
// return true;
// }
// };
// template<>
// class tw::net::Serializer<PlayerJoinRequestMessage> {
// public:
// static bool serialize(Serialization& buffer, PlayerJoinRequestMessage& message) {
// buffer.serialize(message.name);
// return true;
// }
// };
// inline void to_json(json& j, const PlayerJoinRequestMessage& value) {
// j = json{
// {"name", value.name}
// };
// }
// template<>
// class tw::net::Serializer<PlayerJoinResponseMessage> {
// public:
// static bool serialize(Serialization& buffer, PlayerJoinResponseMessage& message) {
// buffer.serialize(&message.is_valid);
// buffer.serialize(&message.entity_id);
// // buffer.serialize(message.position);
// return true;
// }
// };
@@ -0,0 +1,35 @@
#pragma once
#include <cstdint>
#include <glm/glm.hpp>
#include "Serializers.hpp"
#include "packets/Packet.hpp"
struct PlayerUpdate {
uint32_t id;
glm::vec3 position;
};
SERIALIZER(PlayerUpdate, buffer, update) {
buffer.serialize(&update.id);
buffer.serialize(update.position);
return true;
}
#define MAX_PLAYER_UPDATES 10
MESSAGE(PlayerUpdateMessage, PLAYER_UPDATE_PACKET) {
public:
uint32_t update_count;
PlayerUpdate updates[MAX_PLAYER_UPDATES];
};
SERIALIZER(PlayerUpdateMessage, buffer, mesg) {
buffer.serialize(&mesg.update_count);
for(int i = 0; i < mesg.update_count; i++) {
buffer.serialize(mesg.updates[i]);
}
return true;
}
@@ -0,0 +1,190 @@
// automatically generated by the FlatBuffers compiler, do not modify
#ifndef FLATBUFFERS_GENERATED_WORLDSTATE_H_
#define FLATBUFFERS_GENERATED_WORLDSTATE_H_
#include "flatbuffers/flatbuffers.h"
// Ensure the included flatbuffers.h is the same version as when this file was
// generated, otherwise it may not be compatible.
static_assert(FLATBUFFERS_VERSION_MAJOR == 25 &&
FLATBUFFERS_VERSION_MINOR == 12 &&
FLATBUFFERS_VERSION_REVISION == 19,
"Non-compatible flatbuffers version included");
struct Vec3;
struct PlayerInfo;
struct PlayerInfoBuilder;
struct WorldState;
struct WorldStateBuilder;
FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) Vec3 FLATBUFFERS_FINAL_CLASS {
private:
float x_;
float y_;
float z_;
public:
Vec3()
: x_(0),
y_(0),
z_(0) {
}
Vec3(float _x, float _y, float _z)
: x_(::flatbuffers::EndianScalar(_x)),
y_(::flatbuffers::EndianScalar(_y)),
z_(::flatbuffers::EndianScalar(_z)) {
}
float x() const {
return ::flatbuffers::EndianScalar(x_);
}
float y() const {
return ::flatbuffers::EndianScalar(y_);
}
float z() const {
return ::flatbuffers::EndianScalar(z_);
}
};
FLATBUFFERS_STRUCT_END(Vec3, 12);
struct PlayerInfo FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
typedef PlayerInfoBuilder Builder;
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
VT_ID = 4,
VT_POS = 6
};
int32_t id() const {
return GetField<int32_t>(VT_ID, 0);
}
const Vec3 *pos() const {
return GetStruct<const Vec3 *>(VT_POS);
}
template <bool B = false>
bool Verify(::flatbuffers::VerifierTemplate<B> &verifier) const {
return VerifyTableStart(verifier) &&
VerifyField<int32_t>(verifier, VT_ID, 4) &&
VerifyField<Vec3>(verifier, VT_POS, 4) &&
verifier.EndTable();
}
};
struct PlayerInfoBuilder {
typedef PlayerInfo Table;
::flatbuffers::FlatBufferBuilder &fbb_;
::flatbuffers::uoffset_t start_;
void add_id(int32_t id) {
fbb_.AddElement<int32_t>(PlayerInfo::VT_ID, id, 0);
}
void add_pos(const Vec3 *pos) {
fbb_.AddStruct(PlayerInfo::VT_POS, pos);
}
explicit PlayerInfoBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
: fbb_(_fbb) {
start_ = fbb_.StartTable();
}
::flatbuffers::Offset<PlayerInfo> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = ::flatbuffers::Offset<PlayerInfo>(end);
return o;
}
};
inline ::flatbuffers::Offset<PlayerInfo> CreatePlayerInfo(
::flatbuffers::FlatBufferBuilder &_fbb,
int32_t id = 0,
const Vec3 *pos = nullptr) {
PlayerInfoBuilder builder_(_fbb);
builder_.add_pos(pos);
builder_.add_id(id);
return builder_.Finish();
}
struct WorldState FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
typedef WorldStateBuilder Builder;
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
VT_PLAYERS = 4
};
const ::flatbuffers::Vector<::flatbuffers::Offset<PlayerInfo>> *players() const {
return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<PlayerInfo>> *>(VT_PLAYERS);
}
template <bool B = false>
bool Verify(::flatbuffers::VerifierTemplate<B> &verifier) const {
return VerifyTableStart(verifier) &&
VerifyOffset(verifier, VT_PLAYERS) &&
verifier.VerifyVector(players()) &&
verifier.VerifyVectorOfTables(players()) &&
verifier.EndTable();
}
};
struct WorldStateBuilder {
typedef WorldState Table;
::flatbuffers::FlatBufferBuilder &fbb_;
::flatbuffers::uoffset_t start_;
void add_players(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<PlayerInfo>>> players) {
fbb_.AddOffset(WorldState::VT_PLAYERS, players);
}
explicit WorldStateBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
: fbb_(_fbb) {
start_ = fbb_.StartTable();
}
::flatbuffers::Offset<WorldState> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = ::flatbuffers::Offset<WorldState>(end);
return o;
}
};
inline ::flatbuffers::Offset<WorldState> CreateWorldState(
::flatbuffers::FlatBufferBuilder &_fbb,
::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<PlayerInfo>>> players = 0) {
WorldStateBuilder builder_(_fbb);
builder_.add_players(players);
return builder_.Finish();
}
inline ::flatbuffers::Offset<WorldState> CreateWorldStateDirect(
::flatbuffers::FlatBufferBuilder &_fbb,
const std::vector<::flatbuffers::Offset<PlayerInfo>> *players = nullptr) {
auto players__ = players ? _fbb.CreateVector<::flatbuffers::Offset<PlayerInfo>>(*players) : 0;
return CreateWorldState(
_fbb,
players__);
}
inline const WorldState *GetWorldState(const void *buf) {
return ::flatbuffers::GetRoot<WorldState>(buf);
}
inline const WorldState *GetSizePrefixedWorldState(const void *buf) {
return ::flatbuffers::GetSizePrefixedRoot<WorldState>(buf);
}
template <bool B = false>
inline bool VerifyWorldStateBuffer(
::flatbuffers::VerifierTemplate<B> &verifier) {
return verifier.template VerifyBuffer<WorldState>(nullptr);
}
template <bool B = false>
inline bool VerifySizePrefixedWorldStateBuffer(
::flatbuffers::VerifierTemplate<B> &verifier) {
return verifier.template VerifySizePrefixedBuffer<WorldState>(nullptr);
}
inline void FinishWorldStateBuffer(
::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<WorldState> root) {
fbb.Finish(root);
}
inline void FinishSizePrefixedWorldStateBuffer(
::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<WorldState> root) {
fbb.FinishSizePrefixed(root);
}
#endif // FLATBUFFERS_GENERATED_WORLDSTATE_H_