friends cursor broadcast system refined 👍
This commit is contained in:
@@ -18,6 +18,7 @@ impl WS_EVENT {
|
||||
pub const FRIEND_REQUEST_DENIED: &str = "friend-request-denied";
|
||||
pub const UNFRIENDED: &str = "unfriended";
|
||||
pub const FRIEND_CURSOR_POSITION: &str = "friend-cursor-position";
|
||||
pub const FRIEND_DISCONNECTED: &str = "friend-disconnected";
|
||||
}
|
||||
|
||||
fn on_friend_request_received(payload: Payload, _socket: RawClient) {
|
||||
@@ -28,7 +29,7 @@ fn on_friend_request_received(payload: Payload, _socket: RawClient) {
|
||||
.emit(WS_EVENT::FRIEND_REQUEST_RECEIVED, str)
|
||||
.unwrap();
|
||||
}
|
||||
_ => todo!(),
|
||||
_ => error!("Received unexpected payload format for friend request received"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +41,7 @@ fn on_friend_request_accepted(payload: Payload, _socket: RawClient) {
|
||||
.emit(WS_EVENT::FRIEND_REQUEST_ACCEPTED, str)
|
||||
.unwrap();
|
||||
}
|
||||
_ => todo!(),
|
||||
_ => error!("Received unexpected payload format for friend request accepted"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +53,7 @@ fn on_friend_request_denied(payload: Payload, _socket: RawClient) {
|
||||
.emit(WS_EVENT::FRIEND_REQUEST_DENIED, str)
|
||||
.unwrap();
|
||||
}
|
||||
_ => todo!(),
|
||||
_ => error!("Received unexpected payload format for friend request denied"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +63,7 @@ fn on_unfriended(payload: Payload, _socket: RawClient) {
|
||||
println!("Received unfriended: {:?}", str);
|
||||
get_app_handle().emit(WS_EVENT::UNFRIENDED, str).unwrap();
|
||||
}
|
||||
_ => todo!(),
|
||||
_ => error!("Received unexpected payload format for unfriended"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +74,19 @@ fn on_friend_cursor_position(payload: Payload, _socket: RawClient) {
|
||||
.emit(WS_EVENT::FRIEND_CURSOR_POSITION, str)
|
||||
.unwrap();
|
||||
}
|
||||
_ => todo!(),
|
||||
_ => error!("Received unexpected payload format for friend cursor position"),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_friend_disconnected(payload: Payload, _socket: RawClient) {
|
||||
match payload {
|
||||
Payload::Text(str) => {
|
||||
println!("Received friend disconnected: {:?}", str);
|
||||
get_app_handle()
|
||||
.emit(WS_EVENT::FRIEND_DISCONNECTED, str)
|
||||
.unwrap();
|
||||
}
|
||||
_ => error!("Received unexpected payload format for friend disconnected"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,6 +158,7 @@ pub async fn build_ws_client(app_config: &AppConfig) -> rust_socketio::client::C
|
||||
.on(WS_EVENT::FRIEND_REQUEST_DENIED, on_friend_request_denied)
|
||||
.on(WS_EVENT::UNFRIENDED, on_unfriended)
|
||||
.on(WS_EVENT::FRIEND_CURSOR_POSITION, on_friend_cursor_position)
|
||||
.on(WS_EVENT::FRIEND_DISCONNECTED, on_friend_disconnected)
|
||||
.auth(json!({ "token": token }))
|
||||
.connect()
|
||||
})
|
||||
|
||||
@@ -14,14 +14,28 @@ export type FriendCursorPosition = {
|
||||
position: CursorPosition;
|
||||
};
|
||||
|
||||
// Map of userId -> { position: CursorPosition, lastUpdated: number }
|
||||
// We store the timestamp to detect stale cursors
|
||||
type FriendCursorData = {
|
||||
position: CursorPosition;
|
||||
lastUpdated: number;
|
||||
};
|
||||
|
||||
// The exported store will only expose the position part to consumers,
|
||||
// but internally we manage the full data.
|
||||
// Actually, it's easier if we just export the positions and manage state internally.
|
||||
export let friendsCursorPositions = writable<Record<string, CursorPosition>>(
|
||||
{},
|
||||
);
|
||||
|
||||
let unlistenCursor: UnlistenFn | null = null;
|
||||
let unlistenFriendCursor: UnlistenFn | null = null;
|
||||
let unlistenFriendDisconnected: UnlistenFn | null = null;
|
||||
let isListening = false;
|
||||
|
||||
// Internal state to track timestamps
|
||||
let friendCursorState: Record<string, FriendCursorData> = {};
|
||||
|
||||
/**
|
||||
* Initialize cursor tracking for this window.
|
||||
* Can be called from multiple windows - only the first call starts tracking on the Rust side,
|
||||
@@ -67,6 +81,12 @@ export async function initCursorTracking() {
|
||||
// Since we only send one argument { userId, position }, it's the first element
|
||||
const data = Array.isArray(payload) ? payload[0] : payload;
|
||||
|
||||
// Update internal state with timestamp
|
||||
friendCursorState[data.userId] = {
|
||||
position: data.position,
|
||||
lastUpdated: Date.now(),
|
||||
};
|
||||
|
||||
friendsCursorPositions.update((current) => {
|
||||
return {
|
||||
...current,
|
||||
@@ -76,6 +96,36 @@ export async function initCursorTracking() {
|
||||
},
|
||||
);
|
||||
|
||||
// Listen to friend disconnected events
|
||||
unlistenFriendDisconnected = await listen<[{ userId: string }]>(
|
||||
"friend-disconnected",
|
||||
(event) => {
|
||||
let payload = event.payload;
|
||||
if (typeof payload === "string") {
|
||||
try {
|
||||
payload = JSON.parse(payload);
|
||||
} catch (e) {
|
||||
console.error("[Cursor] Failed to parse friend disconnected payload:", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const data = Array.isArray(payload) ? payload[0] : payload;
|
||||
|
||||
// Remove from internal state
|
||||
if (friendCursorState[data.userId]) {
|
||||
delete friendCursorState[data.userId];
|
||||
}
|
||||
|
||||
// Update svelte store
|
||||
friendsCursorPositions.update((current) => {
|
||||
const next = { ...current };
|
||||
delete next[data.userId];
|
||||
return next;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
isListening = true;
|
||||
} catch (err) {
|
||||
console.error("[Cursor] Failed to initialize cursor tracking:", err);
|
||||
@@ -96,6 +146,10 @@ export function stopCursorTracking() {
|
||||
unlistenFriendCursor();
|
||||
unlistenFriendCursor = null;
|
||||
}
|
||||
if (unlistenFriendDisconnected) {
|
||||
unlistenFriendDisconnected();
|
||||
unlistenFriendDisconnected = null;
|
||||
}
|
||||
isListening = false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user