Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 45 additions & 39 deletions VRCFaceTracking.Core/Library/UnifiedLibManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ public void Initialize()
{
replyUpdatePacket.UpdateGlobalExpressionState();
}
replyUpdatePacket.UpdateHeadState();
}

break;
Expand Down Expand Up @@ -445,45 +446,54 @@ private bool TeardownModuleSandboxed(ModuleRuntimeInfo module)
_sandboxServer.SendData(eventTeardownPacket, module.SandboxProcessPort);

// Kill the update thread
module.UpdateCancellationToken.Cancel();
if ( module.UpdateThread.IsAlive )
{
// Edge case, we wait for the thread to finish before unloading the assembly
_logger.LogDebug("Waiting for {module}'s thread to join...", module.ModuleClassName);
module.UpdateThread.Join();
}

module.UpdateCancellationToken?.Cancel();
// Give the module 100ms to kill itself
Thread.Sleep(100);

// Only bother tearing down a module if it's actually shutdown
if ( !module.Process.HasExited )
if ( !(module.Process?.HasExited ?? true) )
{
_logger.LogDebug("Module process has not yet exited");
// @Note: Forcefully kill the process. We'll try to kill it 1000 times and then give up.
int tries = 0;
while ( tries < 1000 )
{
try
try {
if (!(module.Process?.WaitForExit(200) ?? false))
{
tries++;
if ( !module.Process.HasExited )
module.Process.Kill();
if ( module.Process.HasExited )
_logger.LogDebug("Module {id} didn't exit gracefully. Forcing kill...", module.Process?.Id ?? -1);
module.Process?.Kill(entireProcessTree: true);
if (!(module.Process?.WaitForExit(2000) ?? false))
{
tries = int.MaxValue;
break;
// on windows we can use taskkill /F /T /PID {procId} to force kill a process very aggressively. this has a higher success rate than process.kill!
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
using var killer = Process.Start(new ProcessStartInfo
{
FileName = "taskkill",
Arguments = $"/F /T /PID {module.Process.Id}",
CreateNoWindow = true,
UseShellExecute = false
});
killer?.WaitForExit(2000);
} else {
_logger.LogCritical("Process {id} is a zombie or stuck in Kernel I/O. Manual intervention required.", module.Process.Id);
}
return false;
}
} catch ( System.ComponentModel.Win32Exception ex ) {
// Can fail to call OpenProcessEx due to some error such as ACCESS_DENIED (process has higher priveleges, eg Sraniple)
_logger.LogError($"Tried killing process with PID {module.Process.Id}. Got win32 error ({ex.ToString()}");
} catch ( Exception ex ) {
// Tell the user why we got an exception so that we can hopefully fix it.
_logger.LogError($"Tried killing process with PID {module.Process.Id}. Got exception ({ex.HResult}) {ex.Message}");
}
} catch ( System.ComponentModel.Win32Exception ex ) {
// Can fail to call OpenProcessEx due to some error such as ACCESS_DENIED (process has higher priveleges, eg Sraniple)
_logger.LogError($"Tried killing process with PID {module.Process.Id}. Got win32 error ({ex.ToString()}");
} catch ( Exception ex ) {
// Tell the user why we got an exception so that we can hopefully fix it.
_logger.LogError($"Tried killing process with PID {module.Process.Id}. Got exception ({ex.HResult}) {ex.Message}");
}
}

if (module.UpdateThread?.IsAlive ?? false)
{
// Edge case, we wait for the thread to finish before unloading the assembly
var moduleName = module.ModuleInformation?.Name ?? module.ModuleClassName ?? "Unknown";
_logger.LogDebug("Waiting for {module}'s thread to join...", moduleName);
module.UpdateThread?.Join(500);
}

return true;
}

Expand All @@ -495,6 +505,8 @@ public void TeardownAllAndResetAsync()
foreach ( var module in _moduleThreads )
{
var success = false;
if (module == null || (module.Process?.HasExited ?? true))
continue;
try
{
success = TeardownModuleSandboxed(module);
Expand All @@ -510,32 +522,26 @@ public void TeardownAllAndResetAsync()

_moduleThreads.Clear();

EyeStatus = ModuleState.Uninitialized;
ExpressionStatus = ModuleState.Uninitialized;
}

// Signal all active modules to gracefully shut down their respective runtimes
public void TeardownAllAndResetAsyncLegacy()
{
_logger.LogInformation("Tearing down all modules...");

foreach ( var module in _moduleThreads )
foreach ( var module in AvailableSandboxModules )
{
var success = false;
if (module == null || (module.Process?.HasExited ?? true)) // c# objects may be null, use null coalesce to detect if a module has been destroyed but we have a lingering ref to it
continue;
try
{
success = TeardownModuleSandboxed(module);
} finally
{
if ( !success )
{
_logger.LogWarning($"Module: {module.Module.ModuleInformation.Name} failed to shut down. Killing its thread.");
module.UpdateThread.Interrupt();
var moduleName = module.ModuleInformation?.Name ?? module.ModuleClassName ?? "Unknown";
_logger.LogWarning($"Module: {moduleName} failed to shut down. Killing its thread.");
module.UpdateThread?.Interrupt();
}
}
}

_moduleThreads.Clear();
AvailableSandboxModules.Clear();

EyeStatus = ModuleState.Uninitialized;
ExpressionStatus = ModuleState.Uninitialized;
Expand Down
12 changes: 6 additions & 6 deletions VRCFaceTracking.Core/Params/Data/Mutation/ParameterAdjustment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ public void Reset()
[MutationProperty("Tongue Directions")] public (float, float) tongueMove = new(0, 1);
[MutationProperty("Tongue Miscellaneous")] public (float, float) tongueOther = new(0, 1);

[MutationProperty("Head Rotation (Side-to-Side)")] public (float, float) headRotationYaw = new(0, 1);
[MutationProperty("Head Rotation (Up-Down Tilt)")] public (float, float) headRotationPitch = new(0, 1);
[MutationProperty("Head Rotation (Side Tilt)")] public (float, float) headRotationRoll = new(0, 1);
[MutationProperty("Head Position (Side-to-Side)")] public (float, float) headPositionX = new(0, 1);
[MutationProperty("Head Position (Up-Down)")] public (float, float) headPositionY = new(0, 1);
[MutationProperty("Head Position (Forward-Back)")] public (float, float) headPositionZ = new(0, 1);
[MutationProperty("Head Rotation (Side-to-Side)")] public (float, float) headRotationYaw = new(-1, 1);
[MutationProperty("Head Rotation (Up-Down Tilt)")] public (float, float) headRotationPitch = new(-1, 1);
[MutationProperty("Head Rotation (Side Tilt)")] public (float, float) headRotationRoll = new(-1, 1);
[MutationProperty("Head Position (Side-to-Side)")] public (float, float) headPositionX = new(-1, 1);
[MutationProperty("Head Position (Up-Down)")] public (float, float) headPositionY = new(-1, 1);
[MutationProperty("Head Position (Forward-Back)")] public (float, float) headPositionZ = new(-1, 1);

public override string Name => "Parameter Adjustment";

Expand Down
33 changes: 33 additions & 0 deletions VRCFaceTracking.Core/Sandboxing/IPC/ReplyUpdatePacket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ internal class UpdateDataContiguous
internal float Eye_Right_PupilDiameter_MM;
internal float Eye_Right_Openness;

internal float Head_Yaw;
internal float Head_Pitch;
internal float Head_Roll;

internal float Head_PosX;
internal float Head_PosY;
internal float Head_PosZ;

[MarshalAs(UnmanagedType.ByValArray, SizeConst = EXPRESSION_COUNT)]
internal float[] Expression_Shapes;
}
Expand Down Expand Up @@ -64,6 +72,14 @@ public override byte[] GetBytes()
_contiguousUnifiedData.Eye_MaxDilation = UnifiedTracking.Data.Eye._maxDilation;
_contiguousUnifiedData.Eye_MinDilation = UnifiedTracking.Data.Eye._minDilation;

_contiguousUnifiedData.Head_Yaw = UnifiedTracking.Data.Head.HeadPitch;
_contiguousUnifiedData.Head_Pitch = UnifiedTracking.Data.Head.HeadRoll;
_contiguousUnifiedData.Head_Roll = UnifiedTracking.Data.Head.HeadYaw;

_contiguousUnifiedData.Head_PosX = UnifiedTracking.Data.Head.HeadPosX;
_contiguousUnifiedData.Head_PosY = UnifiedTracking.Data.Head.HeadPosY;
_contiguousUnifiedData.Head_PosZ = UnifiedTracking.Data.Head.HeadPosZ;

// Copy face tracking
for ( int i = 0; i < _contiguousUnifiedData.Expression_Shapes.Length; i++ )
{
Expand Down Expand Up @@ -153,6 +169,23 @@ public void UpdateGlobalEyeState()
UnifiedTracking.Data.Eye._minDilation = _contiguousUnifiedData.Eye_MinDilation;
}

public void UpdateHeadState()
{
if ( _contiguousUnifiedData.Head_Yaw != INVALID_FLOAT )
UnifiedTracking.Data.Head.HeadYaw = _contiguousUnifiedData.Head_Yaw;
if ( _contiguousUnifiedData.Head_Pitch != INVALID_FLOAT )
UnifiedTracking.Data.Head.HeadPitch = _contiguousUnifiedData.Head_Pitch;
if ( _contiguousUnifiedData.Head_Roll != INVALID_FLOAT )
UnifiedTracking.Data.Head.HeadRoll = _contiguousUnifiedData.Head_Roll;

if ( _contiguousUnifiedData.Head_PosX != INVALID_FLOAT )
UnifiedTracking.Data.Head.HeadPosX = _contiguousUnifiedData.Head_PosX;
if ( _contiguousUnifiedData.Head_PosY != INVALID_FLOAT )
UnifiedTracking.Data.Head.HeadPosY = _contiguousUnifiedData.Head_PosY;
if ( _contiguousUnifiedData.Head_PosZ != INVALID_FLOAT )
UnifiedTracking.Data.Head.HeadPosZ = _contiguousUnifiedData.Head_PosZ;
}

public void UpdateGlobalExpressionState()
{
// Copy face tracking
Expand Down
17 changes: 16 additions & 1 deletion VRCFaceTracking.Core/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,22 @@ public static void KillAllProcessesOfName(string name)

try
{
proc.Kill();
proc.Kill(entireProcessTree: true);
if (!proc.WaitForExit(2000))
{
// on windows we can use taskkill /F /T /PID {procId} to force kill a process very aggressively. this has a higher success rate than process.kill!
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
using var killer = Process.Start(new ProcessStartInfo
{
FileName = "taskkill",
Arguments = $"/F /T /PID {proc.Id}",
CreateNoWindow = true,
UseShellExecute = false
});
killer?.WaitForExit(2000);
}
}

}
catch
{
Expand Down
Loading