feat: Add network collaboration coordinator for real-time collaboration features
- Introduced NetworkCollaborationCoordinator class to manage WebSocket connections for collaborative sessions. - Implemented session management functions: HostSession, JoinSession, and LeaveSession. - Added message handling for chat messages and participant updates. - Updated CMake configuration to include the new network collaboration source files. - Integrated a stub WebSocket client for initial functionality, with plans for future enhancements.
This commit is contained in:
@@ -662,6 +662,11 @@ if (YAZE_BUILD_LIB)
|
||||
target_compile_definitions(yaze_c PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=0)
|
||||
endif()
|
||||
|
||||
# Link with test support library if available (required by editor)
|
||||
if(TARGET yaze_test_support)
|
||||
target_link_libraries(yaze_c PRIVATE yaze_test_support)
|
||||
endif()
|
||||
|
||||
if(PNG_FOUND)
|
||||
target_link_libraries(yaze_c PRIVATE ${PNG_LIBRARIES})
|
||||
if(NOT YAZE_USE_MODULAR_BUILD)
|
||||
|
||||
@@ -40,6 +40,7 @@ if(YAZE_WITH_GRPC)
|
||||
list(APPEND YAZE_APP_EDITOR_SRC
|
||||
app/editor/system/agent_chat_widget.cc
|
||||
app/editor/system/agent_collaboration_coordinator.cc
|
||||
app/editor/system/network_collaboration_coordinator.cc
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
380
src/app/editor/system/network_collaboration_coordinator.cc
Normal file
380
src/app/editor/system/network_collaboration_coordinator.cc
Normal file
@@ -0,0 +1,380 @@
|
||||
#include "app/editor/system/network_collaboration_coordinator.h"
|
||||
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
|
||||
#ifdef YAZE_WITH_JSON
|
||||
#include "httplib.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
using Json = nlohmann::json;
|
||||
#endif
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
|
||||
#ifdef YAZE_WITH_JSON
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Stub WebSocket client implementation
|
||||
// TODO: Integrate proper WebSocket library (websocketpp, ixwebsocket, or libwebsockets)
|
||||
// This is a placeholder to allow compilation
|
||||
class WebSocketClient {
|
||||
public:
|
||||
explicit WebSocketClient(const std::string& host, int port)
|
||||
: host_(host), port_(port) {
|
||||
std::cerr << "⚠️ WebSocket client stub - not yet implemented" << std::endl;
|
||||
std::cerr << " To use network collaboration, integrate a WebSocket library" << std::endl;
|
||||
}
|
||||
|
||||
bool Connect(const std::string& path) {
|
||||
(void)path; // Suppress unused parameter warning
|
||||
std::cerr << "WebSocket Connect stub called for " << host_ << ":" << port_ << std::endl;
|
||||
// Return false for now - real implementation needed
|
||||
return false;
|
||||
}
|
||||
|
||||
void Close() {
|
||||
// Stub
|
||||
}
|
||||
|
||||
bool Send(const std::string& message) {
|
||||
(void)message; // Suppress unused parameter warning
|
||||
if (!connected_) return false;
|
||||
// Stub - real implementation needed
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string Receive() {
|
||||
if (!connected_) return "";
|
||||
// Stub - real implementation needed
|
||||
return "";
|
||||
}
|
||||
|
||||
bool IsConnected() const { return connected_; }
|
||||
|
||||
private:
|
||||
std::string host_;
|
||||
int port_;
|
||||
bool connected_ = false;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
NetworkCollaborationCoordinator::NetworkCollaborationCoordinator(
|
||||
const std::string& server_url)
|
||||
: server_url_(server_url) {
|
||||
// Parse server URL
|
||||
// Expected format: ws://hostname:port or wss://hostname:port
|
||||
if (server_url_.find("ws://") == 0) {
|
||||
// Extract hostname and port
|
||||
// For now, use default localhost:8765
|
||||
ConnectWebSocket();
|
||||
}
|
||||
}
|
||||
|
||||
NetworkCollaborationCoordinator::~NetworkCollaborationCoordinator() {
|
||||
should_stop_ = true;
|
||||
if (receive_thread_ && receive_thread_->joinable()) {
|
||||
receive_thread_->join();
|
||||
}
|
||||
DisconnectWebSocket();
|
||||
}
|
||||
|
||||
void NetworkCollaborationCoordinator::ConnectWebSocket() {
|
||||
// Parse URL (simple implementation - assumes ws://host:port format)
|
||||
std::string host = "localhost";
|
||||
int port = 8765;
|
||||
|
||||
// Extract from server_url_ if needed
|
||||
if (server_url_.find("ws://") == 0) {
|
||||
std::string url_part = server_url_.substr(5); // Skip "ws://"
|
||||
std::vector<std::string> parts = absl::StrSplit(url_part, ':');
|
||||
if (!parts.empty()) {
|
||||
host = parts[0];
|
||||
}
|
||||
if (parts.size() > 1) {
|
||||
port = std::stoi(parts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
ws_client_ = std::make_unique<detail::WebSocketClient>(host, port);
|
||||
|
||||
if (ws_client_->Connect("/")) {
|
||||
connected_ = true;
|
||||
|
||||
// Start receive thread
|
||||
should_stop_ = false;
|
||||
receive_thread_ = std::make_unique<std::thread>(
|
||||
&NetworkCollaborationCoordinator::WebSocketReceiveLoop, this);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkCollaborationCoordinator::DisconnectWebSocket() {
|
||||
if (ws_client_) {
|
||||
ws_client_->Close();
|
||||
ws_client_.reset();
|
||||
}
|
||||
connected_ = false;
|
||||
}
|
||||
|
||||
absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
|
||||
NetworkCollaborationCoordinator::HostSession(const std::string& session_name,
|
||||
const std::string& username) {
|
||||
if (!connected_) {
|
||||
return absl::FailedPreconditionError("Not connected to collaboration server");
|
||||
}
|
||||
|
||||
username_ = username;
|
||||
|
||||
// Build host_session message
|
||||
Json message = {
|
||||
{"type", "host_session"},
|
||||
{"payload", {
|
||||
{"session_name", session_name},
|
||||
{"username", username}
|
||||
}}
|
||||
};
|
||||
|
||||
SendWebSocketMessage("host_session", message["payload"].dump());
|
||||
|
||||
// TODO: Wait for session_hosted response and parse it
|
||||
// For now, return a placeholder
|
||||
SessionInfo info;
|
||||
info.session_name = session_name;
|
||||
info.session_code = "PENDING"; // Will be updated from server response
|
||||
info.participants = {username};
|
||||
|
||||
in_session_ = true;
|
||||
session_name_ = session_name;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
|
||||
NetworkCollaborationCoordinator::JoinSession(const std::string& session_code,
|
||||
const std::string& username) {
|
||||
if (!connected_) {
|
||||
return absl::FailedPreconditionError("Not connected to collaboration server");
|
||||
}
|
||||
|
||||
username_ = username;
|
||||
session_code_ = session_code;
|
||||
|
||||
// Build join_session message
|
||||
Json message = {
|
||||
{"type", "join_session"},
|
||||
{"payload", {
|
||||
{"session_code", session_code},
|
||||
{"username", username}
|
||||
}}
|
||||
};
|
||||
|
||||
SendWebSocketMessage("join_session", message["payload"].dump());
|
||||
|
||||
// TODO: Wait for session_joined response and parse it
|
||||
SessionInfo info;
|
||||
info.session_code = session_code;
|
||||
|
||||
in_session_ = true;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
absl::Status NetworkCollaborationCoordinator::LeaveSession() {
|
||||
if (!in_session_) {
|
||||
return absl::FailedPreconditionError("Not in a session");
|
||||
}
|
||||
|
||||
Json message = {{"type", "leave_session"}};
|
||||
SendWebSocketMessage("leave_session", "{}");
|
||||
|
||||
in_session_ = false;
|
||||
session_id_.clear();
|
||||
session_code_.clear();
|
||||
session_name_.clear();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status NetworkCollaborationCoordinator::SendMessage(
|
||||
const std::string& sender, const std::string& message) {
|
||||
if (!in_session_) {
|
||||
return absl::FailedPreconditionError("Not in a session");
|
||||
}
|
||||
|
||||
Json msg = {
|
||||
{"type", "chat_message"},
|
||||
{"payload", {
|
||||
{"sender", sender},
|
||||
{"message", message}
|
||||
}}
|
||||
};
|
||||
|
||||
SendWebSocketMessage("chat_message", msg["payload"].dump());
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
bool NetworkCollaborationCoordinator::IsConnected() const {
|
||||
return connected_;
|
||||
}
|
||||
|
||||
void NetworkCollaborationCoordinator::SetMessageCallback(MessageCallback callback) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
message_callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
void NetworkCollaborationCoordinator::SetParticipantCallback(
|
||||
ParticipantCallback callback) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
participant_callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
void NetworkCollaborationCoordinator::SetErrorCallback(ErrorCallback callback) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
error_callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
void NetworkCollaborationCoordinator::SendWebSocketMessage(
|
||||
const std::string& type, const std::string& payload_json) {
|
||||
if (!ws_client_ || !connected_) {
|
||||
return;
|
||||
}
|
||||
|
||||
Json message = {
|
||||
{"type", type},
|
||||
{"payload", Json::parse(payload_json)}
|
||||
};
|
||||
|
||||
ws_client_->Send(message.dump());
|
||||
}
|
||||
|
||||
void NetworkCollaborationCoordinator::HandleWebSocketMessage(
|
||||
const std::string& message_str) {
|
||||
try {
|
||||
Json message = Json::parse(message_str);
|
||||
std::string type = message["type"];
|
||||
|
||||
if (type == "session_hosted") {
|
||||
Json payload = message["payload"];
|
||||
session_id_ = payload["session_id"];
|
||||
session_code_ = payload["session_code"];
|
||||
session_name_ = payload["session_name"];
|
||||
|
||||
if (payload.contains("participants")) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
if (participant_callback_) {
|
||||
std::vector<std::string> participants = payload["participants"];
|
||||
participant_callback_(participants);
|
||||
}
|
||||
}
|
||||
} else if (type == "session_joined") {
|
||||
Json payload = message["payload"];
|
||||
session_id_ = payload["session_id"];
|
||||
session_code_ = payload["session_code"];
|
||||
session_name_ = payload["session_name"];
|
||||
|
||||
if (payload.contains("participants")) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
if (participant_callback_) {
|
||||
std::vector<std::string> participants = payload["participants"];
|
||||
participant_callback_(participants);
|
||||
}
|
||||
}
|
||||
} else if (type == "chat_message") {
|
||||
Json payload = message["payload"];
|
||||
ChatMessage msg;
|
||||
msg.sender = payload["sender"];
|
||||
msg.message = payload["message"];
|
||||
msg.timestamp = payload["timestamp"];
|
||||
|
||||
absl::MutexLock lock(&mutex_);
|
||||
if (message_callback_) {
|
||||
message_callback_(msg);
|
||||
}
|
||||
} else if (type == "participant_joined" || type == "participant_left") {
|
||||
Json payload = message["payload"];
|
||||
if (payload.contains("participants")) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
if (participant_callback_) {
|
||||
std::vector<std::string> participants = payload["participants"];
|
||||
participant_callback_(participants);
|
||||
}
|
||||
}
|
||||
} else if (type == "error") {
|
||||
Json payload = message["payload"];
|
||||
std::string error = payload["error"];
|
||||
|
||||
absl::MutexLock lock(&mutex_);
|
||||
if (error_callback_) {
|
||||
error_callback_(error);
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error parsing WebSocket message: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkCollaborationCoordinator::WebSocketReceiveLoop() {
|
||||
while (!should_stop_ && connected_) {
|
||||
if (!ws_client_) break;
|
||||
|
||||
std::string message = ws_client_->Receive();
|
||||
if (!message.empty()) {
|
||||
HandleWebSocketMessage(message);
|
||||
}
|
||||
|
||||
// Small sleep to avoid busy-waiting
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
}
|
||||
|
||||
#else // !YAZE_WITH_JSON
|
||||
|
||||
// Stub implementations when JSON is not available
|
||||
NetworkCollaborationCoordinator::NetworkCollaborationCoordinator(
|
||||
const std::string& server_url) : server_url_(server_url) {}
|
||||
|
||||
NetworkCollaborationCoordinator::~NetworkCollaborationCoordinator() = default;
|
||||
|
||||
absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
|
||||
NetworkCollaborationCoordinator::HostSession(const std::string&, const std::string&) {
|
||||
return absl::UnimplementedError("Network collaboration requires JSON support");
|
||||
}
|
||||
|
||||
absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
|
||||
NetworkCollaborationCoordinator::JoinSession(const std::string&, const std::string&) {
|
||||
return absl::UnimplementedError("Network collaboration requires JSON support");
|
||||
}
|
||||
|
||||
absl::Status NetworkCollaborationCoordinator::LeaveSession() {
|
||||
return absl::UnimplementedError("Network collaboration requires JSON support");
|
||||
}
|
||||
|
||||
absl::Status NetworkCollaborationCoordinator::SendMessage(
|
||||
const std::string&, const std::string&) {
|
||||
return absl::UnimplementedError("Network collaboration requires JSON support");
|
||||
}
|
||||
|
||||
bool NetworkCollaborationCoordinator::IsConnected() const { return false; }
|
||||
|
||||
void NetworkCollaborationCoordinator::SetMessageCallback(MessageCallback) {}
|
||||
void NetworkCollaborationCoordinator::SetParticipantCallback(ParticipantCallback) {}
|
||||
void NetworkCollaborationCoordinator::SetErrorCallback(ErrorCallback) {}
|
||||
void NetworkCollaborationCoordinator::ConnectWebSocket() {}
|
||||
void NetworkCollaborationCoordinator::DisconnectWebSocket() {}
|
||||
void NetworkCollaborationCoordinator::SendWebSocketMessage(const std::string&, const std::string&) {}
|
||||
void NetworkCollaborationCoordinator::HandleWebSocketMessage(const std::string&) {}
|
||||
void NetworkCollaborationCoordinator::WebSocketReceiveLoop() {}
|
||||
|
||||
#endif // YAZE_WITH_JSON
|
||||
|
||||
} // namespace editor
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_WITH_GRPC
|
||||
99
src/app/editor/system/network_collaboration_coordinator.h
Normal file
99
src/app/editor/system/network_collaboration_coordinator.h
Normal file
@@ -0,0 +1,99 @@
|
||||
#ifndef YAZE_APP_EDITOR_SYSTEM_NETWORK_COLLABORATION_COORDINATOR_H_
|
||||
#define YAZE_APP_EDITOR_SYSTEM_NETWORK_COLLABORATION_COORDINATOR_H_
|
||||
|
||||
#ifdef YAZE_WITH_GRPC // Reuse gRPC build flag for network features
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
|
||||
// Forward declarations to avoid including httplib in header
|
||||
namespace detail {
|
||||
class WebSocketClient;
|
||||
}
|
||||
|
||||
// Coordinates network-based collaboration via WebSocket connections
|
||||
class NetworkCollaborationCoordinator {
|
||||
public:
|
||||
struct SessionInfo {
|
||||
std::string session_id;
|
||||
std::string session_code;
|
||||
std::string session_name;
|
||||
std::vector<std::string> participants;
|
||||
};
|
||||
|
||||
struct ChatMessage {
|
||||
std::string sender;
|
||||
std::string message;
|
||||
int64_t timestamp;
|
||||
};
|
||||
|
||||
// Callbacks for handling incoming events
|
||||
using MessageCallback = std::function<void(const ChatMessage&)>;
|
||||
using ParticipantCallback = std::function<void(const std::vector<std::string>&)>;
|
||||
using ErrorCallback = std::function<void(const std::string&)>;
|
||||
|
||||
explicit NetworkCollaborationCoordinator(const std::string& server_url);
|
||||
~NetworkCollaborationCoordinator();
|
||||
|
||||
// Session management
|
||||
absl::StatusOr<SessionInfo> HostSession(const std::string& session_name,
|
||||
const std::string& username);
|
||||
absl::StatusOr<SessionInfo> JoinSession(const std::string& session_code,
|
||||
const std::string& username);
|
||||
absl::Status LeaveSession();
|
||||
|
||||
// Send chat message to current session
|
||||
absl::Status SendMessage(const std::string& sender, const std::string& message);
|
||||
|
||||
// Connection status
|
||||
bool IsConnected() const;
|
||||
bool InSession() const { return in_session_; }
|
||||
const std::string& session_code() const { return session_code_; }
|
||||
const std::string& session_name() const { return session_name_; }
|
||||
|
||||
// Event callbacks
|
||||
void SetMessageCallback(MessageCallback callback);
|
||||
void SetParticipantCallback(ParticipantCallback callback);
|
||||
void SetErrorCallback(ErrorCallback callback);
|
||||
|
||||
private:
|
||||
void ConnectWebSocket();
|
||||
void DisconnectWebSocket();
|
||||
void SendWebSocketMessage(const std::string& type, const std::string& payload_json);
|
||||
void HandleWebSocketMessage(const std::string& message);
|
||||
void WebSocketReceiveLoop();
|
||||
|
||||
std::string server_url_;
|
||||
std::string username_;
|
||||
std::string session_id_;
|
||||
std::string session_code_;
|
||||
std::string session_name_;
|
||||
bool in_session_ = false;
|
||||
|
||||
std::unique_ptr<detail::WebSocketClient> ws_client_;
|
||||
std::atomic<bool> connected_{false};
|
||||
std::atomic<bool> should_stop_{false};
|
||||
std::unique_ptr<std::thread> receive_thread_;
|
||||
|
||||
mutable absl::Mutex mutex_;
|
||||
MessageCallback message_callback_ ABSL_GUARDED_BY(mutex_);
|
||||
ParticipantCallback participant_callback_ ABSL_GUARDED_BY(mutex_);
|
||||
ErrorCallback error_callback_ ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_WITH_GRPC
|
||||
#endif // YAZE_APP_EDITOR_SYSTEM_NETWORK_COLLABORATION_COORDINATOR_H_
|
||||
Reference in New Issue
Block a user