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

86.15% Statements 56/65
68.42% Branches 13/19
85.71% Functions 12/14
87.27% Lines 48/55

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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129                                                11466x 11466x 11466x 11466x   11466x 4838x 2115x 2115x       11466x   2009x 2009x         11466x 2634x   1442x 1442x 1442x 1442x     11466x   1192x 1192x 1192x 596x     596x   596x     596x     11466x 16591x 1199x 1199x     15392x 2179x       11466x   16409x 16409x 15470x 15470x 15470x         11466x                     11466x   1121x 1121x 1121x 1121x 1121x 75854x   1121x 1121x         11466x                      
import { useCallback, useEffect, 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 batchIntervalMs - When > 0, writes are coalesced into a scheduled
 *                          flush: the first write starts a timer, the next
 *                          flush bumps `version`, and the timer stops. New
 *                          writes start a new interval window. Omit or pass 0
 *                          for immediate re-renders on every write.
 */
export function useByteArray(size: number, batchIntervalMs = 0): ByteArrayHandle {
  const bytesRef = useRef(new Uint8Array(size));
  const countRef = useRef(0);
  const [version, setVersion] = useState(0);
  const timeoutIdRef = useRef<number | null>(null);
 
  const clearScheduledFlush = useCallback(() => {
    if (timeoutIdRef.current !== null) {
      window.clearTimeout(timeoutIdRef.current);
      timeoutIdRef.current = null;
    }
  }, []);
 
  const flushScheduledVersion = useCallback(() => {
    // End this timeout window after the scheduled flush.
    clearScheduledFlush();
    setVersion(v => v + 1);
  }, [clearScheduledFlush]);
 
  // Reset array only when size actually changes — skip on initial mount since
  // bytesRef is already initialised to the correct size via useRef.
  useEffect(() => {
    if (bytesRef.current.length === size) return;
    // Drop any in-flight timeout window when resetting the underlying array.
    clearScheduledFlush();
    bytesRef.current = new Uint8Array(size);
    countRef.current = 0;
    setVersion(v => v + 1);
  }, [size, clearScheduledFlush]);
 
  useEffect(() => {
    // If timing changes mid-window, restart that timeout using the new timing.
    const pendingTimeoutId = timeoutIdRef.current;
    clearScheduledFlush();
    if (batchIntervalMs <= 0) {
      Iif (pendingTimeoutId !== null) {
        setVersion(v => v + 1);
      }
      return;
    }
    Iif (pendingTimeoutId !== null) {
      timeoutIdRef.current = window.setTimeout(flushScheduledVersion, batchIntervalMs);
    }
    return () => clearScheduledFlush();
  }, [batchIntervalMs, clearScheduledFlush, flushScheduledVersion]);
 
  const bump = useCallback(() => {
    if (batchIntervalMs <= 0) {
      setVersion(v => v + 1);
      return;
    }
 
    if (timeoutIdRef.current === null) {
      timeoutIdRef.current = window.setTimeout(flushScheduledVersion, batchIntervalMs);
    }
  }, [batchIntervalMs, flushScheduledVersion]);
 
  const setByte = useCallback(
    (index: number) => {
      const bytes = bytesRef.current;
      if (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++) {
        if (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,
  };
}