You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
785 lines
24 KiB
785 lines
24 KiB
// Needed for NET40 |
|
#if !NET_4_6 |
|
using System; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using System.Threading; |
|
using LinqInternal.Collections.ThreadSafe; |
|
using LinqInternal.Core; |
|
using LinqInternal.Threading; |
|
|
|
namespace LinqInternal.Collections |
|
{ |
|
[Serializable] |
|
internal sealed class Progressor<T> : IObservable<T> |
|
{ |
|
private ProxyObservable<T> _proxy; |
|
private TryTake<T> _tryTake; |
|
private bool _done; |
|
|
|
public Progressor(Progressor<T> wrapped) |
|
{ |
|
if (wrapped == null) |
|
{ |
|
throw new ArgumentNullException("wrapped"); |
|
} |
|
|
|
var control = 0; |
|
|
|
Predicate<T> newFilter = item => Volatile.Read(ref control) == 0; |
|
var buffer = new SafeQueue<T>(); |
|
wrapped.SubscribeAction |
|
( |
|
item => |
|
{ |
|
if (newFilter(item)) |
|
{ |
|
buffer.Add(item); |
|
} |
|
} |
|
); |
|
_proxy = new ProxyObservable<T>(); |
|
|
|
_tryTake = (out T value) => |
|
{ |
|
Interlocked.Increment(ref control); |
|
try |
|
{ |
|
if (buffer.TryTake(out value) || wrapped.TryTake(out value)) |
|
{ |
|
_proxy.OnNext(value); |
|
return true; |
|
} |
|
else |
|
{ |
|
_done = wrapped._done; |
|
return false; |
|
} |
|
} |
|
finally |
|
{ |
|
Interlocked.Decrement(ref control); |
|
} |
|
}; |
|
} |
|
|
|
public Progressor(IEnumerable<T> preface, Progressor<T> wrapped) |
|
{ |
|
if (wrapped == null) |
|
{ |
|
throw new ArgumentNullException("wrapped"); |
|
} |
|
if (preface == null) |
|
{ |
|
throw new ArgumentNullException("preface"); |
|
} |
|
var enumerator = preface.GetEnumerator(); |
|
if (enumerator == null) |
|
{ |
|
throw new ArgumentException("preface.GetEnumerator()"); |
|
} |
|
|
|
var control = 0; |
|
var guard = 0; |
|
|
|
Predicate<T> newFilter = item => Volatile.Read(ref control) == 0; |
|
var buffer = new SafeQueue<T>(); |
|
wrapped.SubscribeAction |
|
( |
|
item => |
|
{ |
|
if (newFilter(item)) |
|
{ |
|
buffer.Add(item); |
|
} |
|
} |
|
); |
|
_proxy = new ProxyObservable<T>(); |
|
|
|
TryTake<T> tryTakeReplacement = (out T value) => |
|
{ |
|
Interlocked.Increment(ref control); |
|
try |
|
{ |
|
if (buffer.TryTake(out value) || wrapped.TryTake(out value)) |
|
{ |
|
_proxy.OnNext(value); |
|
return true; |
|
} |
|
else |
|
{ |
|
_done = wrapped._done; |
|
return false; |
|
} |
|
} |
|
finally |
|
{ |
|
Interlocked.Decrement(ref control); |
|
} |
|
}; |
|
|
|
_tryTake = (out T value) => |
|
{ |
|
value = default(T); |
|
if (Volatile.Read(ref guard) == 0) |
|
{ |
|
bool result; |
|
// We need a lock, there is no way around it. IEnumerator is just awful. Use another overload if possible. |
|
lock (enumerator) |
|
{ |
|
result = enumerator.MoveNext(); |
|
if (result) |
|
{ |
|
value = enumerator.Current; |
|
} |
|
} |
|
if (result) |
|
{ |
|
_proxy.OnNext(value); |
|
return true; |
|
} |
|
enumerator.Dispose(); |
|
Interlocked.CompareExchange(ref guard, 1, 0); |
|
} |
|
if (Interlocked.CompareExchange(ref guard, 2, 1) == 1) |
|
{ |
|
_tryTake = tryTakeReplacement; |
|
Volatile.Write(ref guard, 3); |
|
} |
|
else |
|
{ |
|
ThreadingHelper.SpinWaitUntil(ref guard, 3); |
|
} |
|
var tryTake = _tryTake; |
|
return tryTake(out value); |
|
}; |
|
} |
|
|
|
public Progressor(T[] wrapped) |
|
{ |
|
if (wrapped == null) |
|
{ |
|
throw new ArgumentNullException("wrapped"); |
|
} |
|
|
|
var guard = 0; |
|
var index = -1; |
|
|
|
_proxy = new ProxyObservable<T>(); |
|
|
|
TryTake<T> tryTakeReplacement = (out T value) => |
|
{ |
|
value = default(T); |
|
return false; |
|
}; |
|
|
|
_tryTake = (out T value) => |
|
{ |
|
value = default(T); |
|
if (Volatile.Read(ref guard) == 0) |
|
{ |
|
var currentIndex = Interlocked.Increment(ref index); |
|
if (currentIndex < wrapped.Length) |
|
{ |
|
value = wrapped[currentIndex]; |
|
_proxy.OnNext(value); |
|
return true; |
|
} |
|
Interlocked.CompareExchange(ref guard, 1, 0); |
|
} |
|
if (Interlocked.CompareExchange(ref guard, 2, 1) == 1) |
|
{ |
|
_tryTake = tryTakeReplacement; |
|
} |
|
return false; |
|
}; |
|
} |
|
|
|
public Progressor(T[] preface, Progressor<T> wrapped) |
|
{ |
|
if (wrapped == null) |
|
{ |
|
throw new ArgumentNullException("wrapped"); |
|
} |
|
if (preface == null) |
|
{ |
|
throw new ArgumentNullException("preface"); |
|
} |
|
|
|
var control = 0; |
|
var guard = 0; |
|
var index = -1; |
|
|
|
Predicate<T> newFilter = item => Volatile.Read(ref control) == 0; |
|
var buffer = new SafeQueue<T>(); |
|
wrapped.SubscribeAction |
|
( |
|
item => |
|
{ |
|
if (newFilter(item)) |
|
{ |
|
buffer.Add(item); |
|
} |
|
} |
|
); |
|
_proxy = new ProxyObservable<T>(); |
|
|
|
TryTake<T> tryTakeReplacement = (out T value) => |
|
{ |
|
Interlocked.Increment(ref control); |
|
try |
|
{ |
|
if (buffer.TryTake(out value) || wrapped.TryTake(out value)) |
|
{ |
|
_proxy.OnNext(value); |
|
return true; |
|
} |
|
else |
|
{ |
|
_done = wrapped._done; |
|
return false; |
|
} |
|
} |
|
finally |
|
{ |
|
Interlocked.Decrement(ref control); |
|
} |
|
}; |
|
|
|
_tryTake = (out T value) => |
|
{ |
|
if (Volatile.Read(ref guard) == 0) |
|
{ |
|
var currentIndex = Interlocked.Increment(ref index); |
|
if (currentIndex < preface.Length) |
|
{ |
|
value = preface[currentIndex]; |
|
_proxy.OnNext(value); |
|
return true; |
|
} |
|
Interlocked.CompareExchange(ref guard, 1, 0); |
|
} |
|
if (Interlocked.CompareExchange(ref guard, 2, 1) == 1) |
|
{ |
|
_tryTake = tryTakeReplacement; |
|
Volatile.Write(ref guard, 3); |
|
} |
|
else |
|
{ |
|
ThreadingHelper.SpinWaitUntil(ref guard, 3); |
|
} |
|
var tryTake = _tryTake; |
|
return tryTake(out value); |
|
}; |
|
} |
|
|
|
public Progressor(IEnumerable<T> wrapped) |
|
{ |
|
if (wrapped == null) |
|
{ |
|
throw new ArgumentNullException("wrapped"); |
|
} |
|
var enumerator = wrapped.GetEnumerator(); |
|
if (enumerator == null) |
|
{ |
|
throw new ArgumentException("wrapped.GetEnumerator()"); |
|
} |
|
|
|
var guard = 0; |
|
|
|
_proxy = new ProxyObservable<T>(); |
|
|
|
TryTake<T> tryTakeReplacement = (out T value) => |
|
{ |
|
value = default(T); |
|
return false; |
|
}; |
|
|
|
_tryTake = (out T value) => |
|
{ |
|
value = default(T); |
|
if (Volatile.Read(ref guard) == 0) |
|
{ |
|
bool result; |
|
// We need a lock, there is no way around it. IEnumerator is just awful. Use another overload if possible. |
|
lock (enumerator) |
|
{ |
|
result = enumerator.MoveNext(); |
|
if (result) |
|
{ |
|
value = enumerator.Current; |
|
} |
|
} |
|
if (result) |
|
{ |
|
_proxy.OnNext(value); |
|
return true; |
|
} |
|
enumerator.Dispose(); |
|
Interlocked.CompareExchange(ref guard, 1, 0); |
|
} |
|
if (Interlocked.CompareExchange(ref guard, 2, 1) == 1) |
|
{ |
|
_tryTake = tryTakeReplacement; |
|
} |
|
return false; |
|
}; |
|
} |
|
|
|
public Progressor(TryTake<T> tryTake, bool doneOnFalse) |
|
{ |
|
if (tryTake == null) |
|
{ |
|
throw new ArgumentNullException("tryTake"); |
|
} |
|
var tryTakeCopy = tryTake; |
|
_proxy = new ProxyObservable<T>(); |
|
_tryTake = (out T value) => |
|
{ |
|
// This is not an overridable method, and it is not being called on the constructor. |
|
if (tryTakeCopy(out value)) |
|
{ |
|
_proxy.OnNext(value); |
|
return true; |
|
} |
|
_done = doneOnFalse; |
|
return false; |
|
}; |
|
} |
|
|
|
public Progressor(TryTake<T> tryTake, Func<bool> isDone) |
|
{ |
|
if (tryTake == null) |
|
{ |
|
throw new ArgumentNullException("tryTake"); |
|
} |
|
if (isDone == null) |
|
{ |
|
throw new ArgumentNullException("isDone"); |
|
} |
|
var tryTakeCopy = tryTake; |
|
_proxy = new ProxyObservable<T>(); |
|
_tryTake = (out T value) => |
|
{ |
|
// This is not an overridable method, and it is not being called on the constructor. |
|
if (tryTakeCopy(out value)) |
|
{ |
|
_proxy.OnNext(value); |
|
return true; |
|
} |
|
_done = new ValueFuncClosure<bool>(isDone).InvokeReturn(); |
|
return false; |
|
}; |
|
} |
|
|
|
public Progressor(IObservable<T> wrapped) |
|
{ |
|
var buffer = new SafeQueue<T>(); |
|
wrapped.Subscribe |
|
( |
|
new CustomObserver<T> |
|
( |
|
() => _done = true, |
|
exception => _done = true, |
|
buffer.Add |
|
) |
|
); |
|
_proxy = new ProxyObservable<T>(); |
|
|
|
_tryTake = (out T value) => |
|
{ |
|
if (buffer.TryTake(out value)) |
|
{ |
|
_proxy.OnNext(value); |
|
return true; |
|
} |
|
value = default(T); |
|
return false; |
|
}; |
|
} |
|
|
|
private Progressor(TryTake<T> tryTake, ProxyObservable<T> proxy) |
|
{ |
|
_proxy = proxy; |
|
_tryTake = tryTake; |
|
} |
|
|
|
public bool IsClosed |
|
{ |
|
get { return _tryTake == null; } |
|
} |
|
|
|
public static Progressor<T> CreateConverted<TInput>(Progressor<TInput> wrapped, Func<TInput, T> converter) |
|
{ |
|
if (wrapped == null) |
|
{ |
|
throw new ArgumentNullException("wrapped"); |
|
} |
|
if (converter == null) |
|
{ |
|
throw new ArgumentNullException("converter"); |
|
} |
|
|
|
var control = 0; |
|
|
|
Predicate<TInput> newFilter = item => Volatile.Read(ref control) == 0; |
|
var buffer = new SafeQueue<T>(); |
|
var proxy = new ProxyObservable<T>(); |
|
|
|
var result = new Progressor<T>( |
|
(out T value) => |
|
{ |
|
Interlocked.Increment(ref control); |
|
try |
|
{ |
|
TInput item; |
|
if (buffer.TryTake(out value)) |
|
{ |
|
proxy.OnNext(value); |
|
return true; |
|
} |
|
else if (wrapped.TryTake(out item)) |
|
{ |
|
value = converter(item); |
|
proxy.OnNext(value); |
|
return true; |
|
} |
|
value = default(T); |
|
return false; |
|
} |
|
finally |
|
{ |
|
Interlocked.Decrement(ref control); |
|
} |
|
}, |
|
proxy |
|
); |
|
wrapped.Subscribe |
|
( |
|
new CustomObserver<TInput> |
|
( |
|
() => result._done = true, |
|
exception => result._done = true, |
|
item => |
|
{ |
|
if (newFilter(item)) |
|
{ |
|
buffer.Add(converter(item)); |
|
} |
|
} |
|
) |
|
); |
|
return result; |
|
} |
|
|
|
public static Progressor<T> CreatedFiltered(Progressor<T> wrapped, Predicate<T> filter) |
|
{ |
|
if (wrapped == null) |
|
{ |
|
throw new ArgumentNullException("wrapped"); |
|
} |
|
if (filter == null) |
|
{ |
|
throw new ArgumentNullException("filter"); |
|
} |
|
|
|
var control = 0; |
|
|
|
Predicate<T> newFilter = item => Volatile.Read(ref control) == 0 && filter(item); |
|
var buffer = new SafeQueue<T>(); |
|
var proxy = new ProxyObservable<T>(); |
|
|
|
var result = new Progressor<T>( |
|
(out T value) => |
|
{ |
|
Volatile.Write(ref control, 1); |
|
try |
|
{ |
|
again: |
|
if (buffer.TryTake(out value)) |
|
{ |
|
proxy.OnNext(value); |
|
return true; |
|
} |
|
else if (wrapped.TryTake(out value)) |
|
{ |
|
if (filter(value)) |
|
{ |
|
proxy.OnNext(value); |
|
return true; |
|
} |
|
else |
|
{ |
|
goto again; |
|
} |
|
} |
|
value = default(T); |
|
return false; |
|
} |
|
finally |
|
{ |
|
Interlocked.Decrement(ref control); |
|
} |
|
}, |
|
proxy |
|
); |
|
wrapped.Subscribe |
|
( |
|
new CustomObserver<T> |
|
( |
|
() => result._done = true, |
|
exception => result._done = true, |
|
item => |
|
{ |
|
if (newFilter(item)) |
|
{ |
|
buffer.Add(item); |
|
} |
|
} |
|
) |
|
); |
|
return result; |
|
} |
|
|
|
public static Progressor<T> CreatedFilteredConverted<TInput>(Progressor<TInput> wrapped, Predicate<TInput> filter, Func<TInput, T> converter) |
|
{ |
|
if (wrapped == null) |
|
{ |
|
throw new ArgumentNullException("wrapped"); |
|
} |
|
if (filter == null) |
|
{ |
|
throw new ArgumentNullException("filter"); |
|
} |
|
if (converter == null) |
|
{ |
|
throw new ArgumentNullException("converter"); |
|
} |
|
|
|
var control = 0; |
|
|
|
Predicate<TInput> newFilter = item => Volatile.Read(ref control) == 0 && filter(item); |
|
var buffer = new SafeQueue<T>(); |
|
var proxy = new ProxyObservable<T>(); |
|
|
|
var result = new Progressor<T>( |
|
(out T value) => |
|
{ |
|
Interlocked.Increment(ref control); |
|
try |
|
{ |
|
TInput item; |
|
again: |
|
if (buffer.TryTake(out value)) |
|
{ |
|
proxy.OnNext(value); |
|
return true; |
|
} |
|
else if (wrapped.TryTake(out item)) |
|
{ |
|
if (filter(item)) |
|
{ |
|
value = converter(item); |
|
proxy.OnNext(value); |
|
return true; |
|
} |
|
else |
|
{ |
|
goto again; |
|
} |
|
} |
|
value = default(T); |
|
return false; |
|
} |
|
finally |
|
{ |
|
Interlocked.Decrement(ref control); |
|
} |
|
}, |
|
proxy |
|
); |
|
wrapped.Subscribe |
|
( |
|
new CustomObserver<TInput> |
|
( |
|
() => result._done = true, |
|
exception => result._done = true, |
|
item => |
|
{ |
|
if (newFilter(item)) |
|
{ |
|
buffer.Add(converter(item)); |
|
} |
|
} |
|
) |
|
); |
|
return result; |
|
} |
|
|
|
public static Progressor<T> CreateDistinct(Progressor<T> wrapped) |
|
{ |
|
if (wrapped == null) |
|
{ |
|
throw new ArgumentNullException("wrapped"); |
|
} |
|
|
|
var control = 0; |
|
|
|
var buffer = new SafeDictionary<T, bool>(); |
|
Predicate<T> newFilter = item => Volatile.Read(ref control) == 0; |
|
var proxy = new ProxyObservable<T>(); |
|
|
|
var result = new Progressor<T>( |
|
(out T value) => |
|
{ |
|
Interlocked.Increment(ref control); |
|
try |
|
{ |
|
again: |
|
foreach (var item in buffer.Where(item => !item.Value)) |
|
{ |
|
value = item.Key; |
|
buffer.Set(value, true); |
|
proxy.OnNext(value); |
|
return true; |
|
} |
|
if (wrapped.TryTake(out value)) |
|
{ |
|
bool seen; |
|
if (!buffer.TryGetValue(value, out seen) || !seen) |
|
{ |
|
buffer.Set(value, true); |
|
proxy.OnNext(value); |
|
return true; |
|
} |
|
else |
|
{ |
|
goto again; |
|
} |
|
} |
|
else |
|
{ |
|
return false; |
|
} |
|
} |
|
finally |
|
{ |
|
Interlocked.Decrement(ref control); |
|
} |
|
}, |
|
proxy |
|
); |
|
wrapped.Subscribe |
|
( |
|
new CustomObserver<T> |
|
( |
|
() => result._done = true, |
|
exception => result._done = true, |
|
item => |
|
{ |
|
if (newFilter(item)) |
|
{ |
|
buffer.TryAdd(item, false); |
|
} |
|
} |
|
) |
|
); |
|
return result; |
|
} |
|
|
|
public IEnumerable<T> AsEnumerable() |
|
{ |
|
// After enumerating - the consumer of this method must check if the Progressor is closed. |
|
while (true) |
|
{ |
|
T item; |
|
var tryTake = _tryTake; |
|
if (tryTake(out item)) |
|
{ |
|
yield return item; |
|
} |
|
else |
|
{ |
|
break; |
|
} |
|
} |
|
} |
|
|
|
public void Close() |
|
{ |
|
_tryTake = null; |
|
_proxy.OnCompleted(); |
|
_proxy = null; |
|
} |
|
|
|
public IDisposable Subscribe(IObserver<T> observer) |
|
{ |
|
if (_proxy != null) |
|
{ |
|
return _proxy.Subscribe(observer); |
|
} |
|
return Disposable.Create(ActionHelper.GetNoopAction()); |
|
} |
|
|
|
public bool TryTake(out T item) |
|
{ |
|
if (_tryTake != null) |
|
{ |
|
if (_tryTake.Invoke(out item)) |
|
{ |
|
return true; |
|
} |
|
if (_done) |
|
{ |
|
Close(); |
|
} |
|
return false; |
|
} |
|
item = default(T); |
|
return false; |
|
} |
|
|
|
public IEnumerable<T> While(Predicate<T> condition) |
|
{ |
|
if (condition == null) |
|
{ |
|
throw new ArgumentNullException("condition"); |
|
} |
|
while (true) |
|
{ |
|
T item; |
|
var tryTake = _tryTake; |
|
if (tryTake(out item) && condition(item)) |
|
{ |
|
yield return item; |
|
} |
|
else |
|
{ |
|
break; |
|
} |
|
} |
|
} |
|
|
|
public IEnumerable<T> While(Func<bool> condition) |
|
{ |
|
if (condition == null) |
|
{ |
|
throw new ArgumentNullException("condition"); |
|
} |
|
while (true) |
|
{ |
|
T item; |
|
var tryTake = _tryTake; |
|
if (tryTake(out item) && condition()) |
|
{ |
|
yield return item; |
|
} |
|
else |
|
{ |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
#endif |