From 75f71a537f9402fb7a4b37fc21cd527b866608f4 Mon Sep 17 00:00:00 2001 From: zhenyan121 <3367366583@qq.com> Date: Sat, 18 Jul 2026 20:29:14 +0800 Subject: [PATCH] feat(chat): implement client-server chat messaging Add protobuf definition for ChatMsg, new packet ID CHAT_MSG (4001), and handle sending/receiving chat messages on both client and server side. Wire up UI components (ChatBox, TextField) to allow message input and display, including a message queue in ClientWorld for thread-safe processing. Also add default initialization for ChatMessage::time. --- include/Cubed/gameplay/chat_message.hpp | 2 +- include/Cubed/gameplay/client_world.hpp | 6 +++++ include/Cubed/gameplay/packet.hpp | 4 ++++ include/Cubed/gameplay/server_world.hpp | 4 ++++ include/Cubed/scene/world_scene.hpp | 2 ++ include/Cubed/ui/chat_box.hpp | 2 ++ include/Cubed/ui/text_field.hpp | 3 ++- include/Cubed/ui/world_ui_manager.hpp | 2 ++ src/gameplay/client_world.cpp | 16 +++++++++++++ src/gameplay/network_client.cpp | 6 +++++ src/gameplay/server_world.cpp | 32 +++++++++++++++++++++++++ src/gameplay/session.cpp | 6 +++++ src/proto/chat/chat.proto | 6 +++++ src/proto/packet.proto | 3 ++- src/scene/world_scene.cpp | 5 ++++ src/ui/chat_box.cpp | 6 +++++ src/ui/text_field.cpp | 19 ++++++++------- src/ui/ui_vertex_data.cpp | 6 +---- src/ui/world_ui_manager.cpp | 13 +++++++++- 19 files changed, 126 insertions(+), 17 deletions(-) create mode 100644 src/proto/chat/chat.proto diff --git a/include/Cubed/gameplay/chat_message.hpp b/include/Cubed/gameplay/chat_message.hpp index 87f6a2e..8725cd2 100644 --- a/include/Cubed/gameplay/chat_message.hpp +++ b/include/Cubed/gameplay/chat_message.hpp @@ -6,6 +6,6 @@ namespace Cubed { struct ChatMessage { std::string player; std::string text; - uint64_t time; + uint64_t time = 0; }; } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/client_world.hpp b/include/Cubed/gameplay/client_world.hpp index adcb07e..869f6b8 100644 --- a/include/Cubed/gameplay/client_world.hpp +++ b/include/Cubed/gameplay/client_world.hpp @@ -2,6 +2,7 @@ #include "Cubed/audio/audio_engine.hpp" #include "Cubed/config.hpp" #include "Cubed/gameplay/block.hpp" +#include "Cubed/gameplay/chat_message.hpp" #include "Cubed/gameplay/chunk_pos.hpp" #include "Cubed/gameplay/client_chunk.hpp" #include "Cubed/gameplay/client_player.hpp" @@ -102,6 +103,10 @@ public: Config& get_config(); WorldScene& world_scene(); void set_direct_exit(); + + void receive_chat_message(ChatMsg& msg); + void send_chat_message(ChatMessage& message); + template void register_ticktimer(std::string_view id, TickType threshold, Fn&& f) { m_ticktimers.emplace( @@ -147,6 +152,7 @@ private: tbb::concurrent_queue> m_pending_upload_queue; tbb::concurrent_queue m_dirty_chunk_queue; tbb::concurrent_queue m_pending_sound; + tbb::concurrent_queue m_message_queue; std::deque m_dirty_queue; std::vector m_render_snapshots; diff --git a/include/Cubed/gameplay/packet.hpp b/include/Cubed/gameplay/packet.hpp index 72cd8f7..2769bc0 100644 --- a/include/Cubed/gameplay/packet.hpp +++ b/include/Cubed/gameplay/packet.hpp @@ -61,6 +61,7 @@ enum class PacketEnum : uint16_t { BLOCK_CHANGE_RSP = 3004, S2C_CLEAR_ALL_CHUNKS = 3005, UPDATE_TIME = 3006, + CHAT_MSG = 4001, PING = 9001, PONG = 9002 @@ -121,6 +122,9 @@ template <> constexpr uint16_t get_packet_id() { template <> constexpr uint16_t get_packet_id() { return std::to_underlying(PacketEnum::PLAYER_WATER_SOUND); } +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::CHAT_MSG); +} template requires std::derived_from diff --git a/include/Cubed/gameplay/server_world.hpp b/include/Cubed/gameplay/server_world.hpp index 2c86e2e..e61bc4e 100644 --- a/include/Cubed/gameplay/server_world.hpp +++ b/include/Cubed/gameplay/server_world.hpp @@ -80,6 +80,8 @@ public: void handle_chunk_req(int task_id, const std::string& uuid, ChunkPos pos); void handle_block_change(const BlockChangeReq& req); + void handle_chat_message(ChatMsg& msg); + int chunk_size() const; template void register_timer(std::string_view id, TickType threshold, Fn&& f) { @@ -189,5 +191,7 @@ private: std::atomic>& thread_pool, int threads); void send_server_stop(); + + void boardcast_message(const std::string& name, const std::string& message); }; } // namespace Cubed diff --git a/include/Cubed/scene/world_scene.hpp b/include/Cubed/scene/world_scene.hpp index 42370ba..283642e 100644 --- a/include/Cubed/scene/world_scene.hpp +++ b/include/Cubed/scene/world_scene.hpp @@ -35,6 +35,8 @@ public: void set_error(std::string_view error); void set_mouse(bool pause); void set_chatting(bool chatting); + // Not thread safe + void handle_chat_message(ChatMessage& message); private: bool handle_mouse_move_event(const MouseMoveEvent& e) override; diff --git a/include/Cubed/ui/chat_box.hpp b/include/Cubed/ui/chat_box.hpp index c51666c..4f4330f 100644 --- a/include/Cubed/ui/chat_box.hpp +++ b/include/Cubed/ui/chat_box.hpp @@ -20,6 +20,8 @@ public: ChatBox& set_width(float width); ChatBox& set_show_lines(int lines); ChatBox& set_spacing(float spacing); + ChatBox& clear_input(); + std::string& get_input_text(); float width() const override; float height() const override; void set_d_image(TextureManager& m); diff --git a/include/Cubed/ui/text_field.hpp b/include/Cubed/ui/text_field.hpp index fa0df05..db8a3bd 100644 --- a/include/Cubed/ui/text_field.hpp +++ b/include/Cubed/ui/text_field.hpp @@ -28,13 +28,14 @@ public: TextField& set_auto_scale(bool auto_scale); TextField& set_app(App* app); TextField& set_typing(bool typing); + TextField& clear_input(); bool handle_mouse_move_event(const MouseMoveEvent& e) override; bool handle_mouse_button_event(const MouseButtonEvent& e) override; bool handle_text_input_event(const TextInputEvent& e) override; bool handle_key_event(const KeyEvent& e) override; bool handle_window_resize_event(const WindowResizeEvent& e) override; const std::string& input_text() const; - + std::string& input_text(); template TextField& set_on_finish(F&& f) { m_on_finished = std::forward(f); return *this; diff --git a/include/Cubed/ui/world_ui_manager.hpp b/include/Cubed/ui/world_ui_manager.hpp index 4cb2389..e2b4ae7 100644 --- a/include/Cubed/ui/world_ui_manager.hpp +++ b/include/Cubed/ui/world_ui_manager.hpp @@ -21,6 +21,8 @@ public: void set_chatting(bool chantting); + void add_chat_message(ChatMessage& message); + private: bool handle_key_event(const KeyEvent& e) override; WorldScene& m_scene; diff --git a/src/gameplay/client_world.cpp b/src/gameplay/client_world.cpp index 4324973..24f38dd 100644 --- a/src/gameplay/client_world.cpp +++ b/src/gameplay/client_world.cpp @@ -6,6 +6,7 @@ #include "Cubed/gameplay/packet.hpp" #include "Cubed/scene/world_scene.hpp" #include "Cubed/tools/math_tools.hpp" +#include "Cubed/tools/time_tools.hpp" #include #include @@ -750,6 +751,17 @@ void ClientWorld::request_exit() { } } +void ClientWorld::receive_chat_message(ChatMsg& msg) { + m_message_queue.emplace(msg.name(), msg.msg(), Tools::get_time_ticks()); +} +void ClientWorld::send_chat_message(ChatMessage& message) { + Arena arena; + auto msg = Arena::Create(&arena); + msg->set_name(message.player); + msg->set_msg(message.text); + m_client->send(make_packet(*msg)); +} + void ClientWorld::update(float delta_time) { m_player.update(delta_time); @@ -916,6 +928,10 @@ void ClientWorld::update(float delta_time) { for (auto& [pos, timer] : m_timers) { timer.update(delta_time); } + ChatMessage message; + while (m_message_queue.try_pop(message)) { + m_world_scene.handle_chat_message(message); + } } bool ClientWorld::handle_event(const Event& e) { diff --git a/src/gameplay/network_client.cpp b/src/gameplay/network_client.cpp index 3192917..1d6956e 100644 --- a/src/gameplay/network_client.cpp +++ b/src/gameplay/network_client.cpp @@ -135,6 +135,12 @@ asio::awaitable NetworkClient::read_loop() { m_world.receive_player_water_sound(*rsp); } } break; + case std::to_underlying(PacketEnum::CHAT_MSG): { + auto* msg = Arena::Create(&arena); + if (decode_packet(*msg, body_data, header)) { + m_world.receive_chat_message(*msg); + } + } break; } } } catch (const asio::system_error& e) { diff --git a/src/gameplay/server_world.cpp b/src/gameplay/server_world.cpp index b75552b..3aed224 100644 --- a/src/gameplay/server_world.cpp +++ b/src/gameplay/server_world.cpp @@ -753,6 +753,16 @@ void ServerWorld::handle_chunk_req(int task_id, const std::string& uuid, [task_id, uuid, pos, this]() { send_chunk(task_id, uuid, pos); }); } +void ServerWorld::handle_chat_message(ChatMsg& msg) { + + std::string name = msg.name(); + std::string message = msg.msg(); + auto pool = m_net_thread_pool.load(); + pool->enqueue([this, player = std::move(name), m = std::move(message)]() { + boardcast_message(player, m); + }); +} + void ServerWorld::handle_block_change(const BlockChangeReq& req) { float x = std::floor(req.pos().x()); float y = std::floor(req.pos().y()); @@ -861,6 +871,28 @@ void ServerWorld::send_server_stop() { Logger::info("Send Server Mesaage Success"); } +void ServerWorld::boardcast_message(const std::string& name, + const std::string& message) { + + std::vector> m_session; + { + std::shared_lock lock(m_player_mutex); + for (auto& [_, p] : m_players) { + m_session.emplace_back(p.get_session()); + } + } + + Arena arena; + auto msg = Arena::Create(&arena); + + msg->set_msg(message); + msg->set_name(name); + + for (auto& s : m_session) { + s->send(make_packet(*msg)); + } +} + int ServerWorld::chunk_load_style() const { return std::to_underlying(m_chunk_load_style.load()); } diff --git a/src/gameplay/session.cpp b/src/gameplay/session.cpp index c3a3872..9d02ebd 100644 --- a/src/gameplay/session.cpp +++ b/src/gameplay/session.cpp @@ -99,6 +99,12 @@ asio::awaitable Session::read_loop() { m_server_world.sync_player_water_sound(*req); } } + if (cmd_id == std::to_underlying(PacketEnum::CHAT_MSG)) { + auto* msg = Arena::Create(&arena); + if (decode_packet(*msg, body_data, header)) { + m_server_world.handle_chat_message(*msg); + } + } } } catch (const asio::system_error& e) { auto ec = e.code(); diff --git a/src/proto/chat/chat.proto b/src/proto/chat/chat.proto new file mode 100644 index 0000000..0b3f064 --- /dev/null +++ b/src/proto/chat/chat.proto @@ -0,0 +1,6 @@ +syntax = "proto3"; + +message ChatMsg { + string name = 1; + string msg = 2; +} \ No newline at end of file diff --git a/src/proto/packet.proto b/src/proto/packet.proto index 3b6ad3a..345a0f4 100644 --- a/src/proto/packet.proto +++ b/src/proto/packet.proto @@ -9,4 +9,5 @@ import "system/ping.proto"; import "system/pong.proto"; import "world/chunk_data.proto"; import "world/block_change.proto"; -import "world/time.proto"; \ No newline at end of file +import "world/time.proto"; +import "chat/chat.proto"; \ No newline at end of file diff --git a/src/scene/world_scene.cpp b/src/scene/world_scene.cpp index ed85d61..fb5748e 100644 --- a/src/scene/world_scene.cpp +++ b/src/scene/world_scene.cpp @@ -334,6 +334,11 @@ void WorldScene::set_chatting(bool chatting) { Logger::info("World Scene Chatting {}", chatting); } +// Not thread safe +void WorldScene::handle_chat_message(ChatMessage& message) { + m_hud_ui.add_chat_message(message); +} + void WorldScene::set_error(std::string_view error) { Logger::error("WorldScene Error Set {}", error); m_error_ui.set_error(error); diff --git a/src/ui/chat_box.cpp b/src/ui/chat_box.cpp index 23dd11b..4506a97 100644 --- a/src/ui/chat_box.cpp +++ b/src/ui/chat_box.cpp @@ -52,6 +52,12 @@ ChatBox& ChatBox::set_spacing(float spacing) { m_spacing = spacing; return *this; } +ChatBox& ChatBox::clear_input() { + m_text_field->clear_input(); + return *this; +} +std::string& ChatBox::get_input_text() { return m_text_field->input_text(); } + float ChatBox::width() const { return m_width * m_scale; } float ChatBox::height() const { // Height is dynamically calculated, no scaling needed diff --git a/src/ui/text_field.cpp b/src/ui/text_field.cpp index 4c7974a..ced9fcb 100644 --- a/src/ui/text_field.cpp +++ b/src/ui/text_field.cpp @@ -79,18 +79,15 @@ void TextField::on_update(float dt) { } void TextField::update_show_text() { - if (m_input_text.empty()) { - if (!m_typing) { - m_foreground->set_text(m_show_text); - m_foreground->set_color(Color::GRAY); - } else { - m_foreground->set_text(" "); - } + if (!m_typing) { + m_foreground->set_text(m_show_text); + m_foreground->set_color(Color::GRAY); } else { m_foreground->set_text(m_input_text); m_foreground->set_color(Color::WHITE); } + update_text_scale(); update_input_area(); m_cursor->set_offset({13.0f + m_foreground->width(), 0.0f}); @@ -162,6 +159,12 @@ TextField& TextField::set_typing(bool typing) { return *this; } +TextField& TextField::clear_input() { + m_input_text.clear(); + update_show_text(); + return *this; +} + void TextField::start_typing() { if (m_app) { m_app->start_text_input(); @@ -178,7 +181,7 @@ void TextField::stop_typing() { } const std::string& TextField::input_text() const { return m_input_text; } - +std::string& TextField::input_text() { return m_input_text; } bool TextField::handle_mouse_move_event(const MouseMoveEvent& e) { auto p = pos(); if (e.xpos >= p.x && e.xpos <= p.x + width() && e.ypos >= p.y && diff --git a/src/ui/ui_vertex_data.cpp b/src/ui/ui_vertex_data.cpp index d7eb867..61c23a8 100644 --- a/src/ui/ui_vertex_data.cpp +++ b/src/ui/ui_vertex_data.cpp @@ -1,8 +1,5 @@ - #include "Cubed/ui/ui_vertex_data.hpp" -#include "Cubed/tools/cubed_assert.hpp" -#include "Cubed/tools/log.hpp" namespace Cubed { UIVertexData::UIVertexData() {} UIVertexData::~UIVertexData() { @@ -12,8 +9,7 @@ UIVertexData::~UIVertexData() { void UIVertexData::upload() { if (m_sum == 0) { - Logger::error("You need update_sum first"); - ASSERT(false); + return; } if (m_vertices.size() == 0) { return; diff --git a/src/ui/world_ui_manager.cpp b/src/ui/world_ui_manager.cpp index 8d49238..fce04c9 100644 --- a/src/ui/world_ui_manager.cpp +++ b/src/ui/world_ui_manager.cpp @@ -2,6 +2,7 @@ #include "Cubed/app.hpp" #include "Cubed/debug_collector.hpp" +#include "Cubed/gameplay/client_world.hpp" #include "Cubed/render/renderer.hpp" #include "Cubed/scene/scene_manager.hpp" #include "Cubed/scene/world_scene.hpp" @@ -38,7 +39,12 @@ void WorldUIManager::init() { chat_box.set_text_scale(0.6f); chat_box.set_d_image(m_scene.scene_manager().app().texture_manager()); chat_box.set_app(&m_scene.scene_manager().app()); - chat_box.set_on_finish([]() {}); + chat_box.set_on_finish([this, &chat_box]() { + ChatMessage message{m_scene.client_world().get_player().get_name(), + std::move(chat_box.get_input_text()), 0}; + chat_box.clear_input(); + m_scene.client_world().send_chat_message(message); + }); m_chat_box = &chat_box; } void WorldUIManager::render(Renderer& renderer) { @@ -56,6 +62,11 @@ void WorldUIManager::set_chatting(bool chantting) { m_chat_box->set_typing(chantting); } +void WorldUIManager::add_chat_message(ChatMessage& message) { + + m_chat_box->add_message(message); +} + bool WorldUIManager::handle_key_event(const KeyEvent& e) { return UIManager::handle_key_event(e);