All files / platform/ui-next/src/components/SmartScrollbar useByteArray.ts

0% Statements 0/49
0% Branches 0/17
0% Functions 0/13
0% Lines 0/39

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,
  };
}