Skip to content

Commit

Permalink
add UnityHost instead of Host
Browse files Browse the repository at this point in the history
  • Loading branch information
amelkor committed May 23, 2023
1 parent a96a6c6 commit 1c9ac46
Show file tree
Hide file tree
Showing 9 changed files with 250 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- `ScriptableObjectConfiguration` as configuration source
- hostBuilding event when the host is being built
- `StopManuallyAsync` method to `HostManager`
- Replace `Host` with `UnityHost` to remove console lifetime that was preventing IL2CPP building on Windows
### Updated
- make HostManager events public

Expand Down
12 changes: 10 additions & 2 deletions Runtime/Bsr.Microsoft.Extensions.Hosting.Unity.Runtime.asmdef
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
{
"name": "Microsoft.Extensions.Hosting.Unity",
"rootNamespace": "",
"references": [],
"references": [
"GUID:f51ebe6a0ceec4240a699833d6309b23"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"versionDefines": [
{
"name": "com.cysharp.unitask",
"expression": "",
"define": "UNITASK_ENABLED"
}
],
"noEngineReferences": false
}
14 changes: 9 additions & 5 deletions Runtime/HostManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,18 @@ public IServiceProvider Services
/// </summary>
/// <returns>IHostBuilder.</returns>
// ReSharper disable once VirtualMemberNeverOverridden.Global
protected virtual IHostBuilder CreateHostBuilder() => Host.CreateDefaultBuilder(cmdArguments);
protected virtual IHostBuilder CreateHostBuilder() => UnityHost.CreateDefaultBuilder(cmdArguments);

private void Awake()
{
_hostBuilder = CreateHostBuilder();
_hostBuilder.ConfigureAppConfiguration(builder => { builder.DisableFileConfigurationSourceReloadOnChange(); });
_hostBuilder.ConfigureAppConfiguration(builder =>
{
builder.DisableFileConfigurationSourceReloadOnChange();
builder.AddCommandLine(cmdArguments);
});
_hostBuilder.ConfigureLogging((_, loggingBuilder) =>
{
loggingBuilder.ClearProviders();
loggingBuilder.SetMinimumLevel(logLevel);
});
_hostBuilder.UseMonoBehaviourServiceCollection(servicesInjectionMethodName);
Expand Down Expand Up @@ -243,6 +246,7 @@ private void BuildHost()
var lookup = new ServicesLookup(collection);
collection.AddSingleton(lookup);
});

host = _hostBuilder.Build();

var lifetime = host.Services.GetRequiredService<IHostApplicationLifetime>();
Expand Down Expand Up @@ -287,7 +291,7 @@ private void BuildHost()
hostBuilt?.Invoke();
hostBuilt?.RemoveAllListeners();
}
catch (Exception)
catch (Exception) when(!System.Diagnostics.Debugger.IsAttached)
{
_isBuilt = false;
throw;
Expand Down Expand Up @@ -367,7 +371,7 @@ private async Task StartHostAsync()
_isStarted = true;
await host.StartAsync(_cts.Token);
}
catch (Exception)
catch (Exception) when(!System.Diagnostics.Debugger.IsAttached)
{
_isStarted = false;
throw;
Expand Down
88 changes: 88 additions & 0 deletions Runtime/Internals/UnityHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.EventLog;
using UnityEngine;
using UnityEngine.Scripting;

namespace Microsoft.Extensions.Hosting.Unity
{
[Preserve]
public static class UnityHost
{
[Preserve]
public static IHostBuilder CreateDefaultBuilder() => CreateDefaultBuilder(args: null);

[Preserve]
public static IHostBuilder CreateDefaultBuilder(string[] args)
{
HostBuilder builder = new();
return builder.ConfigureDefaults(args);
}

private static IHostBuilder ConfigureDefaults(this IHostBuilder builder, string[] args)
{
builder.UseContentRoot(Application.dataPath);
builder.ConfigureHostConfiguration(config =>
{
config.AddEnvironmentVariables(prefix: "DOTNET_");
if (args is { Length: > 0 })
{
config.AddCommandLine(args);
}
});

builder.ConfigureAppConfiguration((hostingContext, config) =>
{
IHostEnvironment env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsDevelopment() && env.ApplicationName is { Length: > 0 })
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly is not null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
}
config.AddEnvironmentVariables();
if (args is { Length: > 0 })
{
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddDebug();
logging.AddEventSourceLogger();
logging.Configure(options =>
{
options.ActivityTrackingOptions =
ActivityTrackingOptions.SpanId |
ActivityTrackingOptions.TraceId |
ActivityTrackingOptions.ParentId;
});
})
.UseDefaultServiceProvider((context, options) =>
{
bool isDevelopment = context.HostingEnvironment.IsDevelopment();
options.ValidateScopes = isDevelopment;
options.ValidateOnBuild = isDevelopment;
});

builder.ConfigureServices(services => services.AddSingleton<IHostLifetime, UnityLifetime>());

return builder;
}
}
}
3 changes: 3 additions & 0 deletions Runtime/Internals/UnityHost.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions Runtime/Internals/UnityObjectServiceCollectionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ private static IMonoBehaviourHostRoot GetHostRoot(HostBuilderContext context)
/// <inheritdoc/>
public IUnityObjectServiceCollectionBuilder AddMonoBehaviourSingleton(MonoBehaviour component, Type type = default, bool useHostLifetime = false)
{
if (!component)
throw new ArgumentNullException(nameof(component), "Component is missing");

_hostBuilder.ConfigureServices(services =>
{
type ??= component.GetType();
Expand Down Expand Up @@ -111,6 +114,9 @@ public IUnityObjectServiceCollectionBuilder AddMonoBehaviourHostedService<T, TIm
/// <inheritdoc/>
public IUnityObjectServiceCollectionBuilder AddMonoBehaviourSingleton<T, TImpl>(TImpl component, bool useHostLifetime = false) where TImpl : MonoBehaviour, T where T : class
{
if (!component)
throw new ArgumentNullException(nameof(component), "Component is missing");

_hostBuilder.ConfigureServices(services =>
{
services.AddSingleton<T>(provider =>
Expand Down Expand Up @@ -204,6 +210,9 @@ public IUnityObjectServiceCollectionBuilder AddMonoBehaviourTransient<T, TImpl>(

public IUnityObjectServiceCollectionBuilder AddScriptableObjectSingleton(ScriptableObject scriptableObject, Type type)
{
if (!scriptableObject)
throw new ArgumentNullException(nameof(scriptableObject), "ScriptableObject is missing");

_hostBuilder.ConfigureServices(services =>
{
services.AddSingleton(type, provider =>
Expand All @@ -219,6 +228,9 @@ public IUnityObjectServiceCollectionBuilder AddScriptableObjectSingleton(Scripta

public IUnityObjectServiceCollectionBuilder AddScriptableObjectSingleton<T>(T scriptableObject) where T : ScriptableObject
{
if (!scriptableObject)
throw new ArgumentNullException(nameof(scriptableObject), "ScriptableObject is missing");

_hostBuilder.ConfigureServices(services =>
{
services.AddSingleton<T>(provider =>
Expand All @@ -234,6 +246,9 @@ public IUnityObjectServiceCollectionBuilder AddScriptableObjectSingleton<T>(T sc

public IUnityObjectServiceCollectionBuilder AddScriptableObjectSingleton<T, TImpl>(TImpl scriptableObject) where T : class where TImpl : ScriptableObject, T
{
if (!scriptableObject)
throw new ArgumentNullException(nameof(scriptableObject), "ScriptableObject is missing");

_hostBuilder.ConfigureServices(services =>
{
services.AddSingleton<T>(provider =>
Expand All @@ -251,6 +266,9 @@ public IUnityObjectServiceCollectionBuilder AddScriptableObjectSingleton<T, TImp
/// <inheritdoc/>
public IUnityObjectServiceCollectionBuilder AddMonoBehaviourPrefabSingleton<T>(string resourcesPath) where T : MonoBehaviour
{
if (string.IsNullOrWhiteSpace(resourcesPath))
throw new ArgumentNullException(nameof(resourcesPath), "Resources path is missing");

_hostBuilder.ConfigureServices((context, services) =>
{
var prefab = Resources.Load<T>(resourcesPath);
Expand All @@ -273,6 +291,9 @@ public IUnityObjectServiceCollectionBuilder AddMonoBehaviourPrefabSingleton<T>(s
/// <inheritdoc/>
public IUnityObjectServiceCollectionBuilder AddMonoBehaviourPrefabTransient<T>(string resourcesPath) where T : MonoBehaviour
{
if (string.IsNullOrWhiteSpace(resourcesPath))
throw new ArgumentNullException(nameof(resourcesPath), "Resources path is missing");

_hostBuilder.ConfigureServices((context, services) =>
{
var prefab = Resources.Load<T>(resourcesPath);
Expand All @@ -293,6 +314,9 @@ public IUnityObjectServiceCollectionBuilder AddMonoBehaviourPrefabTransient<T>(s

public IUnityObjectServiceCollectionBuilder AddMonoBehaviourPrefabScoped<T>(string resourcesPath) where T : MonoBehaviour
{
if (string.IsNullOrWhiteSpace(resourcesPath))
throw new ArgumentNullException(nameof(resourcesPath), "Resources path is missing");

_hostBuilder.ConfigureServices((context, services) =>
{
var prefab = Resources.Load<T>(resourcesPath);
Expand Down
111 changes: 111 additions & 0 deletions Runtime/UnityLifetime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using UnityEngine;
using ILogger = Microsoft.Extensions.Logging.ILogger;

namespace Microsoft.Extensions.Hosting.Unity
{
public class UnityLifetime : IHostLifetime, IDisposable
{
private CancellationTokenRegistration _applicationStartedRegistration;
private CancellationTokenRegistration _applicationStoppingRegistration;

private readonly ManualResetEvent _shutdownBlock = new ManualResetEvent(false);

public UnityLifetime(IOptions<ConsoleLifetimeOptions> options, IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, IOptions<HostOptions> hostOptions)
: this(options, environment, applicationLifetime, hostOptions, NullLoggerFactory.Instance)
{
}

public UnityLifetime(IOptions<ConsoleLifetimeOptions> options, IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, IOptions<HostOptions> hostOptions, ILoggerFactory loggerFactory)
{
Options = options?.Value ?? throw new ArgumentNullException(nameof(options));
Environment = environment ?? throw new ArgumentNullException(nameof(environment));
ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
HostOptions = hostOptions?.Value ?? throw new ArgumentNullException(nameof(hostOptions));
Logger = loggerFactory.CreateLogger("Microsoft.Hosting.Lifetime");
}

private ConsoleLifetimeOptions Options { get; }

private IHostEnvironment Environment { get; }

private IHostApplicationLifetime ApplicationLifetime { get; }

private HostOptions HostOptions { get; }

private ILogger Logger { get; }

public Task WaitForStartAsync(CancellationToken cancellationToken)
{
if (!Options.SuppressStatusMessages)
{
_applicationStartedRegistration = ApplicationLifetime.ApplicationStarted.Register(state => { ((UnityLifetime)state).OnApplicationStarted(); },
this);
_applicationStoppingRegistration = ApplicationLifetime.ApplicationStopping.Register(state => { ((UnityLifetime)state).OnApplicationStopping(); },
this);
}

RegisterShutdownHandlers();

return Task.CompletedTask;
}

private void RegisterShutdownHandlers()
{
Application.quitting += OnProcessExit;
}

private void OnApplicationStarted()
{
Logger.LogInformation("Unity host started");
Logger.LogInformation("Hosting environment: {envName}", Environment.EnvironmentName);
Logger.LogInformation("Content root path: {contentRoot}", Environment.ContentRootPath);
}

private void OnProcessExit()
{
ApplicationLifetime.StopApplication();
if (!_shutdownBlock.WaitOne(HostOptions.ShutdownTimeout))
{
Logger.LogInformation("Waiting for the host to be disposed. Ensure all 'IHost' instances are wrapped in 'using' blocks");
}

// wait one more time after the above log message, but only for ShutdownTimeout, so it doesn't hang forever
_shutdownBlock.WaitOne(HostOptions.ShutdownTimeout);

// On Linux if the shutdown is triggered by SIGTERM then that's signaled with the 143 exit code.
// Suppress that since we shut down gracefully. https://github.com/dotnet/aspnetcore/issues/6526
System.Environment.ExitCode = 0;
}

private void OnApplicationStopping()
{
Logger.LogInformation("Unity host is shutting down...");
}

public Task StopAsync(CancellationToken cancellationToken)
{
// There's nothing to do here
return Task.CompletedTask;
}

public void Dispose()
{
UnregisterShutdownHandlers();

_applicationStartedRegistration.Dispose();
_applicationStoppingRegistration.Dispose();
}

private void UnregisterShutdownHandlers()
{
_shutdownBlock.Set();
Application.quitting -= OnProcessExit;
}
}
}
3 changes: 3 additions & 0 deletions Runtime/UnityLifetime.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"description": "Microsoft.Extensions.Hosting adopted for Unity",
"unity": "2021.1",
"release": "0f1",
"version": "2.1.0-preview.5",
"version": "2.1.0-preview.6",
"keywords": [
"unity",
"integration",
Expand Down

0 comments on commit 1c9ac46

Please sign in to comment.