From 02ecf9bffc5b8495642dfc7928f52d0fb677c010 Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Sun, 3 Jan 2021 10:45:24 +1100 Subject: [PATCH] Changed useNetworkState to implement a partial update mechanism --- src/contexts/PlayerContext.js | 4 +++- src/helpers/diff.js | 9 +++++++ src/helpers/useNetworkedState.js | 41 +++++++++++++++++++++----------- 3 files changed, 39 insertions(+), 15 deletions(-) create mode 100644 src/helpers/diff.js diff --git a/src/contexts/PlayerContext.js b/src/contexts/PlayerContext.js index 3e42e1f..19f1fe0 100644 --- a/src/contexts/PlayerContext.js +++ b/src/contexts/PlayerContext.js @@ -22,7 +22,9 @@ export function PlayerProvider({ session, children }) { userId, }, session, - "player_state" + "player_state", + 100, + false ); useEffect(() => { diff --git a/src/helpers/diff.js b/src/helpers/diff.js new file mode 100644 index 0000000..f2853fd --- /dev/null +++ b/src/helpers/diff.js @@ -0,0 +1,9 @@ +import { applyChange, diff as deepDiff } from "deep-diff"; + +export function applyChanges(target, changes) { + for (let change of changes) { + applyChange(target, true, change); + } +} + +export const diff = deepDiff; diff --git a/src/helpers/useNetworkedState.js b/src/helpers/useNetworkedState.js index bb532d6..c2d0857 100644 --- a/src/helpers/useNetworkedState.js +++ b/src/helpers/useNetworkedState.js @@ -1,13 +1,14 @@ import { useEffect, useState, useRef, useCallback } from "react"; -import { applyChange, diff } from "deep-diff"; import useDebounce from "./useDebounce"; +import { diff, applyChanges } from "./diff"; function useNetworkedState( defaultState, session, eventName, - debounceRate = 100 + debounceRate = 100, + partialUpdates = true ) { const [state, _setState] = useState(defaultState); // Used to control whether the state needs to be sent to the socket @@ -25,31 +26,43 @@ function useNetworkedState( }, [eventName]); const debouncedState = useDebounce(state, debounceRate); + const lastSyncedStateRef = useRef(); useEffect(() => { if (session.socket && dirtyRef.current) { - session.socket.emit(eventName, debouncedState); + // If partial updates enabled, send just the changes to the socket + if (lastSyncedStateRef.current && debouncedState && partialUpdates) { + const changes = diff(lastSyncedStateRef.current, debouncedState); + if (changes) { + session.socket.emit(`${eventName}_update`, changes); + } + } else { + session.socket.emit(eventName, debouncedState); + } dirtyRef.current = false; + lastSyncedStateRef.current = debouncedState; } - }, [session.socket, eventName, debouncedState]); - - // Store the uncommitted changes so we can re-apply them when receiving new data - const uncommittedChangesRef = useRef(); - useEffect(() => { - uncommittedChangesRef.current = diff(debouncedState, state); - }, [state, debouncedState]); + }, [session.socket, eventName, debouncedState, partialUpdates]); useEffect(() => { function handleSocketEvent(data) { - const uncommittedChanges = uncommittedChangesRef.current || []; - for (let change of uncommittedChanges) { - applyChange(data, true, change); - } _setState(data); + lastSyncedStateRef.current = data; + } + + function handleSocketUpdateEvent(changes) { + _setState((prevState) => { + let newState = { ...prevState }; + applyChanges(newState, changes); + lastSyncedStateRef.current = newState; + return newState; + }); } session.socket?.on(eventName, handleSocketEvent); + session.socket?.on(`${eventName}_update`, handleSocketUpdateEvent); return () => { session.socket?.off(eventName, handleSocketEvent); + session.socket?.off(`${eventName}_update`, handleSocketUpdateEvent); }; }, [session.socket, eventName]);