From f3dedd52937701be30e96a337a0401d1ee25e6d9 Mon Sep 17 00:00:00 2001 From: Toys0125 Date: Fri, 6 Mar 2026 21:16:01 -0600 Subject: [PATCH 1/2] Fixes InvalidOperation Exceptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Always complete/apply remote face jobs in render callback, even when local player isn’t ready: BasisEventDriver.cs:231, BasisEventDriver.cs:234 * Chain remote face scheduling to prior handle: BasisRemoteFaceManagement.cs:93 * Chain SteamAudio gather jobs to prior frame and complete pending gather jobs before early returns/resizes/dispose: SteamAudioManager.cs:552, SteamAudioManager.cs:580, SteamAudioManager.cs:1016, SteamAudioManager.cs:1032, SteamAudioManager.cs:1055 * Harden uLipSync buffer/job sequencing (complete stale job before reschedule, lock buffer swap/write): BasisUlipSync.cs:55, BasisUlipSync.cs:144, BasisUlipSync.cs:164, BasisUlipSync.cs:451, BasisUlipSync.cs:460 --- .../com.basis.eventdriver/BasisEventDriver.cs | 5 +- .../Networking/BasisRemoteFaceManagement.cs | 2 +- .../com.hecomi.ulipsync/BasisUlipSync.cs | 61 ++++++++++++------- .../Runtime/SteamAudioManager.cs | 24 +++++--- 4 files changed, 59 insertions(+), 33 deletions(-) diff --git a/Basis/Packages/com.basis.eventdriver/BasisEventDriver.cs b/Basis/Packages/com.basis.eventdriver/BasisEventDriver.cs index 4a322bf24..15e6af9d0 100644 --- a/Basis/Packages/com.basis.eventdriver/BasisEventDriver.cs +++ b/Basis/Packages/com.basis.eventdriver/BasisEventDriver.cs @@ -230,10 +230,12 @@ public void LateUpdate() /// private void OnBeforeRender() { + // Ensure remote face jobs are completed/applied even before local player is ready. + BasisRemoteFaceManagement.Apply(); //apply blendshapes + if (BasisLocalPlayer.PlayerReady) { BasisLocalPlayer.Instance.SimulateOnRender(); - BasisRemoteFaceManagement.Apply(); //apply blendshapes BasisLocalCameraDriver.Instance.microphoneIconDriver.Simulate(DeltaTime); //update microphone icon } } @@ -275,3 +277,4 @@ public void OnDrawGizmosSelected() #endif } } + diff --git a/Basis/Packages/com.basis.framework/Networking/BasisRemoteFaceManagement.cs b/Basis/Packages/com.basis.framework/Networking/BasisRemoteFaceManagement.cs index cbc2c1285..502abf207 100644 --- a/Basis/Packages/com.basis.framework/Networking/BasisRemoteFaceManagement.cs +++ b/Basis/Packages/com.basis.framework/Networking/BasisRemoteFaceManagement.cs @@ -90,7 +90,7 @@ public static void Simulate(double t,float dt) blinkOut = blinkOut, }; - handle = job.Schedule(count, BatchSize); + handle = job.Schedule(count, BatchSize, handle); } public static void Apply() diff --git a/Basis/Packages/com.hecomi.ulipsync/BasisUlipSync.cs b/Basis/Packages/com.hecomi.ulipsync/BasisUlipSync.cs index d866195f4..1d4ece47e 100644 --- a/Basis/Packages/com.hecomi.ulipsync/BasisUlipSync.cs +++ b/Basis/Packages/com.hecomi.ulipsync/BasisUlipSync.cs @@ -52,6 +52,7 @@ public unsafe class BasisUlipSync BasisDctPlan _dctPlan; float[] _lastApplied; + readonly object _bufferSwapLock = new object(); public struct BlendMap { @@ -142,6 +143,13 @@ public void Execute() public void Simulate(float DeltaTime) { + if (HasJob) + { + _jobHandle.Complete(); + _jobHandle = default; + HasJob = false; + } + if (Interlocked.Exchange(ref _isDataReceived, 0) != 1) return; if (!_allocated) return; @@ -149,16 +157,23 @@ public void Simulate(float DeltaTime) if (!BasisUlipSyncDriver.IsInitialized) return; - int oldActive = _activeInputBuffer; - int newActive = oldActive ^ 1; + int oldActive; + int frozenStartIndex; + NativeArray frozenInput; + + lock (_bufferSwapLock) + { + oldActive = _activeInputBuffer; + int newActive = oldActive ^ 1; - Volatile.Write(ref _activeInputBuffer, newActive); + Volatile.Write(ref _activeInputBuffer, newActive); - int frozenStartIndex = oldActive == 0 - ? Volatile.Read(ref _writeIndexA) - : Volatile.Read(ref _writeIndexB); + frozenStartIndex = oldActive == 0 + ? Volatile.Read(ref _writeIndexA) + : Volatile.Read(ref _writeIndexB); - NativeArray frozenInput = oldActive == 0 ? _inputA : _inputB; + frozenInput = oldActive == 0 ? _inputA : _inputB; + } byte normalizeScores = (byte)0; @@ -236,6 +251,7 @@ public void Apply() HasJob = false; _jobHandle.Complete(); + _jobHandle = default; if (_mfccForOther.IsCreated && _mfcc.IsCreated) { @@ -441,24 +457,27 @@ public void OnDataReceived(float[] input, int channels, int length) int ch = math.max(channels, 1); - int buf = Volatile.Read(ref _activeInputBuffer); - NativeArray dstArr = (buf == 0) ? _inputA : _inputB; - - float* dst = (float*)NativeArrayUnsafeUtility.GetUnsafePtr(dstArr); - - fixed (float* src = input) + lock (_bufferSwapLock) { - int w = (buf == 0) ? Volatile.Read(ref _writeIndexA) : Volatile.Read(ref _writeIndexB); + int buf = Volatile.Read(ref _activeInputBuffer); + NativeArray dstArr = (buf == 0) ? _inputA : _inputB; + + float* dst = (float*)NativeArrayUnsafeUtility.GetUnsafePtr(dstArr); - for (int s = 0; s < length; s += ch) + fixed (float* src = input) { - dst[w] = src[s]; - w++; - if (w == cap) w = 0; - } + int w = (buf == 0) ? Volatile.Read(ref _writeIndexA) : Volatile.Read(ref _writeIndexB); + + for (int s = 0; s < length; s += ch) + { + dst[w] = src[s]; + w++; + if (w == cap) w = 0; + } - if (buf == 0) Volatile.Write(ref _writeIndexA, w); - else Volatile.Write(ref _writeIndexB, w); + if (buf == 0) Volatile.Write(ref _writeIndexA, w); + else Volatile.Write(ref _writeIndexB, w); + } } Interlocked.Exchange(ref _isDataReceived, 1); diff --git a/Basis/Packages/com.steam.steamaudio/Runtime/SteamAudioManager.cs b/Basis/Packages/com.steam.steamaudio/Runtime/SteamAudioManager.cs index 30274baff..47432bea8 100644 --- a/Basis/Packages/com.steam.steamaudio/Runtime/SteamAudioManager.cs +++ b/Basis/Packages/com.steam.steamaudio/Runtime/SteamAudioManager.cs @@ -549,8 +549,9 @@ private void ScheduleInstance() EnsureSourceCapacity(CurrentArraySource); EnsureListenerCapacity(CurrentArrayListener); - JobHandle sourcesHandle = default; - JobHandle listenersHandle = default; + JobHandle priorHandle = combined; + JobHandle sourcesHandle = priorHandle; + JobHandle listenersHandle = priorHandle; if (CurrentArraySource > 0 && mSourceTransforms.isCreated) { @@ -558,7 +559,7 @@ private void ScheduleInstance() { PoseData = mSourceGathers, }; - sourcesHandle = job.Schedule(mSourceTransforms); + sourcesHandle = job.Schedule(mSourceTransforms, priorHandle); } if (CurrentArrayListener > 0 && mListenerTransforms.isCreated) @@ -567,13 +568,17 @@ private void ScheduleInstance() { PoseData = mListenerGathers, }; - listenersHandle = job.Schedule(mListenerTransforms); + listenersHandle = job.Schedule(mListenerTransforms, priorHandle); } + combined = JobHandle.CombineDependencies(sourcesHandle, listenersHandle); } public JobHandle combined; private void ApplyInstance() { + // Always complete pose gather jobs even when later early-returns skip simulation work. + combined.Complete(); + if (mAudioEngineState == null) return; @@ -621,10 +626,6 @@ private void ApplyInstance() mSimulator.SetSharedInputs(SimulationFlags.Direct, sharedInputs); - - // Complete before calling into Steam Audio (main-thread plugin calls) - combined.Complete(); - // --- Direct inputs from cached pose arrays --- for (int i = 0; i < CurrentArraySource; i++) { @@ -1011,7 +1012,8 @@ private void EnsureSourceCapacity(int required) int newCap = (mSourceCapacity <= 0) ? 8 : mSourceCapacity * 2; if (newCap < required) newCap = required; - // Dispose old arrays if created + // Dispose old arrays if created. Ensure no gather jobs are still using them. + combined.Complete(); if (mSourceGathers.IsCreated) mSourceGathers.Dispose(); mSourceGathers = new NativeArray(newCap, Allocator.Persistent); @@ -1027,6 +1029,7 @@ private void EnsureListenerCapacity(int required) int newCap = (mListenerCapacity <= 0) ? 4 : mListenerCapacity * 2; if (newCap < required) newCap = required; + combined.Complete(); if (mListenerGathers.IsCreated) mListenerGathers.Dispose(); mListenerGathers = new NativeArray(newCap, Allocator.Persistent); @@ -1049,8 +1052,9 @@ private void DisposeTransformAndPoseBuffers() if (mSourceTransforms.isCreated) mSourceTransforms.Dispose(); if (mListenerTransforms.isCreated) mListenerTransforms.Dispose(); - if (mSourceGathers.IsCreated) mSourceGathers.Dispose(); + combined.Complete(); + if (mSourceGathers.IsCreated) mSourceGathers.Dispose(); if (mListenerGathers.IsCreated) mListenerGathers.Dispose(); mSourceCapacity = 0; From 5dfec1cb2141d9b38795fe2816e0ad8a3b096f7a Mon Sep 17 00:00:00 2001 From: Toys0125 Date: Sat, 7 Mar 2026 14:05:16 -0600 Subject: [PATCH 2/2] Add Simplefication to complete prior job before starting new one. --- .../Networking/BasisRemoteFaceManagement.cs | 6 +++++- .../Runtime/SteamAudioManager.cs | 12 +++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Basis/Packages/com.basis.framework/Networking/BasisRemoteFaceManagement.cs b/Basis/Packages/com.basis.framework/Networking/BasisRemoteFaceManagement.cs index 502abf207..5c4207a6b 100644 --- a/Basis/Packages/com.basis.framework/Networking/BasisRemoteFaceManagement.cs +++ b/Basis/Packages/com.basis.framework/Networking/BasisRemoteFaceManagement.cs @@ -55,6 +55,10 @@ public static void Simulate(double t,float dt) { snapshot = BasisNetworkPlayers.ReceiversSnapshot; count = BasisNetworkPlayers.ReceiverCount; + + // Keep job lifecycle simple: finish prior frame work before scheduling again. + handle.Complete(); + if (count <= 0) { return; @@ -90,7 +94,7 @@ public static void Simulate(double t,float dt) blinkOut = blinkOut, }; - handle = job.Schedule(count, BatchSize, handle); + handle = job.Schedule(count, BatchSize); } public static void Apply() diff --git a/Basis/Packages/com.steam.steamaudio/Runtime/SteamAudioManager.cs b/Basis/Packages/com.steam.steamaudio/Runtime/SteamAudioManager.cs index 47432bea8..b65b9ce87 100644 --- a/Basis/Packages/com.steam.steamaudio/Runtime/SteamAudioManager.cs +++ b/Basis/Packages/com.steam.steamaudio/Runtime/SteamAudioManager.cs @@ -549,9 +549,11 @@ private void ScheduleInstance() EnsureSourceCapacity(CurrentArraySource); EnsureListenerCapacity(CurrentArrayListener); - JobHandle priorHandle = combined; - JobHandle sourcesHandle = priorHandle; - JobHandle listenersHandle = priorHandle; + // Keep job lifecycle simple: finish prior frame work before scheduling again. + combined.Complete(); + + JobHandle sourcesHandle = default; + JobHandle listenersHandle = default; if (CurrentArraySource > 0 && mSourceTransforms.isCreated) { @@ -559,7 +561,7 @@ private void ScheduleInstance() { PoseData = mSourceGathers, }; - sourcesHandle = job.Schedule(mSourceTransforms, priorHandle); + sourcesHandle = job.Schedule(mSourceTransforms); } if (CurrentArrayListener > 0 && mListenerTransforms.isCreated) @@ -568,7 +570,7 @@ private void ScheduleInstance() { PoseData = mListenerGathers, }; - listenersHandle = job.Schedule(mListenerTransforms, priorHandle); + listenersHandle = job.Schedule(mListenerTransforms); } combined = JobHandle.CombineDependencies(sourcesHandle, listenersHandle);