yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
wasm_collaboration.h
Go to the documentation of this file.
1#ifndef YAZE_APP_PLATFORM_WASM_COLLABORATION_H_
2#define YAZE_APP_PLATFORM_WASM_COLLABORATION_H_
3
4#ifdef __EMSCRIPTEN__
5
6#include <emscripten.h>
7#include <emscripten/html5.h>
8#include <emscripten/val.h>
9
10#include <functional>
11#include <map>
12#include <memory>
13#include <mutex>
14#include <string>
15#include <vector>
16
17#include "absl/status/status.h"
18#include "absl/status/statusor.h"
20#include "rom/rom.h"
21
22namespace yaze {
23namespace app {
24namespace platform {
25
32class WasmCollaboration {
33 public:
37 struct User {
38 std::string id;
39 std::string name;
40 std::string color; // Hex color for cursor/highlights
41 bool is_active = true;
42 double last_activity = 0; // Timestamp
43 };
44
48 struct CursorInfo {
49 std::string user_id;
50 std::string editor_type; // "overworld", "dungeon", etc.
51 int x = 0;
52 int y = 0;
53 int map_id = -1; // For context (which map/room)
54 };
55
59 struct ChangeEvent {
60 uint32_t offset;
61 std::vector<uint8_t> old_data;
62 std::vector<uint8_t> new_data;
63 std::string user_id;
64 double timestamp;
65 };
66
67 // Connection state enum
68 enum class ConnectionState {
69 Disconnected,
70 Connecting,
71 Connected,
72 Reconnecting
73 };
74
75 // Callbacks for UI updates
76 using UserListCallback = std::function<void(const std::vector<User>&)>;
77 using ChangeCallback = std::function<void(const ChangeEvent&)>;
78 using CursorCallback = std::function<void(const CursorInfo&)>;
79 using StatusCallback = std::function<void(bool connected, const std::string& message)>;
80 using ConnectionStateCallback = std::function<void(ConnectionState state, const std::string& message)>;
81
82 WasmCollaboration();
83 ~WasmCollaboration();
84
99 absl::Status SetWebSocketUrl(const std::string& url) {
100 if (url.empty()) {
101 websocket_url_.clear();
102 return absl::OkStatus();
103 }
104 if (url.find("ws://") != 0 && url.find("wss://") != 0) {
105 return absl::InvalidArgumentError(
106 "WebSocket URL must start with ws:// or wss://");
107 }
108 // Basic URL structure validation
109 if (url.length() < 8) { // Minimum: "ws://x" or "wss://x"
110 return absl::InvalidArgumentError("WebSocket URL is too short");
111 }
112 websocket_url_ = url;
113 return absl::OkStatus();
114 }
115
120 std::string GetWebSocketUrl() const { return websocket_url_; }
121
128 void InitializeFromConfig();
129
134 bool IsConfigured() const { return !websocket_url_.empty(); }
135
142 absl::StatusOr<std::string> CreateSession(const std::string& session_name,
143 const std::string& username,
144 const std::string& password = "");
145
152 absl::Status JoinSession(const std::string& room_code,
153 const std::string& username,
154 const std::string& password = "");
155
159 absl::Status LeaveSession();
160
167 absl::Status BroadcastChange(uint32_t offset,
168 const std::vector<uint8_t>& old_data,
169 const std::vector<uint8_t>& new_data);
170
178 absl::Status SendCursorPosition(const std::string& editor_type,
179 int x, int y, int map_id = -1);
180
185 void SetRom(Rom* rom) { rom_ = rom; }
186
191 void SetChangeCallback(ChangeCallback callback) {
192 change_callback_ = callback;
193 }
194
199 void SetUserListCallback(UserListCallback callback) {
200 user_list_callback_ = callback;
201 }
202
207 void SetCursorCallback(CursorCallback callback) {
208 cursor_callback_ = callback;
209 }
210
215 void SetStatusCallback(StatusCallback callback) {
216 status_callback_ = callback;
217 }
218
223 void SetConnectionStateCallback(ConnectionStateCallback callback) {
224 connection_state_callback_ = callback;
225 }
226
231 ConnectionState GetConnectionState() const {
232 return connection_state_;
233 }
234
239 std::vector<User> GetConnectedUsers() const;
240
245 bool IsConnected() const;
246
250 bool IsApplyingRemoteChange() const { return applying_remote_change_; }
251
256 std::string GetRoomCode() const { return room_code_; }
257
262 std::string GetSessionName() const { return session_name_; }
263
267 std::string GetUserId() const { return user_id_; }
268
273 void SetAutoResolveConflicts(bool enable) {
274 auto_resolve_conflicts_ = enable;
275 }
276
281 void ProcessPendingChanges();
282
283 private:
284 // WebSocket message handlers
285 void HandleMessage(const std::string& message);
286 void HandleCreateResponse(const emscripten::val& data);
287 void HandleJoinResponse(const emscripten::val& data);
288 void HandleUserList(const emscripten::val& data);
289 void HandleChange(const emscripten::val& data);
290 void HandleCursor(const emscripten::val& data);
291 void HandleError(const emscripten::val& data);
292
293 // Utility methods
294 std::string GenerateUserId();
295 std::string GenerateUserColor();
296 void UpdateUserActivity(const std::string& user_id);
297 void CheckUserTimeouts();
298 bool IsChangeValid(const ChangeEvent& change);
299 void ApplyRemoteChange(const ChangeEvent& change);
300
301 // Reconnection management
302 void InitiateReconnection();
303 void AttemptReconnection();
304 void ResetReconnectionState();
305 void UpdateConnectionState(ConnectionState new_state, const std::string& message);
306 void QueueMessageWhileDisconnected(const std::string& message);
307
308 // Connection management
309 std::unique_ptr<net::EmscriptenWebSocket> websocket_;
310 bool is_connected_ = false;
311 ConnectionState connection_state_ = ConnectionState::Disconnected;
312 std::string websocket_url_; // Set via SetWebSocketUrl() or environment
313
314 // Reconnection state
315 int reconnection_attempts_ = 0;
316 int max_reconnection_attempts_ = 10;
317 double reconnection_delay_seconds_ = 1.0; // Initial delay
318 double max_reconnection_delay_ = 30.0; // Max delay between attempts
319 bool should_reconnect_ = false;
320 std::string stored_password_; // Store password for reconnection
321
322 // Session state
323 std::string room_code_;
324 std::string session_name_;
325 std::string user_id_;
326 std::string username_;
327 std::string user_color_;
328
329 // Connected users
330 std::map<std::string, User> users_;
331 mutable std::mutex users_mutex_;
332
333 // Remote cursors
334 std::map<std::string, CursorInfo> cursors_;
335 mutable std::mutex cursors_mutex_;
336
337 // Change queue for conflict resolution
338 std::vector<ChangeEvent> pending_changes_;
339 mutable std::mutex changes_mutex_;
340
341 // Message queue for disconnected state
342 std::vector<std::string> queued_messages_;
343 mutable std::mutex message_queue_mutex_;
344 size_t max_queued_messages_ = 100; // Limit queue size
345
346 // Configuration
347 bool auto_resolve_conflicts_ = true;
348
349 // Callbacks
350 UserListCallback user_list_callback_;
351 ChangeCallback change_callback_;
352 CursorCallback cursor_callback_;
353 StatusCallback status_callback_;
354 ConnectionStateCallback connection_state_callback_;
355
356 // ROM reference for applying changes
357 Rom* rom_ = nullptr;
358
359 // Rate limiting
360 double last_cursor_send_ = 0;
361
362 // Guard to prevent echoing remote writes back to the server
363 bool applying_remote_change_ = false;
364};
365
366// Singleton accessor used by JS bindings and the WASM main loop
367WasmCollaboration& GetWasmCollaborationInstance();
368
369} // namespace platform
370} // namespace app
371} // namespace yaze
372
373#else // !__EMSCRIPTEN__
374
375// Stub for non-WASM builds
376#include "absl/status/status.h"
377#include "absl/status/statusor.h"
378#include <string>
379#include <vector>
380
381namespace yaze {
382namespace app {
383namespace platform {
384
386 public:
387 struct User {
388 std::string id;
389 std::string name;
390 std::string color;
391 bool is_active = true;
392 };
393
394 struct CursorInfo {
395 std::string user_id;
396 std::string editor_type;
397 int x = 0;
398 int y = 0;
399 int map_id = -1;
400 };
401
402 struct ChangeEvent {
403 uint32_t offset;
404 std::vector<uint8_t> old_data;
405 std::vector<uint8_t> new_data;
406 std::string user_id;
407 double timestamp;
408 };
409
410 absl::StatusOr<std::string> CreateSession(const std::string&,
411 const std::string&) {
412 return absl::UnimplementedError("Collaboration requires WASM build");
413 }
414
415 absl::Status JoinSession(const std::string&, const std::string&) {
416 return absl::UnimplementedError("Collaboration requires WASM build");
417 }
418
419 absl::Status LeaveSession() {
420 return absl::UnimplementedError("Collaboration requires WASM build");
421 }
422
423 absl::Status BroadcastChange(uint32_t, const std::vector<uint8_t>&,
424 const std::vector<uint8_t>&) {
425 return absl::UnimplementedError("Collaboration requires WASM build");
426 }
427
428 std::vector<User> GetConnectedUsers() const { return {}; }
429 bool IsConnected() const { return false; }
430 std::string GetRoomCode() const { return ""; }
431 std::string GetSessionName() const { return ""; }
432};
433
434} // namespace platform
435} // namespace app
436} // namespace yaze
437
438#endif // __EMSCRIPTEN__
439
440#endif // YAZE_APP_PLATFORM_WASM_COLLABORATION_H_
absl::Status JoinSession(const std::string &, const std::string &)
std::vector< User > GetConnectedUsers() const
absl::Status BroadcastChange(uint32_t, const std::vector< uint8_t > &, const std::vector< uint8_t > &)
absl::StatusOr< std::string > CreateSession(const std::string &, const std::string &)
ConnectionState
WebSocket connection states.