Skip to content

Commit

Permalink
Refactor setup code, struggling with ConcurrentList
Browse files Browse the repository at this point in the history
  • Loading branch information
jtheisen committed Jan 25, 2023
1 parent 455561d commit 195a17c
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 105 deletions.
2 changes: 1 addition & 1 deletion Moldinium.Tests/Moldinium/CombinedTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public void InterfaceTypeWithParameterizedFactoryInstanceTest()

IDependencyProvider GetProvider() => DependencyProvider.Create(
new DefaultDependencyProviderConfiguration(
Baking: DefaultDependencyProviderBakingMode.Tracking
Mode: MoldiniumDefaultMode.Tracking
)
);

Expand Down
2 changes: 1 addition & 1 deletion Moldinium.Tests/Moldinium/MoldiniumTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

public class MoldiniumTestsBase
{
protected T CreateTestModel<T>(DefaultDependencyProviderBakingMode mode = DefaultDependencyProviderBakingMode.TrackingAndNotifyPropertyChanged, Boolean logReport = false)
protected T CreateTestModel<T>(MoldiniumDefaultMode mode = MoldiniumDefaultMode.TrackingAndNotifyPropertyChanged, Boolean logReport = false)
{
var config = new DefaultDependencyProviderConfiguration(mode) { IsMoldiniumType = t => t.IsInterface && !t.Name.StartsWithFollowedByCapital("I") };

Expand Down
2 changes: 1 addition & 1 deletion Moldinium/Baking/NullableContextAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public static void SetNullableAttributes(Action<CustomAttributeBuilder> target,
arg = moreArgs.Select(a => (Byte)a.Value!).ToArray();
}

var builder = new CustomAttributeBuilder(attribute.Constructor, new Object[] { arg } );
var builder = new CustomAttributeBuilder(attribute.Constructor, new Object[] { arg! } );

target(builder);
}
Expand Down
124 changes: 111 additions & 13 deletions Moldinium/Combined/Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
using Moldinium.Injection;
using System;
using System.Collections.Generic;
using System.Reflection.Emit;

namespace Moldinium;

[Flags]
public enum DefaultDependencyProviderBakingMode
public enum MoldiniumDefaultMode
{
Basic = 0,

Expand Down Expand Up @@ -53,12 +54,42 @@ public IDependencyProvider Build()
}
}

public class MoldiniumConfigurationBuilder
{
MoldiniumDefaultMode mode;
Type? defaultIListAndICollationType;
Predicate<Type>? isModliniumType;

public MoldiniumConfigurationBuilder SetMode(MoldiniumDefaultMode mode)
=> Modify(() => this.mode = mode);

public MoldiniumConfigurationBuilder SetIListAndICollectionImplementationType(Type defaultIListAndICollationType)
=> Modify(() => this.defaultIListAndICollationType = defaultIListAndICollationType);

public MoldiniumConfigurationBuilder IdentifyMoldiniumTypes(Predicate<Type> isModliniumType)
=> Modify(() => this.isModliniumType = isModliniumType);

MoldiniumConfigurationBuilder Modify(Action action)
{
action();
return this;
}

public DefaultDependencyProviderConfiguration Build(IServiceProvider services)
=> new DefaultDependencyProviderConfiguration(mode,
DefaultIListAndICollationType: defaultIListAndICollationType,
IsMoldiniumType: isModliniumType,
Services: services
);
}

public record DefaultDependencyProviderConfiguration(
DefaultDependencyProviderBakingMode? Baking = DefaultDependencyProviderBakingMode.Basic,
MoldiniumDefaultMode? Mode = MoldiniumDefaultMode.Basic,
Boolean BakeAbstract = false,
Boolean InitializeInits = true,
Boolean EnableFactories = true,
Boolean AcceptDefaultConstructibles = false,
Type? DefaultIListAndICollationType = null,
Action<DependencyProviderBuilder>? Build = null,
IServiceProvider? Services = null,
Predicate<Type>? IsMoldiniumType = null
Expand Down Expand Up @@ -89,13 +120,31 @@ public static IDependencyProvider Create(DefaultDependencyProviderConfiguration
providers.Add(new AcceptingDefaultConstructiblesDependencyProvider());
}

if (config.Baking is DefaultDependencyProviderBakingMode bakingMode)
if (config.Mode is MoldiniumDefaultMode mode)
{
var componentGenerators = CreateBakeryComponentGenerators(bakingMode);
var componentGenerators = CreateBakeryComponentGenerators(mode);

var bakeryconfiguration = new BakeryConfiguration(componentGenerators, Defaults.GetDefaultDefaultProvider(), config.BakeAbstract);
String moduleNameSuffix = "";

providers.Add(new BakeryDependencyProvider(new Bakery("TestBakery", bakeryconfiguration), config.IsMoldiniumType));
Type? genericCollectionType;

if (config.DefaultIListAndICollationType is not null)
{
moduleNameSuffix = $".{config.DefaultIListAndICollationType.Name}";
genericCollectionType = config.DefaultIListAndICollationType;
}
else
{
genericCollectionType = GetDefaultGenericCollectionType(mode);
}

var defaultProvider = Defaults.GetDefaultDefaultProvider(genericCollectionType);

var bakeryconfiguration = new BakeryConfiguration(componentGenerators, defaultProvider, config.BakeAbstract);

var bakery = new Bakery($"MoldiniumTypes.{mode}{moduleNameSuffix}", bakeryconfiguration);

providers.Add(new BakeryDependencyProvider(bakery, config.IsMoldiniumType));
}

if (config.EnableFactories)
Expand All @@ -113,15 +162,23 @@ public static IDependencyProvider Create(DefaultDependencyProviderConfiguration
return new CombinedDependencyProvider(providers.ToArray());
}

static ComponentGenerators CreateBakeryComponentGenerators(DefaultDependencyProviderBakingMode mode) => mode switch
static Type GetDefaultGenericCollectionType(MoldiniumDefaultMode mode) => mode switch
{
DefaultDependencyProviderBakingMode.Basic => ComponentGenerators.Create(
MoldiniumDefaultMode.Basic => typeof(List<>),
MoldiniumDefaultMode.NotifyPropertyChanged => typeof(LiveList<>), // just because ObservableCollection doesn't implement IList<>
MoldiniumDefaultMode.Tracking or MoldiniumDefaultMode.TrackingAndNotifyPropertyChanged => typeof(TrackableList<>),
_ => throw new NotImplementedException()
};

static ComponentGenerators CreateBakeryComponentGenerators(MoldiniumDefaultMode mode) => mode switch
{
MoldiniumDefaultMode.Basic => ComponentGenerators.Create(
typeof(SimplePropertyImplementation<>),
typeof(GenericEventImplementation<>)),
DefaultDependencyProviderBakingMode.Tracking => ComponentGenerators.Create(
MoldiniumDefaultMode.Tracking => ComponentGenerators.Create(
typeof(TrackedPropertyImplementation<>),
typeof(TrackedComputedPropertyImplementation<,>)),
DefaultDependencyProviderBakingMode.TrackingAndNotifyPropertyChanged => ComponentGenerators.Create(
MoldiniumDefaultMode.TrackingAndNotifyPropertyChanged => ComponentGenerators.Create(
typeof(TrackedNotifyingPropertyImplementation<,>),
typeof(TrackedNotifyingComputedPropertyImplementation<,,>)),
_ => throw new NotImplementedException()
Expand All @@ -134,17 +191,58 @@ public static partial class Extensions
/// Checks whether <paramref name="name"/> starts with <paramref name="prefix"/> and that the
/// first letter following that prefix is in upper case.
/// Eg. <code>name.StartsWithFollowedByCapital("I")</code> detects interfaces
/// or styleguide violations
/// or styleguide violations such as <see cref="ILGenerator"/>
/// </summary>
public static Boolean StartsWithFollowedByCapital(this String name, String prefix)
=> name.Length > prefix.Length && name.StartsWith(prefix) && Char.IsUpper(name[prefix.Length]);

static IDependencyProvider GetProvider(this Action<MoldiniumConfigurationBuilder> build, IServiceProvider services)
{
var builder = new MoldiniumConfigurationBuilder();
build(builder);
var configuration = builder.Build(services);
return DependencyProvider.Create(configuration);
}

public static IServiceCollection AddMoldiniumRoot<T>(this IServiceCollection services, DefaultDependencyProviderConfiguration configuration)
/// <summary>
/// Adds the type <typeparamref name="T"/> as a resolvable type. It also adds <see cref="Scope{T}"/> as a singleton
/// that can be resolved for early validation and to get the resolution report.
/// </summary>
public static IServiceCollection AddSingletonMoldiniumRoot<T>(this IServiceCollection services, Action<MoldiniumConfigurationBuilder> build)
where T : class => services

.AddSingleton<Scope<T>>(sp => new Scope<T>(DependencyProvider.Create(configuration), DependencyRuntimeMaturity.InitializedInstance))
.AddSingleton<Scope<T>>(sp => new Scope<T>(build.GetProvider(sp), DependencyRuntimeMaturity.InitializedInstance))
.AddSingleton<T>(sp => (T)sp.GetRequiredService<Scope<T>>().CreateRuntimeScope().Root)

;

/// <summary>
/// Adds the type <typeparamref name="T"/> as a resolvable type. It also adds <see cref="Scope{T}"/> as a singleton
/// that can be resolved for early validation and to get the resolution report.
/// </summary>
public static IServiceCollection AddScopedMoldiniumRoot<T>(this IServiceCollection services, Action<MoldiniumConfigurationBuilder> build)
where T : class => services

.AddSingleton<Scope<T>>(sp => new Scope<T>(build.GetProvider(sp), DependencyRuntimeMaturity.InitializedInstance))
.AddScoped<T>(sp => (T)sp.GetRequiredService<Scope<T>>().CreateRuntimeScope().Root)

;

/// <summary>
/// Adds the type <typeparamref name="T"/> as a resolvable type. It also adds <see cref="Scope{T}"/> as a singleton
/// that can be resolved for early validation and to get the resolution report.
/// </summary>
public static IServiceCollection AddTransientMoldiniumRoot<T>(this IServiceCollection services, Action<MoldiniumConfigurationBuilder> build)
where T : class => services

.AddSingleton<Scope<T>>(sp => new Scope<T>(build.GetProvider(sp), DependencyRuntimeMaturity.InitializedInstance))
.AddTransient<T>(sp => (T)sp.GetRequiredService<Scope<T>>().CreateRuntimeScope().Root)

;

/// <summary>
/// Validates dependencies early and gives a dependency report
/// </summary>
public static String ValidateMoldiniumRoot<T>(this IServiceProvider services)
=> services.GetRequiredService<Scope<T>>().CreateDependencyReport();
}
26 changes: 13 additions & 13 deletions Moldinium/Defaults/Defaults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ public interface IDefaultProvider

public class DefaultDefaultProvider : IDefaultProvider
{
private readonly bool useTrackableList;
private readonly Type? genericCollectionType;

public DefaultDefaultProvider(Boolean useTrackableList = true)
public DefaultDefaultProvider(Type? genericCollectionType)
{
this.useTrackableList = useTrackableList;
this.genericCollectionType = genericCollectionType;
}

public Type? GetDefaultType(Type type)
Expand All @@ -73,11 +73,11 @@ public DefaultDefaultProvider(Boolean useTrackableList = true)

if (interfaces.DoesTypeImplement(typeof(ICollection<>)))
{
if (useTrackableList)
if (genericCollectionType is not null)
{
var trackableListDefaultType = GetDefaultImplementationTypeForTrackableList(type);
var defaultImplementationType = GetDefaultImplementationTypeForCollectionType(genericCollectionType, type);

return trackableListDefaultType;
return defaultImplementationType;
}
else if (traits.IsDefaultConstructible)
{
Expand All @@ -88,23 +88,23 @@ public DefaultDefaultProvider(Boolean useTrackableList = true)
return null;
}

Type GetDefaultImplementationTypeForTrackableList(Type type)
Type GetDefaultImplementationTypeForCollectionType(Type genericCollectionType, Type valueType)
{
var arguments = type.GetGenericArguments();
var arguments = valueType.GetGenericArguments();

if (arguments.Length <= 1)
{
var typeArgument = arguments.Length == 1 ? arguments[0] : typeof(Object);

var implementationType = typeof(TrackableList<>).MakeGenericType(typeArgument);
var implementationType = genericCollectionType.MakeGenericType(typeArgument);

var defaultImplementationType = typeof(DefaultDefaultConstructible<,>).MakeGenericType(type, implementationType);
var defaultImplementationType = typeof(DefaultDefaultConstructible<,>).MakeGenericType(valueType, implementationType);

return defaultImplementationType;
}
else
{
throw new Exception($"Expected generic collection type {type} to have at most one type paramter");
throw new Exception($"Expected generic collection type {valueType} to have at most one type paramter");
}
}
}
Expand All @@ -114,6 +114,6 @@ public static class Defaults
public static Type CreateConcreteDefaultImplementationType(Type type, Type valueType)
=> type.IsGenericTypeDefinition ? type.MakeGenericType(valueType) : type;

public static IDefaultProvider GetDefaultDefaultProvider()
=> new DefaultDefaultProvider();
public static IDefaultProvider GetDefaultDefaultProvider(Type? genericCollectionType = null)
=> new DefaultDefaultProvider(genericCollectionType);
}
19 changes: 15 additions & 4 deletions Moldinium/Misc/ConcurrentList.cs
Original file line number Diff line number Diff line change
@@ -1,35 +1,46 @@
using System.Collections;
using System.Collections.Immutable;
using System.Threading;

namespace Moldinium.Misc;

struct LockHelper : IDisposable
{
SpinLock spinLock;

Int32 checkCounter;
Int32 depth;

public LockHelper Lock()
{
if (++depth > 1)
{
if (!spinLock.IsHeldByCurrentThread) throw new Exception($"Expected to hold the lock");

return this;
}

Boolean lockTaken = false;

spinLock.Enter(ref lockTaken);

if (!lockTaken) throw new Exception("Can't take lock");

if (++checkCounter != 1) throw new Exception("Unexpected log check counter");

return this;
}

public void Dispose()
{
if (--checkCounter != 0) throw new Exception("Unexpected log check counter");
if (--depth > 0) return;

spinLock.Exit();
}
}

ref struct LockHelperRef
{
public ref int lockHelper;
}

/// <summary>
/// A simple but likely quite inefficient implementation of a concurrent list that
/// is meant only for demonstration purposes.
Expand Down
41 changes: 0 additions & 41 deletions Moldinium/Tracking/Command.cs

This file was deleted.

6 changes: 1 addition & 5 deletions Moldinium/Tracking/Trackables.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Moldinium;
namespace Moldinium;

interface ITrackSubscription : IDisposable
{
Expand Down
Loading

0 comments on commit 195a17c

Please sign in to comment.