Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | import debounce from 'lodash.debounce';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
export interface ByteArrayHandle {
bytes: Uint8Array;
version: number;
/** True when every byte in the array is set (all positions marked). */
isFull: boolean;
setByte: (index: number) => void;
clearByte: (index: number) => void;
resetWith: (populate: (bytes: Uint8Array) => void) => void;
}
/**
* Manages a mutable Uint8Array (one byte per position) with React change
* detection via an incrementing version counter.
*
* @param size - Number of positions (e.g. total slices in a viewport).
* @param debounceMs - When > 0, version bumps are debounced by this many
* milliseconds. Byte writes are always immediate. Use for
* high-frequency sources (cache prefetch) to batch renders.
* Omit or pass 0 for immediate re-renders (e.g. viewed tracking).
*/
export function useByteArray(size: number, debounceMs = 0): ByteArrayHandle {
const bytesRef = useRef(new Uint8Array(size));
const countRef = useRef(0);
const [version, setVersion] = useState(0);
// Debounced bump — recreated when debounceMs changes; cancelled on unmount
// or when debounceMs changes, following the lodash.debounce pattern used
// throughout ui-next (InputFilter, CinePlayer).
const debouncedBump = useMemo(
() => (debounceMs > 0 ? debounce(() => setVersion(v => v + 1), debounceMs) : null),
[debounceMs]
);
useEffect(() => {
return () => debouncedBump?.cancel();
}, [debouncedBump]);
// Reset array only when size actually changes — skip on initial mount since
// bytesRef is already initialised to the correct size via useRef.
useEffect(() => {
Iif (bytesRef.current.length === size) return;
debouncedBump?.cancel();
bytesRef.current = new Uint8Array(size);
countRef.current = 0;
setVersion(v => v + 1);
}, [size, debouncedBump]);
const bump = useCallback(() => {
if (debouncedBump) {
debouncedBump();
} else {
setVersion(v => v + 1);
}
}, [debouncedBump]);
const setByte = useCallback(
(index: number) => {
const bytes = bytesRef.current;
Iif (index < 0 || index >= bytes.length || bytes[index] === 1) return;
bytes[index] = 1;
countRef.current++;
bump();
},
[bump]
);
const clearByte = useCallback(
(index: number) => {
const bytes = bytesRef.current;
Iif (index < 0 || index >= bytes.length || bytes[index] === 0) return;
bytes[index] = 0;
countRef.current--;
bump();
},
[bump]
);
const resetWith = useCallback(
(populate: (bytes: Uint8Array) => void) => {
const bytes = bytesRef.current;
bytes.fill(0);
populate(bytes);
let count = 0;
for (let i = 0; i < bytes.length; i++) {
Iif (bytes[i]) count++;
}
countRef.current = count;
bump();
},
[bump]
);
return {
bytes: bytesRef.current,
version,
// countRef.current is read at render time (triggered by version bump) so
// it is always up to date when this value is consumed.
isFull: size > 0 && countRef.current === size,
setByte,
clearByte,
resetWith,
};
}
|