Skip to content

Commit

Permalink
Dispose and version
Browse files Browse the repository at this point in the history
Changed CobbleContext to allow being disposed, which allows units to implement dispose if they need to be destroyed at some point
Reduced the .NET Standard version to improve compatability
  • Loading branch information
OrionNebula committed Oct 10, 2018
1 parent 266df53 commit ebc8f5a
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 27 deletions.
26 changes: 26 additions & 0 deletions ProjectCeilidh.Cobble.Tests/AsyncCobbleContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,32 @@ public async Task DictInstanceGenerator()
Assert.Equal("Hi", testUnit.TestValue);
}

[Fact]
public async Task DisposeTest()
{
_context.AddManaged<DisposeTestUnit>();

await _context.ExecuteAsync();
Assert.True(_context.TryGetSingleton(out DisposeTestUnit testUnit) && !testUnit.IsDisposed);
_context.Dispose();
Assert.True(testUnit.IsDisposed);
}

private class DisposeTestUnit : IDisposable
{
public bool IsDisposed { get; private set; }

public DisposeTestUnit()
{
IsDisposed = false;
}

public void Dispose()
{
IsDisposed = true;
}
}

private interface ITestUnit
{
string TestValue { get; }
Expand Down
33 changes: 32 additions & 1 deletion ProjectCeilidh.Cobble.Tests/CobbleContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace ProjectCeilidh.Cobble.Tests
{
public class CobbleContextTests
public class CobbleContextTests : IDisposable
{
private readonly CobbleContext _context;

Expand Down Expand Up @@ -107,6 +107,32 @@ public void DictInstanceGenerator()
Assert.Equal("Hi", testUnit.TestValue);
}

[Fact]
public void DisposeTest()
{
_context.AddManaged<DisposeTestUnit>();

_context.Execute();
Assert.True(_context.TryGetSingleton(out DisposeTestUnit testUnit) && !testUnit.IsDisposed);
_context.Dispose();
Assert.True(testUnit.IsDisposed);
}

private class DisposeTestUnit : IDisposable
{
public bool IsDisposed { get; private set; }

public DisposeTestUnit()
{
IsDisposed = false;
}

public void Dispose()
{
IsDisposed = true;
}
}

private interface ITestUnit
{
string TestValue { get; }
Expand Down Expand Up @@ -151,5 +177,10 @@ public void UnitLoaded(ITestUnit unit)
TestUnits.Add(unit);
}
}

public void Dispose()
{
_context.Dispose();
}
}
}
41 changes: 27 additions & 14 deletions ProjectCeilidh.Cobble/CobbleContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using ProjectCeilidh.Cobble.Data;
using ProjectCeilidh.Cobble.Generator;
Expand All @@ -11,7 +12,8 @@ namespace ProjectCeilidh.Cobble
/// <summary>
/// An application context which allows for dependency graph construction and object creation.
/// </summary>
public sealed class CobbleContext
/// <inheritdoc />
public sealed class CobbleContext : IDisposable
{
public delegate object DuplicateResolutionHandler(Type dependencyType, object[] instances);

Expand All @@ -22,6 +24,7 @@ public sealed class CobbleContext
private readonly List<IInstanceGenerator> _instanceGenerators;
private readonly ConcurrentDictionary<Type, ConcurrentBag<object>> _lateInjectInstances;
private readonly ConcurrentDictionary<Type, ConcurrentBag<object>> _implementations;
private readonly ConcurrentBag<IDisposable> _disposeHooks;

/// <summary>
/// Construct a new CobbleContext.
Expand All @@ -31,6 +34,7 @@ public CobbleContext()
_instanceGenerators = new List<IInstanceGenerator>();
_lateInjectInstances = new ConcurrentDictionary<Type, ConcurrentBag<object>>();
_implementations = new ConcurrentDictionary<Type, ConcurrentBag<object>>();
_disposeHooks = new ConcurrentBag<IDisposable>();

AddUnmanaged(this);
}
Expand Down Expand Up @@ -94,7 +98,7 @@ public void AddManaged(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));

if (type.GetConstructors().Length != 1)
if (type.GetTypeInfo().DeclaredConstructors.Count() != 1)
throw new ArgumentException("Managed types must have exactly one public constructor.", nameof(type));

AddManaged(new TypeLateInstanceGenerator(type));
Expand All @@ -113,13 +117,13 @@ public void AddManaged(IInstanceGenerator generator)
else
{
var inst = CreateInstance(generator, _implementations);
PushInstanceProvides(generator, inst, _implementations);
PushInstanceProvides(generator, inst);
foreach(var prov in generator.Provides)
{
if (!_lateInjectInstances.TryGetValue(prov, out var set)) continue;

foreach (var late in set)
late.GetType().GetMethod(nameof(ILateInject<object>.UnitLoaded), new[] { prov })?.Invoke(late, new []{ inst });
late.GetType().GetRuntimeMethod(nameof(ILateInject<object>.UnitLoaded), new[] { prov })?.Invoke(late, new []{ inst });
}
}
}
Expand All @@ -135,7 +139,7 @@ public void Execute()

// Create a lookup which maps provided type to the set off all generators that provide it.
var implMap = _instanceGenerators
.SelectMany(x => x.Provides.Select(y => (Type: y, Generator: x)))
.SelectMany(x => x.Provides.Select(y => new { Type = y, Generator = x }))
.ToLookup(x => x.Type, x => x.Generator);

var graph = new DirectedGraph<IInstanceGenerator>(_instanceGenerators);
Expand All @@ -147,7 +151,7 @@ public void Execute()
var depType = dep;

if (dep.IsConstructedGenericType && dep.GetGenericTypeDefinition() == typeof(IEnumerable<>))
depType = dep.GetGenericArguments()[0];
depType = dep.GenericTypeArguments[0];

foreach (var impl in implMap[depType])
graph.Link(impl, gen);
Expand All @@ -159,7 +163,7 @@ public void Execute()
foreach (var gen in graph.TopologicalSort()) // Sort the dependency graph topologically - all dependencies should be satisfied by the time we get to each unit
{
var obj = CreateInstance(gen, _implementations);
PushInstanceProvides(gen, obj, _implementations);
PushInstanceProvides(gen, obj);

if (!(gen is ILateInstanceGenerator late)) continue;

Expand Down Expand Up @@ -187,7 +191,7 @@ public async Task ExecuteAsync()

// Create a lookup which maps provided type to the set off all generators that provide it.
var implMap = _instanceGenerators
.SelectMany(x => x.Provides.Select(y => (Type: y, Generator: x)))
.SelectMany(x => x.Provides.Select(y => new { Type = y, Generator = x }))
.ToLookup(x => x.Type, x => x.Generator);

var graph = new DirectedGraph<IInstanceGenerator>(_instanceGenerators);
Expand All @@ -199,7 +203,7 @@ public async Task ExecuteAsync()
var depType = dep;

if (dep.IsConstructedGenericType && dep.GetGenericTypeDefinition() == typeof(IEnumerable<>))
depType = dep.GetGenericArguments()[0];
depType = dep.GenericTypeArguments[0];

foreach (var impl in implMap[depType])
graph.Link(impl, gen);
Expand All @@ -211,7 +215,7 @@ public async Task ExecuteAsync()
await graph.ParallelTopologicalSort(gen =>
{
var obj = CreateInstance(gen, _implementations);
PushInstanceProvides(gen, obj, _implementations);
PushInstanceProvides(gen, obj);
if (!(gen is ILateInstanceGenerator late)) return;
Expand All @@ -230,20 +234,29 @@ await graph.ParallelTopologicalSort(gen =>
}
}

public void Dispose()
{
while (_disposeHooks.TryTake(out var result))
result.Dispose();
}

/// <summary>
/// Given a generator and an instance, push it into the relevant provider dictionaries
/// </summary>
/// <param name="gen">The instance generator that produced the instance.</param>
/// <param name="instance">The instance that was produced.</param>
/// <param name="instances">A dictionary mapping provided types to a set of instances.</param>
private static void PushInstanceProvides(IInstanceGenerator gen, object instance, ConcurrentDictionary<Type, ConcurrentBag<object>> instances)
private void PushInstanceProvides(IInstanceGenerator gen, object instance)
{
foreach (var prov in gen.Provides)
instances.AddOrUpdate(prov, x => new ConcurrentBag<object>(new[] {instance}), (a, b) =>
_implementations.AddOrUpdate(prov, x => new ConcurrentBag<object>(new[] {instance}), (a, b) =>
{
b.Add(instance);
return b;
});

if (instance is IDisposable disp)
_disposeHooks.Add(disp);

}

/// <summary>
Expand All @@ -260,7 +273,7 @@ private object CreateInstance(IInstanceGenerator gen, IDictionary<Type, Concurre
{
if (dep.IsConstructedGenericType && dep.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
var depType = dep.GetGenericArguments()[0];
var depType = dep.GenericTypeArguments[0];

if (instances.TryGetValue(depType, out var set))
{
Expand Down
7 changes: 4 additions & 3 deletions ProjectCeilidh.Cobble/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Reflection;

namespace ProjectCeilidh.Cobble
{
Expand Down Expand Up @@ -36,16 +37,16 @@ public static IEnumerable<T> Unroll<T>(this T value, Func<T, IEnumerable<T>> tra

public static IEnumerable<Type> GetAssignableFrom(this Type type)
{
foreach (var intf in type.GetInterfaces())
foreach (var intf in type.GetTypeInfo().ImplementedInterfaces)
yield return intf;

yield return type;

var b = type.BaseType;
var b = type.GetTypeInfo().BaseType;

while (b != null && b != typeof(object))
{
b = b.BaseType;
b = b.GetTypeInfo().BaseType;
yield return b;
}
}
Expand Down
3 changes: 2 additions & 1 deletion ProjectCeilidh.Cobble/Generator/BareLateInstanceGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace ProjectCeilidh.Cobble.Generator
{
Expand All @@ -21,7 +22,7 @@ public BareLateInstanceGenerator(object instance)
_instance = instance ?? throw new ArgumentNullException(nameof(instance));

var type = _instance.GetType();
LateDependencies = type.GetInterfaces().Where(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(ILateInject<>)).Select(x => x.GetGenericArguments()[0]).ToArray();
LateDependencies = type.GetTypeInfo().ImplementedInterfaces.Where(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(ILateInject<>)).Select(x => x.GenericTypeArguments[0]).ToArray();
Provides = type.GetAssignableFrom().ToArray();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace ProjectCeilidh.Cobble.Generator
/// </summary>
public class DictionaryInstanceGenerator : IInstanceGenerator
{
private static readonly MethodInfo DispatchProxyCreate = typeof(DispatchProxy).GetMethod(nameof(DispatchProxy.Create)) ?? throw new Exception("Cannot find DispatchProxy.Create - this should be impossible.");
private static readonly MethodInfo DispatchProxyCreate = typeof(DispatchProxy).GetRuntimeMethod(nameof(DispatchProxy.Create), new Type[0]) ?? throw new Exception("Cannot find DispatchProxy.Create - this should be impossible.");

public IEnumerable<Type> Provides { get; }
public IEnumerable<Type> Dependencies { get; }
Expand All @@ -28,8 +28,8 @@ public class DictionaryInstanceGenerator : IInstanceGenerator
/// <param name="implementations">A dictionary containing all implemented functions as delegates.</param>
public DictionaryInstanceGenerator(Type contractType, Delegate ctor, IReadOnlyDictionary<MethodInfo, Delegate> implementations)
{
Dependencies = ctor == null ? new Type[0] : ctor.Method.GetParameters().Select(x => x.ParameterType).ToArray();
Provides = contractType.GetInterfaces().Concat(new []{ contractType }).Concat(contractType.Unroll(x => x.BaseType == typeof(object) || x.BaseType == null ? new Type[0] : new []{ x.BaseType }));
Dependencies = ctor == null ? new Type[0] : ctor.GetMethodInfo().GetParameters().Select(x => x.ParameterType).ToArray();
Provides = contractType.GetTypeInfo().ImplementedInterfaces.Concat(new []{ contractType }).Concat(contractType.Unroll(x => x.GetTypeInfo().BaseType == typeof(object) || x.GetTypeInfo().BaseType == null ? new Type[0] : new []{ x.GetTypeInfo().BaseType }));
_contractType = contractType;
_ctor = ctor;
_implementations = implementations;
Expand Down
7 changes: 4 additions & 3 deletions ProjectCeilidh.Cobble/Generator/TypeLateInstanceGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace ProjectCeilidh.Cobble.Generator
{
Expand All @@ -19,14 +20,14 @@ public class TypeLateInstanceGenerator : ILateInstanceGenerator
public TypeLateInstanceGenerator(Type target)
{
_target = target;
Dependencies = target.GetConstructors().Single().GetParameters().Select(x => x.ParameterType).ToArray();
Dependencies = target.GetTypeInfo().DeclaredConstructors.Single().GetParameters().Select(x => x.ParameterType).ToArray();
Provides = target.GetAssignableFrom().ToArray();
LateDependencies = target.GetInterfaces().Where(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(ILateInject<>)).Select(x => x.GetGenericArguments()[0]).ToArray();
LateDependencies = target.GetTypeInfo().ImplementedInterfaces.Where(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(ILateInject<>)).Select(x => x.GenericTypeArguments[0]).ToArray();
}

public object GenerateInstance(object[] args)
{
var ctor = _target.GetConstructors().Single();
var ctor = _target.GetTypeInfo().DeclaredConstructors.Single();
return ctor.Invoke(args);
}

Expand Down
4 changes: 2 additions & 2 deletions ProjectCeilidh.Cobble/ProjectCeilidh.Cobble.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard1.3</TargetFramework>
<LangVersion>latest</LangVersion>
<Authors>Olivia Trewin</Authors>
<Company>Project Ceilidh</Company>
Expand All @@ -12,7 +12,7 @@
<RepositoryUrl>https://github.com/Ceilidh-Team/Cobble.git</RepositoryUrl>
<Copyright>2018 Olivia Trewin</Copyright>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.1.1</Version>
<Version>1.2.0</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Reflection.DispatchProxy" Version="4.5.1" />
Expand Down

0 comments on commit ebc8f5a

Please sign in to comment.