Skip to content

Commit

Permalink
Merge pull request #33 from paulcbetts/logging
Browse files Browse the repository at this point in the history
Move the logging infrastructure from ReactiveUI into Splat
  • Loading branch information
Paul Betts committed Dec 17, 2013
2 parents 0e9751d + 4fc0aa9 commit 5d3ec49
Show file tree
Hide file tree
Showing 9 changed files with 792 additions and 3 deletions.
611 changes: 611 additions & 0 deletions Splat/Logging.cs

Large diffs are not rendered by default.

165 changes: 165 additions & 0 deletions Splat/MemoizingMRUCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics.Contracts;

namespace Splat
{
/// <summary>
/// This data structure is a representation of a memoizing cache - i.e. a
/// class that will evaluate a function, but keep a cache of recently
/// evaluated parameters.
///
/// Since this is a memoizing cache, it is important that this function be a
/// "pure" function in the mathematical sense - that a key *always* maps to
/// a corresponding return value.
/// </summary>
/// <typeparam name="TParam">The type of the parameter to the calculation function.</typeparam>
/// <typeparam name="TVal">The type of the value returned by the calculation
/// function.</typeparam>
public class MemoizingMRUCache<TParam, TVal>
{
private readonly Func<TParam, object, TVal> calculationFunction;
private readonly Action<TVal> releaseFunction;
private readonly int maxCacheSize;

private LinkedList<TParam> cacheMRUList;
private Dictionary<TParam, Tuple<LinkedListNode<TParam>, TVal>> cacheEntries;

/// <summary>
/// Constructor
/// </summary>
/// <param name="calculationFunc">The function whose results you want to cache,
/// which is provided the key value, and an Tag object that is
/// user-defined</param>
/// <param name="maxSize">The size of the cache to maintain, after which old
/// items will start to be thrown out.</param>
/// <param name="onRelease">A function to call when a result gets
/// evicted from the cache (i.e. because Invalidate was called or the
/// cache is full)</param>
public MemoizingMRUCache(Func<TParam, object, TVal> calculationFunc, int maxSize, Action<TVal> onRelease = null)
{
Contract.Requires(calculationFunc != null);
Contract.Requires(maxSize > 0);

calculationFunction = calculationFunc;
releaseFunction = onRelease;
maxCacheSize = maxSize;
InvalidateAll();
}

public TVal Get(TParam key) { return Get(key, null); }

/// <summary>
/// Evaluates the function provided, returning the cached value if possible
/// </summary>
/// <param name="key">The value to pass to the calculation function.</param>
/// <param name="context">An additional optional user-specific parameter.</param>
/// <returns></returns>
public TVal Get(TParam key, object context = null)
{
Contract.Requires(key != null);

if (cacheEntries.ContainsKey(key)) {
var found = cacheEntries[key];
cacheMRUList.Remove(found.Item1);
cacheMRUList.AddFirst(found.Item1);
return found.Item2;
}

var result = calculationFunction(key, context);

var node = new LinkedListNode<TParam>(key);
cacheMRUList.AddFirst(node);
cacheEntries[key] = new Tuple<LinkedListNode<TParam>, TVal>(node, result);
maintainCache();

return result;
}

public bool TryGet(TParam key, out TVal result)
{
Contract.Requires(key != null);

Tuple<LinkedListNode<TParam>, TVal> output;
var ret = cacheEntries.TryGetValue(key, out output);
if (ret && output != null) {
cacheMRUList.Remove(output.Item1);
cacheMRUList.AddFirst(output.Item1);
result = output.Item2;
} else {
result = default(TVal);
}
return ret;
}

/// <summary>
/// Ensure that the next time this key is queried, the calculation
/// function will be called.
/// </summary>
public void Invalidate(TParam key)
{
Contract.Requires(key != null);

if (!cacheEntries.ContainsKey(key))
return;

var to_remove = cacheEntries[key];
if (releaseFunction != null)
releaseFunction(to_remove.Item2);

cacheMRUList.Remove(to_remove.Item1);
cacheEntries.Remove(key);
}

/// <summary>
/// Invalidate all items in the cache
/// </summary>
public void InvalidateAll()
{
if (releaseFunction == null || cacheEntries == null) {
cacheMRUList = new LinkedList<TParam>();
cacheEntries = new Dictionary<TParam, Tuple<LinkedListNode<TParam>, TVal>>();
return;
}

if (cacheEntries.Count == 0)
return;

/* We have to remove them one-by-one to call the release function
* We ToArray() this so we don't get a "modifying collection while
* enumerating" exception. */
foreach (var v in cacheEntries.Keys.ToArray()) { Invalidate(v); }
}

/// <summary>
/// Returns all values currently in the cache
/// </summary>
/// <returns></returns>
public IEnumerable<TVal> CachedValues()
{
return cacheEntries.Select(x => x.Value.Item2);
}

void maintainCache()
{
while (cacheMRUList.Count > maxCacheSize) {
var to_remove = cacheMRUList.Last.Value;
if (releaseFunction != null)
releaseFunction(cacheEntries[to_remove].Item2);

cacheEntries.Remove(cacheMRUList.Last.Value);
cacheMRUList.RemoveLast();
}
}

[ContractInvariantMethod]
void Invariants()
{
Contract.Invariant(cacheEntries.Count == cacheMRUList.Count);
Contract.Invariant(cacheEntries.Count <= maxCacheSize);
}
}
}

// vim: tw=120 ts=4 sw=4 et :
2 changes: 2 additions & 0 deletions Splat/Splat-Net45.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
<Compile Include="RectangleExtensions.cs" />
<Compile Include="ReflectionStubs.cs" />
<Compile Include="ServiceLocation.cs" />
<Compile Include="Logging.cs" />
<Compile Include="MemoizingMRUCache.cs" />
<Compile Include="SizeExtensions.cs" />
<Compile Include="TypeForwardedSystemDrawing.cs" />
<Compile Include="Xaml\Bitmaps.cs" />
Expand Down
2 changes: 2 additions & 0 deletions Splat/Splat-NetCore45.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@
<Compile Include="RectangleExtensions.cs" />
<Compile Include="ReflectionStubs.cs" />
<Compile Include="ServiceLocation.cs" />
<Compile Include="Logging.cs" />
<Compile Include="MemoizingMRUCache.cs" />
<Compile Include="SizeExtensions.cs" />
<Compile Include="WinRT\Bitmaps.cs" />
<Compile Include="WinRT\Color.cs" />
Expand Down
7 changes: 4 additions & 3 deletions Splat/Splat-Portable.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,14 @@
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\Portable-Net45+WinRT45+WP8\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DefineConstants>DEBUG;TRACE;PORTABLE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\Portable-Net45+WinRT45+WP8\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<DefineConstants>TRACE;PORTABLE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
Expand Down Expand Up @@ -58,6 +57,8 @@
<Compile Include="ReflectionStubs.cs" />
<Compile Include="SizeExtensions.cs" />
<Compile Include="ServiceLocation.cs" />
<Compile Include="Logging.cs" />
<Compile Include="MemoizingMRUCache.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Expand Down
2 changes: 2 additions & 0 deletions Splat/Splat-monoandroid.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
<Compile Include="ServiceLocation.cs" />
<Compile Include="SizeExtensions.cs" />
<Compile Include="TypeForwardedSystemDrawing.cs" />
<Compile Include="Logging.cs" />
<Compile Include="MemoizingMRUCache.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\AboutResources.txt" />
Expand Down
2 changes: 2 additions & 0 deletions Splat/Splat-monomac.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
<Compile Include="ServiceLocation.cs" />
<Compile Include="SizeExtensions.cs" />
<Compile Include="TypeForwardedSystemDrawing.cs" />
<Compile Include="Logging.cs" />
<Compile Include="MemoizingMRUCache.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Expand Down
2 changes: 2 additions & 0 deletions Splat/Splat-monotouch.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,7 @@
<Compile Include="Colors\KnownColor.cs" />
<Compile Include="Colors\KnownColors.cs" />
<Compile Include="ServiceLocation.cs" />
<Compile Include="Logging.cs" />
<Compile Include="MemoizingMRUCache.cs" />
</ItemGroup>
</Project>
2 changes: 2 additions & 0 deletions Splat/Splat-wp8.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@
<Compile Include="RectangleExtensions.cs" />
<Compile Include="ReflectionStubs.cs" />
<Compile Include="ServiceLocation.cs" />
<Compile Include="Logging.cs" />
<Compile Include="MemoizingMRUCache.cs" />
<Compile Include="SizeExtensions.cs" />
<Compile Include="Xaml\Bitmaps.cs" />
<Compile Include="Xaml\Color.cs" />
Expand Down

0 comments on commit 5d3ec49

Please sign in to comment.