package com.github.davidmoten.rtree;
import java.util.concurrent.atomic.AtomicLong;
import com.github.davidmoten.guavamini.annotations.VisibleForTesting;
import com.github.davidmoten.rtree.geometry.Geometry;
import com.github.davidmoten.rtree.internal.util.ImmutableStack;
import com.github.davidmoten.rx.util.BackpressureUtils;
import rx.Observable.OnSubscribe;
import rx.Producer;
import rx.Subscriber;
import rx.functions.Func1;
final class OnSubscribeSearch<T, S extends Geometry> implements OnSubscribe<Entry<T, S>> {
private final Node<T, S> node;
private final Func1<? super Geometry, Boolean> condition;
OnSubscribeSearch(Node<T, S> node, Func1<? super Geometry, Boolean> condition) {
this.node = node;
this.condition = condition;
}
@Override
public void call(Subscriber<? super Entry<T, S>> subscriber) {
subscriber.setProducer(new SearchProducer<T, S>(node, condition, subscriber));
}
@VisibleForTesting
static class SearchProducer<T, S extends Geometry> implements Producer {
private final Subscriber<? super Entry<T, S>> subscriber;
private final Node<T, S> node;
private final Func1<? super Geometry, Boolean> condition;
private volatile ImmutableStack<NodePosition<T, S>> stack;
private final AtomicLong requested = new AtomicLong(0);
SearchProducer(Node<T, S> node, Func1<? super Geometry, Boolean> condition,
Subscriber<? super Entry<T, S>> subscriber) {
this.node = node;
this.condition = condition;
this.subscriber = subscriber;
stack = ImmutableStack.create(new NodePosition<T, S>(node, 0));
}
@Override
public void request(long n) {
try {
if (n <= 0 || requested.get() == Long.MAX_VALUE)
// none requested or already started with fast path
return;
else if (n == Long.MAX_VALUE && requested.compareAndSet(0, Long.MAX_VALUE)) {
// fast path
requestAll();
} else
requestSome(n);
} catch (RuntimeException e) {
subscriber.onError(e);
}
}
private void requestAll() {
node.searchWithoutBackpressure(condition, subscriber);
if (!subscriber.isUnsubscribed())
subscriber.onCompleted();
}
private void requestSome(long n) {
// back pressure path
// this algorithm copied roughly from
// rxjava-core/OnSubscribeFromIterable.java
// rxjava used AtomicLongFieldUpdater instead of AtomicLong
// but benchmarks showed no benefit here so reverted to AtomicLong
long previousCount = BackpressureUtils.getAndAddRequest(requested, n);
if (previousCount == 0) {
// don't touch stack every time during the loop because
// is a volatile and every write forces a thread memory
// cache flush
ImmutableStack<NodePosition<T, S>> st = stack;
while (true) {
// minimize atomic reads by assigning to a variable here
long r = requested.get();
st = Backpressure.search(condition, subscriber, st, r);
if (st.isEmpty()) {
// release some state for gc (although empty stack so not very significant)
stack = null;
if (!subscriber.isUnsubscribed()) {
subscriber.onCompleted();
}
return;
} else {
stack = st;
if (requested.addAndGet(-r) == 0)
return;
}
}
}
}
}
}