From 7f512ca0315e74801307439947daff5dd0b651a9 Mon Sep 17 00:00:00 2001 From: "qsxft258@gmail.com" Date: Wed, 15 Apr 2026 00:02:30 +0900 Subject: [PATCH] =?UTF-8?q?Fix=20:=20=EC=A0=80=EC=82=AC=EC=96=91=20?= =?UTF-8?q?=ED=83=9C=EB=B8=94=EB=A6=BF=20=EC=9B=B9=EC=82=AC=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EB=B0=98=EC=98=81=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StreamingleDashboard/dashboard_script.txt | 4 +- .../Streamdeck/StreamDeckServerManager.cs | 147 ++++++++++++------ 2 files changed, 99 insertions(+), 52 deletions(-) diff --git a/Assets/Resources/StreamingleDashboard/dashboard_script.txt b/Assets/Resources/StreamingleDashboard/dashboard_script.txt index 237d666e1..ca560ccbe 100644 --- a/Assets/Resources/StreamingleDashboard/dashboard_script.txt +++ b/Assets/Resources/StreamingleDashboard/dashboard_script.txt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0013b010a51681ed769b4fbfb6b9792f2b0926627cbe42adb43d6e4851194144 -size 70967 +oid sha256:c22de38bea166ac9b7ba696261e8e2f2082d861d79f6c5d543e997e606120c69 +size 79122 diff --git a/Assets/Scripts/Streamdeck/StreamDeckServerManager.cs b/Assets/Scripts/Streamdeck/StreamDeckServerManager.cs index ce7242482..06f132eb2 100644 --- a/Assets/Scripts/Streamdeck/StreamDeckServerManager.cs +++ b/Assets/Scripts/Streamdeck/StreamDeckServerManager.cs @@ -47,6 +47,12 @@ public class StreamDeckServerManager : MonoBehaviour private float lastPingTime = 0f; private const float WsPingInterval = 5f; + // 브로드캐스트 coalescing: 같은 프레임에 여러 번 Notify*() 호출되어도 프레임 끝에 1회만 송신 + private bool pendingCameraNotify, pendingItemNotify, pendingEventNotify, pendingAvatarNotify; + // Origin 클라이언트 추적: ProcessMessage 실행 중인 서비스 (낙관적 UI 에코 억제용) + private StreamDeckService currentOriginator; + private string cameraOriginId, itemOriginId, eventOriginId, avatarOriginId; + // 카메라 프리뷰 내부 상태 private readonly List previewCameraPool = new List(); private RenderTexture previewRT; @@ -124,6 +130,9 @@ public class StreamDeckServerManager : MonoBehaviour } } + // 프레임 내 누적된 state 변경을 1회씩만 브로드캐스트 (저사양 클라이언트 렌더 부하 경감) + FlushPendingBroadcasts(); + // WebSocket 프로토콜 레벨 ping — WiFi 라우터의 유휴 연결 드롭 방지 if (Time.time - lastPingTime >= WsPingInterval) { @@ -274,7 +283,8 @@ public class StreamDeckServerManager : MonoBehaviour version = "1.0", data = new { - session_id = Guid.NewGuid().ToString(), + // session_id는 WebSocketBehavior.ID 사용 — NotifyXxx에서 origin_session으로 그대로 에코됨 + session_id = service.ID, message = "유니티 서버에 연결되었습니다!", camera_data = cameraManager.GetCameraListData(), current_camera = cameraManager.GetCurrentCameraState(), @@ -294,72 +304,102 @@ public class StreamDeckServerManager : MonoBehaviour #endregion #region Broadcast Notifications + // Notify*()는 상태 변경 플래그만 세우고, Update() 말미 FlushPendingBroadcasts에서 프레임당 1회만 실제 송신. + // 다수의 연쇄 변경(예: avatar 전환 → item reset → ... )이 프레임 내 발생해도 클라이언트에는 1회 렌더만 유발. public void NotifyCameraChanged() { - if (cameraManager == null) return; - - BroadcastJson(new - { - type = "camera_changed", - timestamp = DateTime.UtcNow.ToString("o"), - version = "1.0", - data = new - { - camera_data = cameraManager.GetCameraListData(), - current_camera = cameraManager.GetCurrentCameraState() - } - }); + pendingCameraNotify = true; + cameraOriginId = currentOriginator?.ID; } public void NotifyItemChanged() { - if (itemController == null) return; - - BroadcastJson(new - { - type = "item_changed", - timestamp = DateTime.UtcNow.ToString("o"), - version = "1.0", - data = new - { - item_data = itemController.GetItemListData(), - current_item = itemController.GetCurrentItemState() - } - }); + pendingItemNotify = true; + itemOriginId = currentOriginator?.ID; } public void NotifyEventChanged() { - if (eventController == null) return; - - BroadcastJson(new - { - type = "event_changed", - timestamp = DateTime.UtcNow.ToString("o"), - version = "1.0", - data = new - { - event_data = eventController.GetEventListData(), - current_event = eventController.GetCurrentEventState() - } - }); + pendingEventNotify = true; + eventOriginId = currentOriginator?.ID; } public void NotifyAvatarOutfitChanged() { - if (avatarOutfitController == null) return; + pendingAvatarNotify = true; + avatarOriginId = currentOriginator?.ID; + } - BroadcastJson(new + private void FlushPendingBroadcasts() + { + if (pendingCameraNotify && cameraManager != null) { - type = "avatar_outfit_changed", - timestamp = DateTime.UtcNow.ToString("o"), - version = "1.0", - data = new + BroadcastJson(new { - avatar_outfit_data = avatarOutfitController.GetAvatarOutfitListData(), - current_avatar_outfit = avatarOutfitController.GetCurrentAvatarOutfitState() - } - }); + type = "camera_changed", + origin_session = cameraOriginId, + timestamp = DateTime.UtcNow.ToString("o"), + version = "1.0", + data = new + { + camera_data = cameraManager.GetCameraListData(), + current_camera = cameraManager.GetCurrentCameraState() + } + }); + pendingCameraNotify = false; + cameraOriginId = null; + } + if (pendingItemNotify && itemController != null) + { + BroadcastJson(new + { + type = "item_changed", + origin_session = itemOriginId, + timestamp = DateTime.UtcNow.ToString("o"), + version = "1.0", + data = new + { + item_data = itemController.GetItemListData(), + current_item = itemController.GetCurrentItemState() + } + }); + pendingItemNotify = false; + itemOriginId = null; + } + if (pendingEventNotify && eventController != null) + { + BroadcastJson(new + { + type = "event_changed", + origin_session = eventOriginId, + timestamp = DateTime.UtcNow.ToString("o"), + version = "1.0", + data = new + { + event_data = eventController.GetEventListData(), + current_event = eventController.GetCurrentEventState() + } + }); + pendingEventNotify = false; + eventOriginId = null; + } + if (pendingAvatarNotify && avatarOutfitController != null) + { + BroadcastJson(new + { + type = "avatar_outfit_changed", + origin_session = avatarOriginId, + timestamp = DateTime.UtcNow.ToString("o"), + version = "1.0", + data = new + { + avatar_outfit_data = avatarOutfitController.GetAvatarOutfitListData(), + current_avatar_outfit = avatarOutfitController.GetCurrentAvatarOutfitState() + } + }); + pendingAvatarNotify = false; + avatarOriginId = null; + } } private void BroadcastJson(object data) @@ -375,6 +415,13 @@ public class StreamDeckServerManager : MonoBehaviour var message = JsonConvert.DeserializeObject>(messageData); string messageType = message.ContainsKey("type") ? message["type"].ToString() : null; + currentOriginator = service; + try { ProcessMessageInner(message, messageType, messageData, service); } + finally { currentOriginator = null; } + } + + private void ProcessMessageInner(Dictionary message, string messageType, string messageData, StreamDeckService service) + { switch (messageType) { // 카메라