refactor(app): compose settings and developer tools
This commit is contained in:
parent
60a1698ce7
commit
cd7b519f78
24 changed files with 2073 additions and 333 deletions
631
src/AcDream.App/Composition/SettingsDevToolsComposition.cs
Normal file
631
src/AcDream.App/Composition/SettingsDevToolsComposition.cs
Normal file
|
|
@ -0,0 +1,631 @@
|
|||
using AcDream.App.Combat;
|
||||
using AcDream.App.Diagnostics;
|
||||
using AcDream.App.Input;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Settings;
|
||||
using AcDream.Core.Chat;
|
||||
using AcDream.Core.Combat;
|
||||
using AcDream.Core.Player;
|
||||
using AcDream.UI.Abstractions.Input;
|
||||
using AcDream.UI.Abstractions.Panels.Chat;
|
||||
using AcDream.UI.Abstractions.Panels.Debug;
|
||||
using AcDream.UI.Abstractions.Panels.Settings;
|
||||
using AcDream.UI.Abstractions.Panels.Vitals;
|
||||
using AcDream.UI.ImGui;
|
||||
using Silk.NET.Input;
|
||||
using Silk.NET.OpenGL;
|
||||
using Silk.NET.Windowing;
|
||||
|
||||
namespace AcDream.App.Composition;
|
||||
|
||||
internal sealed record SettingsDevToolsResult(
|
||||
AcDream.UI.Abstractions.Settings.QualitySettings ResolvedQuality,
|
||||
DevToolsCompositionOwner? DevTools);
|
||||
|
||||
internal sealed record SettingsDevToolsOptionalDependencies(
|
||||
IDevToolsRuntimeFacts Facts,
|
||||
IRuntimeKeyBindingTarget? KeyBindingTarget,
|
||||
DeferredCanonicalWorldEntityCountSource WorldEntities,
|
||||
DeferredRenderFrameDiagnosticsSource FrameDiagnostics,
|
||||
DeferredDevToolsPlayerModeCommands PlayerModeCommands);
|
||||
|
||||
internal sealed record SettingsDevToolsDependencies(
|
||||
IView Window,
|
||||
RuntimeSettingsController Settings,
|
||||
IRuntimeSettingsStartupTarget StartupTarget,
|
||||
HostQuiescenceGate HostQuiescence,
|
||||
ChatLog Chat,
|
||||
CombatState Combat,
|
||||
LocalPlayerState LocalPlayer,
|
||||
SettingsDevToolsOptionalDependencies? DevTools,
|
||||
RuntimeDiagnosticCommandSlot DiagnosticCommands,
|
||||
CombatFeedbackSlot CombatFeedback,
|
||||
KeyBindings KeyBindings,
|
||||
FrameProfiler FrameProfiler,
|
||||
FramebufferResizeController FramebufferResize,
|
||||
Action<string> Log);
|
||||
|
||||
internal interface IGameWindowSettingsDevToolsPublication
|
||||
{
|
||||
void PublishDevTools(DevToolsCompositionOwner value);
|
||||
}
|
||||
|
||||
internal interface ISettingsDevToolsCompositionFactory
|
||||
{
|
||||
bool IsSupported(IInputContext input);
|
||||
|
||||
IDevToolsInputContext CreateInputContext(
|
||||
IInputContext input,
|
||||
HostQuiescenceGate quiescence);
|
||||
|
||||
IImGuiBootstrapper CreateBootstrap(
|
||||
GL gl,
|
||||
IView window,
|
||||
IInputContext input);
|
||||
|
||||
ImGuiPanelHost CreatePanelHost();
|
||||
|
||||
IDevToolsFrameBackend CreateBackend(
|
||||
IImGuiBootstrapper bootstrap,
|
||||
ImGuiPanelHost panels);
|
||||
}
|
||||
|
||||
internal sealed class RetailSettingsDevToolsCompositionFactory
|
||||
: ISettingsDevToolsCompositionFactory
|
||||
{
|
||||
public bool IsSupported(IInputContext input) =>
|
||||
input.Keyboards.Count > 0
|
||||
&& input.Mice.Count > 0
|
||||
&& input.Mice[0].ScrollWheels.Count > 0;
|
||||
|
||||
public IDevToolsInputContext CreateInputContext(
|
||||
IInputContext input,
|
||||
HostQuiescenceGate quiescence) =>
|
||||
new QuiescentInputContext(input, quiescence);
|
||||
|
||||
public IImGuiBootstrapper CreateBootstrap(
|
||||
GL gl,
|
||||
IView window,
|
||||
IInputContext input) =>
|
||||
new ImGuiBootstrapper(gl, window, input);
|
||||
|
||||
public ImGuiPanelHost CreatePanelHost() => new();
|
||||
|
||||
public IDevToolsFrameBackend CreateBackend(
|
||||
IImGuiBootstrapper bootstrap,
|
||||
ImGuiPanelHost panels) =>
|
||||
new ImGuiDevToolsFrameBackend(bootstrap, panels);
|
||||
}
|
||||
|
||||
internal enum SettingsDevToolsCompositionPoint
|
||||
{
|
||||
SettingsApplied,
|
||||
InputContextCreated,
|
||||
BootstrapCreated,
|
||||
InputActivated,
|
||||
PanelHostCreated,
|
||||
VitalsRegistered,
|
||||
ChatRegistered,
|
||||
DebugViewModelCreated,
|
||||
DiagnosticActionsBound,
|
||||
CombatFeedbackBound,
|
||||
DebugRegistered,
|
||||
SettingsViewModelBound,
|
||||
SettingsRegistered,
|
||||
BackendCreated,
|
||||
PresenterCreated,
|
||||
FramebufferBound,
|
||||
InitialLayoutApplied,
|
||||
DevToolsPublished,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Complete optional developer frontend lifetime. Construction rollback and
|
||||
/// normal shutdown share the same named, retryable release operations.
|
||||
/// </summary>
|
||||
internal sealed class DevToolsCompositionOwner : IDisposable
|
||||
{
|
||||
private readonly IDevToolsInputContext _input;
|
||||
private readonly IDevToolsFrameBackend _backend;
|
||||
private readonly ChatVM _chat;
|
||||
private readonly DebugVM _debug;
|
||||
private readonly RuntimeSettingsViewModelBinding? _settings;
|
||||
private readonly CombatFeedbackSlot _combatFeedback;
|
||||
private readonly FramebufferDevToolsBinding _framebuffer;
|
||||
private readonly ResourceShutdownTransaction _frontendShutdown;
|
||||
private bool _ownsInput;
|
||||
private bool _ownsBackend;
|
||||
private bool _ownsChat;
|
||||
private bool _ownsDebug;
|
||||
private bool _ownsSettings;
|
||||
private bool _ownsCombatBinding;
|
||||
private bool _ownsFramebuffer;
|
||||
|
||||
public DevToolsCompositionOwner(
|
||||
IDevToolsInputContext input,
|
||||
IDevToolsFrameBackend backend,
|
||||
DevToolsFramePresenter presenter,
|
||||
DevToolsCommandBusSource commandBus,
|
||||
VitalsVM vitals,
|
||||
ChatVM chat,
|
||||
DebugVM debug,
|
||||
SettingsDevToolsOptionalDependencies lateBindings,
|
||||
RuntimeSettingsViewModelBinding? settings,
|
||||
CombatFeedbackSlot combatFeedback,
|
||||
FramebufferDevToolsBinding framebuffer)
|
||||
{
|
||||
_input = input ?? throw new ArgumentNullException(nameof(input));
|
||||
_backend = backend ?? throw new ArgumentNullException(nameof(backend));
|
||||
Presenter = presenter ?? throw new ArgumentNullException(nameof(presenter));
|
||||
CommandBus = commandBus ?? throw new ArgumentNullException(nameof(commandBus));
|
||||
Vitals = vitals ?? throw new ArgumentNullException(nameof(vitals));
|
||||
_chat = chat ?? throw new ArgumentNullException(nameof(chat));
|
||||
_debug = debug ?? throw new ArgumentNullException(nameof(debug));
|
||||
LateBindings = lateBindings
|
||||
?? throw new ArgumentNullException(nameof(lateBindings));
|
||||
_settings = settings;
|
||||
_combatFeedback = combatFeedback ?? throw new ArgumentNullException(nameof(combatFeedback));
|
||||
_framebuffer = framebuffer ?? throw new ArgumentNullException(nameof(framebuffer));
|
||||
|
||||
_frontendShutdown = new ResourceShutdownTransaction(
|
||||
new ResourceShutdownStage("developer framebuffer binding",
|
||||
[
|
||||
new("framebuffer resize binding", ReleaseFramebuffer),
|
||||
]),
|
||||
new ResourceShutdownStage("developer backend",
|
||||
[
|
||||
new("ImGui backend", ReleaseBackend),
|
||||
]),
|
||||
new ResourceShutdownStage("developer view models",
|
||||
[
|
||||
new("settings view model binding", ReleaseSettings),
|
||||
new("combat feedback binding", ReleaseCombatBinding),
|
||||
new("debug combat subscriptions", ReleaseDebug),
|
||||
new("chat transcript subscription", ReleaseChat),
|
||||
]));
|
||||
}
|
||||
|
||||
public DevToolsFramePresenter Presenter { get; }
|
||||
public DevToolsCommandBusSource CommandBus { get; }
|
||||
public VitalsVM Vitals { get; }
|
||||
public DebugVM Debug => _debug;
|
||||
public SettingsDevToolsOptionalDependencies LateBindings { get; }
|
||||
|
||||
public bool IsDisposalComplete =>
|
||||
_frontendShutdown.IsComplete && !_ownsInput;
|
||||
|
||||
public void AdoptInput() => _ownsInput = true;
|
||||
public void AdoptBackend() => _ownsBackend = true;
|
||||
public void AdoptChat() => _ownsChat = true;
|
||||
public void AdoptDebug() => _ownsDebug = true;
|
||||
public void AdoptSettings() => _ownsSettings = _settings is not null;
|
||||
public void AdoptCombatBinding() => _ownsCombatBinding = true;
|
||||
public void AdoptFramebuffer() => _ownsFramebuffer = true;
|
||||
|
||||
public void CompleteOwnership()
|
||||
{
|
||||
if (!_ownsInput
|
||||
|| !_ownsBackend
|
||||
|| !_ownsChat
|
||||
|| !_ownsDebug
|
||||
|| !_ownsCombatBinding
|
||||
|| !_ownsFramebuffer
|
||||
|| (_settings is not null && !_ownsSettings))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Developer composition ownership is incomplete.");
|
||||
}
|
||||
}
|
||||
|
||||
public void DeactivateInput()
|
||||
{
|
||||
CommandBus.Deactivate();
|
||||
LateBindings.PlayerModeCommands.Deactivate();
|
||||
LateBindings.FrameDiagnostics.Deactivate();
|
||||
LateBindings.WorldEntities.Deactivate();
|
||||
if (_ownsInput)
|
||||
_input.Deactivate();
|
||||
}
|
||||
|
||||
public void DetachInput()
|
||||
{
|
||||
if (!_ownsInput)
|
||||
return;
|
||||
_input.Dispose();
|
||||
if (!_input.IsDisposalComplete)
|
||||
throw new InvalidOperationException(
|
||||
"Developer input callback cleanup remains incomplete.");
|
||||
_ownsInput = false;
|
||||
}
|
||||
|
||||
public void DisposeFrontend() => _frontendShutdown.CompleteOrThrow();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DeactivateInput();
|
||||
DisposeFrontend();
|
||||
DetachInput();
|
||||
}
|
||||
|
||||
private void ReleaseFramebuffer()
|
||||
{
|
||||
if (!_ownsFramebuffer)
|
||||
return;
|
||||
_framebuffer.Dispose();
|
||||
_ownsFramebuffer = false;
|
||||
}
|
||||
|
||||
private void ReleaseCombatBinding()
|
||||
{
|
||||
if (!_ownsCombatBinding)
|
||||
return;
|
||||
_combatFeedback.Unbind(_debug);
|
||||
_ownsCombatBinding = false;
|
||||
}
|
||||
|
||||
private void ReleaseSettings()
|
||||
{
|
||||
if (!_ownsSettings)
|
||||
return;
|
||||
_settings!.Dispose();
|
||||
_ownsSettings = false;
|
||||
}
|
||||
|
||||
private void ReleaseDebug()
|
||||
{
|
||||
if (!_ownsDebug)
|
||||
return;
|
||||
_debug.Dispose();
|
||||
_ownsDebug = false;
|
||||
}
|
||||
|
||||
private void ReleaseChat()
|
||||
{
|
||||
if (!_ownsChat)
|
||||
return;
|
||||
_chat.Dispose();
|
||||
_ownsChat = false;
|
||||
}
|
||||
|
||||
private void ReleaseBackend()
|
||||
{
|
||||
if (!_ownsBackend)
|
||||
return;
|
||||
_backend.Dispose();
|
||||
_ownsBackend = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class CombatFeedbackBinding : IDisposable
|
||||
{
|
||||
private readonly CombatFeedbackSlot _owner;
|
||||
private readonly DebugVM _viewModel;
|
||||
private bool _disposed;
|
||||
|
||||
public CombatFeedbackBinding(CombatFeedbackSlot owner, DebugVM viewModel)
|
||||
{
|
||||
_owner = owner ?? throw new ArgumentNullException(nameof(owner));
|
||||
_viewModel = viewModel ?? throw new ArgumentNullException(nameof(viewModel));
|
||||
_owner.Bind(_viewModel);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
_owner.Unbind(_viewModel);
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Production Phase 3. Settings are required; the optional developer frontend
|
||||
/// may be abandoned only after every locally acquired edge has been removed.
|
||||
/// </summary>
|
||||
internal sealed class SettingsDevToolsCompositionPhase :
|
||||
ISettingsDevToolsCompositionPhase<
|
||||
GameWindowPlatformResult<GL, IInputContext>,
|
||||
HostInputCameraResult,
|
||||
ContentEffectsAudioResult,
|
||||
SettingsDevToolsResult>
|
||||
{
|
||||
private readonly SettingsDevToolsDependencies _dependencies;
|
||||
private readonly IGameWindowSettingsDevToolsPublication _publication;
|
||||
private readonly ISettingsDevToolsCompositionFactory _factory;
|
||||
private readonly Action<SettingsDevToolsCompositionPoint>? _faultInjection;
|
||||
|
||||
public SettingsDevToolsCompositionPhase(
|
||||
SettingsDevToolsDependencies dependencies,
|
||||
IGameWindowSettingsDevToolsPublication publication,
|
||||
ISettingsDevToolsCompositionFactory? factory = null,
|
||||
Action<SettingsDevToolsCompositionPoint>? faultInjection = null)
|
||||
{
|
||||
_dependencies = dependencies ?? throw new ArgumentNullException(nameof(dependencies));
|
||||
_publication = publication ?? throw new ArgumentNullException(nameof(publication));
|
||||
_factory = factory ?? new RetailSettingsDevToolsCompositionFactory();
|
||||
_faultInjection = faultInjection;
|
||||
}
|
||||
|
||||
public SettingsDevToolsResult Compose(
|
||||
GameWindowPlatformResult<GL, IInputContext> platform,
|
||||
HostInputCameraResult host,
|
||||
ContentEffectsAudioResult content)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(platform);
|
||||
ArgumentNullException.ThrowIfNull(host);
|
||||
ArgumentNullException.ThrowIfNull(content);
|
||||
|
||||
_dependencies.Settings.ApplyStartup(_dependencies.StartupTarget);
|
||||
Fault(SettingsDevToolsCompositionPoint.SettingsApplied);
|
||||
|
||||
DevToolsCompositionOwner? devTools = _dependencies.DevTools is { } optional
|
||||
? ComposeOptionalDevTools(platform, host, optional)
|
||||
: null;
|
||||
return new SettingsDevToolsResult(
|
||||
_dependencies.Settings.ResolvedQuality,
|
||||
devTools);
|
||||
}
|
||||
|
||||
private DevToolsCompositionOwner? ComposeOptionalDevTools(
|
||||
GameWindowPlatformResult<GL, IInputContext> platform,
|
||||
HostInputCameraResult host,
|
||||
SettingsDevToolsOptionalDependencies optional)
|
||||
{
|
||||
var scope = new CompositionAcquisitionScope();
|
||||
bool published = false;
|
||||
try
|
||||
{
|
||||
if (!_factory.IsSupported(platform.Input))
|
||||
{
|
||||
_dependencies.Log(
|
||||
"devtools: keyboard, mouse, or scroll input unavailable — devtools disabled");
|
||||
return null;
|
||||
}
|
||||
|
||||
var inputLease = scope.Acquire(
|
||||
"developer input context",
|
||||
() => _factory.CreateInputContext(
|
||||
platform.Input,
|
||||
_dependencies.HostQuiescence),
|
||||
static value => value.Dispose());
|
||||
IDevToolsInputContext input = inputLease.Resource;
|
||||
Fault(SettingsDevToolsCompositionPoint.InputContextCreated);
|
||||
|
||||
var bootstrapLease = scope.Acquire(
|
||||
"ImGui bootstrap",
|
||||
() => _factory.CreateBootstrap(
|
||||
platform.Graphics,
|
||||
_dependencies.Window,
|
||||
input),
|
||||
static value => value.Dispose());
|
||||
IImGuiBootstrapper bootstrap = bootstrapLease.Resource;
|
||||
Fault(SettingsDevToolsCompositionPoint.BootstrapCreated);
|
||||
|
||||
input.Activate();
|
||||
Fault(SettingsDevToolsCompositionPoint.InputActivated);
|
||||
|
||||
ImGuiPanelHost panelHost = _factory.CreatePanelHost();
|
||||
Fault(SettingsDevToolsCompositionPoint.PanelHostCreated);
|
||||
|
||||
var vitals = new VitalsVM(
|
||||
_dependencies.Combat,
|
||||
_dependencies.LocalPlayer);
|
||||
var vitalsPanel = new VitalsPanel(vitals);
|
||||
panelHost.Register(vitalsPanel);
|
||||
Fault(SettingsDevToolsCompositionPoint.VitalsRegistered);
|
||||
|
||||
var chatLease = scope.Acquire(
|
||||
"developer chat view model",
|
||||
() => new ChatVM(_dependencies.Chat)
|
||||
{
|
||||
FpsProvider = () => optional.Facts.Fps,
|
||||
PositionProvider = () => optional.Facts.PlayerPosition,
|
||||
},
|
||||
static value => value.Dispose());
|
||||
ChatVM chatVm = chatLease.Resource;
|
||||
var chatPanel = new ChatPanel(chatVm);
|
||||
panelHost.Register(chatPanel);
|
||||
Fault(SettingsDevToolsCompositionPoint.ChatRegistered);
|
||||
|
||||
var debugLease = scope.Acquire(
|
||||
"developer debug view model",
|
||||
() => CreateDebugViewModel(optional.Facts),
|
||||
static value => value.Dispose());
|
||||
DebugVM debugVm = debugLease.Resource;
|
||||
Fault(SettingsDevToolsCompositionPoint.DebugViewModelCreated);
|
||||
debugVm.CycleTimeOfDay = _dependencies.DiagnosticCommands.CycleTimeOfDay;
|
||||
debugVm.CycleWeather = _dependencies.DiagnosticCommands.CycleWeather;
|
||||
debugVm.ToggleCollisionWires =
|
||||
_dependencies.DiagnosticCommands.ToggleCollisionWireframes;
|
||||
debugVm.ToggleFlyMode = optional.PlayerModeCommands.ToggleFlyOrChase;
|
||||
Fault(SettingsDevToolsCompositionPoint.DiagnosticActionsBound);
|
||||
|
||||
var combatBindingLease = scope.Acquire(
|
||||
"combat feedback binding",
|
||||
() => new CombatFeedbackBinding(
|
||||
_dependencies.CombatFeedback,
|
||||
debugVm),
|
||||
static value => value.Dispose());
|
||||
Fault(SettingsDevToolsCompositionPoint.CombatFeedbackBound);
|
||||
|
||||
var debugPanel = new DebugPanel(debugVm);
|
||||
panelHost.Register(debugPanel);
|
||||
Fault(SettingsDevToolsCompositionPoint.DebugRegistered);
|
||||
|
||||
RuntimeSettingsViewModelBinding? settingsBinding = null;
|
||||
SettingsPanel? settingsPanel = null;
|
||||
CompositionAcquisitionScope.CompositionAcquisitionLease<RuntimeSettingsViewModelBinding>?
|
||||
settingsLease = null;
|
||||
if (host.InputDispatcher is not null
|
||||
&& optional.KeyBindingTarget is not null)
|
||||
{
|
||||
settingsLease = scope.Acquire(
|
||||
"settings view model binding",
|
||||
() => _dependencies.Settings.CreateViewModelBinding(
|
||||
_dependencies.KeyBindings,
|
||||
host.InputDispatcher,
|
||||
optional.KeyBindingTarget.Apply),
|
||||
static value => value.Dispose());
|
||||
settingsBinding = settingsLease.Resource;
|
||||
Fault(SettingsDevToolsCompositionPoint.SettingsViewModelBound);
|
||||
settingsPanel = new SettingsPanel(settingsBinding.ViewModel);
|
||||
panelHost.Register(settingsPanel);
|
||||
Fault(SettingsDevToolsCompositionPoint.SettingsRegistered);
|
||||
}
|
||||
|
||||
var backendLease = scope.Acquire(
|
||||
"developer frame backend",
|
||||
() => _factory.CreateBackend(bootstrap, panelHost),
|
||||
static value => value.Dispose());
|
||||
IDevToolsFrameBackend backend = backendLease.Resource;
|
||||
bootstrapLease.Transfer();
|
||||
Fault(SettingsDevToolsCompositionPoint.BackendCreated);
|
||||
|
||||
var cameraMenu = new DevToolsCameraMenuOperations(
|
||||
host.CameraController,
|
||||
optional.PlayerModeCommands);
|
||||
var commandBus = new DevToolsCommandBusSource();
|
||||
var presenter = new DevToolsFramePresenter(
|
||||
backend,
|
||||
cameraMenu,
|
||||
commandBus,
|
||||
_dependencies.FrameProfiler,
|
||||
new DevToolsPanelSet(
|
||||
vitalsPanel,
|
||||
chatPanel,
|
||||
debugPanel,
|
||||
debugVm,
|
||||
settingsPanel));
|
||||
Fault(SettingsDevToolsCompositionPoint.PresenterCreated);
|
||||
|
||||
var framebufferLease = scope.Acquire(
|
||||
"developer framebuffer binding",
|
||||
() => new FramebufferDevToolsBinding(
|
||||
_dependencies.FramebufferResize,
|
||||
new DevToolsFramebufferTarget(presenter)),
|
||||
static value => value.Dispose());
|
||||
Fault(SettingsDevToolsCompositionPoint.FramebufferBound);
|
||||
|
||||
presenter.ResetLayout(
|
||||
_dependencies.Window.Size.X,
|
||||
_dependencies.Window.Size.Y,
|
||||
DevToolsPanelLayoutCondition.FirstUseEver);
|
||||
Fault(SettingsDevToolsCompositionPoint.InitialLayoutApplied);
|
||||
|
||||
var ownerLease = scope.Acquire(
|
||||
"developer composition owner",
|
||||
() => new DevToolsCompositionOwner(
|
||||
input,
|
||||
backend,
|
||||
presenter,
|
||||
commandBus,
|
||||
vitals,
|
||||
chatVm,
|
||||
debugVm,
|
||||
optional,
|
||||
settingsBinding,
|
||||
_dependencies.CombatFeedback,
|
||||
framebufferLease.Resource),
|
||||
static value => value.Dispose());
|
||||
DevToolsCompositionOwner owner = ownerLease.Resource;
|
||||
|
||||
inputLease.Transfer();
|
||||
owner.AdoptInput();
|
||||
backendLease.Transfer();
|
||||
owner.AdoptBackend();
|
||||
chatLease.Transfer();
|
||||
owner.AdoptChat();
|
||||
debugLease.Transfer();
|
||||
owner.AdoptDebug();
|
||||
combatBindingLease.Transfer();
|
||||
owner.AdoptCombatBinding();
|
||||
if (settingsLease is not null)
|
||||
{
|
||||
settingsLease.Transfer();
|
||||
owner.AdoptSettings();
|
||||
}
|
||||
framebufferLease.Transfer();
|
||||
owner.AdoptFramebuffer();
|
||||
owner.CompleteOwnership();
|
||||
|
||||
owner = ownerLease.Publish(
|
||||
_publication.PublishDevTools);
|
||||
published = true;
|
||||
Fault(SettingsDevToolsCompositionPoint.DevToolsPublished);
|
||||
scope.Complete();
|
||||
_dependencies.Log(
|
||||
"devtools: ImGui panel host ready " +
|
||||
"(VitalsPanel + ChatPanel + DebugPanel + SettingsPanel registered)");
|
||||
return owner;
|
||||
}
|
||||
catch (Exception failure)
|
||||
{
|
||||
if (published)
|
||||
throw;
|
||||
try
|
||||
{
|
||||
scope.RollbackAndThrow(failure);
|
||||
}
|
||||
catch (Exception rolledBack) when (!HasIncompleteCleanup(rolledBack))
|
||||
{
|
||||
_dependencies.Log(
|
||||
$"devtools: ImGui init failed: {rolledBack.Message} — devtools disabled");
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new System.Diagnostics.UnreachableException();
|
||||
}
|
||||
}
|
||||
|
||||
private DebugVM CreateDebugViewModel(IDevToolsRuntimeFacts facts)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(facts);
|
||||
return new DebugVM(
|
||||
() => facts.PlayerPosition,
|
||||
() => facts.PlayerHeadingDegrees,
|
||||
() => facts.PlayerCellId,
|
||||
() => facts.PlayerOnGround,
|
||||
() => facts.InPlayerMode,
|
||||
() => facts.InFlyMode,
|
||||
() => facts.VerticalVelocity,
|
||||
() => facts.EntityCount,
|
||||
() => facts.AnimatedCount,
|
||||
() => facts.VisibleLandblocks,
|
||||
() => facts.TotalLandblocks,
|
||||
() => facts.ShadowObjectCount,
|
||||
() => facts.NearestObjectDistance,
|
||||
() => facts.NearestObjectLabel,
|
||||
() => facts.Colliding,
|
||||
() => facts.CollisionWireframesVisible,
|
||||
() => facts.StreamingRadius,
|
||||
() => facts.MouseSensitivity,
|
||||
() => facts.ChaseDistance,
|
||||
() => facts.RmbOrbitHeld,
|
||||
() => facts.HourName,
|
||||
() => facts.DayFraction,
|
||||
() => facts.Weather,
|
||||
() => facts.ActiveLights,
|
||||
() => facts.RegisteredLights,
|
||||
() => facts.ParticleCount,
|
||||
() => facts.Fps,
|
||||
() => facts.FrameMilliseconds,
|
||||
_dependencies.Combat);
|
||||
}
|
||||
|
||||
private static bool HasIncompleteCleanup(Exception failure)
|
||||
{
|
||||
if (failure is ImGuiBootstrapperConstructionException)
|
||||
return true;
|
||||
if (failure is IRetryableResourceCleanup cleanup
|
||||
&& !cleanup.IsCleanupComplete)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (failure is AggregateException aggregate)
|
||||
return aggregate.InnerExceptions.Any(HasIncompleteCleanup);
|
||||
return failure.InnerException is { } inner && HasIncompleteCleanup(inner);
|
||||
}
|
||||
|
||||
private void Fault(SettingsDevToolsCompositionPoint point) =>
|
||||
_faultInjection?.Invoke(point);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue