Skip to content

Commit

Permalink
feat: native stacked lists
Browse files Browse the repository at this point in the history
  • Loading branch information
andywiecko committed Dec 6, 2022
1 parent 0a923ce commit cc980aa
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 1 deletion.
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Burst friendly (special) native collections for Unity.
- [NativeIndexedList{Id, T}](#nativeindexedlistid-t)
- [NativePointQuadtree](#nativepointquadtree)
- [NativeStack{T}](#nativestackt)
- [NativeStackedLists{T}](#nativestackedlistst)
- [Dependencies](#dependencies)
- [TODO](#todo)

Expand Down Expand Up @@ -329,7 +330,6 @@ foreach (var (id, value) in data.IdsValues)
Wrapper for `NativeList<T>` which supports indexing via `Id<T>` instead of `int`, where `T` is a non-constraint generic parameter.
See [NativeIndexedArray{Id, T}](#nativeindexedarrayid-t) for more details.


## NativePointQuadtree

The package provides the basic implementation of a [quadtree](https://en.wikipedia.org/wiki/Quadtree) (for points). Similar to the bounding volume tree, the quadtree can be used for computation acceleration.
Expand Down Expand Up @@ -380,6 +380,48 @@ stack.Dispose();

Remarks: implementation probably will be deprecated in the future, when Unity team add stack implementation to `Unity.Collections`.

## NativeStackedLists{T}

The problem with the current version of `Unity.Collections` is that the nested collections are not allowed (e.g. `NativeArray<NativeArray<T>>`) except the _unsafe_ context.
The `NativeStackedLists` is combination of stack and lists.
One could add elements to the list but only for the top one in the stack.
Example:

```csharp
using var stackedLists = new NativeStackedLists<int>(64, 4, Allocator.Persistent);

stackedLists.Push(); // Add new list to the stack
stackedLists.Add(4); // Add elements
stackedLists.Add(5);
stackedLists.Add(6);

stackedLists.Push(); // Add new list to the stack
stackedLists.Add(8); // Add elements
stackedLists.Add(9);

// stacked list is equivalent of the following collection:
// {{4, 5, 6}, {8, 9}}
```

Adding the new elements is restricted only for the top list, however, one could get/set element for any list in the stack:

```csharp
var list0 = stackedLists[0]; // type of NativeArray<T> with read/write access
var list1 = stackedLists[1];
```

There are also implemented a proper enumerators for `foreach` statements:

```csharp
foreach(var list in stackedLists)
{
foreach(var i in list)
{
// ...
}
}
```

## Dependencies

- [`Unity.Burst`][burst]
Expand Down
8 changes: 8 additions & 0 deletions Runtime/NativeStackedLists.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

84 changes: 84 additions & 0 deletions Runtime/NativeStackedLists/NativeStackedLists.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Jobs;

namespace andywiecko.BurstCollections
{
public struct NativeStackedLists<T> :
INativeDisposable,
IEnumerable<IEnumerable<T>>
where T : unmanaged
{
private struct List
{
public int Start { get; set; }
public int Length { get; set; }
public List(int start, int length) => (Start, Length) = (start, length);
public static List operator ++(List @this) => new(@this.Start, @this.Length + 1);
}

public struct Enumerator
{
private NativeStackedLists<T> owner;
private int i;
public Enumerator(NativeStackedLists<T> owner) => (this.owner, i) = (owner, -1);
public NativeArray<T> Current => owner[i];
public bool MoveNext() => ++i < owner.stack.Length;
public Enumerator GetEnumerator() => this;
}

public int Length => stack.Length;
public NativeArray<T> this[int i] => elements.AsArray().GetSubArray(stack[i].Start, stack[i].Length);

private NativeList<List> stack;
private NativeList<T> elements;

public NativeStackedLists(int elementsInitialCapacity, int stackInitialCapacity, Allocator allocator)
{
elements = new(elementsInitialCapacity, allocator);
stack = new(stackInitialCapacity, allocator);
}
public NativeStackedLists(int elementsInitialCapacity, Allocator allocator) : this(elementsInitialCapacity, stackInitialCapacity: 1, allocator) { }
public NativeStackedLists(Allocator allocator) : this(elementsInitialCapacity: 1, stackInitialCapacity: 1, allocator) { }

public void Push() => stack.Add(new() { Start = elements.Length });

public void Add(T element)
{
elements.Add(element);
++stack[^1];
}

public void Clear()
{
stack.Clear();
elements.Clear();
}

public JobHandle Dispose(JobHandle dependencies)
{
dependencies = stack.Dispose(dependencies);
dependencies = elements.Dispose(dependencies);
return dependencies;
}

public void Dispose()
{
stack.Dispose();
elements.Dispose();
}

public Enumerator GetEnumerator() => new(this);

IEnumerator<IEnumerable<T>> IEnumerable<IEnumerable<T>>.GetEnumerator()
{
foreach (var list in this)
{
yield return list;
}
}

IEnumerator IEnumerable.GetEnumerator() => (this as IEnumerable<IEnumerable<T>>).GetEnumerator();
}
}
11 changes: 11 additions & 0 deletions Runtime/NativeStackedLists/NativeStackedLists.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

95 changes: 95 additions & 0 deletions Tests/NativeStackedListsEditorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using NUnit.Framework;
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;

namespace andywiecko.BurstCollections.Editor.Tests
{
public class NativeStackedListsEditorTests
{
[Test]
public void InitTest()
{
using var stackedLists = new NativeStackedLists<int>(64, 4, Allocator.Persistent);
Assert.That(stackedLists.Length, Is.Zero);
}

[Test]
public void ThrowsOnAddWhenLengthZero()
{
using var stackedLists = new NativeStackedLists<int>(64, 4, Allocator.Persistent);
Assert.Throws<IndexOutOfRangeException>(() => stackedLists.Add(default));
}

[Test]
public void PushTest([Values(1, 3, 5)] int count)
{
using var stackedLists = new NativeStackedLists<int>(64, 4, Allocator.Persistent);

for (int i = 0; i < count; i++)
{
stackedLists.Push();
}

Assert.That(stackedLists.Length, Is.EqualTo(count));
}

[Test]
public void AddTest()
{
using var stackedLists = new NativeStackedLists<int>(64, 4, Allocator.Persistent);

stackedLists.Push();
stackedLists.Add(1);
stackedLists.Add(6);
stackedLists.Add(8);

Assert.That(stackedLists[0][0], Is.EqualTo(1));
Assert.That(stackedLists[0][1], Is.EqualTo(6));
Assert.That(stackedLists[0][2], Is.EqualTo(8));
}

[Test]
public void ClearTest()
{
using var stackedLists = new NativeStackedLists<int>(64, 4, Allocator.Persistent);

stackedLists.Push();
stackedLists.Add(1);
stackedLists.Add(6);
stackedLists.Add(8);

var wasNotEmpty = stackedLists.Length != 0;
stackedLists.Clear();

Assert.That(wasNotEmpty, Is.True);
Assert.That(stackedLists, Has.Length.Zero);
}

[Test]
public void EnumerableTest()
{
using var stackedLists = new NativeStackedLists<int>(64, 4, Allocator.Persistent);

stackedLists.Push();
stackedLists.Add(1);
stackedLists.Add(2);
stackedLists.Add(4);

stackedLists.Push();
stackedLists.Add(1);
stackedLists.Add(3);
stackedLists.Add(9);
stackedLists.Add(27);

int[][] expected =
{
new int[] { 1, 2, 4 },
new int[] { 1, 3, 9, 27 }
};
Assert.That(stackedLists, Is.EqualTo(expected));
}
}
}
11 changes: 11 additions & 0 deletions Tests/NativeStackedListsEditorTests.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit cc980aa

Please sign in to comment.