From f6a60343877a8efcaffa4617c39373baa7959e1c Mon Sep 17 00:00:00 2001 From: "qsxft258@gmail.com" Date: Sun, 19 Apr 2026 19:11:14 +0900 Subject: [PATCH] =?UTF-8?q?Fix:=20OptiTrack=20=ED=94=8C=EB=9F=AC=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EB=9F=B0=ED=83=80=EC=9E=84=20=EC=95=88=EC=A0=84?= =?UTF-8?q?=EC=84=B1=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - m_dataDescs null 체크 추가: SkipDataDescriptions=true 시 NatNet 스레드 크래시 방지 - m_dataDescs를 NatNet 콜백에서 로컬 변수로 캡처: UpdateDefinitions() 중 참조 교체 레이스 방지 - m_assetIdToNameCache 클리어를 락으로 보호: NatNet 스레드와의 동시 접근 방지 - _EnterFrameDataUpdateLock/_ExitFrameDataUpdateLock을 internal+Obsolete로 변경: 데드락 위험 차단 - OptitrackRawDataReceiver를 FillBoneSnapshot 패턴으로 변경: torn read 방지 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Scripts/OptitrackRawDataReceiver.cs | 24 +++++++++--- .../Scripts/OptitrackStreamingClient.cs | 37 +++++++++++++------ 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackRawDataReceiver.cs b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackRawDataReceiver.cs index f8bac2697..82ea21bdb 100644 --- a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackRawDataReceiver.cs +++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackRawDataReceiver.cs @@ -10,6 +10,10 @@ public class OptitrackRawDataReceiver : MonoBehaviour private OptitrackSkeletonDefinition m_skeletonDef; private Dictionary m_lastBonePoses = new Dictionary(); + // FillBoneSnapshot 패턴: 락 안에서 데이터를 복사하여 torn read 방지 + private Dictionary m_snapshotPositions = new Dictionary(); + private Dictionary m_snapshotOrientations = new Dictionary(); + void Start() { if (StreamingClient == null) @@ -37,16 +41,24 @@ public class OptitrackRawDataReceiver : MonoBehaviour if (m_skeletonDef == null) return; } - // 최신 스켈레톤 상태 가져오기 - OptitrackSkeletonState skelState = StreamingClient.GetLatestSkeletonState(m_skeletonDef.Id); - if (skelState == null) return; + // FillBoneSnapshot으로 락 보호 하에 스냅샷 복사 (torn read 방지) + OptitrackHiResTimer.Timestamp ts; + if (!StreamingClient.FillBoneSnapshot(m_skeletonDef.Id, m_snapshotPositions, m_snapshotOrientations, out ts)) + return; - // 각 본의 원본 데이터 저장 + // 스냅샷에서 OptitrackPose로 변환하여 저장 foreach (var bone in m_skeletonDef.Bones) { - if (skelState.LocalBonePoses.TryGetValue(bone.Id, out OptitrackPose bonePose)) + if (m_snapshotPositions.TryGetValue(bone.Id, out Vector3 pos) && + m_snapshotOrientations.TryGetValue(bone.Id, out Quaternion ori)) { - m_lastBonePoses[bone.Id] = bonePose; + if (!m_lastBonePoses.TryGetValue(bone.Id, out OptitrackPose existingPose)) + { + existingPose = new OptitrackPose(); + m_lastBonePoses[bone.Id] = existingPose; + } + existingPose.Position = pos; + existingPose.Orientation = ori; } } } diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackStreamingClient.cs b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackStreamingClient.cs index d80092596..5f544b3a4 100644 --- a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackStreamingClient.cs +++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackStreamingClient.cs @@ -1269,7 +1269,12 @@ public class OptitrackStreamingClient : MonoBehaviour m_skeletonDefinitions.Clear(); m_tmarkersetDefinitions.Clear(); m_mirrorBoneIdMaps.Clear(); // 스켈레톤 정의 변경 시 mirror map 캐시 무효화 - m_assetIdToNameCache.Clear(); // assetID→이름 캐시 무효화 + + // NatNet 스레드가 접근하는 캐시는 락으로 보호하여 레이스 방지 + lock (m_frameDataUpdateLock) + { + m_assetIdToNameCache.Clear(); + } // ---------------------------------- // - Translate Rigid Body Definitions @@ -1880,19 +1885,19 @@ public class OptitrackStreamingClient : MonoBehaviour // ----------------------------------------------------- // - Update trained markerset // trained markerset added // ---------------------------------------------------- - //Int32 frameTMarkersetCount = m_dataDescs.AssetDescriptions.Count; - /*result = NaturalPoint.NatNetLib.NativeMethods.NatNet_Frame_GetTMarkersetCount(pFrame, out frameTMarkersetCount); - NatNetException.ThrowIfNotOK(result, "NatNet_Frame_GetTMarkersetCount failed.");*/ - - for (int tmarkIdx = 0; tmarkIdx < m_dataDescs.AssetDescriptions.Count; ++tmarkIdx) + // m_dataDescs를 로컬 변수로 캡처: UpdateDefinitions()(메인 스레드)가 참조를 교체해도 안전 + // null 체크: SkipDataDescriptions=true 또는 UpdateDefinitions() 미완료/실패 시 크래시 방지 + var dataDescsSnapshot = m_dataDescs; + if (dataDescsSnapshot != null && dataDescsSnapshot.AssetDescriptions != null) + for (int tmarkIdx = 0; tmarkIdx < dataDescsSnapshot.AssetDescriptions.Count; ++tmarkIdx) { - Int32 tmarkersetId = m_dataDescs.AssetDescriptions[tmarkIdx].AssetID; + Int32 tmarkersetId = dataDescsSnapshot.AssetDescriptions[tmarkIdx].AssetID; // Ensure we have a state corresponding to this tmarkerset ID. OptitrackTMarkersetState tmarkState = GetOrCreateTMarkersetState(tmarkersetId); // TMarkerset 정의 검색을 본 루프 밖에서 1회만 수행 (기존: 매 본마다 선형 탐색) - Int32 tmarkRbCount = m_dataDescs.AssetDescriptions[tmarkIdx].RigidBodyCount; + Int32 tmarkRbCount = dataDescsSnapshot.AssetDescriptions[tmarkIdx].RigidBodyCount; OptitrackTMarkersetDefinition tmarkDef = GetTMarkersetDefinitionById(tmarkersetId); if (tmarkDef == null) { @@ -1940,7 +1945,7 @@ public class OptitrackStreamingClient : MonoBehaviour // -------------------------------------------- // - Update trained markerset markers // -------------------------------------------- - Int32 tmarkMarkerCount = m_dataDescs.AssetDescriptions[tmarkIdx].MarkerCount; + Int32 tmarkMarkerCount = dataDescsSnapshot.AssetDescriptions[tmarkIdx].MarkerCount; /*result = NaturalPoint.NatNetLib.NativeMethods.NatNet_Frame_TMarkerset_GetMarkerCount(pFrame, tmarkIdx, out tmarkMarkerCount); NatNetException.ThrowIfNotOK(result, "NatNet_Frame_TMarkerset_GetMarkerCount failed.");*/ //Debug.Log("tmark marker count: " + tmarkMarkerCount); // working finally @@ -2398,13 +2403,21 @@ public class OptitrackStreamingClient : MonoBehaviour } - public void _EnterFrameDataUpdateLock() + /// + /// 내부 프레임 데이터 락 진입. 반드시 try-finally 패턴으로 _ExitFrameDataUpdateLock()과 쌍으로 사용하세요. + /// Exit 없이 호출하면 NatNet 스레드가 영구 데드락됩니다. + /// + [System.Obsolete("직접 락 조작 대신 FillBoneSnapshot() 등 스레드 안전 API를 사용하세요.")] + internal void _EnterFrameDataUpdateLock() { Monitor.Enter( m_frameDataUpdateLock ); } - - public void _ExitFrameDataUpdateLock() + /// + /// 내부 프레임 데이터 락 해제. 반드시 _EnterFrameDataUpdateLock()과 쌍으로 사용하세요. + /// + [System.Obsolete("직접 락 조작 대신 FillBoneSnapshot() 등 스레드 안전 API를 사용하세요.")] + internal void _ExitFrameDataUpdateLock() { Monitor.Exit( m_frameDataUpdateLock ); }