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 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()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user