Files
IC.Rebuild/Core/ModuleManager.cs
T

100 lines
4.4 KiB
C#

using System.Collections.Concurrent;
namespace ICRebuild.Core;
// Every resource is registered before activation, including during partial startup.
public sealed class ResourceScope : IDisposable
{
private readonly Stack<IDisposable> resources = new();
private bool disposed;
public T Own<T>(T resource) where T : IDisposable
{
ObjectDisposedException.ThrowIf(disposed, this);
resources.Push(resource);
return resource;
}
public void Defer(Action action) => Own(new ActionDisposable(action));
public void Dispose()
{
if (disposed) return;
disposed = true;
List<Exception> errors = [];
while (resources.TryPop(out var resource))
try { resource.Dispose(); } catch (Exception ex) { errors.Add(ex); }
if (errors.Count > 0) throw new AggregateException("资源释放失败", errors);
}
}
public sealed class ActionDisposable(Action action) : IDisposable
{
private Action? release = action;
public void Dispose() => Interlocked.Exchange(ref release, null)?.Invoke();
}
public sealed class ModuleManager : IDisposable
{
private readonly Dictionary<int, Action<ResourceScope>> factories = [];
private readonly ConcurrentDictionary<int, ResourceScope> active = [];
private readonly Dictionary<int, int[]> dependencies = [];
private readonly Dictionary<int, string> unavailable = [];
private readonly HashSet<int> cleanupFailed = [];
private bool disposed;
public Dictionary<int, ModuleStatus> Status { get; } = Features.All.ToDictionary(f => f.Id, f => new ModuleStatus(f));
public bool IsEnabled(int id) => active.ContainsKey(id);
public void Register(int id, Action<ResourceScope> start, params int[] required)
{
factories.Add(id, start); dependencies[id] = required;
Status[id].HasImplementation=true;
Status[id].Detail = "已关闭";
}
public void Unavailable(int id, string reason)
{
unavailable[id] = reason;
if (Status.TryGetValue(id, out var state)) state.Detail = reason;
}
public void ClearUnavailable(int id) => unavailable.Remove(id);
public bool Enable(int id)
{
ObjectDisposedException.ThrowIf(disposed, this);
if (!Status.TryGetValue(id, out var state)) return false;
if (cleanupFailed.Contains(id)) { state.Detail="上次资源释放失败,重载插件前禁止重复挂接"; return false; }
if (unavailable.TryGetValue(id, out var blockedReason)) { state.State = ModuleState.Failed; state.Detail = blockedReason; return false; }
state.Requested = true;
if (active.ContainsKey(id)) return true;
if (!factories.TryGetValue(id, out var start)) return false;
var missing = dependencies[id].Where(x => !IsEnabled(x)).ToArray();
if (missing.Length > 0)
{ state.State = ModuleState.Failed; state.Detail = $"先启用依赖:{string.Join(", ", missing)}"; return false; }
var scope = new ResourceScope();
try
{
start(scope);
if (!active.TryAdd(id, scope)) throw new InvalidOperationException("模块已经启用");
state.State = ModuleState.Enabled;
if(state.Adaptation!=Adaptation.Verified) state.Adaptation = Adaptation.Testable;
state.Detail = "已开启";
return true;
}
catch (Exception ex)
{
try { scope.Dispose(); } catch (Exception cleanup) { cleanupFailed.Add(id); ex = new AggregateException(ex, cleanup); }
state.State = ModuleState.Failed; state.Detail = ex.Message;
return false;
}
}
public void Disable(int id)
{
if (!Status.TryGetValue(id, out var state)) return;
foreach (var dependent in dependencies.Where(x => x.Value.Contains(id) && IsEnabled(x.Key)).Select(x => x.Key).ToArray())
Disable(dependent);
state.Requested = false;
if (!active.TryRemove(id, out var scope)) return;
try { scope.Dispose(); state.State = ModuleState.Disabled; state.Detail = "已关闭"; }
catch (Exception ex) { cleanupFailed.Add(id); state.State = ModuleState.Failed; state.Detail = $"卸载失败:{ex.Message}"; }
}
public void DisableAll() { foreach (var id in active.Keys.Reverse().ToArray()) Disable(id); }
public void Dispose() { if (disposed) return; DisableAll(); disposed = true; }
}