Make DI services scoped

This commit is contained in:
Matt Parker
2025-10-18 16:20:57 +10:00
parent d90f0e6c28
commit 69d72307f0
4 changed files with 43 additions and 24 deletions

View File

@@ -14,18 +14,19 @@ public class InjectAttribute : Attribute;
public partial class DiAutoload : Node public partial class DiAutoload : Node
{ {
private ServiceProvider? _serviceProvider; private ServiceProvider? _serviceProvider;
private IServiceScope? _currentScope;
public override void _EnterTree() public override void _EnterTree()
{ {
GD.Print("[Injector] _EnterTree called"); GD.Print("[Injector] _EnterTree called");
var services = new ServiceCollection(); var services = new ServiceCollection();
// Register services here // Register services here
services.AddSingleton<BuildService>(); services.AddScoped<BuildService>();
services.AddSingleton<RunService>(); services.AddScoped<RunService>();
services.AddSingleton<IdeFileExternalChangeHandler>(); services.AddScoped<IdeFileExternalChangeHandler>();
services.AddSingleton<IdeFileSavedToDiskHandler>(); services.AddScoped<IdeFileSavedToDiskHandler>();
services.AddSingleton<IdeFileWatcher>(); services.AddScoped<IdeFileWatcher>();
services.AddSingleton<IdeOpenTabsFileManager>(); services.AddScoped<IdeOpenTabsFileManager>();
_serviceProvider = services.BuildServiceProvider(); _serviceProvider = services.BuildServiceProvider();
GetTree().NodeAdded += OnNodeAdded; GetTree().NodeAdded += OnNodeAdded;
@@ -37,6 +38,13 @@ public partial class DiAutoload : Node
} }
/// The solution has changed, so reset the scope to get new services
public void ResetScope()
{
_currentScope?.Dispose();
_currentScope = _serviceProvider!.CreateScope();
}
private void OnNodeAdded(Node node) private void OnNodeAdded(Node node)
{ {
// Inject dependencies into every new node // Inject dependencies into every new node
@@ -52,11 +60,18 @@ public partial class DiAutoload : Node
{ {
if (Attribute.IsDefined(field, typeof(InjectAttribute))) if (Attribute.IsDefined(field, typeof(InjectAttribute)))
{ {
var service = _serviceProvider!.GetService(field.FieldType); if (_currentScope is null)
{
GD.PrintErr("[Injector] _currentScope was null when attempting to resolve service");
GetTree().Quit();
return;
}
var service = _currentScope!.ServiceProvider.GetService(field.FieldType);
if (service is null) if (service is null)
{ {
GD.PrintErr($"[Injector] No service registered for {field.FieldType}"); GD.PrintErr($"[Injector] No service registered for {field.FieldType}");
GetTree().Quit(); GetTree().Quit();
return;
} }
field.SetValue(target, service); field.SetValue(target, service);

View File

@@ -1,7 +1,10 @@
using Godot; using Godot;
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence; using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
using SharpIDE.Godot.Features.Build;
using SharpIDE.Godot.Features.Debug_;
using SharpIDE.Godot.Features.IdeDiagnostics; using SharpIDE.Godot.Features.IdeDiagnostics;
using SharpIDE.Godot.Features.Problems; using SharpIDE.Godot.Features.Problems;
using SharpIDE.Godot.Features.Run;
namespace SharpIDE.Godot.Features.BottomPanel; namespace SharpIDE.Godot.Features.BottomPanel;
@@ -17,9 +20,9 @@ public partial class BottomPanelManager : Panel
} }
} }
private Control _runPanel = null!; private RunPanel _runPanel = null!;
private Control _debugPanel = null!; private DebugPanel _debugPanel = null!;
private Control _buildPanel = null!; private BuildPanel _buildPanel = null!;
private ProblemsPanel _problemsPanel = null!; private ProblemsPanel _problemsPanel = null!;
private IdeDiagnosticsPanel _ideDiagnosticsPanel = null!; private IdeDiagnosticsPanel _ideDiagnosticsPanel = null!;
@@ -27,9 +30,9 @@ public partial class BottomPanelManager : Panel
public override void _Ready() public override void _Ready()
{ {
_runPanel = GetNode<Control>("%RunPanel"); _runPanel = GetNode<RunPanel>("%RunPanel");
_debugPanel = GetNode<Control>("%DebugPanel"); _debugPanel = GetNode<DebugPanel>("%DebugPanel");
_buildPanel = GetNode<Control>("%BuildPanel"); _buildPanel = GetNode<BuildPanel>("%BuildPanel");
_problemsPanel = GetNode<ProblemsPanel>("%ProblemsPanel"); _problemsPanel = GetNode<ProblemsPanel>("%ProblemsPanel");
_ideDiagnosticsPanel = GetNode<IdeDiagnosticsPanel>("%IdeDiagnosticsPanel"); _ideDiagnosticsPanel = GetNode<IdeDiagnosticsPanel>("%IdeDiagnosticsPanel");

View File

@@ -42,6 +42,7 @@ public partial class IdeRoot : Control
[Inject] private readonly IdeFileExternalChangeHandler _fileExternalChangeHandler = null!; [Inject] private readonly IdeFileExternalChangeHandler _fileExternalChangeHandler = null!;
[Inject] private readonly IdeFileWatcher _fileWatcher = null!; [Inject] private readonly IdeFileWatcher _fileWatcher = null!;
[Inject] private readonly BuildService _buildService = null!; [Inject] private readonly BuildService _buildService = null!;
[Inject] private readonly IdeOpenTabsFileManager _openTabsFileManager = null!;
public override void _EnterTree() public override void _EnterTree()
{ {
@@ -52,7 +53,8 @@ public partial class IdeRoot : Control
public override void _ExitTree() public override void _ExitTree()
{ {
_fileWatcher.Dispose(); _fileWatcher?.Dispose();
GetTree().GetRoot().FocusExited -= OnFocusExited;
} }
public override void _Ready() public override void _Ready()
@@ -80,8 +82,15 @@ public partial class IdeRoot : Control
_cleanSlnButton.Pressed += OnCleanSlnButtonPressed; _cleanSlnButton.Pressed += OnCleanSlnButtonPressed;
_restoreSlnButton.Pressed += OnRestoreSlnButtonPressed; _restoreSlnButton.Pressed += OnRestoreSlnButtonPressed;
GodotGlobalEvents.Instance.BottomPanelVisibilityChangeRequested.Subscribe(async show => await this.InvokeAsync(() => _invertedVSplitContainer.InvertedSetCollapsed(!show))); GodotGlobalEvents.Instance.BottomPanelVisibilityChangeRequested.Subscribe(async show => await this.InvokeAsync(() => _invertedVSplitContainer.InvertedSetCollapsed(!show)));
GetTree().GetRoot().FocusExited += OnFocusExited;
_nodeReadyTcs.SetResult(); _nodeReadyTcs.SetResult();
} }
// TODO: Problematic, as this is called even when the focus shifts to an embedded subwindow, such as a popup
private void OnFocusExited()
{
_ = Task.GodotRun(async () => await _openTabsFileManager.SaveAllOpenFilesAsync());
}
private void OnRunMenuButtonPressed() private void OnRunMenuButtonPressed()
{ {

View File

@@ -21,8 +21,6 @@ public partial class IdeWindow : Control
private IdeRoot? _ideRoot; private IdeRoot? _ideRoot;
private SlnPicker? _slnPicker; private SlnPicker? _slnPicker;
[Inject] private readonly IdeOpenTabsFileManager _openTabsFileManager = null!;
public override void _Ready() public override void _Ready()
{ {
GD.Print("IdeWindow _Ready called"); GD.Print("IdeWindow _Ready called");
@@ -31,7 +29,6 @@ public partial class IdeWindow : Control
MSBuildLocator.RegisterDefaults(); MSBuildLocator.RegisterDefaults();
GodotServiceDefaults.AddServiceDefaults(); GodotServiceDefaults.AddServiceDefaults();
Singletons.AppState = AppStateLoader.LoadAppStateFromConfigFile(); Singletons.AppState = AppStateLoader.LoadAppStateFromConfigFile();
GetWindow().FocusExited += OnFocusExited;
//GetWindow().SetMinSize(new Vector2I(1152, 648)); //GetWindow().SetMinSize(new Vector2I(1152, 648));
Callable.From(() => PickSolution(true)).CallDeferred(); Callable.From(() => PickSolution(true)).CallDeferred();
} }
@@ -47,12 +44,6 @@ public partial class IdeWindow : Control
// PrintOrphanNodes(); // PrintOrphanNodes();
} }
// TODO: Problematic, as this is called even when the focus shifts to an embedded subwindow, such as a popup
private void OnFocusExited()
{
_ = Task.GodotRun(async () => await _openTabsFileManager.SaveAllOpenFilesAsync());
}
public void PickSolution(bool fullscreen = false) public void PickSolution(bool fullscreen = false)
{ {
if (_slnPicker is not null) throw new InvalidOperationException("Solution picker is already active"); if (_slnPicker is not null) throw new InvalidOperationException("Solution picker is already active");
@@ -109,7 +100,8 @@ public partial class IdeWindow : Control
{ {
GetWindow().Mode = Window.ModeEnum.Maximized; GetWindow().Mode = Window.ModeEnum.Maximized;
} }
_ideRoot = ideRoot; _ideRoot = ideRoot; // This has no DI services, until it is added to the scene tree
GetNode<DiAutoload>("/root/DiAutoload").ResetScope();
AddChild(ideRoot); AddChild(ideRoot);
}); });
}); });