diff --git a/.gitignore b/.gitignore index 74a2f8d0c3..b6ea476bfe 100644 --- a/.gitignore +++ b/.gitignore @@ -83,6 +83,9 @@ user-token.json /cli/tests/bin/ /cli/tests/obj/ /cli/tests/.idea/ +/cli/benchmarks/bin/ +/cli/benchmarks/obj/ +/cli/benchmarks/.idea/ /cli/*.sln.DotSettings.* cli/cli/beamoLocalRuntime.json diff --git a/cli/Benchmarks/Benchmarks.csproj b/cli/Benchmarks/Benchmarks.csproj new file mode 100644 index 0000000000..1838a372fb --- /dev/null +++ b/cli/Benchmarks/Benchmarks.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + Exe + + + + + + + + + + + diff --git a/cli/Benchmarks/DependencyProviderBenchmarks.cs b/cli/Benchmarks/DependencyProviderBenchmarks.cs new file mode 100644 index 0000000000..aa00f03ef9 --- /dev/null +++ b/cli/Benchmarks/DependencyProviderBenchmarks.cs @@ -0,0 +1,67 @@ +using Beamable.Common.Dependencies; +using BenchmarkDotNet.Attributes; +using System.Collections.Concurrent; + +namespace Benchmarks; + +[MemoryDiagnoser] +[ShortRunJob] +public class DependencyProviderBenchmarks +{ + // [Benchmark] + // public void JustAService() + // { + // var x = new ServiceDescriptor(); + // } + // [Benchmark] + // public void Dict_Concurrent() + // { + // var x = new ConcurrentDictionary(); + // } + // [Benchmark] + // public void Dict_Regular() + // { + // var x = new Dictionary(); + // } + + // [Benchmark] + public void BaseCase_JustProvider() + { + var provider = new DependencyProvider(null, null); + } + + [Benchmark] + public void BaseCase_NoDispose() + { + var builder = new DependencyBuilder(); + // builder.AddSingleton(); + var provider = builder.Build(); + + // var service = provider.GetService(); + } + // + // [Benchmark] + public void BaseCase_NoDispose_RegisterAndResolve() + { + var builder = new DependencyBuilder(); + // builder.AddSingleton(); + var provider = builder.Build(); + // var service = provider.GetService(); + // var serv = new TestService(); + } + // + // + [Benchmark] + public void BaseCase_Dispose() + { + var builder = new DependencyBuilder(); + var provider = builder.Build(); + provider.Dispose(); + } + + + public class TestService + { + + } +} diff --git a/cli/Benchmarks/MultiThreadedAccessBenchmarks.cs b/cli/Benchmarks/MultiThreadedAccessBenchmarks.cs new file mode 100644 index 0000000000..700a45a812 --- /dev/null +++ b/cli/Benchmarks/MultiThreadedAccessBenchmarks.cs @@ -0,0 +1,37 @@ +using Beamable.Common.Dependencies; +using BenchmarkDotNet.Attributes; + +namespace Benchmarks; + +[MemoryDiagnoser] +[ShortRunJob] +public class MultiThreadedAccessBenchmarks +{ + [Benchmark] + public async Task SingletonOnlyGetsMadeOnce() + { + var builder = new DependencyBuilder(); + int count = 0; + builder.AddSingleton(_ => + { + count++; + return new A(); + }); + var provider = builder.Build(); + + var tasks = Enumerable.Range(0, 10_000).Select(i => Task.Run(async () => + { + await Task.Delay(1); + provider.GetService(); + })); + + await Task.WhenAll(tasks); + + + } + + public class A + { + // no-op class just for testing + } +} diff --git a/cli/Benchmarks/Program.cs b/cli/Benchmarks/Program.cs new file mode 100644 index 0000000000..6e4feb1faa --- /dev/null +++ b/cli/Benchmarks/Program.cs @@ -0,0 +1,14 @@ +using BenchmarkDotNet.Running; + +namespace Benchmarks; + +public class Program +{ + public static void Main(string[] args) + { + // BenchmarkRunner.Run(); + BenchmarkRunner.Run(); + // BenchmarkRunner.Run(); + + } +} diff --git a/cli/Benchmarks/PromiseBenchmarks.cs b/cli/Benchmarks/PromiseBenchmarks.cs new file mode 100644 index 0000000000..3cf584fbca --- /dev/null +++ b/cli/Benchmarks/PromiseBenchmarks.cs @@ -0,0 +1,94 @@ +using Beamable.Common; +using BenchmarkDotNet.Attributes; +using System.Runtime.InteropServices.JavaScript; + +namespace Benchmarks; + +[MemoryDiagnoser] +[ShortRunJob] +public class PromiseBenchmarks +{ + + // [GlobalSetup] + public void Setup() + { + var x = PromiseBase.Unit; + } + // [Benchmark] + public Promise PromiseComplete() + { + var p = new Promise(); + //p.CompleteSuccess(); + return p; + } + // [Benchmark] + public void PromiseAllocation_Many() + { + for (var i = 0; i < 10_000; i++) + { + var p = new Promise(); + } + } + + [Benchmark] + public async Task Await() + { + var p = new Promise(); + p.CompleteSuccess(3); + + return await p; + } + + // [Benchmark] + public Promise PromiseAllocation() + { + var p = new Promise(); + return p; + } + + + // [Benchmark] + public async Promise ReturnAsyncPromise() + { + // var p = new Promise(); + + } + + + // [Benchmark] + public async Promise AsyncAwait2() + { + var p = new Promise(); + p.CompleteSuccess(); + } + + // [Benchmark] + public async Task Sequence() + { + var pList = Enumerable.Range(0, 10).Select(_ => new Promise()).ToList(); + var final = Promise.Sequence(pList); + + var _ = pList.Select(p => Task.Run(async () => + { + await Task.Delay(1); + p.CompleteSuccess(1); + })).ToList(); + + await final; + } + + // [Benchmark] + public async Task WhenAll() + { + var pList = Enumerable.Range(0, 10).Select(_ => new Promise()).ToList(); + var final = Promise.WhenAll(pList); + + var _ = pList.Select(p => Task.Run(async () => + { + await Task.Delay(1); + p.CompleteSuccess(1); + })).ToList(); + + await final; + } +} diff --git a/cli/beamable.common/Runtime/Dependencies/DependencyBuilder.cs b/cli/beamable.common/Runtime/Dependencies/DependencyBuilder.cs index 77176ff41c..875bbf2285 100644 --- a/cli/beamable.common/Runtime/Dependencies/DependencyBuilder.cs +++ b/cli/beamable.common/Runtime/Dependencies/DependencyBuilder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Runtime.ExceptionServices; @@ -8,10 +9,10 @@ namespace Beamable.Common.Dependencies public enum DependencyLifetime { - Unknown, - Transient, - Scoped, - Singleton + Unknown = 1, + Transient = 2, + Scoped = 3, + Singleton = 4 } /// @@ -449,18 +450,21 @@ public class BuildOptions /// public class DependencyBuilder : IDependencyBuilder { - public List TransientServices { get; protected set; } = new List(); - public List ScopedServices { get; protected set; } = new List(); - public List SingletonServices { get; protected set; } = new List(); + // public List TransientServices { get; protected set; } = new List(); + // public List ScopedServices { get; protected set; } = new List(); + // public List SingletonServices { get; protected set; } = new List(); - List IDependencyBuilder.GetTransientServices() => TransientServices; - List IDependencyBuilder.GetScopedServices() => ScopedServices; - List IDependencyBuilder.GetSingletonServices() => SingletonServices; + public List Descriptors { get; protected set; } = new List(); + + List IDependencyBuilder.GetTransientServices() => Descriptors.Where(x => x.Lifetime == DependencyLifetime.Transient).ToList(); + List IDependencyBuilder.GetScopedServices() => Descriptors.Where(x => x.Lifetime == DependencyLifetime.Scoped).ToList(); + List IDependencyBuilder.GetSingletonServices() => Descriptors.Where(x => x.Lifetime == DependencyLifetime.Singleton).ToList(); public IDependencyBuilder AddTransient(Func factory) where TImpl : TInterface { - TransientServices.Add(new ServiceDescriptor + Descriptors.Add(new ServiceDescriptor { + Lifetime = DependencyLifetime.Transient, Interface = typeof(TInterface), Implementation = typeof(TImpl), Factory = (provider) => factory(provider) @@ -482,8 +486,9 @@ public IDependencyBuilder AddTransient() where TImpl : TInter public IDependencyBuilder AddScoped(Func factory) where TImpl : TInterface { - ScopedServices.Add(new ServiceDescriptor + Descriptors.Add(new ServiceDescriptor { + Lifetime = DependencyLifetime.Scoped, Interface = typeof(TInterface), Implementation = typeof(TImpl), Factory = (provider) => factory(provider) @@ -511,8 +516,9 @@ public IDependencyBuilder AddScoped() where TImpl : TInterfac public IDependencyBuilder AddSingleton(Func factory) where TImpl : TInterface { - SingletonServices.Add(new ServiceDescriptor + Descriptors.Add(new ServiceDescriptor { + Lifetime = DependencyLifetime.Singleton, Interface = typeof(TInterface), Implementation = typeof(TImpl), Factory = (provider) => factory(provider) @@ -522,8 +528,9 @@ public IDependencyBuilder AddSingleton(Func Instantiate(type, provider) @@ -534,8 +541,9 @@ public IDependencyBuilder AddSingleton(Type type) public IDependencyBuilder AddSingleton(Type registeringType, Func factory) { System.Diagnostics.Debug.Assert(typeof(T).IsAssignableFrom(registeringType), $"RegisteringType [{registeringType.Name}] does not implement/inherit from [{typeof(T).Name}]!"); - SingletonServices.Add(new ServiceDescriptor + Descriptors.Add(new ServiceDescriptor { + Lifetime = DependencyLifetime.Singleton, Interface = registeringType, Implementation = registeringType, Factory = (provider) => factory() @@ -584,8 +592,9 @@ public IDependencyBuilder ReplaceSingleton(Func(); } - SingletonServices.Add(new ServiceDescriptor + Descriptors.Add(new ServiceDescriptor { + Lifetime = DependencyLifetime.Singleton, Interface = typeof(TExisting), Implementation = typeof(TNew), Factory = provider => factory(provider) @@ -677,37 +686,56 @@ public IDependencyProviderScope Build(BuildOptions options = null) public IDependencyBuilder Remove() { - if (TryGetTransient(typeof(T), out var transient)) + // foreach (var service in Descriptors) + for (var i = 0 ; i < Descriptors.Count; i ++) { - TransientServices.Remove(transient); - return this; - } - - if (TryGetScoped(typeof(T), out var scoped)) - { - ScopedServices.Remove(scoped); - return this; - } - - if (TryGetSingleton(typeof(T), out var singleton)) - { - SingletonServices.Remove(singleton); - return this; + if (Descriptors[i].Interface == typeof(T)) + { + Descriptors.RemoveAt(i); + return this; + } } + + // if (TryGetTransient(typeof(T), out var transient)) + // { + // TransientServices.Remove(transient); + // return this; + // } + // + // if (TryGetScoped(typeof(T), out var scoped)) + // { + // ScopedServices.Remove(scoped); + // return this; + // } + // + // if (TryGetSingleton(typeof(T), out var singleton)) + // { + // SingletonServices.Remove(singleton); + // return this; + // } throw new Exception($"Service does not exist, so cannot be removed. type=[{typeof(T)}]"); } public bool Has() { - return TryGetTransient(typeof(T), out _) || TryGetScoped(typeof(T), out _) || TryGetSingleton(typeof(T), out _); + for (var i = 0 ; i < Descriptors.Count; i ++) + { + if (Descriptors[i].Interface == typeof(T)) + { + return true; + } + } + + return false; } + [Obsolete] public bool TryGetTransient(Type type, out ServiceDescriptor descriptor) { - foreach (var serviceDescriptor in TransientServices) + foreach (var serviceDescriptor in Descriptors) { - if (serviceDescriptor.Interface == type) + if (serviceDescriptor.Interface == type && serviceDescriptor.Lifetime == DependencyLifetime.Transient) { descriptor = serviceDescriptor; return true; @@ -717,11 +745,13 @@ public bool TryGetTransient(Type type, out ServiceDescriptor descriptor) descriptor = default(ServiceDescriptor); return false; } + + [Obsolete] public bool TryGetScoped(Type type, out ServiceDescriptor descriptor) { - foreach (var serviceDescriptor in ScopedServices) + foreach (var serviceDescriptor in Descriptors) { - if (serviceDescriptor.Interface == type) + if (serviceDescriptor.Interface == type && serviceDescriptor.Lifetime == DependencyLifetime.Scoped) { descriptor = serviceDescriptor; return true; @@ -732,11 +762,12 @@ public bool TryGetScoped(Type type, out ServiceDescriptor descriptor) return false; } + [Obsolete] public bool TryGetSingleton(Type type, out ServiceDescriptor descriptor) { - foreach (var serviceDescriptor in SingletonServices) + foreach (var serviceDescriptor in Descriptors) { - if (serviceDescriptor.Interface == type) + if (serviceDescriptor.Interface == type && serviceDescriptor.Lifetime == DependencyLifetime.Singleton) { descriptor = serviceDescriptor; return true; @@ -752,9 +783,7 @@ public IDependencyBuilder Clone() { return new DependencyBuilder { - ScopedServices = new List(ScopedServices), - TransientServices = new List(TransientServices), - SingletonServices = new List(SingletonServices) + Descriptors = new List(Descriptors), }; } } diff --git a/cli/beamable.common/Runtime/Dependencies/DependencyProvider.cs b/cli/beamable.common/Runtime/Dependencies/DependencyProvider.cs index 13a539ede1..0f783803b4 100644 --- a/cli/beamable.common/Runtime/Dependencies/DependencyProvider.cs +++ b/cli/beamable.common/Runtime/Dependencies/DependencyProvider.cs @@ -165,14 +165,23 @@ public interface IDependencyProviderScope : IDependencyProvider void RemoveChild(IDependencyProviderScope child); } - public class DependencyProvider : IDependencyProviderScope + public interface IDependencyOrderComparer : IComparer { - private ConcurrentDictionary Transients { get; set; } - private ConcurrentDictionary Scoped { get; set; } - private ConcurrentDictionary Singletons { get; set; } + + } + public class DependencyProvider : IDependencyProviderScope, IDependencyOrderComparer + { + + // private Dictionary Transients { get; set; } + // private Dictionary Scoped { get; set; } + // private Dictionary Singletons { get; set; } + private Dictionary Descriptors { get; set; } + + private Dictionary InstanceCache { get; set; } = new Dictionary(); - private ConcurrentDictionary SingletonCache { get; set; } = new ConcurrentDictionary(); - private ConcurrentDictionary ScopeCache { get; set; } = new ConcurrentDictionary(); + private SortedDictionary> SortedInstanceValues = new SortedDictionary>(); + // private Dictionary SingletonCache { get; set; } = new Dictionary(); + // private Dictionary ScopeCache { get; set; } = new Dictionary(); private bool _destroyed; private bool _isDestroying; @@ -180,9 +189,9 @@ public class DependencyProvider : IDependencyProviderScope public bool IsDisposed => _destroyed; public bool IsActive => !_isDestroying && !_destroyed; - public IEnumerable TransientServices => Transients.Values; - public IEnumerable ScopedServices => Scoped.Values; - public IEnumerable SingletonServices => Singletons.Values; + public IEnumerable TransientServices => Descriptors.Values.Where(x => x.Lifetime == DependencyLifetime.Transient); + public IEnumerable ScopedServices => Descriptors.Values.Where(x => x.Lifetime == DependencyLifetime.Scoped); + public IEnumerable SingletonServices => Descriptors.Values.Where(x => x.Lifetime == DependencyLifetime.Singleton); public IDependencyProviderScope Parent { get; private set; } private HashSet _children = new HashSet(); @@ -203,30 +212,24 @@ public DependencyProvider(DependencyBuilder builder, BuildOptions options = null } _options = options; - Transients = new ConcurrentDictionary(); - foreach (var desc in builder.TransientServices) + Descriptors = new Dictionary(); + if (builder == null) return; + + foreach (var desc in builder.Descriptors) { - if (!Transients.TryAdd(desc.Interface, desc)) + if (Descriptors.TryGetValue(desc.Interface, out var existingDesc)) { - Debug.LogError($"Failed to add transient interface=[{desc.Interface.Name}] because it already existed."); - } - } + if (existingDesc.Lifetime <= desc.Lifetime) + { + throw new Exception( + $"Cannot add service=[{existingDesc.Interface.Name}] to scope as lifetime=[{desc.Lifetime}], because the service has already been added to the scope as existing-lifetime=[{existingDesc.Lifetime}]. "); + } - Scoped = new ConcurrentDictionary(); - foreach (var desc in builder.ScopedServices) - { - if (!Scoped.TryAdd(desc.Interface, desc)) - { - Debug.LogError($"Failed to add scoped interface=[{desc.Interface.Name}] because it already existed."); + Descriptors[desc.Interface] = desc; } - } - - Singletons = new ConcurrentDictionary(); - foreach (var desc in builder.SingletonServices) - { - if (!Singletons.TryAdd(desc.Interface, desc)) + else { - Debug.LogError($"Failed to add singleton interface=[{desc.Interface.Name}] because it already existed."); + Descriptors.Add(desc.Interface, desc); } } } @@ -234,15 +237,7 @@ public DependencyProvider(DependencyBuilder builder, BuildOptions options = null public bool TryGetServiceDescriptor(out ServiceDescriptor descriptor) { descriptor = null; - if (Transients.TryGetValue(typeof(T), out descriptor)) - { - return true; - } - if (Scoped.TryGetValue(typeof(T), out descriptor)) - { - return true; - } - if (Singletons.TryGetValue(typeof(T), out descriptor)) + if (Descriptors.TryGetValue(typeof(T), out descriptor)) { return true; } @@ -254,19 +249,9 @@ public bool TryGetServiceLifetime(out DependencyLifetime lifetime) { lifetime = DependencyLifetime.Unknown; - if (Transients.TryGetValue(typeof(T), out _)) + if (Descriptors.TryGetValue(typeof(T), out var desc)) { - lifetime = DependencyLifetime.Transient; - return true; - } - if (Scoped.TryGetValue(typeof(T), out _)) - { - lifetime = DependencyLifetime.Scoped; - return true; - } - if (Singletons.TryGetValue(typeof(T), out _)) - { - lifetime = DependencyLifetime.Singleton; + lifetime = desc.Lifetime; return true; } @@ -294,50 +279,59 @@ public bool CanBuildService(Type t) { if (_destroyed) throw new ServiceScopeDisposedException(nameof(CanBuildService), t, this); - return Transients.ContainsKey(t) || Scoped.ContainsKey(t) || Singletons.ContainsKey(t) || (Parent?.CanBuildService(t) ?? false); + return Descriptors.ContainsKey(t) || (Parent?.CanBuildService(t) ?? false); } + public object GetService(Type t) { - if (_destroyed) throw new ServiceScopeDisposedException(nameof(GetService), t, this); - - if (t == typeof(IDependencyProvider)) return this; - if (t == typeof(IDependencyProviderScope)) return this; - - if (Transients.TryGetValue(t, out var descriptor)) + lock (this) { - var service = descriptor.Factory(this); - return service; - } + if (_destroyed) throw new ServiceScopeDisposedException(nameof(GetService), t, this); - if (Scoped.TryGetValue(t, out descriptor)) - { - if (ScopeCache.TryGetValue(t, out var instance)) - { - return instance; - } + if (t == typeof(IDependencyProvider)) return this; + if (t == typeof(IDependencyProviderScope)) return this; - return ScopeCache[t] = descriptor.Factory(this); - } + if (Descriptors.TryGetValue(t, out var descriptor)) + { + if (descriptor.Lifetime != DependencyLifetime.Transient) + { + if (InstanceCache.TryGetValue(t, out var instance)) + { + return instance; + } + else + { + var service = InstanceCache[t] = descriptor.Factory(this); + var sortOrder = 0; + if (service is IBeamableDisposableOrder orderable) + { + sortOrder = orderable.DisposeOrder; + } + if (!SortedInstanceValues.TryGetValue(sortOrder, out var services)) + { + services = new List(); + SortedInstanceValues.Add(sortOrder, services); + } + services.Add(service); + return service; + } + } + else + { + return descriptor.Factory(this); + } + } - if (Singletons.TryGetValue(t, out descriptor)) - { - if (SingletonCache.TryGetValue(t, out var instance)) + if (Parent != null) { - return instance; + return Parent.GetService(t); } - return SingletonCache[t] = descriptor.Factory(this); - } - if (Parent != null) - { - return Parent.GetService(t); + throw new Exception($"Service not found {t.Name}"); } - - - throw new Exception($"Service not found {t.Name}"); } List> childRemovalPromises = new List>(); @@ -353,8 +347,7 @@ public async Promise Dispose() lock (_children) { - var childrenClone = new List(_children); - foreach (var child in childrenClone) + foreach (var child in _children) { if (child != null) { @@ -367,23 +360,20 @@ public async Promise Dispose() } } - await Promise.Sequence(childRemovalPromises); + + await Promise.WhenAll(childRemovalPromises); - async Promise DisposeServices(IEnumerable services) + async Promise DisposeServices(SortedDictionary> groups) { - var clonedList = new List(services); - var groups = clonedList.GroupBy(x => - { - if (x is IBeamableDisposableOrder disposableOrder) - return disposableOrder.DisposeOrder; - return 0; - }); - groups = groups.OrderBy(x => x.Key); - foreach (var group in groups) + if (groups.Count == 0) return; + + foreach (var kvp in groups) { + var services = kvp.Value; + var promises = new List>(); - - foreach (var service in group) + + foreach (var service in services) { if (service == null) continue; if (service is IBeamableDisposable disposable) @@ -395,13 +385,13 @@ async Promise DisposeServices(IEnumerable services) } } } - + var final = Promise.Sequence(promises); await final; } } - void ClearServices(ConcurrentDictionary descriptors) + void ClearServices(Dictionary descriptors) { foreach (var kvp in descriptors) { @@ -414,26 +404,21 @@ void ClearServices(ConcurrentDictionary descriptors) } - await DisposeServices(SingletonCache.Values.Distinct()); - await DisposeServices(ScopeCache.Values.Distinct()); + await DisposeServices(SortedInstanceValues); - SingletonCache.Clear(); - ScopeCache.Clear(); + InstanceCache.Clear(); + SortedInstanceValues.Clear(); if (!_options.allowHydration) { // remove from parent. Parent?.RemoveChild(this); - ClearServices(Singletons); - ClearServices(Transients); - ClearServices(Scoped); - Singletons = null; - Transients = null; - Scoped = null; + ClearServices(Descriptors); + Descriptors = null; - SingletonCache = null; - ScopeCache = null; + InstanceCache = null; + SortedInstanceValues = null; } _destroyed = true; @@ -445,32 +430,25 @@ public void Hydrate(IDependencyProviderScope other) _destroyed = other.IsDisposed; _isDestroying = false; - Transients = new ConcurrentDictionary(); - foreach (var desc in other.TransientServices) - { - if (!Transients.TryAdd(desc.Interface, desc)) - { - Debug.LogError($"Failed to hydrate transient interface=[{desc.Interface.Name}] because it already existed"); - } - } - Scoped = new ConcurrentDictionary(); - foreach (var desc in other.ScopedServices) - { - if (!Scoped.TryAdd(desc.Interface, desc)) - { - Debug.LogError($"Failed to hydrate scoped interface=[{desc.Interface.Name}] because it already existed"); - } - } - Singletons = new ConcurrentDictionary(); + Descriptors = new Dictionary(); foreach (var desc in other.SingletonServices) - { - if (!Singletons.TryAdd(desc.Interface, desc)) - { - Debug.LogError($"Failed to hydrate singleton interface=[{desc.Interface.Name}] because it already existed"); - } - } - SingletonCache.Clear(); - ScopeCache.Clear(); + // Transients = new Dictionary(); + // foreach (var desc in other.TransientServices) + // { + // Transients.Add(desc.Interface, desc); + // } + // Scoped = new Dictionary(); + // foreach (var desc in other.ScopedServices) + // { + // Scoped.Add(desc.Interface, desc); + // } + // Singletons = new Dictionary(); + // foreach (var desc in other.SingletonServices) + // { + // Singletons.Add(desc.Interface, desc); + // } + InstanceCache.Clear(); + SortedInstanceValues.Clear(); lock (_children) { @@ -502,7 +480,7 @@ public void RemoveChild(IDependencyProviderScope child) } void AddDescriptors(List target, - ConcurrentDictionary source, + Dictionary source, Func factory) { foreach (var kvp in source) @@ -531,15 +509,41 @@ public IDependencyProviderScope Fork(Action configure = null var builder = new DependencyBuilder(); // populate all of the existing services we have in this scope. + foreach (var source in Descriptors) + { + switch (source.Value.Lifetime) + { + case DependencyLifetime.Transient: + case DependencyLifetime.Scoped: + builder.Descriptors.Add(new ServiceDescriptor + { + Interface = source.Value.Interface, + Implementation = source.Value.Implementation, + Lifetime = source.Value.Lifetime, + Factory = (nextProvider) => source.Value.Factory(nextProvider) + }); + break; + case DependencyLifetime.Singleton: + builder.Descriptors.Add(new ServiceDescriptor + { + Interface = source.Value.Interface, + Implementation = source.Value.Implementation, + Lifetime = source.Value.Lifetime, + Factory = _ => GetService(source.Value.Interface) + }); + break; + + } + } // transients are stupid, and I should probably delete them. - AddDescriptors(builder.TransientServices, Transients, (nextProvider, desc) => desc.Factory(nextProvider)); - - // all scoped descriptors - AddDescriptors(builder.ScopedServices, Scoped, (nextProvider, desc) => desc.Factory(nextProvider)); - // scopes services build brand new instances per provider - - // singletons use their parent singleton cache. - AddDescriptors(builder.SingletonServices, Scoped, (_, desc) => GetService(desc.Interface)); + //AddDescriptors(builder.TransientServices, Transients, (nextProvider, desc) => desc.Factory(nextProvider)); + // + // // all scoped descriptors + // AddDescriptors(builder.ScopedServices, Scoped, (nextProvider, desc) => desc.Factory(nextProvider)); + // // scopes services build brand new instances per provider + // + // // singletons use their parent singleton cache. + // AddDescriptors(builder.SingletonServices, Scoped, (_, desc) => GetService(desc.Interface)); configure?.Invoke(builder); @@ -552,6 +556,27 @@ public IDependencyProviderScope Fork(Action configure = null return provider; } + + + public int Compare(object x, object y) + { + if (x == null || y == null) return 0; + + var xOrder = 0; + var yOrder = 0; + + if (x is IBeamableDisposableOrder xDisposable) + { + xOrder = xDisposable.DisposeOrder; + } + + if (y is IBeamableDisposableOrder yDisposable) + { + yOrder = yDisposable.DisposeOrder; + } + + return xOrder.CompareTo(yOrder); + } } } diff --git a/cli/beamable.common/Runtime/Dependencies/Utils.cs b/cli/beamable.common/Runtime/Dependencies/Utils.cs index 0638be8e3a..dcbe3d1a0a 100644 --- a/cli/beamable.common/Runtime/Dependencies/Utils.cs +++ b/cli/beamable.common/Runtime/Dependencies/Utils.cs @@ -38,10 +38,12 @@ public class ServiceDescriptor { public Type Interface, Implementation; public Func Factory; + public DependencyLifetime Lifetime; + public ServiceDescriptor Clone() { - return new ServiceDescriptor { Interface = Interface, Implementation = Implementation, Factory = Factory }; + return new ServiceDescriptor { Interface = Interface, Implementation = Implementation, Factory = Factory, Lifetime = Lifetime }; } } } diff --git a/cli/beamable.common/Runtime/Promise.cs b/cli/beamable.common/Runtime/Promise.cs index 16f56ee43d..16beff0eb2 100644 --- a/cli/beamable.common/Runtime/Promise.cs +++ b/cli/beamable.common/Runtime/Promise.cs @@ -48,8 +48,6 @@ public abstract class PromiseBase protected Exception err; protected ExceptionDispatchInfo errInfo; - protected StackTrace _errStackTrace; - protected object _lock = new object(); internal bool RaiseInnerException { get; set; } #if DISABLE_THREADING @@ -67,7 +65,7 @@ protected bool done } #endif - public static readonly Unit Unit = new Unit(); + public static Unit Unit = new Unit(); public static Promise SuccessfulUnit => Promise.Successful(Unit); @@ -81,6 +79,7 @@ protected bool done /// public bool IsFailed => done && err != null; + private static event PromiseEvent OnPotentialUncaughtError; public static bool HasUncaughtErrorHandler => OnPotentialUncaughtError != null; @@ -152,8 +151,8 @@ public static Promise ToPromise(this ITaskLike))] public class Promise : PromiseBase, ICriticalNotifyCompletion { - private Action _callbacks; - private T _val; + protected Action _callbacks; + protected T _val; /// /// Call to set the value and resolve the %Promise @@ -161,7 +160,7 @@ public class Promise : PromiseBase, ICriticalNotifyCompletion /// public void CompleteSuccess(T val) { - lock (_lock) + lock (this) { if (done) { @@ -190,7 +189,7 @@ public void CompleteSuccess(T val) /// public void CompleteError(Exception ex) { - lock (_lock) + lock (this) { if (done) { @@ -232,7 +231,7 @@ public void CompleteError(Exception ex) /// public Promise Then(Action callback) { - lock (_lock) + lock (this) { if (done) { @@ -292,7 +291,7 @@ public Promise Merge(Promise other) /// public Promise Error(Action errback) { - lock (_lock) + lock (this) { HadAnyErrbacks = true; if (done) @@ -657,9 +656,32 @@ public class Promise : Promise { public static Promise Success { get; } = new Promise { done = true }; - public void CompleteSuccess() => CompleteSuccess(PromiseBase.Unit); + public void CompleteSuccess() + { + // CompleteSuccessRef(); + lock (this) + { + if (done) + { + return; + } + + _val = Unit; + done = true; + try + { + _callbacks?.Invoke(Unit); + } + catch (Exception e) + { + BeamableLogger.LogException(e); + } + + _callbacks = null; + errbacks = null; + } + } - /// /// This function accepts a generator that will produce a promise, and then /// that promise will be executed. @@ -740,6 +762,29 @@ public static SequencePromise ObservableSequence(IList> promise return result; } + public static Promise WhenAll(List> promises) + { + if (promises.Count == 0) + return Success; + + var result = new Promise(); + Check(); + return result; + void Check() + { + for (var i = 0; i < promises.Count; i++) + { + if (promises[i].IsCompleted) + continue; + + promises[i].Error(ex => result.CompleteError(ex)); + promises[i].Then(_ => Check()); + return; + } + result.CompleteSuccess(); + } + } + /// /// Create a of List from a List of s. /// @@ -1250,25 +1295,38 @@ public void Start(ref TStateMachine stateMachine) public Promise Task => _promise; } - public sealed class PromiseAsyncMethodBuilder + public struct PromiseAsyncMethodBuilder { private IAsyncStateMachine _stateMachine; - private Promise _promise = new Promise(); // TODO: allocation. + private Promise _promise;// = new Promise(); // TODO: allocation. + + private Promise Promise + { + get + { + if (_promise == null) + { + _promise = new Promise(); // TODO: allocation? + } + return _promise; + } + } + public static PromiseAsyncMethodBuilder Create() { - return new PromiseAsyncMethodBuilder(); + return default; } public void SetResult() { - _promise.CompleteSuccess(PromiseBase.Unit); + Promise.CompleteSuccess(); } public void SetException(Exception ex) { - _promise.RaiseInnerException = true; - _promise.CompleteError(ex); + Promise.RaiseInnerException = true; + Promise.CompleteError(ex); } public void SetStateMachine(IAsyncStateMachine machine) @@ -1287,10 +1345,11 @@ public void AwaitOnCompleted( _stateMachine.SetStateMachine(stateMachine); } - awaiter.OnCompleted(() => - { - _stateMachine.MoveNext(); - }); + awaiter.OnCompleted(_stateMachine.MoveNext); + // awaiter.OnCompleted(() => + // { + // // _stateMachine.MoveNext(); + // }); } public void AwaitUnsafeOnCompleted( @@ -1307,6 +1366,6 @@ public void Start(ref TStateMachine stateMachine) stateMachine.MoveNext(); } - public Promise Task => _promise; + public Promise Task => Promise; } } diff --git a/cli/cli.sln b/cli/cli.sln index 1bd4245495..b2199da2ea 100644 --- a/cli/cli.sln +++ b/cli/cli.sln @@ -14,6 +14,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "beamable.server.common", "b EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "unityenginestubs.addressables", "..\microservice\unityEngineStubs.addressables\unityenginestubs.addressables.csproj", "{04402A61-B144-4536-BCA1-E2122B342B04}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "Benchmarks\Benchmarks.csproj", "{B78AE042-D90B-4E38-AEC6-F0CC61E4DA3C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -48,5 +50,9 @@ Global {04402A61-B144-4536-BCA1-E2122B342B04}.Debug|Any CPU.Build.0 = Debug|Any CPU {04402A61-B144-4536-BCA1-E2122B342B04}.Release|Any CPU.ActiveCfg = Release|Any CPU {04402A61-B144-4536-BCA1-E2122B342B04}.Release|Any CPU.Build.0 = Release|Any CPU + {B78AE042-D90B-4E38-AEC6-F0CC61E4DA3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B78AE042-D90B-4E38-AEC6-F0CC61E4DA3C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B78AE042-D90B-4E38-AEC6-F0CC61E4DA3C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B78AE042-D90B-4E38-AEC6-F0CC61E4DA3C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/cli/tests/DI/MultiThreadedAccessTests.cs b/cli/tests/DI/MultiThreadedAccessTests.cs new file mode 100644 index 0000000000..ebdbd68ded --- /dev/null +++ b/cli/tests/DI/MultiThreadedAccessTests.cs @@ -0,0 +1,44 @@ +using Beamable.Common.Dependencies; +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace tests.DI; + +public class MultiThreadedAccessTests +{ + + [Test] + public void SetTest() + { + } + + [Test] + public async Task SingletonOnlyGetsMadeOnce() + { + var builder = new DependencyBuilder(); + int count = 0; + builder.AddSingleton(_ => + { + count++; + return new A(); + }); + var provider = builder.Build(); + + var tasks = Enumerable.Range(0, 10_000).Select(i => Task.Run(async () => + { + await Task.Delay(1); + provider.GetService(); + })); + + await Task.WhenAll(tasks); + + Assert.That(count, Is.EqualTo(1), "the factory function should only be invoked once."); + } + + public class A + { + // no-op class just for testing + } +} diff --git a/microservice/microservice/dbmicroservice/MicroserviceBootstrapper.cs b/microservice/microservice/dbmicroservice/MicroserviceBootstrapper.cs index 7521705797..95165562e1 100644 --- a/microservice/microservice/dbmicroservice/MicroserviceBootstrapper.cs +++ b/microservice/microservice/dbmicroservice/MicroserviceBootstrapper.cs @@ -236,10 +236,7 @@ public static IDependencyBuilder ConfigureServices(IMicroserviceArgs envArgs) return new MicroserviceHttpRequester(envArgs, new HttpClient(handler)); }) .AddSingleton(envArgs) - .AddSingleton(_ => - { - return Instances[0].SocketContext; - }) + .AddSingleton(_ => Instances[0].SocketContext) .AddScoped(provider => new MicroserviceRequester(provider.GetService(), provider.GetService(), provider.GetService(), true)) .AddScoped(provider => provider.GetService()) diff --git a/microservice/microserviceTests/PromiseTests/AsyncTests.cs b/microservice/microserviceTests/PromiseTests/AsyncTests.cs new file mode 100644 index 0000000000..10b5d2cd27 --- /dev/null +++ b/microservice/microserviceTests/PromiseTests/AsyncTests.cs @@ -0,0 +1,28 @@ +using Beamable.Common; +using Beamable.Common.Api; +using NUnit.Framework; +using System.Threading.Tasks; + +namespace microserviceTests.PromiseTests; + +public class AsyncTests +{ + [Test] + public async Task Test() + { + async Promise Method() + { + await Task.Delay(50); + } + await Method(); + + } + + [Test] + public async Task SimpleAwait() + { + var p = new Promise(); + p.CompleteSuccess(); + await p; + } +} diff --git a/microservice/microserviceTests/PromiseTests/WhenAllTests.cs b/microservice/microserviceTests/PromiseTests/WhenAllTests.cs new file mode 100644 index 0000000000..a921875d9c --- /dev/null +++ b/microservice/microserviceTests/PromiseTests/WhenAllTests.cs @@ -0,0 +1,38 @@ +using Beamable.Common; +using NUnit.Framework; +using System.Collections.Generic; + +namespace microserviceTests.PromiseTests; + +public class WhenAllTests +{ + [Test] + public void Simple() + { + var pList = new List> + { + new Promise(), + new Promise(), + new Promise(), + new Promise(), + }; + + var whenAll = Promise.WhenAll(pList); + var ran = false; + whenAll.Then(_ => + { + for (var i = 0; i < pList.Count; i++) + { + Assert.That(pList[i].IsCompleted, $"promise index=[{i}] must be completed."); + } + + ran = true; + }); + + foreach (var p in pList) + { + p.CompleteSuccess(1); + } + Assert.That(ran, Is.True); + } +}