diff --git a/Hubs/RenderingHub.cs b/Hubs/RenderingHub.cs index 62d8d79..0503a37 100644 --- a/Hubs/RenderingHub.cs +++ b/Hubs/RenderingHub.cs @@ -6,453 +6,443 @@ using Hi.Webapi.Services; namespace Hi.Webapi.Hubs { - /// - /// SignalR Hub 用於處理渲染畫布的實時通信 - /// - public class RenderingHub : Hub - { - private readonly RenderingService _renderingService; - private readonly ILogger _logger; - private readonly Dictionary _lastFrameCache = []; - private readonly Dictionary _lastFrameTime = []; - private readonly Dictionary _sketchViewCache = []; + /// + /// SignalR Hub 用於處理渲染畫布的實時通信 + /// + public class RenderingHub : Hub + { + private readonly RenderingService _renderingService; + private readonly ILogger _logger; + private readonly Dictionary _lastFrameCache = []; + private readonly Dictionary _sketchViewCache = []; - public RenderingHub(RenderingService renderingService, ILogger logger) - { - _renderingService = renderingService; - _logger = logger; - } + public RenderingHub(RenderingService renderingService, ILogger logger) + { + _renderingService = renderingService; + _logger = logger; + } - /// - /// 客戶端連接時初始化渲染引擎 - /// - public async Task InitializeCanvas(int width, int height) - { - var sessionId = Context.ConnectionId; - _logger.LogInformation($"InitializeCanvas called - SessionId: {sessionId}, Width: {width}, Height: {height}"); - - var engine = _renderingService.GetOrCreateEngine(sessionId); - _logger.LogInformation($"Engine created/retrieved - SessionId: {sessionId}"); - - // 啟動引擎(必須在設置回調之前) - engine.Start(width, height); - _logger.LogInformation($"Engine started with size {width}x{height} - SessionId: {sessionId}"); - - // 在設置回調之前,先捕獲客戶端代理 - var clientProxy = Clients.Caller; - var logger = _logger; - var frameCount = 0; - byte[] preImageRgba = null; - - // 設置渲染回調 - unsafe - { - engine.ImageRequestAfterBufferSwapped += (byte* bgra, int w, int h) => - { - if (bgra == null) - return; - - // 幀率限制 - 最多每秒30幀 - if (_lastFrameTime.TryGetValue(sessionId, out var lastTime)) - { - var elapsed = DateTime.Now - lastTime; - if (elapsed.TotalMilliseconds < 33) // 30 FPS - { - return; - } - } - _lastFrameTime[sessionId] = DateTime.Now; - - frameCount++; - if (frameCount <= 5) // 只記錄前5幀 - { - logger.LogInformation($"Frame {frameCount} rendered - Size: {w}x{h}, SessionId: {sessionId}"); - } - - // 檢查寬度和高度是否有效 - if (w <= 0 || h <= 0) - { - logger.LogWarning($"Invalid image size: {w}x{h} - SessionId: {sessionId}"); - return; - } - - try - { - // 在回調中同步處理圖像數據,轉換為 byte[] - int wh = w * h; - byte[] rgba = new byte[wh * 4]; - for (int i = 0; i < wh; i++) - { - rgba[i * 4] = bgra[i * 4 + 2]; - rgba[i * 4 + 1] = bgra[i * 4 + 1]; - rgba[i * 4 + 2] = bgra[i * 4]; - rgba[i * 4 + 3] = bgra[i * 4 + 3]; - } - - // 檢查圖像是否有變化 - if (preImageRgba != null && preImageRgba.SequenceEqual(rgba)) - return; - preImageRgba = rgba; + /// + /// 客戶端連接時初始化渲染引擎 + /// + public async Task InitializeCanvas(int width, int height) + { + var sessionId = Context.ConnectionId; + _logger.LogInformation($"InitializeCanvas called - SessionId: {sessionId}, Width: {width}, Height: {height}"); - if (frameCount <= 5) - { - logger.LogInformation($"Frame {frameCount} sending - Size: {w}x{h}, Data: {rgba.Length} bytes"); - } + var engine = _renderingService.GetOrCreateEngine(sessionId); + _logger.LogInformation($"Engine created/retrieved - SessionId: {sessionId}"); - // 壓縮數據 - using MemoryStream dstMemoryStream = new MemoryStream(); - using GZipStream gZipStream = new GZipStream(dstMemoryStream, CompressionMode.Compress); - using MemoryStream srcMemoryStream = new MemoryStream(rgba); - srcMemoryStream.CopyTo(gZipStream); - gZipStream.Close(); - byte[] compressedRgbaArray = dstMemoryStream.ToArray(); - - if (compressedRgbaArray == null) - return; - - var compressionRatio = (float)compressedRgbaArray.Length / rgba.Length * 100; - - if (frameCount <= 5) - { - logger.LogInformation($"Compressed: {compressedRgbaArray.Length} bytes ({compressionRatio:F1}% of original)"); - } + // 啟動引擎(必須在設置回調之前) + engine.Start(width, height); + _logger.LogInformation($"Engine started with size {width}x{height} - SessionId: {sessionId}"); - // 發送壓縮數據到客戶端(注意:第二個參數應該是原始長度,不是壓縮後的長度) - _ = clientProxy.SendAsync("ImageUpdate", - compressedRgbaArray, - rgba.Length, - w, - h) - .ContinueWith(t => - { - if (t.IsFaulted) - { - logger.LogError($"Failed to send frame: {t.Exception?.GetBaseException().Message} - SessionId: {sessionId}"); - } - }); - } - catch (ObjectDisposedException) - { - // Hub 已經被釋放,忽略這個錯誤 - logger.LogDebug($"Hub已釋放,忽略渲染回調 - SessionId: {sessionId}"); - } - catch (Exception ex) - { - logger.LogError(ex, $"渲染回調錯誤 - SessionId: {sessionId}"); - } - }; - } - - // 設置初始視圖 - if (_sketchViewCache.TryGetValue(sessionId, out var cachedView)) - { - engine.SketchView = cachedView; - } - else - { - engine.SetViewToHomeView(); - _sketchViewCache[sessionId] = engine.SketchView; - } - _logger.LogInformation($"View initialized - SessionId: {sessionId}"); - - // 確保引擎可見並開始渲染 - engine.IsVisible = true; - _logger.LogInformation($"Engine visibility set to true - SessionId: {sessionId}"); - - await Clients.Caller.SendAsync("CanvasInitialized", sessionId); - _logger.LogInformation($"Canvas initialized for session: {sessionId}"); - } + // 在設置回調之前,先捕獲客戶端代理 + var clientProxy = Clients.Caller; + var logger = _logger; + var frameCount = 0; - /// - /// 處理鼠標移動事件 - /// - public Task HandleMouseMove(double x, double y, int buttonMask) - { - var sessionId = Context.ConnectionId; - _logger.LogDebug($"HandleMouseMove - SessionId: {sessionId}, X: {x}, Y: {y}, ButtonMask: {buttonMask}"); - - var engine = _renderingService.GetOrCreateEngine(sessionId); - var p = new Vec2i((int)x, (int)y); - - // 移動鼠標 - engine.MouseMove(p.x, p.y); - - // 如果有按鈕按下,處理拖曳 - if (buttonMask > 0) - { - engine.MouseDragTransform(p.x, p.y, - new mouse_button_table__transform_view_by_mouse_drag_t() - { - LEFT_BUTTON = 0, - RIGHT_BUTTON = 2 - }); - _sketchViewCache[sessionId] = engine.SketchView; - } - - return Task.CompletedTask; - } + // 設置渲染回調 + unsafe + { + engine.ImageRequestAfterBufferSwapped += (byte* bgra, int w, int h) => + { + if (bgra == null) + return; - /// - /// 處理鼠標按下事件 - /// - public Task HandleMouseDown(double x, double y, int button) - { - var sessionId = Context.ConnectionId; - _logger.LogInformation($"HandleMouseDown - SessionId: {sessionId}, X: {x}, Y: {y}, Button: {button}"); - - var engine = _renderingService.GetOrCreateEngine(sessionId); - engine.MouseButtonDown(button); - return Task.CompletedTask; - } + frameCount++; + if (frameCount <= 5) // 只記錄前5幀 + { + logger.LogInformation($"Frame {frameCount} rendered - Size: {w}x{h}, SessionId: {sessionId}"); + } - /// - /// 處理鼠標釋放事件 - /// - public Task HandleMouseUp(double x, double y, int button) - { - var sessionId = Context.ConnectionId; - _logger.LogInformation($"HandleMouseUp - SessionId: {sessionId}, X: {x}, Y: {y}, Button: {button}"); - - var engine = _renderingService.GetOrCreateEngine(sessionId); - engine.MouseButtonUp(button); - return Task.CompletedTask; - } + // 檢查寬度和高度是否有效 + if (w <= 0 || h <= 0) + { + logger.LogWarning($"Invalid image size: {w}x{h} - SessionId: {sessionId}"); + return; + } - /// - /// 處理鼠標滾輪事件 - /// - public Task HandleMouseWheel(double x, double y, double deltaX, double deltaY, string browserBrand = "chrome") - { - var sessionId = Context.ConnectionId; - _logger.LogInformation($"HandleMouseWheel - SessionId: {sessionId}, X: {x}, Y: {y}, DeltaX: {deltaX}, DeltaY: {deltaY}, Browser: {browserBrand}"); - - var engine = _renderingService.GetOrCreateEngine(sessionId); - - // 根據瀏覽器類型獲取縮放比例 - double scale = GetWheelScaling(browserBrand); - - engine.MouseWheel((int)(deltaX * scale), -(int)(deltaY * scale)); - - // 使用 MouseWheelTransform 進行視圖變換 - engine.MouseWheelTransform( - (int)(deltaX * scale * 1000), -(int)(deltaY * scale * 1000), 0.1 / 1000); - - _sketchViewCache[sessionId] = engine.SketchView; - - return Task.CompletedTask; - } + try + { + // 在回調中同步處理圖像數據,轉換為 byte[] + int wh = w * h; + byte[] rgba = new byte[wh * 4]; + for (int i = 0; i < wh; i++) + { + rgba[i * 4] = bgra[i * 4 + 2]; + rgba[i * 4 + 1] = bgra[i * 4 + 1]; + rgba[i * 4 + 2] = bgra[i * 4]; + rgba[i * 4 + 3] = bgra[i * 4 + 3]; + } - /// - /// 處理窗口大小變化 - /// - public Task HandleResize(int width, int height) - { - var sessionId = Context.ConnectionId; - _logger.LogInformation($"HandleResize - SessionId: {sessionId}, Width: {width}, Height: {height}"); - - var engine = _renderingService.GetOrCreateEngine(sessionId); - - // 重置圖像緩存以強制重新渲染 - if (_lastFrameCache.ContainsKey(sessionId)) - { - _lastFrameCache.Remove(sessionId); - } - - engine.Resize(width, height); - - _logger.LogInformation($"Resize completed - SessionId: {sessionId}"); - return Task.CompletedTask; - } + if (_lastFrameCache.TryGetValue( + sessionId, out byte[] preImageRgba)) + { + if (preImageRgba != null && + preImageRgba.SequenceEqual(rgba)) + return; + } + _lastFrameCache[sessionId] = rgba; - /// - /// 處理可見性變化 - /// - public Task HandleVisibilityChange(string visibilityState) - { - var sessionId = Context.ConnectionId; - _logger.LogInformation($"HandleVisibilityChange - SessionId: {sessionId}, State: {visibilityState}"); - - var engine = _renderingService.GetOrCreateEngine(sessionId); - engine.IsVisible = visibilityState == "visible"; - - return Task.CompletedTask; - } - /// - /// 設置視圖 - /// - public Task SetView(string viewType) - { - var sessionId = Context.ConnectionId; - _logger.LogInformation($"SetView called - SessionId: {sessionId}, ViewType: {viewType}"); - - var engine = _renderingService.GetOrCreateEngine(sessionId); - - switch (viewType.ToLower()) - { - case "front": - engine.SetViewToFrontView(); - break; - case "back": - engine.SetViewToFrontView(); - engine.TurnBackView(); - break; - case "left": - engine.SetViewToRightView(); - engine.TurnBackView(); - break; - case "right": - engine.SetViewToRightView(); - break; - case "top": - engine.SetViewToTopView(); - break; - case "bottom": - engine.SetViewToTopView(); - engine.TurnBackView(); - break; - case "isometric": - engine.SetViewToIsometricView(); - break; - case "home": - engine.SetViewToHomeView(); - break; - default: - _logger.LogWarning($"Unknown view type: {viewType} - SessionId: {sessionId}"); - break; - } - - _sketchViewCache[sessionId] = engine.SketchView; - - return Task.CompletedTask; - } + if (frameCount <= 5) + { + logger.LogInformation($"Frame {frameCount} sending - Size: {w}x{h}, Data: {rgba.Length} bytes"); + } - /// - /// 處理鍵盤按下事件 - /// - public Task HandleKeyDown(string key, string code, bool ctrlKey, bool shiftKey, bool altKey) - { - var sessionId = Context.ConnectionId; - _logger.LogInformation($"HandleKeyDown - SessionId: {sessionId}, Key: {key}, Code: {code}, Ctrl: {ctrlKey}, Shift: {shiftKey}, Alt: {altKey}"); - - var engine = _renderingService.GetOrCreateEngine(sessionId); - - // 使用 key 的 HashCode(與 Blazor 版本一致) - long keyCode = key.ToLower().GetHashCode(); - engine.KeyDown(keyCode); - - // 使用 KeyDownTransform 進行視圖變換 - engine.KeyDownTransform(keyCode, new key_table__transform_view_by_key_pressing_t() - { - HOME = "home".ToLower().GetHashCode(), - PAGE_UP = "pageup".ToLower().GetHashCode(), - PAGE_DOWN = "pagedown".ToLower().GetHashCode(), - F1 = "f1".ToLower().GetHashCode(), - F2 = "f2".ToLower().GetHashCode(), - F3 = "f3".ToLower().GetHashCode(), - F4 = "f4".ToLower().GetHashCode(), - SHIFT = "shift".ToLower().GetHashCode(), - ARROW_LEFT = "arrowleft".ToLower().GetHashCode(), - ARROW_RIGHT = "arrowright".ToLower().GetHashCode(), - ARROW_DOWN = "arrowdown".ToLower().GetHashCode(), - ARROW_UP = "arrowup".ToLower().GetHashCode() - }); - - _sketchViewCache[sessionId] = engine.SketchView; - - return Task.CompletedTask; - } + using MemoryStream dstMemoryStream = new MemoryStream(); + using GZipStream gZipStream = new GZipStream(dstMemoryStream, CompressionMode.Compress); + using MemoryStream srcMemoryStream = new MemoryStream(rgba); + srcMemoryStream.CopyTo(gZipStream); + gZipStream.Close(); + byte[] compressedRgbaArray = dstMemoryStream.ToArray(); - /// - /// 處理鍵盤釋放事件 - /// - public Task HandleKeyUp(string key, string code, bool ctrlKey, bool shiftKey, bool altKey) - { - var sessionId = Context.ConnectionId; - _logger.LogInformation($"HandleKeyUp - SessionId: {sessionId}, Key: {key}, Code: {code}, Ctrl: {ctrlKey}, Shift: {shiftKey}, Alt: {altKey}"); - - var engine = _renderingService.GetOrCreateEngine(sessionId); - - long keyCode = key.ToLower().GetHashCode(); - engine.KeyUp(keyCode); - - return Task.CompletedTask; - } + if (compressedRgbaArray == null) + return; - /// - /// 處理觸摸按下事件 - /// - public Task HandleTouchDown(int pointerId, double x, double y) - { - var sessionId = Context.ConnectionId; - _logger.LogInformation($"HandleTouchDown - SessionId: {sessionId}, PointerId: {pointerId}, X: {x}, Y: {y}"); - - var engine = _renderingService.GetOrCreateEngine(sessionId); - engine.TouchDown(pointerId, (int)x, (int)y); - - return Task.CompletedTask; - } + var compressionRatio = (float)compressedRgbaArray.Length / rgba.Length * 100; - /// - /// 處理觸摸移動事件 - /// - public Task HandleTouchMove(int pointerId, double x, double y) - { - var sessionId = Context.ConnectionId; - _logger.LogDebug($"HandleTouchMove - SessionId: {sessionId}, PointerId: {pointerId}, X: {x}, Y: {y}"); - - var engine = _renderingService.GetOrCreateEngine(sessionId); - engine.TouchMove(pointerId, (int)x, (int)y); - - return Task.CompletedTask; - } + if (frameCount <= 5) + { + logger.LogInformation($"Compressed: {compressedRgbaArray.Length} bytes ({compressionRatio:F1}% of original)"); + } - /// - /// 處理觸摸釋放事件 - /// - public Task HandleTouchUp(int pointerId) - { - var sessionId = Context.ConnectionId; - _logger.LogInformation($"HandleTouchUp - SessionId: {sessionId}, PointerId: {pointerId}"); - - var engine = _renderingService.GetOrCreateEngine(sessionId); - engine.TouchUp(pointerId); - - return Task.CompletedTask; - } + // 發送壓縮數據到客戶端(注意:第二個參數應該是原始長度,不是壓縮後的長度) + _ = clientProxy.SendAsync("ImageUpdate", + compressedRgbaArray, + rgba.Length, + w, + h) + .ContinueWith(t => + { + if (t.IsFaulted) + { + logger.LogError($"Failed to send frame: {t.Exception?.GetBaseException().Message} - SessionId: {sessionId}"); + } + }); + } + catch (ObjectDisposedException) + { + // Hub 已經被釋放,忽略這個錯誤 + logger.LogDebug($"Hub已釋放,忽略渲染回調 - SessionId: {sessionId}"); + } + catch (Exception ex) + { + logger.LogError(ex, $"渲染回調錯誤 - SessionId: {sessionId}"); + } + }; + } - /// - /// 獲取瀏覽器滾輪縮放比例 - /// - private double GetWheelScaling(string browserBrand) - { - switch (browserBrand.ToLower()) - { - case "firefox": - return 1.0; - case "chrome": - case "edge": - case "safari": - default: - return 0.01; - } - } + // 設置初始視圖 + if (_sketchViewCache.TryGetValue(sessionId, out var cachedView)) + { + engine.SketchView = cachedView; + } + else + { + engine.SetViewToHomeView(); + _sketchViewCache[sessionId] = engine.SketchView; + } + _logger.LogInformation($"View initialized - SessionId: {sessionId}"); - /// - /// 客戶端斷開連接時清理資源 - /// - public override async Task OnDisconnectedAsync(Exception exception) - { - var sessionId = Context.ConnectionId; - - // 清理緩存 - _lastFrameCache.Remove(sessionId); - _lastFrameTime.Remove(sessionId); - _sketchViewCache.Remove(sessionId); - - _renderingService.RemoveEngine(sessionId); - _logger.LogInformation($"Client disconnected: {sessionId}"); - await base.OnDisconnectedAsync(exception); - } - } -} \ No newline at end of file + // 確保引擎可見並開始渲染 + engine.IsVisible = true; + _logger.LogInformation($"Engine visibility set to true - SessionId: {sessionId}"); + + await Clients.Caller.SendAsync("CanvasInitialized", sessionId); + _logger.LogInformation($"Canvas initialized for session: {sessionId}"); + } + + /// + /// 處理鼠標移動事件 + /// + public Task HandleMouseMove(double x, double y, int buttonMask) + { + var sessionId = Context.ConnectionId; + _logger.LogDebug($"HandleMouseMove - SessionId: {sessionId}, X: {x}, Y: {y}, ButtonMask: {buttonMask}"); + + var engine = _renderingService.GetOrCreateEngine(sessionId); + var p = new Vec2i((int)x, (int)y); + + // 移動鼠標 + engine.MouseMove(p.x, p.y); + + // 如果有按鈕按下,處理拖曳 + if (buttonMask > 0) + { + engine.MouseDragTransform(p.x, p.y, + new mouse_button_table__transform_view_by_mouse_drag_t() + { + LEFT_BUTTON = 0, + RIGHT_BUTTON = 2 + }); + _sketchViewCache[sessionId] = engine.SketchView; + } + + return Task.CompletedTask; + } + + /// + /// 處理鼠標按下事件 + /// + public Task HandleMouseDown(double x, double y, int button) + { + var sessionId = Context.ConnectionId; + _logger.LogInformation($"HandleMouseDown - SessionId: {sessionId}, X: {x}, Y: {y}, Button: {button}"); + + var engine = _renderingService.GetOrCreateEngine(sessionId); + engine.MouseButtonDown(button); + return Task.CompletedTask; + } + + /// + /// 處理鼠標釋放事件 + /// + public Task HandleMouseUp(double x, double y, int button) + { + var sessionId = Context.ConnectionId; + _logger.LogInformation($"HandleMouseUp - SessionId: {sessionId}, X: {x}, Y: {y}, Button: {button}"); + + var engine = _renderingService.GetOrCreateEngine(sessionId); + engine.MouseButtonUp(button); + return Task.CompletedTask; + } + + /// + /// 處理鼠標滾輪事件 + /// + public Task HandleMouseWheel(double x, double y, double deltaX, double deltaY, string browserBrand = "chrome") + { + var sessionId = Context.ConnectionId; + _logger.LogInformation($"HandleMouseWheel - SessionId: {sessionId}, X: {x}, Y: {y}, DeltaX: {deltaX}, DeltaY: {deltaY}, Browser: {browserBrand}"); + + var engine = _renderingService.GetOrCreateEngine(sessionId); + + // 根據瀏覽器類型獲取縮放比例 + double scale = GetWheelScaling(browserBrand); + + engine.MouseWheel((int)(deltaX * scale), -(int)(deltaY * scale)); + + // 使用 MouseWheelTransform 進行視圖變換 + engine.MouseWheelTransform( + (int)(deltaX * scale * 1000), -(int)(deltaY * scale * 1000), 0.1 / 1000); + + _sketchViewCache[sessionId] = engine.SketchView; + + return Task.CompletedTask; + } + + /// + /// 處理窗口大小變化 + /// + public Task HandleResize(int width, int height) + { + var sessionId = Context.ConnectionId; + _logger.LogInformation($"HandleResize - SessionId: {sessionId}, Width: {width}, Height: {height}"); + + var engine = _renderingService.GetOrCreateEngine(sessionId); + + // 重置圖像緩存以強制重新渲染 + if (_lastFrameCache.ContainsKey(sessionId)) + { + _lastFrameCache.Remove(sessionId); + } + + engine.Resize(width, height); + + _logger.LogInformation($"Resize completed - SessionId: {sessionId}"); + return Task.CompletedTask; + } + + /// + /// 處理可見性變化 + /// + public Task HandleVisibilityChange(string visibilityState) + { + var sessionId = Context.ConnectionId; + _logger.LogInformation($"HandleVisibilityChange - SessionId: {sessionId}, State: {visibilityState}"); + + var engine = _renderingService.GetOrCreateEngine(sessionId); + engine.IsVisible = visibilityState == "visible"; + + return Task.CompletedTask; + } + + /// + /// 設置視圖 + /// + public Task SetView(string viewType) + { + var sessionId = Context.ConnectionId; + _logger.LogInformation($"SetView called - SessionId: {sessionId}, ViewType: {viewType}"); + + var engine = _renderingService.GetOrCreateEngine(sessionId); + + switch (viewType.ToLower()) + { + case "front": + engine.SetViewToFrontView(); + break; + case "back": + engine.SetViewToFrontView(); + engine.TurnBackView(); + break; + case "left": + engine.SetViewToRightView(); + engine.TurnBackView(); + break; + case "right": + engine.SetViewToRightView(); + break; + case "top": + engine.SetViewToTopView(); + break; + case "bottom": + engine.SetViewToTopView(); + engine.TurnBackView(); + break; + case "isometric": + engine.SetViewToIsometricView(); + break; + case "home": + engine.SetViewToHomeView(); + break; + default: + _logger.LogWarning($"Unknown view type: {viewType} - SessionId: {sessionId}"); + break; + } + + _sketchViewCache[sessionId] = engine.SketchView; + + return Task.CompletedTask; + } + + /// + /// 處理鍵盤按下事件 + /// + public Task HandleKeyDown(string key, string code, bool ctrlKey, bool shiftKey, bool altKey) + { + var sessionId = Context.ConnectionId; + _logger.LogInformation($"HandleKeyDown - SessionId: {sessionId}, Key: {key}, Code: {code}, Ctrl: {ctrlKey}, Shift: {shiftKey}, Alt: {altKey}"); + + var engine = _renderingService.GetOrCreateEngine(sessionId); + + // 使用 key 的 HashCode(與 Blazor 版本一致) + long keyCode = key.ToLower().GetHashCode(); + engine.KeyDown(keyCode); + + // 使用 KeyDownTransform 進行視圖變換 + engine.KeyDownTransform(keyCode, new key_table__transform_view_by_key_pressing_t() + { + HOME = "home".ToLower().GetHashCode(), + PAGE_UP = "pageup".ToLower().GetHashCode(), + PAGE_DOWN = "pagedown".ToLower().GetHashCode(), + F1 = "f1".ToLower().GetHashCode(), + F2 = "f2".ToLower().GetHashCode(), + F3 = "f3".ToLower().GetHashCode(), + F4 = "f4".ToLower().GetHashCode(), + SHIFT = "shift".ToLower().GetHashCode(), + ARROW_LEFT = "arrowleft".ToLower().GetHashCode(), + ARROW_RIGHT = "arrowright".ToLower().GetHashCode(), + ARROW_DOWN = "arrowdown".ToLower().GetHashCode(), + ARROW_UP = "arrowup".ToLower().GetHashCode() + }); + + _sketchViewCache[sessionId] = engine.SketchView; + + return Task.CompletedTask; + } + + /// + /// 處理鍵盤釋放事件 + /// + public Task HandleKeyUp(string key, string code, bool ctrlKey, bool shiftKey, bool altKey) + { + var sessionId = Context.ConnectionId; + _logger.LogInformation($"HandleKeyUp - SessionId: {sessionId}, Key: {key}, Code: {code}, Ctrl: {ctrlKey}, Shift: {shiftKey}, Alt: {altKey}"); + + var engine = _renderingService.GetOrCreateEngine(sessionId); + + long keyCode = key.ToLower().GetHashCode(); + engine.KeyUp(keyCode); + + return Task.CompletedTask; + } + + /// + /// 處理觸摸按下事件 + /// + public Task HandleTouchDown(int pointerId, double x, double y) + { + var sessionId = Context.ConnectionId; + _logger.LogInformation($"HandleTouchDown - SessionId: {sessionId}, PointerId: {pointerId}, X: {x}, Y: {y}"); + + var engine = _renderingService.GetOrCreateEngine(sessionId); + engine.TouchDown(pointerId, (int)x, (int)y); + + return Task.CompletedTask; + } + + /// + /// 處理觸摸移動事件 + /// + public Task HandleTouchMove(int pointerId, double x, double y) + { + var sessionId = Context.ConnectionId; + _logger.LogDebug($"HandleTouchMove - SessionId: {sessionId}, PointerId: {pointerId}, X: {x}, Y: {y}"); + + var engine = _renderingService.GetOrCreateEngine(sessionId); + engine.TouchMove(pointerId, (int)x, (int)y); + + return Task.CompletedTask; + } + + /// + /// 處理觸摸釋放事件 + /// + public Task HandleTouchUp(int pointerId) + { + var sessionId = Context.ConnectionId; + _logger.LogInformation($"HandleTouchUp - SessionId: {sessionId}, PointerId: {pointerId}"); + + var engine = _renderingService.GetOrCreateEngine(sessionId); + engine.TouchUp(pointerId); + + return Task.CompletedTask; + } + + /// + /// 獲取瀏覽器滾輪縮放比例 + /// + private double GetWheelScaling(string browserBrand) + { + switch (browserBrand.ToLower()) + { + case "firefox": + return 1.0; + case "chrome": + case "edge": + case "safari": + default: + return 0.01; + } + } + + /// + /// 客戶端斷開連接時清理資源 + /// + public override async Task OnDisconnectedAsync(Exception exception) + { + var sessionId = Context.ConnectionId; + + // 清理緩存 + _lastFrameCache.Remove(sessionId); + _sketchViewCache.Remove(sessionId); + + _renderingService.RemoveEngine(sessionId); + _logger.LogInformation($"Client disconnected: {sessionId}"); + await base.OnDisconnectedAsync(exception); + } + } +} \ No newline at end of file