Skip to content

Commit

Permalink
Basic plugin loading implemented.
Browse files Browse the repository at this point in the history
  • Loading branch information
steviegt6 committed Dec 7, 2021
1 parent 1a2706b commit 39c138e
Show file tree
Hide file tree
Showing 11 changed files with 283 additions and 1 deletion.
4 changes: 3 additions & 1 deletion Aurora.Desktop.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"Aurora.Desktop\\Aurora.Desktop.csproj",
"Aurora.Game\\Aurora.Game.csproj",
"Aurora.Game.Tests\\Aurora.Game.Tests.csproj",
"Aurora.Resources\\Aurora.Resources.csproj"
"Aurora.Resources\\Aurora.Resources.csproj",
"Aurora.Game.Plugins.LunarClient\\Aurora.Game.Plugins.LunarClient.csproj",
"Aurora.Game.Plugins.LunarClient.Test\\Aurora.Game.Plugins.LunarClient.Test.csproj"
]
}
}
1 change: 1 addition & 0 deletions Aurora.Desktop/Aurora.Desktop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\Aurora.Game.Plugins.LunarClient\Aurora.Game.Plugins.LunarClient.csproj" />
<ProjectReference Include="..\Aurora.Game\Aurora.Game.csproj" />
</ItemGroup>
<ItemGroup Label="Resources">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<GenerateProgramFile>false</GenerateProgramFile>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\Aurora.Game.Plugins.LunarClient\Aurora.Game.Plugins.LunarClient.csproj" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Aurora.Game\Aurora.Game.csproj" />
</ItemGroup>

</Project>
9 changes: 9 additions & 0 deletions Aurora.Game.Plugins.LunarClient/LunarClientPlugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Aurora.Game.API;

namespace Aurora.Game.Plugins.LunarClient
{
public class LunarClientPlugin : Plugin
{
public override PluginType PluginType => PluginType.LauncherContent;
}
}
7 changes: 7 additions & 0 deletions Aurora.Game/API/Plugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Aurora.Game.API
{
public abstract class Plugin
{
public abstract PluginType PluginType { get; }
}
}
144 changes: 144 additions & 0 deletions Aurora.Game/API/PluginLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using osu.Framework;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Logging;
using osu.Framework.Platform;

namespace Aurora.Game.API
{
/// <summary>
/// Instanced plugin loading class.
/// </summary>
public class PluginLoader : IDisposable
{
public const string PLUGIN_LIBRARY_PREFIX = "Aurora.Game.Plugins";

public readonly Dictionary<Assembly, Type> LoadedAssemblies = new();

public List<Plugin> LoadedPlugins = new();

private readonly Storage? storage;

public PluginLoader(Storage? storage)
{
this.storage = storage;
}

public void LoadPlugins()
{
loadFromDisk();

AppDomain.CurrentDomain.AssemblyResolve += resolvePluginDependencyAssembly;

Storage? pluginStorage = storage?.GetStorageForDirectory("plugins");

if (pluginStorage != null)
loadUserPlugins(pluginStorage);

LoadedPlugins.Clear();
LoadedPlugins.AddRange(LoadedAssemblies.Values
.Select(x => Activator.CreateInstance(x) as Plugin)
.Where(x => x is not null)
.Select(x => x.AsNonNull())
.ToList());
}

private void loadFromDisk()
{
try
{
string[] files = Directory.GetFiles(RuntimeInfo.StartupDirectory, $"{PLUGIN_LIBRARY_PREFIX}.*.dll");

foreach (string file in files.Where(x => !Path.GetFileName(x).Contains("Tests")))
loadPluginFromFile(file);
}
catch (Exception e)
{
Logger.Error(e, $"Could not load plug-in from directory {RuntimeInfo.StartupDirectory}");
}
}

private void loadPluginFromFile(string file)
{
string fileName = Path.GetFileNameWithoutExtension(file);

if (LoadedAssemblies.Values.Any(x => Path.GetFileNameWithoutExtension(x.Assembly.Location) == fileName))
return;

try
{
addPlugin(Assembly.LoadFrom(file));
}
catch (Exception e)
{
Logger.Error(e, $"Failed to load plug-in {fileName}");
}
}

private void addPlugin(Assembly assembly)
{
if (LoadedAssemblies.ContainsKey(assembly))
return;

if (LoadedAssemblies.Any(x => x.Key.FullName == assembly.FullName))
return;

try
{
LoadedAssemblies[assembly] = assembly.GetTypes().First(x =>
x.IsPublic &&
x.IsSubclassOf(typeof(Plugin)) &&
!x.IsAbstract &&
x.GetConstructor(Array.Empty<Type>()) != null
);
}
catch (Exception e)
{
Logger.Error(e, $"Failed to add plug-in {assembly}");
}
}

private Assembly? resolvePluginDependencyAssembly(object? sender, ResolveEventArgs args)
{
AssemblyName asm = new(args.Name);

Assembly? domainAssembly = AppDomain.CurrentDomain.GetAssemblies()
.Where(x =>
{
string? name = x.GetName().Name;

if (name is null)
return false;

return args.Name.Contains(name, StringComparison.Ordinal);
})
.OrderByDescending(x => x.GetName().Version)
.FirstOrDefault();

return domainAssembly ?? LoadedAssemblies.Keys.FirstOrDefault(x => x.FullName == asm.FullName);
}

private void loadUserPlugins(Storage pluginStorage)
{
IEnumerable<string>? plugins = pluginStorage.GetFiles(".", $"{PLUGIN_LIBRARY_PREFIX}.*.dll");

foreach (string? plugin in plugins.Where(x => !x.Contains("Tests")))
loadPluginFromFile(pluginStorage.GetFullPath(plugin));
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
AppDomain.CurrentDomain.AssemblyResolve -= resolvePluginDependencyAssembly;
}
}
}
18 changes: 18 additions & 0 deletions Aurora.Game/API/PluginType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Aurora.Game.API
{
/// <summary>
/// Indicates the type of plug-in functionality your plug-in holds.
/// </summary>
public enum PluginType
{
/// <summary>
/// Means that your plug-in introduces a new launcher section (i.e. makes it possible to launch a PvP client).
/// </summary>
LauncherContent,

/// <summary>
/// Means that your plug-in only gets loaded with the purpose of making tweaks to code or events.
/// </summary>
LauncherModification
}
}
22 changes: 22 additions & 0 deletions Aurora.Game/AuroraGameBase.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using Aurora.Game.API;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.IO.Stores;
using osuTK;
using Aurora.Resources;
using osu.Framework.Platform;

namespace Aurora.Game
{
Expand All @@ -15,6 +17,11 @@ public class AuroraGameBase : osu.Framework.Game

protected override Container<Drawable> Content { get; }

protected Storage? Storage { get; set; }

private DependencyContainer dependencies;
private PluginLoader pluginLoader;

protected AuroraGameBase()
{
// Ensure game and tests scale with window size and screen DPI.
Expand All @@ -30,9 +37,24 @@ private void load()
{
Resources.AddStore(new DllResourceStore(typeof(AuroraResources).Assembly));

dependencies.CacheAs(Storage);

pluginLoader = new PluginLoader(Storage);
dependencies.CacheAs(pluginLoader);

addFonts();
}

protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));

public override void SetHost(GameHost host)
{
base.SetHost(host);

Storage ??= host.Storage;
}

private void addFonts()
{
AddFont(Resources, "Fonts/Torus-Bold");
Expand Down
23 changes: 23 additions & 0 deletions Aurora.Game/Screens/InitialLoadingScreen.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using Aurora.Game.API;
using Aurora.Game.Graphics.Containers;
using Aurora.Game.Graphics.Utilities;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osuTK;

Expand All @@ -19,6 +21,14 @@ public class InitialLoadingScreen : Screen

protected LinkTextFlowContainer SupporterText { get; private set; }

protected LinkTextFlowContainer LoadingText { get; private set; }

[Resolved]
private Storage? storage { get; set; }

[Resolved]
private PluginLoader pluginLoader { get; set; }

[BackgroundDependencyLoader]
private void load()
{
Expand Down Expand Up @@ -60,6 +70,18 @@ private void load()
Padding = new MarginPadding(20f),
Alpha = 0f,
Spacing = new Vector2(0f, 2f)
},

LoadingText = new LinkTextFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
TextAnchor = Anchor.CentreLeft,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Padding = new MarginPadding(20f),
Alpha = 1f,
Spacing = new Vector2(0f, 2f)
}
};

Expand Down Expand Up @@ -166,6 +188,7 @@ public override void OnEntering(IScreen last)
base.OnEntering(last);

LoadComponentAsync(ScreenToExitTo);
Scheduler.Add(() => pluginLoader.LoadPlugins());

CheckIfLoaded();
}
Expand Down
28 changes: 28 additions & 0 deletions Aurora.sln
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Aurora.sln.DotSettings = Aurora.sln.DotSettings
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aurora.Game.Plugins.LunarClient", "Aurora.Game.Plugins.LunarClient\Aurora.Game.Plugins.LunarClient.csproj", "{C37325F8-C154-42F3-A64C-D337965C5993}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aurora.Game.Plugins.LunarClient.Test", "Aurora.Game.Plugins.LunarClient.Test\Aurora.Game.Plugins.LunarClient.Test.csproj", "{687AF42B-4A87-4671-BF04-2F9D7028BC38}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -70,6 +74,30 @@ Global
{31D3872C-3A4F-437E-9655-3EF58DD62F46}.Release|iPhone.Build.0 = Release|Any CPU
{31D3872C-3A4F-437E-9655-3EF58DD62F46}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{31D3872C-3A4F-437E-9655-3EF58DD62F46}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{C37325F8-C154-42F3-A64C-D337965C5993}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C37325F8-C154-42F3-A64C-D337965C5993}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C37325F8-C154-42F3-A64C-D337965C5993}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C37325F8-C154-42F3-A64C-D337965C5993}.Release|Any CPU.Build.0 = Release|Any CPU
{C37325F8-C154-42F3-A64C-D337965C5993}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{C37325F8-C154-42F3-A64C-D337965C5993}.Debug|iPhone.Build.0 = Debug|Any CPU
{C37325F8-C154-42F3-A64C-D337965C5993}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{C37325F8-C154-42F3-A64C-D337965C5993}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{C37325F8-C154-42F3-A64C-D337965C5993}.Release|iPhone.ActiveCfg = Release|Any CPU
{C37325F8-C154-42F3-A64C-D337965C5993}.Release|iPhone.Build.0 = Release|Any CPU
{C37325F8-C154-42F3-A64C-D337965C5993}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{C37325F8-C154-42F3-A64C-D337965C5993}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{687AF42B-4A87-4671-BF04-2F9D7028BC38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{687AF42B-4A87-4671-BF04-2F9D7028BC38}.Debug|Any CPU.Build.0 = Debug|Any CPU
{687AF42B-4A87-4671-BF04-2F9D7028BC38}.Release|Any CPU.ActiveCfg = Release|Any CPU
{687AF42B-4A87-4671-BF04-2F9D7028BC38}.Release|Any CPU.Build.0 = Release|Any CPU
{687AF42B-4A87-4671-BF04-2F9D7028BC38}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{687AF42B-4A87-4671-BF04-2F9D7028BC38}.Debug|iPhone.Build.0 = Debug|Any CPU
{687AF42B-4A87-4671-BF04-2F9D7028BC38}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{687AF42B-4A87-4671-BF04-2F9D7028BC38}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{687AF42B-4A87-4671-BF04-2F9D7028BC38}.Release|iPhone.ActiveCfg = Release|Any CPU
{687AF42B-4A87-4671-BF04-2F9D7028BC38}.Release|iPhone.Build.0 = Release|Any CPU
{687AF42B-4A87-4671-BF04-2F9D7028BC38}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{687AF42B-4A87-4671-BF04-2F9D7028BC38}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down

0 comments on commit 39c138e

Please sign in to comment.