Skip to content

Commit 00a7c8b

Browse files
author
Oren (electricessence)
committed
Updated to 1.4.0
1 parent 8c425e6 commit 00a7c8b

4 files changed

Lines changed: 121 additions & 50 deletions

File tree

Node.Factory.cs

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Open.Disposable;
22
using System;
3+
using System.Diagnostics.Contracts;
34

45
namespace Open.Hierarchy
56
{
@@ -14,7 +15,7 @@ public sealed class Factory : DisposableBase
1415
public Factory()
1516
{
1617
_pool = new ConcurrentQueueObjectPool<Node<T>>(
17-
() => new Node<T>(), PrepareForPool, null, ushort.MaxValue);
18+
() => new Node<T>(this), PrepareForPool, null, ushort.MaxValue);
1819
}
1920

2021
protected override void OnDispose(bool calledExplicitly)
@@ -33,21 +34,28 @@ protected override void OnDispose(bool calledExplicitly)
3334
/// <returns>A blank node.</returns>
3435
public Node<T> GetBlankNode()
3536
{
37+
var p = _pool;
3638
AssertIsAlive();
3739

38-
return _pool?.Take();
40+
var n = p.Take();
41+
n._recycled = false;
42+
return n;
3943
}
4044

45+
4146
/// <summary>
4247
/// Recycles the node into the object pool, returning the value contained.
4348
/// </summary>
44-
/// <param name="n">The node to be recycled.</param>
49+
/// <param name="node">The node to be recycled.</param>
4550
/// <returns>The value contained in the node.</returns>
46-
public T Recycle(Node<T> n)
51+
public T Recycle(Node<T> node)
4752
{
48-
AssertIsAlive();
53+
if (node == null) throw new ArgumentNullException(nameof(node));
54+
if (node._factory != this)
55+
throw new ArgumentException("The node being provided for recycling does not belong to this factory.", nameof(node));
56+
Contract.EndContractBlock();
4957

50-
return RecycleInternal(n);
58+
return RecycleInternal(node);
5159
}
5260

5361
internal T RecycleInternal(Node<T> n)
@@ -59,8 +67,12 @@ internal T RecycleInternal(Node<T> n)
5967
return value;
6068
}
6169

62-
void PrepareForPool(Node<T> n)
63-
=> n.Recycle(this);
70+
static void PrepareForPool(Node<T> n)
71+
{
72+
n.Recycle();
73+
n._recycled = true;
74+
}
75+
6476
#endregion
6577

6678
/// <summary>
@@ -78,9 +90,16 @@ public Node<T> Clone(
7890
Node<T> newParentForClone = null,
7991
Action<Node<T>, Node<T>> onNodeCloned = null)
8092
{
93+
if (target == null) throw new ArgumentNullException(nameof(target));
94+
if (target._factory != this)
95+
throw new ArgumentException("The node being provided for cloning does not belong to this factory.", nameof(target));
96+
if (newParentForClone != null && newParentForClone._factory != this)
97+
throw new ArgumentException("The node being provided for cloning does not belong to this factory.", nameof(newParentForClone));
98+
Contract.EndContractBlock();
99+
81100
AssertIsAlive();
82101

83-
var clone = _pool.Take();
102+
var clone = GetBlankNode();
84103
clone.Value = target.Value;
85104
newParentForClone?.Add(clone);
86105

@@ -92,7 +111,6 @@ public Node<T> Clone(
92111
return clone;
93112
}
94113

95-
96114
/// <summary>
97115
/// Clones a node by recreating the tree and copying the values.
98116
/// </summary>
@@ -102,9 +120,7 @@ public Node<T> Clone(
102120
public Node<T> Clone(
103121
Node<T> target,
104122
Action<Node<T>, Node<T>> onNodeCloned)
105-
{
106-
return Clone(target, null, onNodeCloned);
107-
}
123+
=> Clone(target, null, onNodeCloned);
108124

109125
/// <summary>
110126
/// Create's a clone of the entire tree but only returns the clone of this node.
@@ -132,14 +148,13 @@ public Node<T> Map<TRoot>(TRoot root)
132148
{
133149
AssertIsAlive();
134150

135-
var current = _pool.Take();
151+
var current = GetBlankNode();
136152
current.Value = root;
137153

138-
if (!(root is IParent<T> parent)) return current;
139-
foreach (var child in parent.Children)
140-
{
141-
current.Add(Map<T>(child));
142-
}
154+
// Mapping is deferred and occurs on demand.
155+
// If values or children change in the node, mapping is disregarded.
156+
current._needsMapping = true;
157+
143158

144159
return current;
145160
}
@@ -150,10 +165,7 @@ public Node<T> Map<TRoot>(TRoot root)
150165
/// </summary>
151166
/// <param name="root">The root instance.</param>
152167
/// <returns>The full map of the root.</returns>
153-
public Node<T> Map(T root)
154-
{
155-
return Map<T>(root);
156-
}
168+
public Node<T> Map(T root) => Map<T>(root);
157169

158170
/// <summary>
159171
/// Generates a full hierarchy if the root of the container is an IParent and uses the root as the value of the hierarchy.
@@ -163,10 +175,7 @@ public Node<T> Map(T root)
163175
/// <param name="container">The container of the root instance.</param>
164176
/// <returns>The full map of the root.</returns>
165177
public Node<T> Map<TRoot>(IHaveRoot<TRoot> container)
166-
where TRoot : T
167-
{
168-
return Map(container.Root);
169-
}
178+
where TRoot : T => Map(container.Root);
170179

171180
}
172181

Node.cs

Lines changed: 80 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,65 @@ public sealed partial class Node<T> : INode<Node<T>>
1212

1313
#region IParent<Node<T>> Implementation
1414
private readonly List<Node<T>> _children;
15+
private readonly IReadOnlyList<Node<T>> _childrenReadOnly;
16+
1517
/// <inheritdoc />
16-
public IReadOnlyList<Node<T>> Children { get; }
18+
public IReadOnlyList<Node<T>> Children => EnsureChildrenMapped();
1719
/// <inheritdoc />
18-
IReadOnlyList<object> IParent.Children => Children;
20+
IReadOnlyList<object> IParent.Children => EnsureChildrenMapped();
1921
#endregion
2022

23+
private bool _recycled;
24+
void AssertNotRecycled()
25+
{
26+
if (_recycled)
27+
throw new InvalidOperationException("Attempting to modify a node that has been recyled.");
28+
}
29+
2130
// ReSharper disable once UnusedAutoPropertyAccessor.Global
2231
/// <summary>
2332
/// The value for the node to hold on to.
2433
/// </summary>
25-
public T Value { get; set; }
34+
private T _value;
35+
public T Value
36+
{
37+
get => _value;
38+
set
39+
{
40+
AssertNotRecycled();
41+
_needsMapping = false;
42+
_value = value;
43+
}
44+
}
45+
46+
private readonly Factory _factory;
47+
private bool _needsMapping;
48+
49+
IReadOnlyList<Node<T>> EnsureChildrenMapped()
50+
{
51+
if (!_needsMapping)
52+
return _childrenReadOnly;
53+
54+
// Need to avoid double mapping and this method is primarily called when 'reading' from the node and contention will only occur if mapping is needed.
55+
lock (_children)
56+
{
57+
if (!_needsMapping) return _childrenReadOnly;
58+
if (_value is IParent<T> p)
59+
{
60+
foreach (var child in p.Children)
61+
_children.Add(_factory.Map(child));
62+
}
63+
_needsMapping = false;
64+
}
65+
66+
return _childrenReadOnly;
67+
}
2668

27-
private Node()
69+
internal Node(Factory factory)
2870
{
71+
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
2972
_children = new List<Node<T>>();
30-
Children = _children.AsReadOnly();
73+
_childrenReadOnly = _children.AsReadOnly();
3174
}
3275

3376
// WARNING: Care must be taken not to have duplicate nodes anywhere in the tree but having duplicate values are allowed.
@@ -52,6 +95,9 @@ public bool Remove(Node<T> node)
5295
Contract.EndContractBlock();
5396

5497
if (!_children.Remove(node)) return false;
98+
99+
AssertNotRecycled();
100+
_needsMapping = false;
55101
node.Parent = null; // Need to be very careful about retaining parent references as it may cause a 'leak' per-se.
56102
return true;
57103
}
@@ -68,13 +114,20 @@ public void Add(Node<T> node)
68114
throw new InvalidOperationException("Adding a child node more than once.");
69115
throw new InvalidOperationException("Adding a node that belongs to another parent.");
70116
}
117+
118+
AssertNotRecycled();
119+
EnsureChildrenMapped(); // Adding to potentially existing nodes.
71120
node.Parent = this;
72121
_children.Add(node);
73122
}
74123

75124
/// <inheritdoc />
76125
public void Clear()
77126
{
127+
if (_children.Count == 0) return;
128+
129+
AssertNotRecycled();
130+
_needsMapping = false;
78131
foreach (var c in _children)
79132
c.Parent = null;
80133
_children.Clear();
@@ -86,15 +139,18 @@ public void Clear()
86139

87140
/// <inheritdoc />
88141
public IEnumerator<Node<T>> GetEnumerator()
89-
=> _children.GetEnumerator();
142+
=> Children.GetEnumerator();
90143

91144
/// <inheritdoc />
92145
IEnumerator IEnumerable.GetEnumerator()
93146
=> GetEnumerator();
94147

95148
/// <inheritdoc />
96149
public void CopyTo(Node<T>[] array, int arrayIndex)
97-
=> _children.CopyTo(array, arrayIndex);
150+
{
151+
EnsureChildrenMapped();
152+
_children.CopyTo(array, arrayIndex);
153+
}
98154
#endregion
99155

100156
/// <summary>
@@ -113,6 +169,9 @@ public void Replace(Node<T> node, Node<T> replacement)
113169
var i = _children.IndexOf(node);
114170
if (i == -1)
115171
throw new InvalidOperationException("Node being replaced does not belong to this parent.");
172+
173+
AssertNotRecycled();
174+
_needsMapping = false;
116175
_children[i] = replacement;
117176
node.Parent = null;
118177
replacement.Parent = this;
@@ -123,6 +182,7 @@ public void Replace(Node<T> node, Node<T> replacement)
123182
/// </summary>
124183
public void Detatch()
125184
{
185+
AssertNotRecycled();
126186
Parent?.Remove(this);
127187
Parent = null;
128188
}
@@ -149,6 +209,7 @@ public Node<T> Root
149209
/// </summary>
150210
public void Teardown()
151211
{
212+
_needsMapping = false;
152213
Value = default;
153214
Detatch(); // If no parent then this does nothing...
154215
TeardownChildren();
@@ -159,6 +220,10 @@ public void Teardown()
159220
/// </summary>
160221
public void TeardownChildren()
161222
{
223+
if (_children.Count == 0) return;
224+
225+
AssertNotRecycled();
226+
_needsMapping = false;
162227
foreach (var c in _children)
163228
{
164229
c.Parent = null; // Don't initiate a 'Detach' (which does a lookup) since we are clearing here;
@@ -170,35 +235,30 @@ public void TeardownChildren()
170235
/// <summary>
171236
/// Recycles this node.
172237
/// </summary>
173-
/// <param name="factory">The factory to use as a recycler.</param>
174238
// ReSharper disable once UnusedMethodReturnValue.Global
175-
public T Recycle(Factory factory)
239+
public T Recycle()
176240
{
177-
if (factory == null)
178-
throw new ArgumentNullException(nameof(factory));
179-
Contract.EndContractBlock();
180-
241+
AssertNotRecycled(); // Avoid double entry in the pool.
242+
_needsMapping = false;
181243
var value = Value;
182244
Value = default;
183245
Detatch(); // If no parent then this does nothing...
184-
RecycleChildren(factory);
246+
RecycleChildren();
185247
return value;
186248
}
187249

188250
/// <summary>
189251
/// Recycles all the children of this node.
190252
/// </summary>
191-
/// <param name="factory">The factory to use as a recycler.</param>
192-
public void RecycleChildren(Factory factory)
253+
public void RecycleChildren()
193254
{
194-
if (factory == null)
195-
throw new ArgumentNullException(nameof(factory));
196-
Contract.EndContractBlock();
255+
if (_children.Count == 0) return;
197256

257+
_needsMapping = false;
198258
foreach (var c in _children)
199259
{
200260
c.Parent = null; // Don't initiate a 'Detach' (which does a lookup) since we are clearing here;
201-
factory.RecycleInternal(c);
261+
_factory.RecycleInternal(c);
202262
}
203263
_children.Clear();
204264
}

Open.Hierarchy.csproj

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ Part of the "Open" set of libraries.</Description>
1414
<RepositoryUrl>https://github.com/electricessence/Open.Hierarchy/</RepositoryUrl>
1515
<RepositoryType>git</RepositoryType>
1616
<PackageTags>dotnet, dotnetcore, cs, tree, node, hierarchy, parent, child, root</PackageTags>
17-
<Version>1.3.0</Version>
18-
<PackageReleaseNotes>Cleanup and inspection with Resharper.</PackageReleaseNotes>
17+
<Version>1.4.0</Version>
18+
<PackageReleaseNotes>Deferred unidirectional to bidirectional tree mapping.
19+
Improved assertions and added recycled node validation.
20+
Simplified factory and recycling usage: nodes now have a referene to their source factory.</PackageReleaseNotes>
1921
</PropertyGroup>
2022

2123
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

TraversalExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace Open.Hierarchy
66
{
77
public static class TraversalExtensions
88
{
9-
// NOTE: super recursive, possibly could be better, but this is a unidirectional heirarchy so it's not so easy to avoid recursion.
9+
// NOTE: not recursive, but could produce a large stack while traversing, possibly could be better, but this is a unidirectional heirarchy so it's not so easy to avoid recursion.
1010

1111
/// <summary>
1212
/// Returns all descendant nodes.

0 commit comments

Comments
 (0)