Fix: 초기 연결 실패 시에도 자동 재시도 (간헐적 NatNetError_Network 대응)
기존 AutoReconnect는 첫 프레임 수신 후 끊긴 경우(케이스 C)에만 작동했음. 이제 케이스 A(Connect 자체 throw)와 케이스 B(Connect 성공했으나 첫 프레임 미수신)도 백오프 재시도로 자동 복구함. - InitialConnectWithRetry 코루틴: 최대 10회, 1→2→3→5→10초 백오프 - CleanupClient 헬퍼: 케이스 B 재시도 시 m_client 안전 정리 - AutoReconnect=true이면 OnEnable이 InitialConnectWithRetry로 분기 - AutoReconnect=false이면 기존 동작 유지 (1회 시도, 실패 시 종료) 효과: Motive 소켓 바인딩 지연, Unity가 Motive보다 먼저 켜짐, 짧은 네트워크 블립 등 일시적 원인의 간헐적 연결 실패가 사용자 개입 없이 자동 복구됨. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3b642416af
commit
f5b6690aee
@ -349,7 +349,7 @@ public class OptitrackStreamingClient : MonoBehaviour
|
||||
[Tooltip("Skips getting data descriptions. Skeletons will not work with this feature turned on, but it will reduce network usage with a large number of rigid bodies.")]
|
||||
public bool SkipDataDescriptions = false;
|
||||
|
||||
[Tooltip("스트리밍이 끊어졌을 때 최대 5회까지 자동으로 재연결을 시도합니다.")]
|
||||
[Tooltip("초기 연결 실패 시 최대 10회 백오프 재시도, 스트리밍이 끊어졌을 때도 최대 5회까지 자동으로 재연결을 시도합니다.")]
|
||||
public bool AutoReconnect = true;
|
||||
|
||||
[Tooltip("Changes to the version of Natnet used by the server")]
|
||||
@ -1559,9 +1559,12 @@ public class OptitrackStreamingClient : MonoBehaviour
|
||||
// dev.ChannelNames is a fixed-size 32 array (NatNet C# wrapper limit).
|
||||
// Plugin can register devices with > 32 channels (Motive stores them all),
|
||||
// but only the first 32 names survive the wrapper marshaling.
|
||||
// Clamp ChannelCount to the marshaled name array's actual length to avoid
|
||||
// IndexOutOfRangeException on the loop below.
|
||||
int actualNameCount = (dev.ChannelNames != null)
|
||||
// IMPORTANT: ChannelCount is preserved as the full count reported by Motive
|
||||
// (could be > 32, e.g. iFacialMocap primary = 54). Downstream consumers
|
||||
// need this to match the wire frame's nCh (which may also be > 32 if Motive
|
||||
// doesn't truncate broadcast). ChannelNames is clamped to the marshaled
|
||||
// array length to avoid IndexOutOfRangeException.
|
||||
int marshalNameCount = (dev.ChannelNames != null)
|
||||
? Math.Min(dev.ChannelCount, dev.ChannelNames.Length)
|
||||
: 0;
|
||||
|
||||
@ -1572,11 +1575,11 @@ public class OptitrackStreamingClient : MonoBehaviour
|
||||
SerialNumber = dev.SerialNo,
|
||||
DeviceType = dev.DeviceType,
|
||||
ChannelDataType = dev.ChannelDataType,
|
||||
ChannelCount = actualNameCount,
|
||||
ChannelNames = new List<string>(actualNameCount),
|
||||
ChannelCount = dev.ChannelCount,
|
||||
ChannelNames = new List<string>(marshalNameCount),
|
||||
};
|
||||
|
||||
for (int i = 0; i < actualNameCount; ++i)
|
||||
for (int i = 0; i < marshalNameCount; ++i)
|
||||
{
|
||||
deviceDef.ChannelNames.Add(dev.ChannelNames[i]);
|
||||
}
|
||||
@ -1643,7 +1646,81 @@ public class OptitrackStreamingClient : MonoBehaviour
|
||||
void OnEnable()
|
||||
{
|
||||
m_receivedFrameSinceConnect = false;
|
||||
StartCoroutine( ConnectCoroutine() );
|
||||
// AutoReconnect가 켜져있으면 초기 연결 실패 시에도 자동 재시도
|
||||
if (AutoReconnect)
|
||||
StartCoroutine( InitialConnectWithRetry() );
|
||||
else
|
||||
StartCoroutine( ConnectCoroutine() );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 초기 연결을 백오프와 함께 자동 재시도합니다.
|
||||
/// 처리하는 케이스:
|
||||
/// A) NatNet_Client_Connect 자체가 throw (NatNetError_Network 등) — Motive PC 미응답, 방화벽, 잘못된 IP 등
|
||||
/// B) Connect는 성공했으나 첫 프레임이 영영 안 옴 — Motive Streaming 미활성화, Multicast 라우팅 차단 등
|
||||
/// 케이스 C(연결 후 프레임 끊김)는 기존 CheckConnectionHealth+ReconnectCoroutine에서 처리됨.
|
||||
/// </summary>
|
||||
private System.Collections.IEnumerator InitialConnectWithRetry()
|
||||
{
|
||||
const int maxAttempts = 10;
|
||||
// 백오프: 1, 2, 3, 5, 10, 10, ... 초 (총 약 60초간 시도)
|
||||
float[] retryDelays = { 1f, 2f, 3f, 5f, 10f };
|
||||
|
||||
for (int attempt = 1; attempt <= maxAttempts; attempt++)
|
||||
{
|
||||
// ConnectCoroutine 실행
|
||||
yield return StartCoroutine(ConnectCoroutine());
|
||||
|
||||
// 케이스 A: m_client가 null이면 Connect() 자체가 실패한 것
|
||||
if (m_client != null)
|
||||
{
|
||||
// 케이스 B 확인: Connect는 성공 — 첫 프레임 도착까지 5초 대기
|
||||
float deadline = Time.realtimeSinceStartup + 5.0f;
|
||||
while (!m_receivedFrameSinceConnect && Time.realtimeSinceStartup < deadline)
|
||||
yield return null;
|
||||
|
||||
if (m_receivedFrameSinceConnect)
|
||||
{
|
||||
if (attempt > 1)
|
||||
Debug.Log(string.Format("{0}: 초기 연결 성공 (시도 {1}/{2})", GetType().FullName, attempt, maxAttempts), this);
|
||||
yield break; // 성공
|
||||
}
|
||||
|
||||
// 케이스 B: 연결됐지만 프레임 미수신 — 클라이언트 정리 후 재시도
|
||||
Debug.LogWarning(string.Format("{0}: 시도 {1}/{2} — 연결됐으나 프레임 미수신. 정리 후 재시도.", GetType().FullName, attempt, maxAttempts), this);
|
||||
CleanupClient();
|
||||
}
|
||||
|
||||
if (attempt == maxAttempts)
|
||||
{
|
||||
Debug.LogError(string.Format("{0}: {1}회 초기 연결 시도 모두 실패. 인스펙터에서 'Reconnect' 또는 컴포넌트 토글로 수동 재시도하세요.", GetType().FullName, maxAttempts), this);
|
||||
yield break;
|
||||
}
|
||||
|
||||
float delay = retryDelays[Mathf.Min(attempt - 1, retryDelays.Length - 1)];
|
||||
Debug.Log(string.Format("{0}: 초기 연결 시도 {1}/{2} 실패 — {3}초 후 재시도", GetType().FullName, attempt, maxAttempts, delay), this);
|
||||
yield return new WaitForSeconds(delay);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// m_client와 관련 코루틴/이벤트를 안전하게 정리합니다.
|
||||
/// InitialConnectWithRetry의 케이스 B 재시도 시 사용.
|
||||
/// </summary>
|
||||
private void CleanupClient()
|
||||
{
|
||||
if (m_connectionHealthCoroutine != null)
|
||||
{
|
||||
StopCoroutine(m_connectionHealthCoroutine);
|
||||
m_connectionHealthCoroutine = null;
|
||||
}
|
||||
if (m_client != null)
|
||||
{
|
||||
try { m_client.NativeFrameReceived -= OnNatNetFrameReceived; } catch (Exception) { }
|
||||
try { m_client.Disconnect(); } catch (Exception) { }
|
||||
try { m_client.Dispose(); } catch (Exception) { }
|
||||
m_client = null;
|
||||
}
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator ConnectCoroutine()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user