"use client"; import React, { useRef, useEffect, useState } from "react"; import { useTheme } from "next-themes"; type MatrixEngine = { tick: () => void; update_mouse: (x: number, y: number) => void; trigger_glitch: (x: number, y: number) => void; set_drawing: (is_drawing: boolean) => void; set_theme: (is_vpn_on: boolean, is_dark_mode: boolean) => void; resize: (width: number, height: number) => void; }; interface NetrunnerMatrixProps { isSecure?: boolean; } export const NetrunnerMatrix: React.FC = ({ isSecure = false, }) => { const containerRef = useRef(null); const canvasRef = useRef(null); const engineRef = useRef(null); const animationFrameId = useRef(null); const [wasmLoaded, setWasmLoaded] = useState(false); const { resolvedTheme } = useTheme(); const isDarkMode = resolvedTheme === "dark"; useEffect(() => { const canvas = canvasRef.current; const container = containerRef.current; if (!canvas || !container) return; const loadWasm = async () => { try { const wasmPath = "/wasm-matrix/matrix_engine.js"; const wasmModule = await import( /* @vite-ignore */ /* webpackIgnore: true */ wasmPath ); await wasmModule.default("/wasm-matrix/matrix_engine_bg.wasm"); // 1:1 Разрешение без мыльного скейлинга canvas.width = container.clientWidth; canvas.height = container.clientHeight; const engine = new wasmModule.MatrixEngine(canvas, 16); engineRef.current = engine; engine.set_theme(isSecure, isDarkMode); setWasmLoaded(true); // ОПТИМИЗАЦИЯ: Ограничиваем до 35 FPS для устранения лагов let lastTime = 0; const fpsInterval = 1000 / 35; // ~28ms на кадр const render = (time: number) => { animationFrameId.current = requestAnimationFrame(render); const elapsed = time - lastTime; if (elapsed > fpsInterval) { lastTime = time - (elapsed % fpsInterval); engine.tick(); } }; requestAnimationFrame(render); } catch (e) { console.error("Wasm Engine Load Failed:", e); } }; loadWasm(); const handleResize = () => { if (canvas && container && engineRef.current) { canvas.width = container.clientWidth; canvas.height = container.clientHeight; engineRef.current.resize(canvas.width, canvas.height); } }; window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); if (animationFrameId.current) cancelAnimationFrame(animationFrameId.current); }; }, []); useEffect(() => { if (engineRef.current) engineRef.current.set_theme(isSecure, isDarkMode); }, [isSecure, isDarkMode]); // ГЛОБАЛЬНЫЙ ПЕРЕХВАТ (Убраны костыли со скейлингом) useEffect(() => { const triggerAction = (clientX: number, clientY: number) => { if (!canvasRef.current || !engineRef.current) return; const rect = canvasRef.current.getBoundingClientRect(); const x = clientX - rect.left; const y = clientY - rect.top; if (y < 0 || y > rect.height) return; engineRef.current.set_drawing(true); engineRef.current.trigger_glitch(x, y); }; const moveAction = (clientX: number, clientY: number) => { if (!canvasRef.current || !engineRef.current) return; const rect = canvasRef.current.getBoundingClientRect(); engineRef.current.update_mouse(clientX - rect.left, clientY - rect.top); }; const handlePointerDown = (e: PointerEvent) => triggerAction(e.clientX, e.clientY); const handlePointerMove = (e: PointerEvent) => moveAction(e.clientX, e.clientY); const handlePointerUp = () => engineRef.current?.set_drawing(false); // Поддержка пальцев на мобилках const handleTouchStart = (e: TouchEvent) => triggerAction(e.touches[0].clientX, e.touches[0].clientY); const handleTouchMove = (e: TouchEvent) => moveAction(e.touches[0].clientX, e.touches[0].clientY); window.addEventListener("pointerdown", handlePointerDown); window.addEventListener("pointermove", handlePointerMove); window.addEventListener("pointerup", handlePointerUp); window.addEventListener("touchstart", handleTouchStart, { passive: true }); window.addEventListener("touchmove", handleTouchMove, { passive: true }); window.addEventListener("touchend", handlePointerUp); return () => { window.removeEventListener("pointerdown", handlePointerDown); window.removeEventListener("pointermove", handlePointerMove); window.removeEventListener("pointerup", handlePointerUp); window.removeEventListener("touchstart", handleTouchStart); window.removeEventListener("touchmove", handleTouchMove); window.removeEventListener("touchend", handlePointerUp); }; }, []); return (
); };