feat: add NuGet and Godot distribution
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
|
||||
namespace ShrinkModFramework.Godot;
|
||||
|
||||
public sealed class ShrinkGodotModLoader : IDisposable
|
||||
{
|
||||
private sealed record LoadedMod(AssemblyLoadContext LoadContext, WeakReference UnloadReference,
|
||||
ShrinkModContext Context, IReadOnlyList<IShrinkMod> Instances);
|
||||
private readonly Dictionary<string, LoadedMod> _loaded = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public IReadOnlyCollection<string> LoadedAssemblyPaths => _loaded.Keys;
|
||||
|
||||
public IReadOnlyList<IShrinkMod> Load(string assemblyPath)
|
||||
{
|
||||
assemblyPath = Path.GetFullPath(assemblyPath);
|
||||
if (_loaded.ContainsKey(assemblyPath)) throw new InvalidOperationException($"Mod assembly is already loaded: {assemblyPath}");
|
||||
var loadContext = new AssemblyLoadContext($"ShrinkMod:{Path.GetFileNameWithoutExtension(assemblyPath)}", true);
|
||||
loadContext.Resolving += (_, name) => ResolveDependency(loadContext, Path.GetDirectoryName(assemblyPath)!, name);
|
||||
try
|
||||
{
|
||||
var assembly = loadContext.LoadFromAssemblyPath(assemblyPath);
|
||||
var entries = assembly.GetTypes().Where(type => !type.IsAbstract && typeof(IShrinkMod).IsAssignableFrom(type) &&
|
||||
type.GetCustomAttribute<ShrinkModAttribute>() != null).OrderBy(type => type.GetCustomAttribute<ShrinkModAttribute>()!.LoadOrder).ToArray();
|
||||
var context = new ShrinkModContext(Path.GetDirectoryName(assemblyPath)!);
|
||||
var instances = entries.Select(type => (IShrinkMod)(Activator.CreateInstance(type) ??
|
||||
throw new InvalidOperationException($"Failed to create mod entry: {type.FullName}"))).ToArray();
|
||||
foreach (var mod in instances) mod.OnConstruct(context);
|
||||
foreach (var mod in instances) mod.OnRegisterContent(context);
|
||||
foreach (var mod in instances) mod.OnInitialize(context);
|
||||
foreach (var mod in instances) mod.OnReady(context);
|
||||
_loaded[assemblyPath] = new LoadedMod(loadContext, new WeakReference(loadContext), context, instances);
|
||||
return instances;
|
||||
}
|
||||
catch { loadContext.Unload(); throw; }
|
||||
}
|
||||
|
||||
public bool Unload(string assemblyPath)
|
||||
{
|
||||
assemblyPath = Path.GetFullPath(assemblyPath);
|
||||
if (!_loaded.Remove(assemblyPath, out var loaded)) return false;
|
||||
for (var index = loaded.Instances.Count - 1; index >= 0; index--)
|
||||
if (loaded.Instances[index] is IShrinkModUnload unload) unload.OnUnload(loaded.Context);
|
||||
loaded.LoadContext.Unload();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var path in _loaded.Keys.ToArray()) Unload(path);
|
||||
}
|
||||
|
||||
private static Assembly? ResolveDependency(AssemblyLoadContext context, string directory, AssemblyName name)
|
||||
{
|
||||
var shared = AssemblyLoadContext.Default.Assemblies.FirstOrDefault(assembly => assembly.GetName().Name == name.Name);
|
||||
if (shared != null) return shared;
|
||||
var path = Path.Combine(directory, name.Name + ".dll");
|
||||
return File.Exists(path) ? context.LoadFromAssemblyPath(path) : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AssemblyName>ShrinkModFramework.Godot</AssemblyName>
|
||||
<RootNamespace>ShrinkModFramework.Godot</RootNamespace>
|
||||
<PackageId>ShrinkSDK.ModFramework.Godot</PackageId>
|
||||
<Version>0.1.0</Version>
|
||||
<Description>Collectible AssemblyLoadContext host for ShrinkSDK mods in Godot.</Description>
|
||||
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DotNet~\ShrinkSDK.ModFramework.csproj" />
|
||||
<PackageReference Include="ShrinkSDK.Godot" Version="0.1.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user