2021-01-01 02:06:56 +00:00
|
|
|
import { useEffect, useState, useRef, useCallback } from "react";
|
2020-12-05 06:16:06 +00:00
|
|
|
|
2021-01-01 02:06:56 +00:00
|
|
|
import useDebounce from "./useDebounce";
|
2021-01-02 23:45:24 +00:00
|
|
|
import { diff, applyChanges } from "./diff";
|
2021-01-01 02:06:56 +00:00
|
|
|
|
|
|
|
function useNetworkedState(
|
|
|
|
defaultState,
|
|
|
|
session,
|
|
|
|
eventName,
|
2021-01-02 23:45:24 +00:00
|
|
|
debounceRate = 100,
|
|
|
|
partialUpdates = true
|
2021-01-01 02:06:56 +00:00
|
|
|
) {
|
2020-12-05 06:16:06 +00:00
|
|
|
const [state, _setState] = useState(defaultState);
|
|
|
|
// Used to control whether the state needs to be sent to the socket
|
2021-01-01 02:06:56 +00:00
|
|
|
const dirtyRef = useRef(false);
|
2020-12-05 06:16:06 +00:00
|
|
|
|
2021-01-03 05:45:55 +00:00
|
|
|
// Used to force a full update
|
|
|
|
const forceUpdateRef = useRef(false);
|
|
|
|
|
2020-12-05 06:16:06 +00:00
|
|
|
// Update dirty at the same time as state
|
2021-01-03 05:45:55 +00:00
|
|
|
const setState = useCallback((update, sync = true, force = false) => {
|
2021-01-01 02:06:56 +00:00
|
|
|
dirtyRef.current = sync;
|
2021-01-03 05:45:55 +00:00
|
|
|
forceUpdateRef.current = force;
|
2021-01-01 02:25:19 +00:00
|
|
|
_setState(update);
|
2020-12-31 06:56:51 +00:00
|
|
|
}, []);
|
2020-12-05 06:16:06 +00:00
|
|
|
|
2021-01-01 02:06:56 +00:00
|
|
|
const eventNameRef = useRef(eventName);
|
|
|
|
useEffect(() => {
|
|
|
|
eventNameRef.current = eventName;
|
|
|
|
}, [eventName]);
|
|
|
|
|
|
|
|
const debouncedState = useDebounce(state, debounceRate);
|
2021-01-02 23:45:24 +00:00
|
|
|
const lastSyncedStateRef = useRef();
|
2020-12-05 06:16:06 +00:00
|
|
|
useEffect(() => {
|
2021-01-01 02:06:56 +00:00
|
|
|
if (session.socket && dirtyRef.current) {
|
2021-01-02 23:45:24 +00:00
|
|
|
// If partial updates enabled, send just the changes to the socket
|
2021-01-03 05:45:55 +00:00
|
|
|
if (
|
|
|
|
lastSyncedStateRef.current &&
|
|
|
|
debouncedState &&
|
|
|
|
partialUpdates &&
|
|
|
|
!forceUpdateRef.current
|
|
|
|
) {
|
2021-01-02 23:45:24 +00:00
|
|
|
const changes = diff(lastSyncedStateRef.current, debouncedState);
|
|
|
|
if (changes) {
|
|
|
|
session.socket.emit(`${eventName}_update`, changes);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
session.socket.emit(eventName, debouncedState);
|
|
|
|
}
|
2021-01-01 02:06:56 +00:00
|
|
|
dirtyRef.current = false;
|
2021-01-03 05:45:55 +00:00
|
|
|
forceUpdateRef.current = false;
|
2021-01-02 23:45:24 +00:00
|
|
|
lastSyncedStateRef.current = debouncedState;
|
2020-12-05 06:16:06 +00:00
|
|
|
}
|
2021-01-02 23:45:24 +00:00
|
|
|
}, [session.socket, eventName, debouncedState, partialUpdates]);
|
2021-01-02 12:59:21 +00:00
|
|
|
|
2020-12-11 02:24:39 +00:00
|
|
|
useEffect(() => {
|
|
|
|
function handleSocketEvent(data) {
|
|
|
|
_setState(data);
|
2021-01-02 23:45:24 +00:00
|
|
|
lastSyncedStateRef.current = data;
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleSocketUpdateEvent(changes) {
|
|
|
|
_setState((prevState) => {
|
|
|
|
let newState = { ...prevState };
|
|
|
|
applyChanges(newState, changes);
|
|
|
|
lastSyncedStateRef.current = newState;
|
|
|
|
return newState;
|
|
|
|
});
|
2020-12-11 02:24:39 +00:00
|
|
|
}
|
|
|
|
|
2021-01-01 01:44:56 +00:00
|
|
|
session.socket?.on(eventName, handleSocketEvent);
|
2021-01-02 23:45:24 +00:00
|
|
|
session.socket?.on(`${eventName}_update`, handleSocketUpdateEvent);
|
2020-12-11 02:24:39 +00:00
|
|
|
return () => {
|
2021-01-01 01:44:56 +00:00
|
|
|
session.socket?.off(eventName, handleSocketEvent);
|
2021-01-02 23:45:24 +00:00
|
|
|
session.socket?.off(`${eventName}_update`, handleSocketUpdateEvent);
|
2020-12-11 02:24:39 +00:00
|
|
|
};
|
2020-12-31 06:56:51 +00:00
|
|
|
}, [session.socket, eventName]);
|
2020-12-11 02:24:39 +00:00
|
|
|
|
2020-12-05 06:16:06 +00:00
|
|
|
return [state, setState];
|
|
|
|
}
|
|
|
|
|
|
|
|
export default useNetworkedState;
|