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