QueryWrapper.cs
10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
/******************************************************************************
* Copyright (C) Leap Motion, Inc. 2011-2017. *
* Leap Motion proprietary and confidential. *
* *
* Use subject to the terms of the Leap Motion SDK Agreement available at *
* https://developer.leapmotion.com/sdk_agreement, or another agreement *
* between Leap Motion and you, your company or other organization. *
******************************************************************************/
using System;
using System.Collections;
using System.Collections.Generic;
namespace Leap.Unity.Query {
/// <summary>
/// The wrapper for all leap queries. These queries are meant to mirror the queries present in the
/// System.Linq.Enumerable class. These queries are meant to be functionaly identical, but allocate
/// zero garbage, both during the generation of the query, as well as the execution. The speed is
/// also aimed to be as fast or faster.
///
/// There is one big difference between using Linq and using Leap queries.You must prefix your query
/// with a call to a Query() method if you are starting with an external data structure. So for
/// example if you want to query a list, your method call would look something like this:
///
/// myList.Query().Where(someCondition).First();
/// </summary>
public partial struct QueryWrapper<QueryType, QueryOp> where QueryOp : IQueryOp<QueryType> {
private QueryOp _op;
/// <summary>
/// Returns the actual query operation wrapped by this wrapper. This operation always
/// implements IQueryOp, which is an interface similar to IEnumerator. You usually
/// do not need to access this op directly to use the query. Instead you can simply
/// foreach over this wrapper, or use one of the direct query operations like First,
/// ToList, Any, ect...
/// </summary>
public QueryOp op {
get {
return _op;
}
}
/// <summary>
/// Constructs a new wrapper given a specific query operation.
/// </summary>
public QueryWrapper(QueryOp op) {
_op = op;
}
/// <summary>
/// Returns an enumerator object that is able to enumerate through the query operation
/// wrapped by this wrapper. You can call this directly and step through the result
/// by using MoveNext/Current, or use it indirectly by using the foreach construct.
/// </summary>
public Enumerator GetEnumerator() {
return new Enumerator(_op);
}
public struct Enumerator : IEnumerator<QueryType> {
private QueryOp _op;
private QueryType _current;
public Enumerator(QueryOp op) {
_op = op;
_current = default(QueryType);
}
public bool MoveNext() {
return _op.TryGetNext(out _current);
}
public void Reset() {
_op.Reset();
_current = default(QueryType);
}
public void Dispose() {
_op.Reset();
}
public QueryType Current {
get {
return _current;
}
}
object IEnumerator.Current {
get {
throw new NotImplementedException();
}
}
}
}
/// <summary>
/// The interface all query operations must follow. It is a modified version of the
/// IEnumerator interface, optimized for speed and conciseness.
/// </summary>
public interface IQueryOp<T> {
/// <summary>
/// Tries to get the next value in the sequence. If this method returns true,
/// the next value will be placed into the out parameter t. If this method
/// returns false, the sequence is at an end and t will be the default value
/// of T.
///
/// Once TryGetNext returns false, it can NEVER return true again until the
/// Reset operator is called.
/// </summary>
bool TryGetNext(out T t);
/// <summary>
/// Resets the internal state of this query operation to the begining of
/// the sequence.
/// </summary>
void Reset();
}
/// <summary>
/// Data structures require special conversion operations to turn them into
/// objects that implement IQueryOp. These conversions can unfortunately not
/// be done automatically, which is the reason for the call to Query(). All
/// Query() calls are housed in this class for ease of use.
/// </summary>
public static class QueryConversionExtensions {
/// <summary>
/// Converts an IList object into a query operation, and returns a query wrapper
/// that wraps this new operation.
/// </summary>
public static QueryWrapper<T, ListQueryOp<T>> Query<T>(this IList<T> list) {
return new QueryWrapper<T, ListQueryOp<T>>(new ListQueryOp<T>(list));
}
/// <summary>
/// Converts a two dimensional array into a query operation, and returns a query
/// wrapper that wraps this new operation. Elements are traversed in row-major
/// (C-style) order.
/// </summary>
public static QueryWrapper<T, Array2DQueryOp<T>> Query<T>(this T[,] array) {
return new QueryWrapper<T, Array2DQueryOp<T>>(new Array2DQueryOp<T>(array));
}
/// <summary>
/// Converts a ReadonlyList object into a query operation, and returns a query wrapper
/// that wraps this new operation.
/// </summary>
public static QueryWrapper<T, ReadonlyListQueryOp<T>> Query<T>(this ReadonlyList<T> list) {
return new QueryWrapper<T, ReadonlyListQueryOp<T>>(new ReadonlyListQueryOp<T>(list));
}
/// <summary>
/// Converts a Dictionary object into a query operation, and returns a query wrapper
/// that wraps this new operation.
/// </summary>
public static QueryWrapper<KeyValuePair<K, V>, EnumerableQueryOp<KeyValuePair<K, V>, Dictionary<K, V>.Enumerator>> Query<T, K, V>(this Dictionary<K, V> dictionary) {
return new QueryWrapper<KeyValuePair<K, V>, EnumerableQueryOp<KeyValuePair<K, V>, Dictionary<K, V>.Enumerator>>(new EnumerableQueryOp<KeyValuePair<K, V>, Dictionary<K, V>.Enumerator>(dictionary.GetEnumerator()));
}
/// <summary>
/// Converts a HashSet object into a query operation, and returns a query wrapper
/// that wraps this new operation.
/// </summary>
public static QueryWrapper<T, EnumerableQueryOp<T, HashSet<T>.Enumerator>> Query<T>(this HashSet<T> hashSet) {
return new QueryWrapper<T, EnumerableQueryOp<T, HashSet<T>.Enumerator>>(new EnumerableQueryOp<T, HashSet<T>.Enumerator>(hashSet.GetEnumerator()));
}
/// <summary>
/// Converts a ReadonlyHashSet object into a query operation, and returns a query
/// wrapper that wraps this new operation.
/// </summary>
public static QueryWrapper<T, EnumerableQueryOp<T, HashSet<T>.Enumerator>> Query<T>(this ReadonlyHashSet<T> hashSet) {
return new QueryWrapper<T, EnumerableQueryOp<T, HashSet<T>.Enumerator>>(new EnumerableQueryOp<T, HashSet<T>.Enumerator>(hashSet.GetEnumerator()));
}
/// <summary>
/// Converts a Queue object into a query operation, and returns a query wrapper
/// that wraps this new operation.
/// </summary>
public static QueryWrapper<T, EnumerableQueryOp<T, Queue<T>.Enumerator>> Query<T>(this Queue<T> queue) {
return new QueryWrapper<T, EnumerableQueryOp<T, Queue<T>.Enumerator>>(new EnumerableQueryOp<T, Queue<T>.Enumerator>(queue.GetEnumerator()));
}
/// <summary>
/// Generic fallback for calling Query on any IEnumerator.
///
/// IMPORTANT! Since this uses the IEnumerator interface, it MUST create a small allocation
/// during the call to GetEnumerator that cannot be avoided.
/// </summary>
public static QueryWrapper<T, EnumerableQueryOp<T, IEnumerator<T>>> Query<T>(this IEnumerator<T> enumerator) {
return new QueryWrapper<T, EnumerableQueryOp<T, IEnumerator<T>>>(new EnumerableQueryOp<T, IEnumerator<T>>(enumerator));
}
/// <summary>
/// Generic fallback for calling Query on any IEnumerable.
///
/// IMPORTANT! Since this uses the IEnumerable interface, it MUST create a small allocation
/// during the call to GetEnumerator that cannot be avoided.
/// </summary>
public static QueryWrapper<T, EnumerableQueryOp<T, IEnumerator<T>>> Query<T>(this IEnumerable<T> enumerable) {
return new QueryWrapper<T, EnumerableQueryOp<T, IEnumerator<T>>>(new EnumerableQueryOp<T, IEnumerator<T>>(enumerable.GetEnumerator()));
}
public struct ListQueryOp<T> : IQueryOp<T> {
private IList<T> _list;
private int _index;
public ListQueryOp(IList<T> list) {
_list = list;
_index = 0;
}
public bool TryGetNext(out T t) {
if (_index >= _list.Count) {
t = default(T);
return false;
} else {
t = _list[_index++];
return true;
}
}
public void Reset() {
_index = 0;
}
}
public struct Array2DQueryOp<T> : IQueryOp<T> {
private T[,] _array;
private int _numCols, _numRows;
private int _colIdx, _rowIdx;
public Array2DQueryOp(T[,] array) {
_array = array;
_numRows = array.GetLength(0);
_numCols = array.GetLength(1);
_rowIdx = 0;
_colIdx = 0;
}
public bool TryGetNext(out T t) {
if (_rowIdx >= _numRows) {
t = default(T);
return false;
}
t = _array[_rowIdx, _colIdx]; // C-style, row-major (C# standard)
_colIdx++;
if (_colIdx >= _numCols) {
_colIdx = 0;
_rowIdx++;
}
return true;
}
public void Reset() {
_rowIdx = 0;
_colIdx = 0;
}
}
public struct ReadonlyListQueryOp<T> : IQueryOp<T> {
private ReadonlyList<T> _list;
private int _index;
public ReadonlyListQueryOp(ReadonlyList<T> list) {
_list = list;
_index = 0;
}
public bool TryGetNext(out T t) {
if (_index >= _list.Count) {
t = default(T);
return false;
} else {
t = _list[_index++];
return true;
}
}
public void Reset() {
_index = 0;
}
}
public struct EnumerableQueryOp<T, Enumerable> : IQueryOp<T>
where Enumerable : IEnumerator<T> {
private Enumerable _source;
public EnumerableQueryOp(Enumerable source) {
_source = source;
}
public bool TryGetNext(out T t) {
if (_source.MoveNext()) {
t = _source.Current;
return true;
} else {
t = default(T);
return false;
}
}
public void Reset() {
_source.Reset();
}
}
}
}