Skip to content

Instantly share code, notes, and snippets.

@aethercowboy
Last active October 21, 2024 12:37
Show Gist options
  • Save aethercowboy/163c52da5c277620e257cfe980e73b8e to your computer and use it in GitHub Desktop.
Save aethercowboy/163c52da5c277620e257cfe980e73b8e to your computer and use it in GitHub Desktop.
Monogame with IHostedService DI

Monogame with IHostedService / DI

Here's a basic structure for a Monogame project making use of the ASP.NET Core HostedService technologies.

This lets you easily make use of DI within your game, but also include pluggable architecture.

Files

  • Program.cs - The entrypoint to your application. Uses dotnet core's CreateDefaultBuilder to make the DI setup easier.
  • Worker.cs - The main game service. Handles the buildup/teardown of the the game class.
  • ServiceCollectionExtensions.cs - A collection of extension methods to set up base DI defs and allow for adding plugins.
  • IGame.cs - An interface that defines the layout of the main Game class. Implemented by your game class. Ideally should include all the Properties/Methods/Events of the base Game classs.
  • ExampleGame.cs - An example of how to use the IGame interface.
  • IStartupProvider.cs - An interface that defines how your plugin defines its DI and (possibly) additiona HostedServices.
  • ExampleStartupProvider.cs - An example of how to use IStartupProvider so MEF detects it. Put the DLL in your executable directory and your program will automatically detect it and load it.
public class ExampleGame : Game, IGame
{
public Game Game => this;
private readonly ISomeDependency _someDependency;
public ExampleGame(ISomeDependency someDependency)
{
_someDependency = someDependency;
}
// ... implementation here
}
[Export(typeof(IStartupProvider))]
public class StartupProvider : IStartupProvider
{
void IStartupProvider.ConfigureServices(IServiceCollection services)
{
// plugin DI def here
}
}
public interface IGame : IDisposable
{
Game Game { get; }
// include all the Properties/Methods that you'd want to use on your Game class below.
GameWindow Window { get; }
event EventHandler<EventArgs> Exiting;
void Run();
void Exit();
}
public interface IStartupProvider
{
void ConfigureServices(IServiceCollection services);
}
public static class Program
{
[STAThread]
private static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
services.AddGameConfiguration();
services.AddPlugins();
});
}
public static void AddGameConfiguration(this IServiceCollection services)
{
// your base DI def here
}
public static void AddPlugins(this IServiceCollection services)
{
var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var dirCat = new DirectoryCatalog(path);
var importDef = BuildImportDefinition();
try
{
using var aggregateCatalog = new AggregateCatalog();
aggregateCatalog.Catalogs.Add(dirCat);
using var compositionContainer = new CompositionContainer(aggregateCatalog);
var exports = compositionContainer.GetExports(importDef);
foreach (var module in exports
.Select(x => x.Value as IStartupProvider)
.Where(x => x != null))
{
module.ConfigureServices(services);
}
}
catch (ReflectionTypeLoadException e)
{
var builder = new StringBuilder();
foreach (var loaderException in e.LoaderExceptions)
{
builder.AppendFormat("{0}\n", loaderException.Message);
}
throw new TypeLoadException(builder.ToString(), e);
}
}
private static ImportDefinition BuildImportDefinition()
{
return new ImportDefinition(x => true, typeof(IStartupProvider).FullName, ImportCardinality.ZeroOrMore, false, false);
}
public class Worker : IHostedService
{
public static GraphicsDeviceManager Graphics { get; private set; }
private readonly IGame _game;
private readonly IHostApplicationLifetime _appLifetime;
public Worker(IGame game, IHostApplicationLifetime appLifetime)
{
_game = game;
_appLifetime = appLifetime;
Graphics = new GraphicsDeviceManager(game.Game);
}
public Task StartAsync(CancellationToken cancellationToken)
{
_appLifetime.ApplicationStarted.Register(OnStarted);
_appLifetime.ApplicationStopping.Register(OnStopping);
_appLifetime.ApplicationStopped.Register(OnStopped);
_game.Exiting += OnGameExiting;
return Task.CompletedTask;
}
private void OnGameExiting(object sender, System.EventArgs e)
{
StopAsync(new CancellationToken());
}
public Task StopAsync(CancellationToken cancellationToken)
{
_appLifetime.StopApplication();
return Task.CompletedTask;
}
private void OnStarted()
{
_game.Run();
}
private void OnStopping()
{
}
private void OnStopped()
{
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment