diff --git a/Hi.Webapi.csproj b/Hi.Webapi.csproj
index c11c764..b04ed81 100644
--- a/Hi.Webapi.csproj
+++ b/Hi.Webapi.csproj
@@ -10,7 +10,7 @@
Hi.Webapi
$(AssemblyName)
HiNC webapi class library.
- 23
+ 24
3.1.$(VersionBuild)
HiAPI
Debug;Release
diff --git a/Hubs/RenderingHub.cs b/Hubs/RenderingHub.cs
index 3974e2d..d1f4194 100644
--- a/Hubs/RenderingHub.cs
+++ b/Hubs/RenderingHub.cs
@@ -7,14 +7,13 @@ using Hi.Webapi.Services;
namespace Hi.Webapi.Hubs
{
///
- /// SignalR Hub 用於處理渲染畫布的實時通信
+ /// SignalR Hub for Rendering.
///
public class RenderingHub : Hub
{
- private readonly Dictionary _lastFrameCache = [];
- private readonly Dictionary _sketchViewCache = [];
public RenderingService RenderingService { get; }
public ILogger Logger { get; }
+ Dictionary SketchViewCache { get; } = [];
public RenderingHub(RenderingService renderingService, ILogger logger)
{
@@ -27,19 +26,19 @@ namespace Hi.Webapi.Hubs
///
public async Task InitializeCanvas(int width, int height)
{
- var sessionId = Context.ConnectionId;
- Logger.LogInformation($"InitializeCanvas called - SessionId: {sessionId}, Width: {width}, Height: {height}");
+ var connectionId = Context.ConnectionId;
+ Logger.LogInformation($"InitializeCanvas called - ConnectionId: {connectionId}, Width: {width}, Height: {height}");
- var engine = RenderingService.GetOrCreateEngine(sessionId);
- Logger.LogInformation($"Engine created/retrieved - SessionId: {sessionId}");
+ var engine = RenderingService.GetOrCreateEngine(connectionId);
+ Logger.LogInformation($"Engine created/retrieved - ConnectionId: {connectionId}");
engine.Start(width, height);
- Logger.LogInformation($"Engine started with size {width}x{height} - SessionId: {sessionId}");
+ Logger.LogInformation($"Engine started with size {width}x{height} - ConnectionId: {connectionId}");
- // 在設置回調之前,先捕獲客戶端代理
- var clientProxy = Clients.Caller;
+ // 捕獲必要的參數,避免閉包問題
var logger = Logger;
var frameCount = 0;
+ var renderingService = RenderingService;
// 設置渲染回調
unsafe
@@ -51,11 +50,11 @@ namespace Hi.Webapi.Hubs
frameCount++;
if (frameCount <= 5) // 只記錄前5幀
- logger.LogInformation($"Frame {frameCount} rendered - Size: {w}x{h}, SessionId: {sessionId}");
+ logger.LogInformation($"Frame {frameCount} rendered - Size: {w}x{h}, ConnectionId: {connectionId}");
if (w <= 0 || h <= 0)
{
- logger.LogWarning($"Invalid image size: {w}x{h} - SessionId: {sessionId}");
+ logger.LogWarning($"Invalid image size: {w}x{h} - ConnectionId: {connectionId}");
return;
}
@@ -71,15 +70,6 @@ namespace Hi.Webapi.Hubs
rgba[i * 4 + 3] = bgra[i * 4 + 3];
}
- if (_lastFrameCache.TryGetValue(
- sessionId, out byte[] preImageRgba))
- {
- if (preImageRgba != null &&
- preImageRgba.SequenceEqual(rgba))
- return;
- }
- _lastFrameCache[sessionId] = rgba;
-
if (frameCount <= 5)
logger.LogInformation($"Frame {frameCount} sending - Size: {w}x{h}, Data: {rgba.Length} bytes");
@@ -98,8 +88,9 @@ namespace Hi.Webapi.Hubs
if (frameCount <= 5)
logger.LogInformation($"Compressed: {compressedRgbaArray.Length} bytes ({compressionRatio:F1}% of original)");
- // 發送壓縮數據到客戶端(注意:第二個參數應該是原始長度,不是壓縮後的長度)
- _ = clientProxy.SendAsync("ImageUpdate",
+ // 使用 RenderingService 的線程安全方法發送數據
+ _ = renderingService.SendImageToClient(
+ connectionId,
compressedRgbaArray,
rgba.Length,
w,
@@ -108,40 +99,35 @@ namespace Hi.Webapi.Hubs
{
if (t.IsFaulted)
{
- logger.LogError($"Failed to send frame: {t.Exception?.GetBaseException().Message} - SessionId: {sessionId}");
+ logger.LogError($"Failed to send frame: {t.Exception?.GetBaseException().Message} - ConnectionId: {connectionId}");
}
});
}
- catch (ObjectDisposedException)
- {
- // Hub 已經被釋放,忽略這個錯誤
- logger.LogDebug($"Hub已釋放,忽略渲染回調 - SessionId: {sessionId}");
- }
catch (Exception ex)
{
- logger.LogError(ex, $"渲染回調錯誤 - SessionId: {sessionId}");
+ logger.LogError(ex, $"渲染回調錯誤 - ConnectionId: {connectionId}");
}
};
}
// 設置初始視圖
- if (_sketchViewCache.TryGetValue(sessionId, out var cachedView))
+ if (SketchViewCache.TryGetValue(connectionId, out var cachedView))
{
engine.SketchView = cachedView;
}
else
{
engine.SetViewToHomeView();
- _sketchViewCache[sessionId] = engine.SketchView;
+ SketchViewCache[connectionId] = engine.SketchView;
}
- Logger.LogInformation($"View initialized - SessionId: {sessionId}");
+ Logger.LogInformation($"View initialized - ConnectionId: {connectionId}");
// 確保引擎可見並開始渲染
engine.IsVisible = true;
- Logger.LogInformation($"Engine visibility set to true - SessionId: {sessionId}");
+ Logger.LogInformation($"Engine visibility set to true - ConnectionId: {connectionId}");
- await Clients.Caller.SendAsync("CanvasInitialized", sessionId);
- Logger.LogInformation($"Canvas initialized for session: {sessionId}");
+ await Clients.Caller.SendAsync("CanvasInitialized", connectionId);
+ Logger.LogInformation($"Canvas initialized for session: {connectionId}");
}
///
@@ -149,10 +135,10 @@ namespace Hi.Webapi.Hubs
///
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 connectionId = Context.ConnectionId;
+ Logger.LogDebug($"HandleMouseMove - ConnectionId: {connectionId}, X: {x}, Y: {y}, ButtonMask: {buttonMask}");
- var engine = RenderingService.GetOrCreateEngine(sessionId);
+ var engine = RenderingService.GetOrCreateEngine(connectionId);
var p = new Vec2i((int)x, (int)y);
// 移動鼠標
@@ -167,7 +153,7 @@ namespace Hi.Webapi.Hubs
LEFT_BUTTON = 0,
RIGHT_BUTTON = 2
});
- _sketchViewCache[sessionId] = engine.SketchView;
+ SketchViewCache[connectionId] = engine.SketchView;
}
return Task.CompletedTask;
@@ -178,10 +164,10 @@ namespace Hi.Webapi.Hubs
///
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 connectionId = Context.ConnectionId;
+ Logger.LogInformation($"HandleMouseDown - ConnectionId: {connectionId}, X: {x}, Y: {y}, Button: {button}");
- var engine = RenderingService.GetOrCreateEngine(sessionId);
+ var engine = RenderingService.GetOrCreateEngine(connectionId);
engine.MouseButtonDown(button);
return Task.CompletedTask;
}
@@ -191,10 +177,10 @@ namespace Hi.Webapi.Hubs
///
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 connectionId = Context.ConnectionId;
+ Logger.LogInformation($"HandleMouseUp - ConnectionId: {connectionId}, X: {x}, Y: {y}, Button: {button}");
- var engine = RenderingService.GetOrCreateEngine(sessionId);
+ var engine = RenderingService.GetOrCreateEngine(connectionId);
engine.MouseButtonUp(button);
return Task.CompletedTask;
}
@@ -204,10 +190,10 @@ namespace Hi.Webapi.Hubs
///
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 connectionId = Context.ConnectionId;
+ Logger.LogInformation($"HandleMouseWheel - ConnectionId: {connectionId}, X: {x}, Y: {y}, DeltaX: {deltaX}, DeltaY: {deltaY}, Browser: {browserBrand}");
- var engine = RenderingService.GetOrCreateEngine(sessionId);
+ var engine = RenderingService.GetOrCreateEngine(connectionId);
// 根據瀏覽器類型獲取縮放比例
double scale = GetWheelScaling(browserBrand);
@@ -218,7 +204,7 @@ namespace Hi.Webapi.Hubs
engine.MouseWheelTransform(
(int)(deltaX * scale * 1000), -(int)(deltaY * scale * 1000), 0.1 / 1000);
- _sketchViewCache[sessionId] = engine.SketchView;
+ SketchViewCache[connectionId] = engine.SketchView;
return Task.CompletedTask;
}
@@ -228,20 +214,18 @@ namespace Hi.Webapi.Hubs
///
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);
- }
+ var connectionId = Context.ConnectionId;
+ Logger.LogInformation($"HandleResize - ConnectionId: {connectionId}, Width: {width}, Height: {height}");
+ var engine = RenderingService.GetOrCreateEngine(connectionId);
+ //since the resize from js is floating value,
+ //the resize event may raise with the same integer size.
+ //the canvas will be blank after resize event.
+ //ClearCache to trigger the rendering process.
+ engine.ClearCache();
engine.Resize(width, height);
- Logger.LogInformation($"Resize completed - SessionId: {sessionId}");
+ Logger.LogInformation($"Resize completed - ConnectionId: {connectionId}");
return Task.CompletedTask;
}
@@ -250,10 +234,10 @@ namespace Hi.Webapi.Hubs
///
public Task HandleVisibilityChange(string visibilityState)
{
- var sessionId = Context.ConnectionId;
- Logger.LogInformation($"HandleVisibilityChange - SessionId: {sessionId}, State: {visibilityState}");
+ var connectionId = Context.ConnectionId;
+ Logger.LogInformation($"HandleVisibilityChange - ConnectionId: {connectionId}, State: {visibilityState}");
- var engine = RenderingService.GetOrCreateEngine(sessionId);
+ var engine = RenderingService.GetOrCreateEngine(connectionId);
engine.IsVisible = visibilityState == "visible";
return Task.CompletedTask;
@@ -264,10 +248,10 @@ namespace Hi.Webapi.Hubs
///
public Task SetView(string viewType)
{
- var sessionId = Context.ConnectionId;
- Logger.LogInformation($"SetView called - SessionId: {sessionId}, ViewType: {viewType}");
+ var connectionId = Context.ConnectionId;
+ Logger.LogInformation($"SetView called - ConnectionId: {connectionId}, ViewType: {viewType}");
- var engine = RenderingService.GetOrCreateEngine(sessionId);
+ var engine = RenderingService.GetOrCreateEngine(connectionId);
switch (viewType.ToLower())
{
@@ -299,11 +283,11 @@ namespace Hi.Webapi.Hubs
engine.SetViewToHomeView();
break;
default:
- Logger.LogWarning($"Unknown view type: {viewType} - SessionId: {sessionId}");
+ Logger.LogWarning($"Unknown view type: {viewType} - ConnectionId: {connectionId}");
break;
}
- _sketchViewCache[sessionId] = engine.SketchView;
+ SketchViewCache[connectionId] = engine.SketchView;
return Task.CompletedTask;
}
@@ -313,10 +297,10 @@ namespace Hi.Webapi.Hubs
///
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 connectionId = Context.ConnectionId;
+ Logger.LogInformation($"HandleKeyDown - ConnectionId: {connectionId}, Key: {key}, Code: {code}, Ctrl: {ctrlKey}, Shift: {shiftKey}, Alt: {altKey}");
- var engine = RenderingService.GetOrCreateEngine(sessionId);
+ var engine = RenderingService.GetOrCreateEngine(connectionId);
// 使用 key 的 HashCode(與 Blazor 版本一致)
long keyCode = key.ToLower().GetHashCode();
@@ -339,7 +323,7 @@ namespace Hi.Webapi.Hubs
ARROW_UP = "arrowup".ToLower().GetHashCode()
});
- _sketchViewCache[sessionId] = engine.SketchView;
+ SketchViewCache[connectionId] = engine.SketchView;
return Task.CompletedTask;
}
@@ -349,10 +333,10 @@ namespace Hi.Webapi.Hubs
///
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 connectionId = Context.ConnectionId;
+ Logger.LogInformation($"HandleKeyUp - ConnectionId: {connectionId}, Key: {key}, Code: {code}, Ctrl: {ctrlKey}, Shift: {shiftKey}, Alt: {altKey}");
- var engine = RenderingService.GetOrCreateEngine(sessionId);
+ var engine = RenderingService.GetOrCreateEngine(connectionId);
long keyCode = key.ToLower().GetHashCode();
engine.KeyUp(keyCode);
@@ -365,10 +349,10 @@ namespace Hi.Webapi.Hubs
///
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 connectionId = Context.ConnectionId;
+ Logger.LogInformation($"HandleTouchDown - ConnectionId: {connectionId}, PointerId: {pointerId}, X: {x}, Y: {y}");
- var engine = RenderingService.GetOrCreateEngine(sessionId);
+ var engine = RenderingService.GetOrCreateEngine(connectionId);
engine.TouchDown(pointerId, (int)x, (int)y);
return Task.CompletedTask;
@@ -379,10 +363,10 @@ namespace Hi.Webapi.Hubs
///
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 connectionId = Context.ConnectionId;
+ Logger.LogDebug($"HandleTouchMove - ConnectionId: {connectionId}, PointerId: {pointerId}, X: {x}, Y: {y}");
- var engine = RenderingService.GetOrCreateEngine(sessionId);
+ var engine = RenderingService.GetOrCreateEngine(connectionId);
engine.TouchMove(pointerId, (int)x, (int)y);
return Task.CompletedTask;
@@ -393,10 +377,10 @@ namespace Hi.Webapi.Hubs
///
public Task HandleTouchUp(int pointerId)
{
- var sessionId = Context.ConnectionId;
- Logger.LogInformation($"HandleTouchUp - SessionId: {sessionId}, PointerId: {pointerId}");
+ var connectionId = Context.ConnectionId;
+ Logger.LogInformation($"HandleTouchUp - ConnectionId: {connectionId}, PointerId: {pointerId}");
- var engine = RenderingService.GetOrCreateEngine(sessionId);
+ var engine = RenderingService.GetOrCreateEngine(connectionId);
engine.TouchUp(pointerId);
return Task.CompletedTask;
@@ -424,14 +408,12 @@ namespace Hi.Webapi.Hubs
///
public override async Task OnDisconnectedAsync(Exception exception)
{
- var sessionId = Context.ConnectionId;
+ var connectionId = Context.ConnectionId;
- // 清理緩存
- _lastFrameCache.Remove(sessionId);
- _sketchViewCache.Remove(sessionId);
+ SketchViewCache.Remove(connectionId);
- RenderingService.RemoveEngine(sessionId);
- Logger.LogInformation($"Client disconnected: {sessionId}");
+ RenderingService.RemoveEngine(connectionId);
+ Logger.LogInformation($"Client disconnected: {connectionId}");
await base.OnDisconnectedAsync(exception);
}
}
diff --git a/Services/RenderingService.cs b/Services/RenderingService.cs
index 12d2e1e..c367638 100644
--- a/Services/RenderingService.cs
+++ b/Services/RenderingService.cs
@@ -1,6 +1,7 @@
using Hi.Disp;
using Hi.Geom;
-using Hi.Native;
+using Microsoft.AspNetCore.SignalR;
+using Hi.Webapi.Hubs;
using System.Collections.Concurrent;
namespace Hi.Webapi.Services
@@ -16,21 +17,23 @@ namespace Hi.Webapi.Services
///
public ConcurrentDictionary EngineDictionary { get; } = new();
ILogger Logger { get; }
+ IHubContext HubContext { get; }
private bool disposedValue;
- public RenderingService(ILogger logger)
+ public RenderingService(ILogger logger, IHubContext hubContext)
{
Logger = logger;
+ HubContext = hubContext;
}
- ///
- /// 創建或獲取一個 DispEngine 實例
- ///
- public DispEngine GetOrCreateEngine(string sessionId)
+ ///
+ /// Get Or Create an DispEngine.
+ ///
+ public DispEngine GetOrCreateEngine(string connectionId)
{
- return EngineDictionary.GetOrAdd(sessionId, id =>
+ return EngineDictionary.GetOrAdd(connectionId, id =>
{
- Logger.LogInformation($"創建新的 DispEngine,SessionId: {id}");
+ Logger.LogInformation($"Create new DispEngine,ConnectionId: {id}");
var engine = new DispEngine();
engine.BackgroundColor = new Vec3d(0.1, 0.1, 0.5);
engine.BackgroundOpacity = 0.1;
@@ -39,25 +42,38 @@ namespace Hi.Webapi.Services
}
///
- /// 移除指定的 DispEngine
+ /// Send image to specific client.
///
- public bool RemoveEngine(string sessionId)
+ public async Task SendImageToClient(string connectionId, byte[] compressedData, int originalLength, int width, int height)
{
- if (EngineDictionary.TryRemove(sessionId, out var engine))
+ try
{
- Logger.LogInformation($"移除 DispEngine,SessionId: {sessionId}");
+ await HubContext.Clients.Client(connectionId).SendAsync("ImageUpdate",
+ compressedData, originalLength, width, height);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, $"Failed to send image to client: {connectionId}");
+ }
+ }
+
+ ///
+ /// Remove DispEngine
+ ///
+ public bool RemoveEngine(string connectionId)
+ {
+ if (EngineDictionary.TryRemove(connectionId, out var engine))
+ {
+ Logger.LogInformation($"Remove DispEngine,ConnectionId: {connectionId}");
try
{
- // 停止渲染
engine.IsVisible = false;
-
- // 釋放資源
engine.Dispose();
}
catch (Exception ex)
{
- Logger.LogError(ex, $"清理 DispEngine 時發生錯誤,SessionId: {sessionId}");
+ Logger.LogError(ex, $"清理 DispEngine 時發生錯誤,ConnectionId: {connectionId}");
}
return true;
@@ -65,10 +81,10 @@ namespace Hi.Webapi.Services
return false;
}
- ///
- /// 獲取當前活動的引擎數量
- ///
- public int GetActiveEngineCount() => EngineDictionary.Count;
+ ///
+ /// Get Active Engine Count.
+ ///
+ public int GetActiveEngineCount() => EngineDictionary.Count;
///
protected virtual void Dispose(bool disposing)
{
@@ -85,7 +101,7 @@ namespace Hi.Webapi.Services
}
catch (Exception ex)
{
- Logger.LogError(ex, $"Dispose 時清理引擎錯誤,SessionId: {kvp.Key}");
+ Logger.LogError(ex, $"Dispose DispEngine Error,ConnectionId: {kvp.Key}");
}
}
EngineDictionary.Clear();
@@ -102,4 +118,4 @@ namespace Hi.Webapi.Services
}
}
-}
\ No newline at end of file
+}
\ No newline at end of file