Skip to content

Commit f37e819

Browse files
authored
Merge pull request #457 from leapmotion/feature-produce-consume-buffer
Adds thread safe queue
2 parents 2d516f1 + 01e013f commit f37e819

File tree

4 files changed

+173
-0
lines changed

4 files changed

+173
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System;
2+
using System.Threading;
3+
using NUnit.Framework;
4+
5+
namespace Leap.Unity.Tests {
6+
7+
public class ProduceConsumeBufferTest {
8+
9+
private ProduceConsumeBuffer<TestStruct> buffer;
10+
11+
[SetUp]
12+
public void Setup() {
13+
buffer = new ProduceConsumeBuffer<TestStruct>(16);
14+
}
15+
16+
[TearDown]
17+
public void Teardown() {
18+
buffer = null;
19+
}
20+
21+
[Test]
22+
[Timeout(1000)]
23+
public void Test() {
24+
Thread consumer = new Thread(new ThreadStart(consumerThread));
25+
Thread producer = new Thread(new ThreadStart(producerThread));
26+
27+
consumer.Start();
28+
producer.Start();
29+
30+
consumer.Join();
31+
producer.Join();
32+
}
33+
34+
private void consumerThread() {
35+
try {
36+
for (int i = 0; i < buffer.Capacity; i++) {
37+
TestStruct s;
38+
s.index = i;
39+
s.name = i.ToString();
40+
while (!buffer.TryEnqueue(ref s)) { }
41+
}
42+
} catch (Exception e) {
43+
Assert.Fail(e.Message);
44+
}
45+
}
46+
47+
private void producerThread() {
48+
try {
49+
for (int i = 0; i < buffer.Capacity; i++) {
50+
TestStruct s;
51+
while (!buffer.TryDequeue(out s)) { }
52+
53+
Assert.That(s.index, Is.EqualTo(i));
54+
Assert.That(s.name, Is.EqualTo(i.ToString()));
55+
}
56+
} catch (Exception e) {
57+
Assert.Fail(e.Message);
58+
}
59+
}
60+
61+
private struct TestStruct {
62+
public int index;
63+
public string name;
64+
}
65+
}
66+
}

Assets/LeapMotion/Scripts/DataStructures/Editor/Tests/ProduceConsumeBufferTest.cs.meta

+12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using UnityEngine;
2+
using System;
3+
4+
namespace Leap.Unity {
5+
6+
public class ProduceConsumeBuffer<T> {
7+
private T[] _buffer;
8+
private uint _bufferMask;
9+
private uint _head, _tail;
10+
11+
/// <summary>
12+
/// Constructs a new produce consumer buffer of at least a certain capacity. Once the
13+
/// buffer is created, the capacity cannot be modified.
14+
///
15+
/// If the minimum capacity is a power of two, it will be used as the actual capacity.
16+
/// If the minimum capacity is not a power of two, the next highest power of two will
17+
/// be used as the capacity. This behavior is an optimization, Internally this class
18+
/// uses a bitwise AND operation instead of a slower modulus operation for indexing,
19+
/// which only is possible if the array length is a power of two.
20+
/// </summary>
21+
public ProduceConsumeBuffer(int minCapacity) {
22+
if (minCapacity <= 0) {
23+
throw new ArgumentOutOfRangeException("The capacity of the ProduceConsumeBuffer must be positive and non-zero.");
24+
}
25+
26+
int capacity;
27+
int closestPowerOfTwo = Mathf.ClosestPowerOfTwo(minCapacity);
28+
if (closestPowerOfTwo == minCapacity) {
29+
capacity = minCapacity;
30+
} else {
31+
if (closestPowerOfTwo < minCapacity) {
32+
capacity = closestPowerOfTwo * 2;
33+
} else {
34+
capacity = closestPowerOfTwo;
35+
}
36+
}
37+
38+
_buffer = new T[capacity];
39+
_bufferMask = (uint)(capacity - 1);
40+
_head = 0;
41+
_tail = 0;
42+
}
43+
44+
/// <summary>
45+
/// Returns the maximum number of elements that the buffer can hold.
46+
/// </summary>
47+
public int Capacity {
48+
get {
49+
return _buffer.Length;
50+
}
51+
}
52+
53+
/// <summary>
54+
/// Tries to enqueue a value into the buffer. If the buffer is already full, this
55+
/// method will perform no action and return false. This method is only safe to
56+
/// be called from a single producer thread.
57+
/// </summary>
58+
public bool TryEnqueue(ref T t) {
59+
uint nextTail = (_tail + 1) & _bufferMask;
60+
if (nextTail == _head) return false;
61+
62+
_buffer[_tail] = t;
63+
_tail = nextTail;
64+
return true;
65+
}
66+
67+
/// <summary>
68+
/// Tries to dequeue a value off of the buffer. If the buffer is empty this method
69+
/// will perform no action and return false. This method is only safe to be
70+
/// called from a single consumer thread.
71+
/// </summary>
72+
public bool TryDequeue(out T t) {
73+
if (_tail == _head) {
74+
t = default(T);
75+
return false;
76+
}
77+
78+
t = _buffer[_head];
79+
_head = (_head + 1) & _bufferMask;
80+
return true;
81+
}
82+
}
83+
}

Assets/LeapMotion/Scripts/DataStructures/ProduceConsumeBuffer.cs.meta

+12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)