tune design pattern of IProjectService
This commit is contained in:
parent
feb25e5871
commit
b87779ade7
@ -6,453 +6,443 @@ using Hi.Webapi.Services;
|
||||
|
||||
namespace Hi.Webapi.Hubs
|
||||
{
|
||||
/// <summary>
|
||||
/// SignalR Hub 用於處理渲染畫布的實時通信
|
||||
/// </summary>
|
||||
public class RenderingHub : Hub
|
||||
{
|
||||
private readonly RenderingService _renderingService;
|
||||
private readonly ILogger<RenderingHub> _logger;
|
||||
private readonly Dictionary<string, byte[]> _lastFrameCache = [];
|
||||
private readonly Dictionary<string, DateTime> _lastFrameTime = [];
|
||||
private readonly Dictionary<string, Mat4d> _sketchViewCache = [];
|
||||
/// <summary>
|
||||
/// SignalR Hub 用於處理渲染畫布的實時通信
|
||||
/// </summary>
|
||||
public class RenderingHub : Hub
|
||||
{
|
||||
private readonly RenderingService _renderingService;
|
||||
private readonly ILogger<RenderingHub> _logger;
|
||||
private readonly Dictionary<string, byte[]> _lastFrameCache = [];
|
||||
private readonly Dictionary<string, Mat4d> _sketchViewCache = [];
|
||||
|
||||
public RenderingHub(RenderingService renderingService, ILogger<RenderingHub> logger)
|
||||
{
|
||||
_renderingService = renderingService;
|
||||
_logger = logger;
|
||||
}
|
||||
public RenderingHub(RenderingService renderingService, ILogger<RenderingHub> logger)
|
||||
{
|
||||
_renderingService = renderingService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 客戶端連接時初始化渲染引擎
|
||||
/// </summary>
|
||||
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;
|
||||
/// <summary>
|
||||
/// 客戶端連接時初始化渲染引擎
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 處理鼠標移動事件
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 處理鼠標按下事件
|
||||
/// </summary>
|
||||
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}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 處理鼠標釋放事件
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 處理鼠標滾輪事件
|
||||
/// </summary>
|
||||
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];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 處理窗口大小變化
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 處理可見性變化
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 設置視圖
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 處理鍵盤按下事件
|
||||
/// </summary>
|
||||
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();
|
||||
|
||||
/// <summary>
|
||||
/// 處理鍵盤釋放事件
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 處理觸摸按下事件
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 處理觸摸移動事件
|
||||
/// </summary>
|
||||
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)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 處理觸摸釋放事件
|
||||
/// </summary>
|
||||
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}");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 獲取瀏覽器滾輪縮放比例
|
||||
/// </summary>
|
||||
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}");
|
||||
|
||||
/// <summary>
|
||||
/// 客戶端斷開連接時清理資源
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 確保引擎可見並開始渲染
|
||||
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}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 處理鼠標移動事件
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 處理鼠標按下事件
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 處理鼠標釋放事件
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 處理鼠標滾輪事件
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 處理窗口大小變化
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 處理可見性變化
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 設置視圖
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 處理鍵盤按下事件
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 處理鍵盤釋放事件
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 處理觸摸按下事件
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 處理觸摸移動事件
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 處理觸摸釋放事件
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 獲取瀏覽器滾輪縮放比例
|
||||
/// </summary>
|
||||
private double GetWheelScaling(string browserBrand)
|
||||
{
|
||||
switch (browserBrand.ToLower())
|
||||
{
|
||||
case "firefox":
|
||||
return 1.0;
|
||||
case "chrome":
|
||||
case "edge":
|
||||
case "safari":
|
||||
default:
|
||||
return 0.01;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 客戶端斷開連接時清理資源
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user