Fix: OptiTrack 플러그인 런타임 안전성 강화
- 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) <noreply@anthropic.com>
This commit is contained in:
parent
5aa805e16a
commit
f6a6034387
@ -10,6 +10,10 @@ public class OptitrackRawDataReceiver : MonoBehaviour
|
|||||||
private OptitrackSkeletonDefinition m_skeletonDef;
|
private OptitrackSkeletonDefinition m_skeletonDef;
|
||||||
private Dictionary<int, OptitrackPose> m_lastBonePoses = new Dictionary<int, OptitrackPose>();
|
private Dictionary<int, OptitrackPose> m_lastBonePoses = new Dictionary<int, OptitrackPose>();
|
||||||
|
|
||||||
|
// FillBoneSnapshot 패턴: 락 안에서 데이터를 복사하여 torn read 방지
|
||||||
|
private Dictionary<int, Vector3> m_snapshotPositions = new Dictionary<int, Vector3>();
|
||||||
|
private Dictionary<int, Quaternion> m_snapshotOrientations = new Dictionary<int, Quaternion>();
|
||||||
|
|
||||||
void Start()
|
void Start()
|
||||||
{
|
{
|
||||||
if (StreamingClient == null)
|
if (StreamingClient == null)
|
||||||
@ -37,16 +41,24 @@ public class OptitrackRawDataReceiver : MonoBehaviour
|
|||||||
if (m_skeletonDef == null) return;
|
if (m_skeletonDef == null) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 최신 스켈레톤 상태 가져오기
|
// FillBoneSnapshot으로 락 보호 하에 스냅샷 복사 (torn read 방지)
|
||||||
OptitrackSkeletonState skelState = StreamingClient.GetLatestSkeletonState(m_skeletonDef.Id);
|
OptitrackHiResTimer.Timestamp ts;
|
||||||
if (skelState == null) return;
|
if (!StreamingClient.FillBoneSnapshot(m_skeletonDef.Id, m_snapshotPositions, m_snapshotOrientations, out ts))
|
||||||
|
return;
|
||||||
|
|
||||||
// 각 본의 원본 데이터 저장
|
// 스냅샷에서 OptitrackPose로 변환하여 저장
|
||||||
foreach (var bone in m_skeletonDef.Bones)
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1269,7 +1269,12 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
m_skeletonDefinitions.Clear();
|
m_skeletonDefinitions.Clear();
|
||||||
m_tmarkersetDefinitions.Clear();
|
m_tmarkersetDefinitions.Clear();
|
||||||
m_mirrorBoneIdMaps.Clear(); // 스켈레톤 정의 변경 시 mirror map 캐시 무효화
|
m_mirrorBoneIdMaps.Clear(); // 스켈레톤 정의 변경 시 mirror map 캐시 무효화
|
||||||
m_assetIdToNameCache.Clear(); // assetID→이름 캐시 무효화
|
|
||||||
|
// NatNet 스레드가 접근하는 캐시는 락으로 보호하여 레이스 방지
|
||||||
|
lock (m_frameDataUpdateLock)
|
||||||
|
{
|
||||||
|
m_assetIdToNameCache.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// - Translate Rigid Body Definitions
|
// - Translate Rigid Body Definitions
|
||||||
@ -1880,19 +1885,19 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
// - Update trained markerset // trained markerset added
|
// - Update trained markerset // trained markerset added
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
//Int32 frameTMarkersetCount = m_dataDescs.AssetDescriptions.Count;
|
// m_dataDescs를 로컬 변수로 캡처: UpdateDefinitions()(메인 스레드)가 참조를 교체해도 안전
|
||||||
/*result = NaturalPoint.NatNetLib.NativeMethods.NatNet_Frame_GetTMarkersetCount(pFrame, out frameTMarkersetCount);
|
// null 체크: SkipDataDescriptions=true 또는 UpdateDefinitions() 미완료/실패 시 크래시 방지
|
||||||
NatNetException.ThrowIfNotOK(result, "NatNet_Frame_GetTMarkersetCount failed.");*/
|
var dataDescsSnapshot = m_dataDescs;
|
||||||
|
if (dataDescsSnapshot != null && dataDescsSnapshot.AssetDescriptions != null)
|
||||||
for (int tmarkIdx = 0; tmarkIdx < m_dataDescs.AssetDescriptions.Count; ++tmarkIdx)
|
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.
|
// Ensure we have a state corresponding to this tmarkerset ID.
|
||||||
OptitrackTMarkersetState tmarkState = GetOrCreateTMarkersetState(tmarkersetId);
|
OptitrackTMarkersetState tmarkState = GetOrCreateTMarkersetState(tmarkersetId);
|
||||||
|
|
||||||
// TMarkerset 정의 검색을 본 루프 밖에서 1회만 수행 (기존: 매 본마다 선형 탐색)
|
// TMarkerset 정의 검색을 본 루프 밖에서 1회만 수행 (기존: 매 본마다 선형 탐색)
|
||||||
Int32 tmarkRbCount = m_dataDescs.AssetDescriptions[tmarkIdx].RigidBodyCount;
|
Int32 tmarkRbCount = dataDescsSnapshot.AssetDescriptions[tmarkIdx].RigidBodyCount;
|
||||||
OptitrackTMarkersetDefinition tmarkDef = GetTMarkersetDefinitionById(tmarkersetId);
|
OptitrackTMarkersetDefinition tmarkDef = GetTMarkersetDefinitionById(tmarkersetId);
|
||||||
if (tmarkDef == null)
|
if (tmarkDef == null)
|
||||||
{
|
{
|
||||||
@ -1940,7 +1945,7 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
// --------------------------------------------
|
// --------------------------------------------
|
||||||
// - Update trained markerset markers
|
// - 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);
|
/*result = NaturalPoint.NatNetLib.NativeMethods.NatNet_Frame_TMarkerset_GetMarkerCount(pFrame, tmarkIdx, out tmarkMarkerCount);
|
||||||
NatNetException.ThrowIfNotOK(result, "NatNet_Frame_TMarkerset_GetMarkerCount failed.");*/
|
NatNetException.ThrowIfNotOK(result, "NatNet_Frame_TMarkerset_GetMarkerCount failed.");*/
|
||||||
//Debug.Log("tmark marker count: " + tmarkMarkerCount); // working finally
|
//Debug.Log("tmark marker count: " + tmarkMarkerCount); // working finally
|
||||||
@ -2398,13 +2403,21 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void _EnterFrameDataUpdateLock()
|
/// <summary>
|
||||||
|
/// 내부 프레임 데이터 락 진입. 반드시 try-finally 패턴으로 _ExitFrameDataUpdateLock()과 쌍으로 사용하세요.
|
||||||
|
/// Exit 없이 호출하면 NatNet 스레드가 영구 데드락됩니다.
|
||||||
|
/// </summary>
|
||||||
|
[System.Obsolete("직접 락 조작 대신 FillBoneSnapshot() 등 스레드 안전 API를 사용하세요.")]
|
||||||
|
internal void _EnterFrameDataUpdateLock()
|
||||||
{
|
{
|
||||||
Monitor.Enter( m_frameDataUpdateLock );
|
Monitor.Enter( m_frameDataUpdateLock );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
public void _ExitFrameDataUpdateLock()
|
/// 내부 프레임 데이터 락 해제. 반드시 _EnterFrameDataUpdateLock()과 쌍으로 사용하세요.
|
||||||
|
/// </summary>
|
||||||
|
[System.Obsolete("직접 락 조작 대신 FillBoneSnapshot() 등 스레드 안전 API를 사용하세요.")]
|
||||||
|
internal void _ExitFrameDataUpdateLock()
|
||||||
{
|
{
|
||||||
Monitor.Exit( m_frameDataUpdateLock );
|
Monitor.Exit( m_frameDataUpdateLock );
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user