feat(render): Campaign V slice V11 commit 1 - delete ImGui, Studio, and the DevTools frontend
The ImGui developer-tools stack (AcDream.UI.ImGui), UI Studio (src/AcDream.App/Studio), and the DevToolsFramePresenter/ SettingsDevToolsCompositionPhase ImGui composition machinery are removed. Vulkan never composed a DevTools frontend (DevToolsEnabled already forced false whenever the backend was Vulkan); this commit makes that permanent by deleting the only implementation rather than leaving a dead branch behind. What moved: Studio/SampleData.cs is a live production dependency (InteractionRetainedUiComposition's character-sheet fallback, plus three UI.Layout test files) - git mv'd to src/AcDream.App/UI/Layout/SampleData.cs, namespace AcDream.App.UI.Layout, and trimmed to the SampleCharacter API that is actually still called (BuildObjectTable/AddItem/AddEquipped/the item-guid and icon constants had zero callers left once the Studio fixture provider that used them was deleted). What survives as backend-neutral seams, per the tests that still exercise them: IDevToolsFrameLifecycle (moved into RenderFramePreparationController.cs, now always bound to null), IFramebufferDevToolsTarget/FramebufferDevToolsBinding in FramebufferResizeController.cs (its concrete DevToolsFramebufferTarget adapter is deleted), and IDevToolsGameplayCommands in GameplayInputCommandController.cs (DevToolsGameplayCommands becomes a documented no-op instead of forwarding to the deleted presenter). A follow-up re-homes Settings/Debug onto the retained UI through IPanelRenderer; until then keybind remapping falls back to editing keybinds.json. DevToolsEnabled is now `private const bool DevToolsEnabled = false`. RuntimeOptions.DevTools is unchanged and still reaches VulkanGraphicsContext for the optional debug-utils extensions; Program.cs now logs one line when ACDREAM_DEVTOOLS=1 explaining that the ImGui UI is gone and the flag is Vulkan-only now. Removed: AcDream.UI.ImGui (project + ImGui.NET/Silk.NET.OpenGL.Extensions.ImGui package refs), src/AcDream.App/Studio (minus SampleData.cs), DevToolsFramePresenter.cs and everything only it constructed (ISettingsDevToolsCompositionFactory, RetailSettingsDevToolsCompositionFactory, DevToolsCompositionOwner, IGameWindowSettingsDevToolsPublication, SettingsDevToolsOptionalDependencies, the "developer tools" shutdown-ledger stage and its DevTools-typed fields on IngressShutdownRoots/ RenderShutdownRoots), the ui-studio Program.cs verb, and the cimgui native manifest entries in GraphicalHostPlatformServices. GameWindow.cs's DevTools composition branch, its _vitalsVm/_debugVm/_devToolsComposition/ _devToolsFramePresenter/_devToolsCommandBus fields, and every settingsDevTools .DevTools?.* access across FrameRootComposition.cs/SessionPlayerComposition.cs are gone with it. Build green; complete Release solution suite 8,830 / 5 skips (App Tests 4,097/3 skips run standalone - one #250-family zero-allocation test flakes under the full parallel `dotnet test AcDream.slnx` run, a pre-existing, documented class unrelated to this change). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
db4426d5ef
commit
844cf092a1
48 changed files with 227 additions and 6201 deletions
|
|
@ -433,16 +433,17 @@ internal sealed class FrameRootCompositionPhase
|
|||
// Campaign V slice V6j: no debug-line renderer on the Vulkan arm.
|
||||
// DrawAndPublish flushes it INSIDE the world phase and that
|
||||
// renderer opens its own pass, which the frame's one backbuffer
|
||||
// pass forbids. The collision-wireframe toggle is DevTools-only
|
||||
// and DevTools is not composed there, so nothing is lost —
|
||||
// composing it would throw on the first wireframe frame rather
|
||||
// than silently misdraw (plan §5.5.14 item 7).
|
||||
// pass forbids. The collision-wireframe toggle was DevTools-only
|
||||
// and the ImGui DevTools frontend is gone as of Campaign V slice
|
||||
// V11, so nothing is lost — composing it would throw on the
|
||||
// first wireframe frame rather than silently misdraw (plan
|
||||
// §5.5.14 item 7).
|
||||
gl is not null ? foundation.DebugLines : null,
|
||||
d.PhysicsEngine,
|
||||
d.PlayerMode,
|
||||
d.PlayerController,
|
||||
d.DebugVmRenderFacts,
|
||||
settings.DevTools is not null);
|
||||
debugVmConsumerActive: false);
|
||||
var worldScenePasses = new WorldScenePassExecutor(
|
||||
worldPassSurface,
|
||||
worldFrameGlState,
|
||||
|
|
@ -570,11 +571,15 @@ internal sealed class FrameRootCompositionPhase
|
|||
live.PaperdollPresenter,
|
||||
live.CreatureAppraisalPresenter),
|
||||
retainedGameplayUi,
|
||||
settings.DevTools?.Presenter,
|
||||
// The ImGui developer-tools frontend was removed at Campaign V
|
||||
// slice V11; this optional hook is unbound until a follow-up
|
||||
// re-homes it onto the retained UI (IDevToolsFrameLifecycle stays
|
||||
// as the seam).
|
||||
devTools: null,
|
||||
privateScreenshot);
|
||||
var framePreparation = new RenderFramePreparationController(
|
||||
renderFrameResources,
|
||||
settings.DevTools?.Presenter,
|
||||
devTools: null,
|
||||
renderWeatherFrame);
|
||||
IRenderFramePostDiagnosticsPhase postDiagnostics =
|
||||
renderSceneShadowComparison is not null
|
||||
|
|
@ -602,8 +607,7 @@ internal sealed class FrameRootCompositionPhase
|
|||
privatePresentation,
|
||||
live.FrameDiagnostics,
|
||||
postDiagnostics,
|
||||
(IRenderFrameFailureRecovery?)settings.DevTools?.Presenter
|
||||
?? NullRenderFrameFailureRecovery.Instance);
|
||||
NullRenderFrameFailureRecovery.Instance);
|
||||
Fault(FrameRootCompositionPoint.RenderRootCreated);
|
||||
|
||||
var liveFrameCoordinator = new RetailLiveFrameCoordinator(
|
||||
|
|
|
|||
|
|
@ -426,7 +426,7 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
|
|||
d.Character.LocalPlayer,
|
||||
playerGuid: () => d.PlayerIdentity.ServerGuid,
|
||||
activeToonName: () => d.Settings.ActiveToonKey,
|
||||
fallbackSheet: Studio.SampleData.SampleCharacter,
|
||||
fallbackSheet: SampleData.SampleCharacter,
|
||||
canSendRaise: () => late.GameRuntime.IsInWorld,
|
||||
sendRaiseAttribute: (statId, cost) =>
|
||||
late.GameRuntime.Advance(
|
||||
|
|
|
|||
|
|
@ -740,15 +740,6 @@ internal sealed class SessionPlayerCompositionPhase
|
|||
d.MovementDiagnostics,
|
||||
d.Character.MovementSkills,
|
||||
d.ViewportAspect);
|
||||
if (d.SettingsDevTools.DevTools is { } devTools)
|
||||
{
|
||||
bindings.Adopt(
|
||||
"developer player mode",
|
||||
devTools.LateBindings.PlayerModeCommands.BindOwned(playerMode));
|
||||
bindings.Adopt(
|
||||
"developer command bus",
|
||||
devTools.CommandBus.BindOwned(liveSessionSource));
|
||||
}
|
||||
var playerModeAutoEntry = new PlayerModeAutoEntry(
|
||||
new LivePlayerModeAutoEntryContext(
|
||||
liveSessionSource,
|
||||
|
|
@ -832,8 +823,7 @@ internal sealed class SessionPlayerCompositionPhase
|
|||
static value => value.Dispose());
|
||||
|
||||
AcDream.UI.Abstractions.Panels.Vitals.VitalsVM? vitals =
|
||||
d.SettingsDevTools.DevTools?.Vitals
|
||||
?? interaction.RetainedUi?.Vitals;
|
||||
interaction.RetainedUi?.Vitals;
|
||||
var sessionRuntimeFactory = new LiveSessionRuntimeFactory(
|
||||
new LiveSessionPlayerRuntime(
|
||||
d.PlayerIdentity,
|
||||
|
|
@ -893,9 +883,10 @@ internal sealed class SessionPlayerCompositionPhase
|
|||
d.Options.LivePass ?? string.Empty));
|
||||
Fault(SessionPlayerCompositionPoint.SessionHostCreated);
|
||||
|
||||
Action<string>? debugToast = d.SettingsDevTools.DevTools is { } devOwner
|
||||
? message => devOwner.Debug.AddToast(message)
|
||||
: null;
|
||||
// The ImGui developer-tools debug toast sink was removed at Campaign V
|
||||
// slice V11 along with the rest of the ImGui frontend; there is no
|
||||
// replacement toast surface yet.
|
||||
Action<string>? debugToast = null;
|
||||
var combatModeOperations = new LiveCombatModeOperations(
|
||||
new LiveSessionCombatModeAuthority(sessionHost),
|
||||
new LocalPlayerCombatEquipmentSource(
|
||||
|
|
@ -954,8 +945,7 @@ internal sealed class SessionPlayerCompositionPhase
|
|||
var commands = new GameplayInputCommandController(
|
||||
new RetainedGameplayWindowCommands(
|
||||
interaction.RetainedUi?.Runtime),
|
||||
new DevToolsGameplayCommands(
|
||||
d.SettingsDevTools.DevTools?.Presenter),
|
||||
new DevToolsGameplayCommands(),
|
||||
runtimeDiagnostics,
|
||||
new PlayerModeGameplayCommands(
|
||||
d.PlayerMode,
|
||||
|
|
|
|||
|
|
@ -1,336 +1,27 @@
|
|||
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.Runtime;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Resolved display/audio/quality settings for the session. The optional
|
||||
/// ImGui developer-tools frontend that used to compose here (VitalsPanel,
|
||||
/// ChatPanel, DebugPanel, SettingsPanel via <c>AcDream.UI.ImGui</c>) was
|
||||
/// removed at Campaign V slice V11 along with the OpenGL backend it
|
||||
/// required — see docs/plans/2026-07-27-vulkan-campaign.md. A follow-up
|
||||
/// re-homes the Settings and Debug panels onto the retained UI through a new
|
||||
/// <c>IPanelRenderer</c> implementation; until then keybind remapping falls
|
||||
/// back to editing keybinds.json.
|
||||
/// </summary>
|
||||
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);
|
||||
AcDream.UI.Abstractions.Settings.QualitySettings ResolvedQuality);
|
||||
|
||||
internal sealed record SettingsDevToolsDependencies(
|
||||
IView Window,
|
||||
RuntimeSettingsController Settings,
|
||||
IRuntimeSettingsStartupTarget StartupTarget,
|
||||
HostQuiescenceGate HostQuiescence,
|
||||
GameRuntime Runtime,
|
||||
SettingsDevToolsOptionalDependencies? DevTools,
|
||||
RuntimeDiagnosticCommandSlot DiagnosticCommands,
|
||||
CombatFeedbackSlot CombatFeedback,
|
||||
KeyBindings KeyBindings,
|
||||
FrameProfiler FrameProfiler,
|
||||
FramebufferResizeController FramebufferResize,
|
||||
Action<string> Log)
|
||||
{
|
||||
public RuntimeCommunicationState Communication =>
|
||||
Runtime.CommunicationOwner;
|
||||
|
||||
public CombatState Combat => Runtime.ActionOwner.Combat;
|
||||
|
||||
public LocalPlayerState LocalPlayer => Runtime.CharacterOwner.LocalPlayer;
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
IRuntimeSettingsStartupTarget StartupTarget);
|
||||
|
||||
/// <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;
|
||||
|
||||
internal bool IsFrontendDisposalComplete => _frontendShutdown.IsComplete;
|
||||
|
||||
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.
|
||||
/// Production Phase 3: applies the resolved startup display/audio settings.
|
||||
/// </summary>
|
||||
internal sealed class SettingsDevToolsCompositionPhase :
|
||||
ISettingsDevToolsCompositionPhase<
|
||||
|
|
@ -340,20 +31,10 @@ internal sealed class SettingsDevToolsCompositionPhase :
|
|||
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)
|
||||
public SettingsDevToolsCompositionPhase(SettingsDevToolsDependencies dependencies)
|
||||
{
|
||||
_dependencies = dependencies ?? throw new ArgumentNullException(nameof(dependencies));
|
||||
_publication = publication ?? throw new ArgumentNullException(nameof(publication));
|
||||
_factory = factory ?? new RetailSettingsDevToolsCompositionFactory();
|
||||
_faultInjection = faultInjection;
|
||||
}
|
||||
|
||||
public SettingsDevToolsResult Compose(
|
||||
|
|
@ -366,282 +47,6 @@ internal sealed class SettingsDevToolsCompositionPhase :
|
|||
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);
|
||||
return new SettingsDevToolsResult(_dependencies.Settings.ResolvedQuality);
|
||||
}
|
||||
|
||||
private DevToolsCompositionOwner? ComposeOptionalDevTools(
|
||||
GameWindowPlatformResult<GameWindowGraphics, 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",
|
||||
// ImGui is a GL-only frontend and is not ported to Vulkan — the
|
||||
// campaign deletes it at slice V11 — so a Vulkan host composes
|
||||
// no DevTools at all and never reaches here.
|
||||
() => _factory.CreateBootstrap(
|
||||
platform.Graphics.RequireGl("developer UI"),
|
||||
_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.Communication.Chat,
|
||||
commandTargets:
|
||||
_dependencies.Communication.CommandTargets)
|
||||
{
|
||||
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