Able
Askowl Base Library Enabler
LinkedList.cs
1 // Copyright 2018 (C) paul@marrington.net http://www.askowl.net/unity-packages
2 
3 // ReSharper disable StaticMemberInGenericType
4 
5 namespace Askowl {
6  using System;
7  using System.Reflection;
8  using System.Text;
9  using UnityEngine;
10 
11  /// <a href="http://bit.ly/2RhsEws">LinkedList - a different perspective</a>
12  // ReSharper disable once ClassWithVirtualMembersNeverInherited.Global
13  public class LinkedList<T> : IDisposable {
14  /// <a href="http://bit.ly/2OssV13">LinkedList node</a>
15  public class Node : IDisposable {
16  /// <a href="http://bit.ly/2OssV13">Node links</a>
17  public Node Previous, Next;
18 
19  /// <a href="http://bit.ly/2RezEKu">List that currently owns this node is called Owner. It is changed by `MoveTo` Home is the list in which the node was first inserted. It is to this recycle bin it will return on Dispose().</a>
20  public LinkedList<T> Owner, Home;
21 
22  /// <a href="http://bit.ly/2OssV13">Item can be value type (int, float ... struct) or object (class instance)</a>
23  public T Item { get; protected internal set; }
24 
25  /// <a href="http://bit.ly/2NX3sgR">Node comparison Operator</a>
26  public static bool operator <(Node left, Node right) => left.Owner.CompareItem(left, right) < 0;
27 
28  /// <a href="http://bit.ly/2NX3sgR">Node comparison Operator</a>
29  public static bool operator <=(Node left, Node right) => left.Owner.CompareItem(left, right) <= 0;
30 
31  /// <a href="http://bit.ly/2NX3sgR">Node comparison Operator</a>
32  public static bool operator >(Node left, Node right) => left.Owner.CompareItem(left, right) > 0;
33 
34  /// <a href="http://bit.ly/2NX3sgR">Node comparison Operator</a>
35  public static bool operator >=(Node left, Node right) => left.Owner.CompareItem(left, right) >= 0;
36 
37  /// <a href="http://bit.ly/2Rh1Igm">Move Node to Another List</a>
38  public Node MoveTo(LinkedList<T> to) => to.Insert(this);
39 
40  /// <a href="http://bit.ly/2Rh1Igm">Move Node to Another List</a>
41  // ReSharper disable once UnusedMethodReturnValue.Global
42  public Node MoveToEndOf(LinkedList<T> to) => to.Append(this);
43 
44  /// <a href="http://bit.ly/2Rh3dv0">Dispose of a Node Forever</a>
45  public void Destroy() {
46  Owner.DeactivateItem(this);
47  Item = default;
48  Home.Unlink(this);
49  reverseLookup?.Remove(this);
50  Home = null;
51  }
52 
53  /// <a href="http://bit.ly/2Rh3dv0">Deactivate an entry and put the containing node in the recycling bin</a>
54  public Node Recycle() {
55  nodeForNext.Next = Next;
56  if (Owner == Home.recycleBin) return this;
57  Owner.DeactivateItem(this);
58  MoveToEndOf(Home.RecycleBin);
59  return nodeForNext;
60  }
61 
62  private static readonly Node nodeForNext = new Node();
63 
64  /// <inheritdoc />
65  public void Dispose() => Recycle();
66 
67  /// <inheritdoc />
68  public override string ToString() => $"{Owner,25} << {Home,-25}:: {Item}";
69 
70  /// <a href="http://bit.ly/2RhcvXR">Get a node and item either recycled or created new.</a>
71  public Node GetRecycledOrNew() => Home.GetRecycledOrNew().MoveTo(Owner);
72 
73  /// <a href="http://bit.ly/2NXfQgH">Fetch an unused node and use it as the container for the provided new item</a>
74  public Node Push(T t) => Home.Push(item: t).MoveTo(Owner);
75 
76  /// <a href="http://bit.ly/2OvNqd4">Fetch an unused node and use it as the container for the provided new item</a>
77  public Node Add(T t) => Home.Add(item: t).MoveTo(Owner);
78  }
79 
80  #region Private create, deactivation and activation support
81  private static Func<T> CallConstructor() {
82  var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
83  var constructor = typeof(T).GetConstructor(flags, null, Type.EmptyTypes, null);
84  if (constructor == null) return null;
85  return () => (T) constructor.Invoke(parameters: null);
86  }
87 
88  private static MethodInfo DefaultMethod(string name, Type[] parameters) {
89  var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
90  return typeof(T).GetMethod(name, flags, null, CallingConventions.HasThis, parameters, null);
91  }
92 
93  private static readonly Type[] comparisonParameters = {typeof(T), typeof(T)};
94 
95  private static Action<Node> DefaultActivation(string name) {
96  var method = DefaultMethod(name, Type.EmptyTypes);
97  if (method == null) return null;
98  return node => method.Invoke(node.Item, null);
99  }
100 
101  private static Func<Node, Node, int> GetDefaultCompareItem() {
102  var method = DefaultMethod("CompareItem", comparisonParameters);
103  if (method == null) return (node, other) => 0;
104 
105  orderedDynamic = true;
106  return (node, other) => (int) method.Invoke(node.Item, new object[] {node.Item, other.Item});
107  }
108 
109  private static Func<T> GetDefaultCreateItem() {
110  var flags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
111 
112  var method = typeof(T).GetMethod("CreateItem", flags, null, CallingConventions.Standard, Type.EmptyTypes, null);
113 
114  if (method != null) return () => (T) method.Invoke(null, null);
115 
116  return CallConstructor() ?? (() => default);
117  }
118 
119  private static Action<Node> GetDefaultDeactivateItem() => DefaultActivation("DeactivateItem") ?? (node => { });
120 
121  private static Action<Node> GetDefaultReactivateItem() => DefaultActivation("ReactivateItem") ?? (node => { });
122 
123  private static bool orderedStatic;
124  private static bool orderedDynamic;
125  private bool ordered = orderedStatic || orderedDynamic;
126  private static int ordinal;
127  #endregion
128 
129  #region Public List Creation and Destruction
130  /// <a href="http://bit.ly/2RhsEws">Get an instance of this type of linked list</a>
131  public LinkedList(string name = null) =>
132  Name = string.IsNullOrWhiteSpace(name) ? $"{typeof(T).Name}-{++ordinal}" : name;
133 
134  /// <a href="http://bit.ly/2RhsEws">Linked List Name</a>
135  // ReSharper disable once FieldCanBeMadeReadOnly.Global
136  public string Name;
137 
138  /// <a href="http://bit.ly/2NVwTjo">Item Creation and Preparation when new() is not enough</a>
139  public static Func<T> CreateItemStatic = GetDefaultCreateItem();
140 
141  /// <a href="http://bit.ly/2NVwTjo">Item Creation and Preparation when new() is not enough</a>
142  public Func<T> CreateItem { private get; set; } = () => CreateItemStatic();
143 
144  /// <a href="http://bit.ly/2Oq9C8y">Actor we can create if reactivation requires additional code</a>
145  public static Action<Node> ReactivateItemStatic = GetDefaultReactivateItem();
146 
147  /// <a href="http://bit.ly/2Oq9C8y">Prepare an idle item for reuse</a>
148  public Action<Node> ReactivateItem { private get; set; } = (node) => ReactivateItemStatic(node);
149 
150  /// <a href="http://bit.ly/2Rj0PEa">Called before an item is returned to recycling</a>
151  public static Action<Node> DeactivateItemStatic = GetDefaultDeactivateItem();
152 
153  /// <a href="http://bit.ly/2Rj0PEa">For Deactivation when Dispose() is not enough</a>
154  public Action<Node> DeactivateItem { private get; set; } = (node) => DeactivateItemStatic(node);
155 
156  /// <a href="http://bit.ly/2OzziQl">Used to insert items into the correct location for sorted lists</a>
157  public static Func<Node, Node, int> CompareItemStatic {
158  set {
159  orderedStatic = true;
160  compareItemStatic = value;
161  }
162  }
163 
164  private static Func<Node, Node, int> compareItemStatic = GetDefaultCompareItem();
165 
166  /// <a href="http://bit.ly/2OzziQl">Used to insert items into the correct location for sorted lists</a>
167  public Func<Node, Node, int> CompareItem {
168  private get { return compareItem; }
169  set {
170  orderedDynamic = ordered = true;
171  compareItem = value;
172  }
173  }
174 
175  private Func<Node, Node, int> compareItem = (left, right) => compareItemStatic(left, right);
176 
177  /// <a href="http://bit.ly/2OvDxfs">For the rare times we need to clear a linked list</a>
178  public void Destroy() {
179  Dispose();
180  if (recycleBin == null) return;
181 
182  while (recycleBin.First != null) recycleBin.First.Destroy();
183  recycleBin.First = recycleBin.Last = null;
184  }
185 
186  /// <a href="http://bit.ly/2OzDMGF">For the rare times we need to clear a linked list</a>
187  public void Dispose() {
188  reverseLookup = null;
189  while (First != null) First.Dispose();
190  First = Last = null;
191  }
192  #endregion
193 
194  #region Node Creation and Movement
195  /// <a href="http://bit.ly/2OtGxZV">Add an Item to a List</a>
196  public Node Add(T item) {
197  Node node = GetRecycledOrNew();
198  reverseLookup?.Remove(node.Item).Add(item, node);
199  node.Item = item;
200  return node;
201  }
202 
203  /// <a href="http://bit.ly/2OtGxZV">Retrieve a node - either from recycling or creating it anew</a>
204  public Node GetRecycledOrNew() {
205  if (RecycleBin.Empty) return Insert(NewNode(CreateItem()));
206 
207  var node = RecycleBin.First.MoveTo(this);
208  ReactivateItem(node);
209  return node;
210  }
211 
212  /// <a href="http://bit.ly/2OvDxfs">For node disposal using reverse lookup</a>
213  public Node ReverseLookup(T item) {
214  if (reverseLookup == null) {
215  reverseLookup = new Map();
216  for (var node = First; node != null; node = node.Next) reverseLookup.Add(node.Item, node);
217  }
218 
219  return reverseLookup?[item].Value as Node;
220  }
221 
222  /// <a href="http://bit.ly/2OvDxfs">Node Dispose using reverse lookup</a>
223  public void Dispose(T item) => ReverseLookup(item)?.Dispose();
224 
225  private static Map reverseLookup;
226 
227  private Node NewNode(T item) {
228  Node newNode = new Node() {Item = item, Owner = this, Home = this};
229  reverseLookup?.Add(item, newNode);
230  return newNode;
231  }
232 
233  private Node Insert(Node nodeToInsert) {
234  if (DebugMode) DebugMessage(nodeToInsert);
235  Count++;
236  Unlink(nodeToInsert);
237 
238  nodeToInsert.Owner = this;
239  if (Empty) return Last = First = nodeToInsert;
240 
241  Node after = First;
242 
243  if (ordered) {
244  while (nodeToInsert > after) {
245  if ((after = after.Next) == null) {
246  nodeToInsert.Previous = Last;
247  Last.Next = nodeToInsert;
248  return Last = nodeToInsert;
249  }
250  }
251  }
252 
253  nodeToInsert.Next = after;
254  nodeToInsert.Previous = after.Previous;
255 
256  if (after.Previous != null) after.Previous.Next = nodeToInsert;
257  after.Previous = nodeToInsert;
258 
259  if (Last == null) Last = nodeToInsert;
260  if (after == First) First = nodeToInsert;
261  return nodeToInsert;
262  }
263 
264  private Node Append(Node nodeToAppend) {
265  if (DebugMode) DebugMessage(nodeToAppend, "end of ");
266  Count++;
267  Unlink(nodeToAppend);
268 
269  nodeToAppend.Owner = this;
270  if (Empty) return Last = First = nodeToAppend;
271 
272  nodeToAppend.Previous = Last;
273  Last.Next = nodeToAppend;
274  Last = nodeToAppend;
275  return nodeToAppend;
276  }
277 
278  private void Unlink(Node node) {
279  if (node == node.Owner.First) { node.Owner.First = node.Next; } else if (node == node.Owner.Last) {
280  node.Owner.Last = node.Previous;
281  } else if ((node.Previous == null) && (node.Next == null)) return;
282 
283  node.Owner.Count--;
284 
285  if (node.Previous != null) node.Previous.Next = node.Next;
286  if (node.Next != null) node.Next.Previous = node.Previous;
287 
288  node.Previous = node.Next = null;
289  node.Owner = null;
290  }
291 
292  /// <a href="http://bit.ly/2OvDxfs">List for Unused Nodes</a>
293  public LinkedList<T> RecycleBin => isRecycleBin ? null : recycleBin ?? (recycleBin = NewRecycleBin);
294 
295  private LinkedList<T> NewRecycleBin => new LinkedList<T>($"{Name} Recycling Bin") {isRecycleBin = true};
296 
297  private bool isRecycleBin;
298  private LinkedList<T> recycleBin;
299  #endregion
300 
301  #region FiFo Stack
302  /// <a href="http://bit.ly/2O11goE">First Node in List or null</a>
303  public Node First;
304 
305  /// <a href="http://bit.ly/2Oq9ypk">Last Node in List or null</a>
306  public Node Last;
307 
308  /// <a href="http://bit.ly/2Rj0O34">Second item in the list or null</a>
309  public Node Second => First?.Next;
310 
311  /// <a href="http://bit.ly/2Ov3MCP">Is list empty?</a>
312  public bool Empty => First == null;
313 
314  /// <a href="http://bit.ly/2NUH8Ek">Calculate number of items in a list</a>
315  public int Count { get; private set; }
316 
317  /// <a href="http://bit.ly/2O02sID">Add an item to the list</a>
318  public Node Push(T item) => Add(item);
319 
320  /// <a href="http://bit.ly/2O02sID">Add a node to the list</a>
321  public Node Push(Node node) => node.MoveTo(this);
322 
323  /// <a href="http://bit.ly/2Ov490b">Retrieve the first list item - `Node.Recycle`</a>
324  public T Pop() {
325  if (First == null) return default;
326  var item = First.Item;
327  First.Recycle();
328  return item;
329  }
330 
331  /// <a href="http://bit.ly/2NTD9Ih">Pull the last item from the list</a>
332  public T Pull() {
333  if (Last == null) return default;
334  var item = Last.Item;
335  Last.Recycle();
336  return item;
337  }
338  #endregion
339 
340  #region Debugging
341  /// <a href="http://bit.ly/2RezKBQ">Debug mode logs changes</a>
342  // ReSharper disable once StaticMemberInGenericType
343  public static bool DebugMode = false;
344 
345  private void DebugMessage(Node node, string append = "") =>
346  Debug.Log(
347  node.Owner == this
348  ? $"**** LinkedList: Add to {this}"
349  : $"**** LinkedList: move {node.Owner} to {append}{this}");
350 
351  /// <a href="http://bit.ly/2OxOL3m">Return list contents as a string</a>
352  public string Dump(int maxEntriesToDump = 1000) {
353  var builder = new StringBuilder();
354  var line = 0;
355 
356  for (Node node = First; (node != null) && (maxEntriesToDump-- > 0); node = node.Next) {
357  builder.AppendLine($"{++line}:\t{node}");
358  }
359 
360  return builder.ToString();
361  }
362 
363  /// <inheritdoc />
364  public override string ToString() {
365  string count = recycleBin != null ? $"({Count}/{recycleBin.Count})" : $"({Count})";
366  return $"{Name} {count,8}";
367  }
368  #endregion
369  }
370 }
Definition: Clock.cs:3
Node MoveTo(LinkedList< T > to)
Move Node to Another List
Node GetRecycledOrNew()
Get a node and item either recycled or created new.
T Pop()
Retrieve the first list item - Node.Recycle
Definition: LinkedList.cs:324
LinkedList< T > RecycleBin
List for Unused Nodes
Definition: LinkedList.cs:293
static bool operator>=(Node left, Node right)
Node comparison Operator
static Action< Node > DeactivateItemStatic
Called before an item is returned to recycling
Definition: LinkedList.cs:151
void Destroy()
Dispose of a Node Forever
Definition: LinkedList.cs:45
override string ToString()
Definition: LinkedList.cs:364
static bool operator>(Node left, Node right)
Node comparison Operator
Func< T > CreateItem
Item Creation and Preparation when new() is not enough
Definition: LinkedList.cs:142
A dictionary wrapper
Definition: Map.cs:10
T Pull()
Pull the last item from the list
Definition: LinkedList.cs:332
void Dispose()
For the rare times we need to clear a linked list
Definition: LinkedList.cs:187
override string ToString()
LinkedList - a different perspective
Definition: LinkedList.cs:13
Node GetRecycledOrNew()
Retrieve a node - either from recycling or creating it anew
Definition: LinkedList.cs:204
Map Add(object key, object value=null)
Add a map or set entry
Definition: Map.cs:68
static bool DebugMode
Debug mode logs changes
Definition: LinkedList.cs:343
Node MoveToEndOf(LinkedList< T > to)
Move Node to Another List
Node Second
Second item in the list or null
Definition: LinkedList.cs:309
string Dump(int maxEntriesToDump=1000)
Return list contents as a string
Definition: LinkedList.cs:352
LinkedList node
Definition: LinkedList.cs:15
Node Push(T item)
Add an item to the list
Node Recycle()
Deactivate an entry and put the containing node in the recycling bin
Definition: LinkedList.cs:54
Node Add(T t)
Fetch an unused node and use it as the container for the provided new item
static Action< Node > ReactivateItemStatic
Actor we can create if reactivation requires additional code
Definition: LinkedList.cs:145
LinkedList< T > Owner
List that currently owns this node is called Owner. It is changed by MoveTo Home is the list in which...
Definition: LinkedList.cs:20
Node ReverseLookup(T item)
For node disposal using reverse lookup
Definition: LinkedList.cs:213
void Destroy()
For the rare times we need to clear a linked list
Definition: LinkedList.cs:178
Node Push(T t)
Fetch an unused node and use it as the container for the provided new item
int Count
Calculate number of items in a list
Definition: LinkedList.cs:315
Node Previous
Node links
Definition: LinkedList.cs:17
Node Last
Last Node in List or null
Definition: LinkedList.cs:306
Map Remove(object key, bool dispose=true)
Remove an entry, optionally calling Dispose()
Definition: Map.cs:22
string Name
Linked List Name
Definition: LinkedList.cs:136
T Item
Item can be value type (int, float ... struct) or object (class instance)
Definition: LinkedList.cs:23
bool Empty
Is list empty?
Definition: LinkedList.cs:312
Node First
First Node in List or null
Definition: LinkedList.cs:303
Action< Node > ReactivateItem
Prepare an idle item for reuse
Definition: LinkedList.cs:148
Node Add(T item)
Add an Item to a List
Definition: LinkedList.cs:196
LinkedList(string name=null)
Get an instance of this type of linked list
static Func< Node, Node, int > CompareItemStatic
Used to insert items into the correct location for sorted lists
Definition: LinkedList.cs:157
object Value
Last value found (null for failure)
Definition: Map.cs:86
static Func< T > CreateItemStatic
Item Creation and Preparation when new() is not enough
Definition: LinkedList.cs:139
Func< Node, Node, int > CompareItem
Used to insert items into the correct location for sorted lists
Definition: LinkedList.cs:167
Action< Node > DeactivateItem
For Deactivation when Dispose() is not enough
Definition: LinkedList.cs:154