friends cursor broadcast system refined 👍

This commit is contained in:
2025-12-16 01:56:46 +08:00
parent 9363a2e96b
commit df934afefa
2 changed files with 73 additions and 5 deletions

View File

@@ -18,6 +18,7 @@ impl WS_EVENT {
pub const FRIEND_REQUEST_DENIED: &str = "friend-request-denied"; pub const FRIEND_REQUEST_DENIED: &str = "friend-request-denied";
pub const UNFRIENDED: &str = "unfriended"; pub const UNFRIENDED: &str = "unfriended";
pub const FRIEND_CURSOR_POSITION: &str = "friend-cursor-position"; 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) { 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) .emit(WS_EVENT::FRIEND_REQUEST_RECEIVED, str)
.unwrap(); .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) .emit(WS_EVENT::FRIEND_REQUEST_ACCEPTED, str)
.unwrap(); .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) .emit(WS_EVENT::FRIEND_REQUEST_DENIED, str)
.unwrap(); .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); println!("Received unfriended: {:?}", str);
get_app_handle().emit(WS_EVENT::UNFRIENDED, str).unwrap(); 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) .emit(WS_EVENT::FRIEND_CURSOR_POSITION, str)
.unwrap(); .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::FRIEND_REQUEST_DENIED, on_friend_request_denied)
.on(WS_EVENT::UNFRIENDED, on_unfriended) .on(WS_EVENT::UNFRIENDED, on_unfriended)
.on(WS_EVENT::FRIEND_CURSOR_POSITION, on_friend_cursor_position) .on(WS_EVENT::FRIEND_CURSOR_POSITION, on_friend_cursor_position)
.on(WS_EVENT::FRIEND_DISCONNECTED, on_friend_disconnected)
.auth(json!({ "token": token })) .auth(json!({ "token": token }))
.connect() .connect()
}) })

View File

@@ -14,14 +14,28 @@ export type FriendCursorPosition = {
position: CursorPosition; 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>>( export let friendsCursorPositions = writable<Record<string, CursorPosition>>(
{}, {},
); );
let unlistenCursor: UnlistenFn | null = null; let unlistenCursor: UnlistenFn | null = null;
let unlistenFriendCursor: UnlistenFn | null = null; let unlistenFriendCursor: UnlistenFn | null = null;
let unlistenFriendDisconnected: UnlistenFn | null = null;
let isListening = false; let isListening = false;
// Internal state to track timestamps
let friendCursorState: Record<string, FriendCursorData> = {};
/** /**
* Initialize cursor tracking for this window. * Initialize cursor tracking for this window.
* Can be called from multiple windows - only the first call starts tracking on the Rust side, * 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 // Since we only send one argument { userId, position }, it's the first element
const data = Array.isArray(payload) ? payload[0] : payload; 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) => { friendsCursorPositions.update((current) => {
return { return {
...current, ...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; isListening = true;
} catch (err) { } catch (err) {
console.error("[Cursor] Failed to initialize cursor tracking:", err); console.error("[Cursor] Failed to initialize cursor tracking:", err);
@@ -96,6 +146,10 @@ export function stopCursorTracking() {
unlistenFriendCursor(); unlistenFriendCursor();
unlistenFriendCursor = null; unlistenFriendCursor = null;
} }
if (unlistenFriendDisconnected) {
unlistenFriendDisconnected();
unlistenFriendDisconnected = null;
}
isListening = false; isListening = false;
} }