using System.Reflection; using System.Runtime.Loader; namespace Engine.Kernel.Plugins; /// /// The collectible ALC a plugin's implementation assembly loads into. See /// docs/kernel-contract.md ยง4. /// /// The one thing this has to get right: must defer to /// whatever's already sitting in the Default ALC โ€” Engine.Kernel, and this /// plugin's own Contracts assembly, both loaded there before this context /// exists โ€” rather than loading a second copy of either. A second copy /// would carry its own distinct runtime Type objects, and every /// is T / GetComponent<T>() check across the plugin /// boundary would silently fail. Only a genuinely private dependency of /// this specific plugin should ever load through this context. /// internal sealed class PluginLoadContext : AssemblyLoadContext { private readonly AssemblyDependencyResolver _resolver; public PluginLoadContext(string pluginId, string mainAssemblyPath) : base(name: $"plugin:{pluginId}", isCollectible: true) { _resolver = new AssemblyDependencyResolver(mainAssemblyPath); } protected override Assembly? Load(AssemblyName assemblyName) { var alreadyInDefault = Default.Assemblies .Any(a => a.GetName().Name == assemblyName.Name); if (alreadyInDefault) return null; // defer โ€” the runtime resolves this to the Default copy var path = _resolver.ResolveAssemblyToPath(assemblyName); return path is not null ? LoadFromAssemblyPath(path) : null; } protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) { var path = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); return path is not null ? LoadUnmanagedDllFromPath(path) : IntPtr.Zero; } }