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",