Phase 10: RIO hardware exists only on pods + dev boxes, so production is one RIOJoy copy inside each podized game folder, no resident tray. ConfigLocator makes a config.json beside the exe win over %APPDATA%; --exit-with <exe|pid> (CompanionTarget/CompanionExit, 60s startup grace) tears down and quits when the game exits; a starting --exit-with instance waits up to 15s for the predecessor mutex instead of silently exiting. deploy/build-pod.ps1 emits the ~4.5MB drop-in (app + portable config wrapping the profile + start script, no drivers) - verified against the shipped Descent profile. 455 tests; PLAN.md Phase 10 + INPUT-INTEGRATION.md pod section. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
64 lines
2.2 KiB
C#
64 lines
2.2 KiB
C#
using System.Globalization;
|
|
using RioJoy.Core.Profiles;
|
|
|
|
namespace RioJoy.Core.Hosting;
|
|
|
|
/// <summary>
|
|
/// The process a pod-bundled RIOJoy lives alongside (<c>--exit-with</c>):
|
|
/// either a PID or an executable name, normalized the same way auto-switch
|
|
/// triggers are (basename, no <c>.exe</c>, lower-case) so launch scripts can
|
|
/// pass whatever they have.
|
|
/// </summary>
|
|
public sealed record CompanionTarget
|
|
{
|
|
public int? Pid { get; init; }
|
|
|
|
/// <summary>Normalized executable name (when <see cref="Pid"/> is null).</summary>
|
|
public string? Name { get; init; }
|
|
|
|
public static CompanionTarget Parse(string value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
throw new ArgumentException("Companion target is required.", nameof(value));
|
|
|
|
return int.TryParse(value.Trim(), NumberStyles.None, CultureInfo.InvariantCulture, out int pid)
|
|
? new CompanionTarget { Pid = pid }
|
|
: new CompanionTarget { Name = AutoSwitchResolver.Normalize(value) };
|
|
}
|
|
|
|
public override string ToString() => Pid is int p ? $"pid {p}" : Name ?? "?";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pure decision core of <c>--exit-with</c>: RIOJoy should exit once its
|
|
/// companion game has run and then gone away. Launch order is not guaranteed
|
|
/// (the pod start script fires both), so a companion that has <i>never</i>
|
|
/// been seen only triggers exit after a startup grace — covering both "game
|
|
/// still loading" and "game failed to launch, don't linger forever". The
|
|
/// caller polls (the tray's 1 s timer) and supplies elapsed time, so this
|
|
/// stays clock-free and unit-testable.
|
|
/// </summary>
|
|
public sealed class CompanionExit
|
|
{
|
|
public static readonly TimeSpan DefaultStartupGrace = TimeSpan.FromSeconds(60);
|
|
|
|
private readonly TimeSpan _grace;
|
|
private bool _seen;
|
|
|
|
public CompanionExit(TimeSpan? startupGrace = null)
|
|
{
|
|
_grace = startupGrace ?? DefaultStartupGrace;
|
|
}
|
|
|
|
/// <summary>True once RIOJoy should tear down and exit.</summary>
|
|
public bool ShouldExit(bool companionRunning, TimeSpan elapsed)
|
|
{
|
|
if (companionRunning)
|
|
{
|
|
_seen = true;
|
|
return false;
|
|
}
|
|
return _seen || elapsed >= _grace;
|
|
}
|
|
}
|