Skip to content

Commit c1102db

Browse files
committed
2 parents 79f3c9c + 2483f34 commit c1102db

File tree

3 files changed

+240
-10
lines changed

3 files changed

+240
-10
lines changed

managed/managed.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
4747
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
4848
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
49+
<PackageReference Include="Mono.Cecil" Version="0.11.6" />
4950
<PackageReference Include="MySqlConnector" Version="2.4.0" />
5051
<PackageReference Include="Npgsql" Version="9.0.3" />
5152
<PackageReference Include="Spectre.Console" Version="0.51.1" />
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
using Mono.Cecil;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace SwiftlyS2.Core.Modules.Plugins;
5+
6+
internal class DependencyResolver
7+
{
8+
private readonly ILogger _logger;
9+
private readonly Dictionary<string, List<string>> _dependencyGraph = new();
10+
private readonly Dictionary<string, string> _pluginPaths = new();
11+
12+
public DependencyResolver(ILogger logger)
13+
{
14+
_logger = logger;
15+
}
16+
17+
public void AnalyzeDependencies(IEnumerable<string> pluginDirectories)
18+
{
19+
_dependencyGraph.Clear();
20+
_pluginPaths.Clear();
21+
22+
var exportAssemblies = new Dictionary<string, string>(); // assemblyName -> path
23+
24+
foreach (var pluginDir in pluginDirectories)
25+
{
26+
var exportDir = Path.Combine(pluginDir, "resources", "exports");
27+
if (Directory.Exists(exportDir))
28+
{
29+
var exportFiles = Directory.GetFiles(exportDir, "*.dll");
30+
foreach (var exportFile in exportFiles)
31+
{
32+
try
33+
{
34+
using var assembly = AssemblyDefinition.ReadAssembly(exportFile);
35+
var assemblyName = assembly.Name.Name;
36+
exportAssemblies[assemblyName] = exportFile;
37+
_pluginPaths[assemblyName] = exportFile;
38+
39+
if (!_dependencyGraph.ContainsKey(assemblyName))
40+
{
41+
_dependencyGraph[assemblyName] = new List<string>();
42+
}
43+
44+
_logger.LogDebug($"Found export assembly: {assemblyName} at {exportFile}");
45+
}
46+
catch (Exception ex)
47+
{
48+
_logger.LogWarning(ex, $"Failed to read assembly {exportFile}");
49+
}
50+
}
51+
}
52+
}
53+
54+
foreach (var (assemblyName, assemblyPath) in exportAssemblies)
55+
{
56+
try
57+
{
58+
using var assembly = AssemblyDefinition.ReadAssembly(assemblyPath);
59+
var dependencies = new List<string>();
60+
61+
foreach (var reference in assembly.MainModule.AssemblyReferences)
62+
{
63+
var refName = reference.Name;
64+
65+
if (exportAssemblies.ContainsKey(refName))
66+
{
67+
dependencies.Add(refName);
68+
_logger.LogDebug($"{assemblyName} depends on {refName}");
69+
}
70+
}
71+
72+
_dependencyGraph[assemblyName] = dependencies;
73+
}
74+
catch (Exception ex)
75+
{
76+
_logger.LogWarning(ex, $"Failed to analyze dependencies for {assemblyName}");
77+
}
78+
}
79+
}
80+
81+
public List<string> GetLoadOrder()
82+
{
83+
var result = new List<string>();
84+
var visited = new HashSet<string>();
85+
var visiting = new HashSet<string>();
86+
87+
foreach (var assembly in _dependencyGraph.Keys)
88+
{
89+
if (!visited.Contains(assembly))
90+
{
91+
TopologicalSort(assembly, visited, visiting, result);
92+
}
93+
}
94+
95+
return result.Select(name => _pluginPaths[name]).ToList();
96+
}
97+
98+
private void TopologicalSort(
99+
string assembly,
100+
HashSet<string> visited,
101+
HashSet<string> visiting,
102+
List<string> result)
103+
{
104+
if (visiting.Contains(assembly))
105+
{
106+
var cycle = BuildCyclePath(assembly, visiting);
107+
throw new InvalidOperationException(
108+
$"Circular dependency detected: {cycle}");
109+
}
110+
111+
if (visited.Contains(assembly))
112+
{
113+
return;
114+
}
115+
116+
visiting.Add(assembly);
117+
118+
if (_dependencyGraph.TryGetValue(assembly, out var dependencies))
119+
{
120+
foreach (var dependency in dependencies)
121+
{
122+
TopologicalSort(dependency, visited, visiting, result);
123+
}
124+
}
125+
126+
visiting.Remove(assembly);
127+
visited.Add(assembly);
128+
result.Add(assembly);
129+
}
130+
private string BuildCyclePath(string start, HashSet<string> visiting)
131+
{
132+
var path = new List<string> { start };
133+
var current = start;
134+
135+
if (_dependencyGraph.TryGetValue(current, out var deps))
136+
{
137+
foreach (var dep in deps)
138+
{
139+
if (visiting.Contains(dep))
140+
{
141+
path.Add(dep);
142+
if (dep == start)
143+
{
144+
break;
145+
}
146+
current = dep;
147+
}
148+
}
149+
}
150+
151+
return string.Join(" -> ", path);
152+
}
153+
154+
public string GetDependencyGraphVisualization()
155+
{
156+
var lines = new List<string> { "Dependency Graph:" };
157+
158+
foreach (var (assembly, dependencies) in _dependencyGraph.OrderBy(x => x.Key))
159+
{
160+
if (dependencies.Any())
161+
{
162+
lines.Add($" {assembly} -> [{string.Join(", ", dependencies)}]");
163+
}
164+
else
165+
{
166+
lines.Add($" {assembly} (no dependencies)");
167+
}
168+
}
169+
170+
return string.Join(Environment.NewLine, lines);
171+
}
172+
}
173+

managed/src/SwiftlyS2.Core/Modules/Plugins/PluginManager.cs

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ RootDirService rootDirService
4747
}
4848

4949
public void Initialize() {
50-
LoadContracts();
50+
LoadExports();
5151
LoadPlugins();
5252
}
5353

@@ -83,20 +83,76 @@ public void HandlePluginChange(object sender, FileSystemEventArgs e)
8383
}
8484
}
8585

86-
private void LoadContracts()
86+
private void LoadExports()
8787
{
8888
var pluginDirs = Directory.GetDirectories(_RootDirService.GetPluginsRoot());
8989

90-
foreach (var pluginDir in pluginDirs) {
91-
var pluginName = Path.GetFileName(pluginDir);
92-
var contractPath = Path.Combine(pluginDir, pluginName + ".Contracts.dll");
93-
if (File.Exists(contractPath)) {
94-
var assembly = Assembly.LoadFrom(Path.Combine(pluginDir, pluginName + ".Contracts.dll"));
95-
var contracts = assembly.GetTypes();
96-
foreach (var contract in contracts) {
97-
_SharedTypes.Add(contract);
90+
var resolver = new DependencyResolver(_Logger);
91+
92+
try
93+
{
94+
resolver.AnalyzeDependencies(pluginDirs);
95+
96+
_Logger.LogInformation(resolver.GetDependencyGraphVisualization());
97+
98+
var loadOrder = resolver.GetLoadOrder();
99+
100+
_Logger.LogInformation($"Loading {loadOrder.Count} export assemblies in dependency order.");
101+
102+
foreach (var exportFile in loadOrder)
103+
{
104+
try
105+
{
106+
var assembly = Assembly.LoadFrom(exportFile);
107+
var exports = assembly.GetTypes();
108+
109+
_Logger.LogDebug($"Loaded {exports.Length} types from {Path.GetFileName(exportFile)}.");
110+
111+
112+
foreach (var export in exports)
113+
{
114+
_SharedTypes.Add(export);
115+
}
116+
}
117+
catch (Exception ex)
118+
{
119+
_Logger.LogWarning(ex, $"Failed to load export assembly: {exportFile}");
98120
}
99121
}
122+
123+
_Logger.LogInformation($"Successfully loaded {_SharedTypes.Count} shared types.");
124+
}
125+
catch (InvalidOperationException ex) when (ex.Message.Contains("Circular dependency"))
126+
{
127+
_Logger.LogError(ex, "Circular dependency detected in plugin exports. Loading exports without dependency resolution.");
128+
129+
foreach (var pluginDir in pluginDirs)
130+
{
131+
if (Directory.Exists(Path.Combine(pluginDir, "resources", "exports")))
132+
{
133+
var exportFiles = Directory.GetFiles(Path.Combine(pluginDir, "resources", "exports"), "*.dll");
134+
foreach (var exportFile in exportFiles)
135+
{
136+
try
137+
{
138+
var assembly = Assembly.LoadFrom(exportFile);
139+
var exports = assembly.GetTypes();
140+
foreach (var export in exports)
141+
{
142+
_SharedTypes.Add(export);
143+
}
144+
}
145+
catch (Exception innerEx)
146+
{
147+
_Logger.LogWarning(innerEx, $"Failed to load export assembly: {exportFile}");
148+
}
149+
}
150+
}
151+
}
152+
}
153+
catch (Exception ex)
154+
{
155+
_Logger.LogError(ex, "Unexpected error during export loading");
100156
}
101157
}
102158

0 commit comments

Comments
 (0)