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
{
private ServiceProvider? _serviceProvider;
private IServiceScope? _currentScope;
public override void _EnterTree()
{
GD.Print("[Injector] _EnterTree called");
var services = new ServiceCollection();
// Register services here
services.AddSingleton<BuildService>();
services.AddSingleton<RunService>();
services.AddSingleton<IdeFileExternalChangeHandler>();
services.AddSingleton<IdeFileSavedToDiskHandler>();
services.AddSingleton<IdeFileWatcher>();
services.AddSingleton<IdeOpenTabsFileManager>();
services.AddScoped<BuildService>();
services.AddScoped<RunService>();
services.AddScoped<IdeFileExternalChangeHandler>();
services.AddScoped<IdeFileSavedToDiskHandler>();
services.AddScoped<IdeFileWatcher>();
services.AddScoped<IdeOpenTabsFileManager>();
_serviceProvider = services.BuildServiceProvider();
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)
{
// Inject dependencies into every new node
@@ -52,11 +60,18 @@ public partial class DiAutoload : Node
{
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)
{
GD.PrintErr($"[Injector] No service registered for {field.FieldType}");
GetTree().Quit();
return;
}
field.SetValue(target, service);

View File

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

View File

@@ -42,6 +42,7 @@ public partial class IdeRoot : Control
[Inject] private readonly IdeFileExternalChangeHandler _fileExternalChangeHandler = null!;
[Inject] private readonly IdeFileWatcher _fileWatcher = null!;
[Inject] private readonly BuildService _buildService = null!;
[Inject] private readonly IdeOpenTabsFileManager _openTabsFileManager = null!;
public override void _EnterTree()
{
@@ -52,7 +53,8 @@ public partial class IdeRoot : Control
public override void _ExitTree()
{
_fileWatcher.Dispose();
_fileWatcher?.Dispose();
GetTree().GetRoot().FocusExited -= OnFocusExited;
}
public override void _Ready()
@@ -80,8 +82,15 @@ public partial class IdeRoot : Control
_cleanSlnButton.Pressed += OnCleanSlnButtonPressed;
_restoreSlnButton.Pressed += OnRestoreSlnButtonPressed;
GodotGlobalEvents.Instance.BottomPanelVisibilityChangeRequested.Subscribe(async show => await this.InvokeAsync(() => _invertedVSplitContainer.InvertedSetCollapsed(!show)));
GetTree().GetRoot().FocusExited += OnFocusExited;
_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()
{

View File

@@ -21,8 +21,6 @@ public partial class IdeWindow : Control
private IdeRoot? _ideRoot;
private SlnPicker? _slnPicker;
[Inject] private readonly IdeOpenTabsFileManager _openTabsFileManager = null!;
public override void _Ready()
{
GD.Print("IdeWindow _Ready called");
@@ -31,7 +29,6 @@ public partial class IdeWindow : Control
MSBuildLocator.RegisterDefaults();
GodotServiceDefaults.AddServiceDefaults();
Singletons.AppState = AppStateLoader.LoadAppStateFromConfigFile();
GetWindow().FocusExited += OnFocusExited;
//GetWindow().SetMinSize(new Vector2I(1152, 648));
Callable.From(() => PickSolution(true)).CallDeferred();
}
@@ -47,12 +44,6 @@ public partial class IdeWindow : Control
// 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)
{
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;
}
_ideRoot = ideRoot;
_ideRoot = ideRoot; // This has no DI services, until it is added to the scene tree
GetNode<DiAutoload>("/root/DiAutoload").ResetScope();
AddChild(ideRoot);
});
});