From 1c9ac463f4581d48e744c8481bc39b2b39347ef8 Mon Sep 17 00:00:00 2001
From: cmdexecutor <30626539+amelkor@users.noreply.github.com>
Date: Tue, 23 May 2023 20:09:31 +0700
Subject: [PATCH] add UnityHost instead of Host
---
CHANGELOG.md | 1 +
...ft.Extensions.Hosting.Unity.Runtime.asmdef | 12 +-
Runtime/HostManager.cs | 14 ++-
Runtime/Internals/UnityHost.cs | 88 ++++++++++++++
Runtime/Internals/UnityHost.cs.meta | 3 +
.../UnityObjectServiceCollectionBuilder.cs | 24 ++++
Runtime/UnityLifetime.cs | 111 ++++++++++++++++++
Runtime/UnityLifetime.cs.meta | 3 +
package.json | 2 +-
9 files changed, 250 insertions(+), 8 deletions(-)
create mode 100644 Runtime/Internals/UnityHost.cs
create mode 100644 Runtime/Internals/UnityHost.cs.meta
create mode 100644 Runtime/UnityLifetime.cs
create mode 100644 Runtime/UnityLifetime.cs.meta
diff --git a/CHANGELOG.md b/CHANGELOG.md
index df7ef37..f3665af 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/Runtime/Bsr.Microsoft.Extensions.Hosting.Unity.Runtime.asmdef b/Runtime/Bsr.Microsoft.Extensions.Hosting.Unity.Runtime.asmdef
index e6e4624..909a395 100644
--- a/Runtime/Bsr.Microsoft.Extensions.Hosting.Unity.Runtime.asmdef
+++ b/Runtime/Bsr.Microsoft.Extensions.Hosting.Unity.Runtime.asmdef
@@ -1,7 +1,9 @@
{
"name": "Microsoft.Extensions.Hosting.Unity",
"rootNamespace": "",
- "references": [],
+ "references": [
+ "GUID:f51ebe6a0ceec4240a699833d6309b23"
+ ],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
@@ -9,6 +11,12 @@
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
- "versionDefines": [],
+ "versionDefines": [
+ {
+ "name": "com.cysharp.unitask",
+ "expression": "",
+ "define": "UNITASK_ENABLED"
+ }
+ ],
"noEngineReferences": false
}
\ No newline at end of file
diff --git a/Runtime/HostManager.cs b/Runtime/HostManager.cs
index e203659..9db0c49 100644
--- a/Runtime/HostManager.cs
+++ b/Runtime/HostManager.cs
@@ -115,15 +115,18 @@ public IServiceProvider Services
///
/// IHostBuilder.
// 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);
@@ -243,6 +246,7 @@ private void BuildHost()
var lookup = new ServicesLookup(collection);
collection.AddSingleton(lookup);
});
+
host = _hostBuilder.Build();
var lifetime = host.Services.GetRequiredService();
@@ -287,7 +291,7 @@ private void BuildHost()
hostBuilt?.Invoke();
hostBuilt?.RemoveAllListeners();
}
- catch (Exception)
+ catch (Exception) when(!System.Diagnostics.Debugger.IsAttached)
{
_isBuilt = false;
throw;
@@ -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;
diff --git a/Runtime/Internals/UnityHost.cs b/Runtime/Internals/UnityHost.cs
new file mode 100644
index 0000000..9173dff
--- /dev/null
+++ b/Runtime/Internals/UnityHost.cs
@@ -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());
+
+ return builder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Internals/UnityHost.cs.meta b/Runtime/Internals/UnityHost.cs.meta
new file mode 100644
index 0000000..fdfc73c
--- /dev/null
+++ b/Runtime/Internals/UnityHost.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: d2da01d229db4ba4a0818678de2a23e5
+timeCreated: 1684507260
\ No newline at end of file
diff --git a/Runtime/Internals/UnityObjectServiceCollectionBuilder.cs b/Runtime/Internals/UnityObjectServiceCollectionBuilder.cs
index 4b904d4..e11fe85 100644
--- a/Runtime/Internals/UnityObjectServiceCollectionBuilder.cs
+++ b/Runtime/Internals/UnityObjectServiceCollectionBuilder.cs
@@ -31,6 +31,9 @@ private static IMonoBehaviourHostRoot GetHostRoot(HostBuilderContext context)
///
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();
@@ -111,6 +114,9 @@ public IUnityObjectServiceCollectionBuilder AddMonoBehaviourHostedService
public IUnityObjectServiceCollectionBuilder AddMonoBehaviourSingleton(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(provider =>
@@ -204,6 +210,9 @@ public IUnityObjectServiceCollectionBuilder AddMonoBehaviourTransient(
public IUnityObjectServiceCollectionBuilder AddScriptableObjectSingleton(ScriptableObject scriptableObject, Type type)
{
+ if (!scriptableObject)
+ throw new ArgumentNullException(nameof(scriptableObject), "ScriptableObject is missing");
+
_hostBuilder.ConfigureServices(services =>
{
services.AddSingleton(type, provider =>
@@ -219,6 +228,9 @@ public IUnityObjectServiceCollectionBuilder AddScriptableObjectSingleton(Scripta
public IUnityObjectServiceCollectionBuilder AddScriptableObjectSingleton(T scriptableObject) where T : ScriptableObject
{
+ if (!scriptableObject)
+ throw new ArgumentNullException(nameof(scriptableObject), "ScriptableObject is missing");
+
_hostBuilder.ConfigureServices(services =>
{
services.AddSingleton(provider =>
@@ -234,6 +246,9 @@ public IUnityObjectServiceCollectionBuilder AddScriptableObjectSingleton(T sc
public IUnityObjectServiceCollectionBuilder AddScriptableObjectSingleton(TImpl scriptableObject) where T : class where TImpl : ScriptableObject, T
{
+ if (!scriptableObject)
+ throw new ArgumentNullException(nameof(scriptableObject), "ScriptableObject is missing");
+
_hostBuilder.ConfigureServices(services =>
{
services.AddSingleton(provider =>
@@ -251,6 +266,9 @@ public IUnityObjectServiceCollectionBuilder AddScriptableObjectSingleton
public IUnityObjectServiceCollectionBuilder AddMonoBehaviourPrefabSingleton(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(resourcesPath);
@@ -273,6 +291,9 @@ public IUnityObjectServiceCollectionBuilder AddMonoBehaviourPrefabSingleton(s
///
public IUnityObjectServiceCollectionBuilder AddMonoBehaviourPrefabTransient(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(resourcesPath);
@@ -293,6 +314,9 @@ public IUnityObjectServiceCollectionBuilder AddMonoBehaviourPrefabTransient(s
public IUnityObjectServiceCollectionBuilder AddMonoBehaviourPrefabScoped(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(resourcesPath);
diff --git a/Runtime/UnityLifetime.cs b/Runtime/UnityLifetime.cs
new file mode 100644
index 0000000..8b53d14
--- /dev/null
+++ b/Runtime/UnityLifetime.cs
@@ -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 options, IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, IOptions hostOptions)
+ : this(options, environment, applicationLifetime, hostOptions, NullLoggerFactory.Instance)
+ {
+ }
+
+ public UnityLifetime(IOptions options, IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, IOptions 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/UnityLifetime.cs.meta b/Runtime/UnityLifetime.cs.meta
new file mode 100644
index 0000000..61439cf
--- /dev/null
+++ b/Runtime/UnityLifetime.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: d8e9164bbeda424485b4396564531c41
+timeCreated: 1684508760
\ No newline at end of file
diff --git a/package.json b/package.json
index b657cb0..9e83d8f 100644
--- a/package.json
+++ b/package.json
@@ -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",