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)
|
target_compile_definitions(yaze_c PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=0)
|
||||||
endif()
|
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)
|
if(PNG_FOUND)
|
||||||
target_link_libraries(yaze_c PRIVATE ${PNG_LIBRARIES})
|
target_link_libraries(yaze_c PRIVATE ${PNG_LIBRARIES})
|
||||||
if(NOT YAZE_USE_MODULAR_BUILD)
|
if(NOT YAZE_USE_MODULAR_BUILD)
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ if(YAZE_WITH_GRPC)
|
|||||||
list(APPEND YAZE_APP_EDITOR_SRC
|
list(APPEND YAZE_APP_EDITOR_SRC
|
||||||
app/editor/system/agent_chat_widget.cc
|
app/editor/system/agent_chat_widget.cc
|
||||||
app/editor/system/agent_collaboration_coordinator.cc
|
app/editor/system/agent_collaboration_coordinator.cc
|
||||||
|
app/editor/system/network_collaboration_coordinator.cc
|
||||||
)
|
)
|
||||||
endif()
|
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