All files / systems/movement/helpers AccelerationUpdater.tsx

22.22% Statements 6/27
12.5% Branches 2/16
50% Functions 1/2
22.22% Lines 6/27

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 130 131                                                                                                                            2x                     132x 132x   132x   132x                                                                                               132x        
/**
 * AccelerationUpdater - Component that updates movement acceleration at 60fps
 *
 * Uses useFrame to track continuous movement time and calculate acceleration-based speed.
 * Throttles state updates to only call onSpeedUpdate when speed changes meaningfully.
 * This component only updates movement state and renders no visual elements.
 *
 * @module systems/movement/helpers/AccelerationUpdater
 * @category Movement
 * @korean 가속업데이터
 */
 
import { useFrame } from "@react-three/fiber";
import React, { useRef } from "react";
import {
  ACCELERATION_CONSTANTS,
  calculateAcceleratedSpeed,
  isDirectionConsistent,
  isSpeedChangeMeaningful,
} from "./accelerationUtils";
 
/**
 * Props for AccelerationUpdater component
 */
export interface AccelerationUpdaterProps {
  /** Whether player is currently moving */
  readonly isMoving: boolean;
  /** Current velocity vector */
  readonly velocity: { x: number; y: number } | undefined;
  /** Ref to track accumulated movement time */
  readonly movementTimeRef: React.MutableRefObject<number>;
  /** Ref to track last movement direction */
  readonly lastDirectionRef: React.MutableRefObject<{ x: number; y: number }>;
  /** Callback to update calculated speed - only called on meaningful changes */
  readonly onSpeedUpdate: (speed: number) => void;
  /** Walking speed in m/s (from archetype or default) */
  readonly walkSpeed?: number;
  /** Running speed in m/s (from archetype or default) */
  readonly runSpeed?: number;
}
 
/**
 * AccelerationUpdater Component
 *
 * Updates movement acceleration at 60fps using Three.js useFrame hook.
 * Tracks continuous movement time and calculates speed based on direction consistency.
 * Only calls onSpeedUpdate when speed changes by more than epsilon AND sufficient time
 * has passed (100ms throttle), preventing excessive React re-renders at frame rate.
 *
 * @example
 * ```tsx
 * <AccelerationUpdater
 *   isMoving={isMoving}
 *   velocity={velocity}
 *   movementTimeRef={movementTimeRef}
 *   lastDirectionRef={lastDirectionRef}
 *   onSpeedUpdate={setAccelerationBasedSpeed}
 *   walkSpeed={physicalAttributes.walkSpeed}
 *   runSpeed={physicalAttributes.runSpeed}
 * />
 * ```
 */
export const AccelerationUpdater: React.FC<AccelerationUpdaterProps> = ({
  isMoving,
  velocity,
  movementTimeRef,
  lastDirectionRef,
  onSpeedUpdate,
  walkSpeed = ACCELERATION_CONSTANTS.DEFAULT_WALK_SPEED,
  runSpeed = ACCELERATION_CONSTANTS.DEFAULT_RUN_SPEED,
}) => {
  // Track last reported speed and time to throttle updates
  // Initialize with walk speed (archetype-specific or default)
  const lastReportedSpeedRef = useRef<number>(walkSpeed);
  const lastUpdateTimeRef = useRef<number>(0);
  // Throttle interval: update at most every ~100ms (10Hz) instead of 60fps
  const UPDATE_THROTTLE_MS = 100;
 
  useFrame((_state, delta) => {
    // If not moving, reset timers and direction
    if (!isMoving || !velocity || (velocity.x === 0 && velocity.y === 0)) {
      movementTimeRef.current = 0;
      lastDirectionRef.current = { x: 0, y: 0 };
      
      // Only update if changed meaningfully
      if (isSpeedChangeMeaningful(lastReportedSpeedRef.current, walkSpeed)) {
        lastReportedSpeedRef.current = walkSpeed;
        onSpeedUpdate(walkSpeed);
        lastUpdateTimeRef.current = performance.now();
      }
      return;
    }
 
    // Check direction consistency (within 45 degrees)
    const currentDir = { x: velocity.x, y: velocity.y };
    const isSameDirection = isDirectionConsistent(currentDir, lastDirectionRef.current);
 
    // Reset accumulated movement time if direction changed too much
    if (!isSameDirection) {
      movementTimeRef.current = 0;
    } else {
      // Accumulate movement time while moving in a consistent direction
      movementTimeRef.current += delta;
    }
 
    // Update last direction for the next frame
    lastDirectionRef.current = currentDir;
 
    // Calculate new speed with archetype-specific walk/run speeds
    const newSpeed = calculateAcceleratedSpeed(movementTimeRef.current, walkSpeed, runSpeed);
 
    // Throttle updates by both time and epsilon
    // Only call onSpeedUpdate if enough time has passed AND speed changed meaningfully
    const now = performance.now();
    const timeSinceLastUpdate = now - lastUpdateTimeRef.current;
    
    if (
      timeSinceLastUpdate >= UPDATE_THROTTLE_MS &&
      isSpeedChangeMeaningful(lastReportedSpeedRef.current, newSpeed)
    ) {
      lastReportedSpeedRef.current = newSpeed;
      onSpeedUpdate(newSpeed);
      lastUpdateTimeRef.current = now;
    }
  });
 
  return null; // Component only updates movement state, renders no visual elements
};
 
export default AccelerationUpdater;