I currently have a program that listens to a network stream and fires events when a new message has been deserialized.
while(true)
{
byte[] lengthBytes = new byte[10];
networkStream.Read(lengthBytes, 0, 10);
int messageLength = Int32.Parse(Encoding.UTF8.GetString(lengthBytes));
var messageBytes = new byte[messageLength + 10];
Array.Copy(lengthBytes, messageBytes, 10);
int bytesReadTotal = 10;
while (bytesReadTotal < 10 + messageLength)
bytesReadTotal += networkStream.Read(messageBytes, bytesReadTotal, messageLength - bytesReadTotal + 10);
OnNewMessage(new MessageEventArgs(messageFactory.GetMessage(messageBytes)));
}
I want to rewrite this using the reactive extensions so that instead of the event there is an IObservable<Message>. This could be done using
Observable.FromEvent<EventHandler<MessageEventArgs>, MessageEventArgs>(
(h) => NewMessage += h,
(h) => NewMessage -= h)
.Select( (e) => { return e.Message; });
However I would prefer to rewrite the listening process using System.Reactive instead. My starting point (from here) is
Func<byte[], int, int, IObservable<int>> read;
read = Observable.FromAsyncPattern<byte[], int, int, int>(
networkStream.BeginRead,
networkStream.EndRead);
which allows
byte[] lengthBytes = new byte[10];
read(lengthBytes, 0, lengthBytes.Length).Subscribe(
{
(bytesRead) => ;
});
I'm struggling to see how to continue though. Does anyone have an implementation?
I came up with the following, but I feel it should be possible without creating a class and using Subject<T> (e.g. via some projection of the header packet to the body packet to the message object, but the problem with that is EndRead() doesn't return the byte array, but the number of bytes read. So you need an object or atleast a closure at some point).
class Message
{
public string Text { get; set; }
}
class MessageStream : IObservable<Message>
{
private readonly Subject<Message> messages = new Subject<Message>();
public void Start()
{
// Get your real network stream here.
var stream = Console.OpenStandardInput();
GetNextMessage( stream );
}
private void GetNextMessage(Stream stream)
{
var header = new byte[10];
var read = Observable.FromAsyncPattern<byte [], int, int, int>( stream.BeginRead, stream.EndRead );
read( header, 0, 10 ).Subscribe( b =>
{
var bodyLength = BitConverter.ToInt32( header, 0 );
var body = new byte[bodyLength];
read( body, 0, bodyLength ).Subscribe( b2 =>
{
var message = new Message() {Text = Encoding.UTF8.GetString( body )};
messages.OnNext( message );
GetNextMessage( stream );
} );
} );
}
public IDisposable Subscribe( IObserver<Message> observer )
{
return messages.Subscribe( observer );
}
}
Since Observable.FromAsyncPattern only makes the async call once, you will need to make a function that will call it multiple times instead. This should get you started, but probably has lots of room for improvement. It assumes that you can make the async calls repeatedly with the same arguments and assumes that the selector will handle any issues that arise from this.
Function FromRepeatedAsyncPattern(Of T1, T2, T3, TCallResult, TResult)(
begin As Func(Of T1, T2, T3, AsyncCallback, Object, IAsyncResult),
[end] As Func(Of IAsyncResult, TCallResult),
selector As Func(Of TCallResult, TResult),
isComplete As Func(Of TCallResult, Boolean)
) As Func(Of T1, T2, T3, IObservable(Of TResult))
Return Function(a1, a2, a3) Observable.Create(Of TResult)(
Function(obs)
Dim serial As New SerialDisposable()
Dim fac = Observable.FromAsyncPattern(begin, [end])
Dim onNext As Action(Of TCallResult) = Nothing
'this function will restart the subscription and will be
'called every time a value is found
Dim subscribe As Func(Of IDisposable) =
Function()
'note that we are REUSING the arguments, the
'selector should handle this appropriately
Return fac(a1, a2, a3).Subscribe(onNext,
Sub(ex)
obs.OnError(ex)
serial.Dispose()
End Sub)
End Function
'set up the OnNext handler to restart the observer
'every time it completes
onNext = Sub(v)
obs.OnNext(selector(v))
'subscriber disposed, do not check for completion
'or resubscribe
If serial.IsDisposed Then Exit Sub
If isComplete(v) Then
obs.OnCompleted()
serial.Dispose()
Else
'using the scheduler lets the OnNext complete before
'making the next async call.
'you could parameterize the scheduler, but it may not be
'helpful, and it won't work if Immediate is passed.
Scheduler.CurrentThread.Schedule(Sub() serial.Disposable = subscribe())
End If
End Sub
'start the first subscription
serial.Disposable = subscribe()
Return serial
End Function)
End Function
From here, you can get an IObservable(Of Byte) like so:
Dim buffer(4096 - 1) As Byte
Dim obsFac = FromRepeatedAsyncPattern(Of Byte(), Integer, Integer, Integer, Byte())(
AddressOf stream.BeginRead, AddressOf stream.EndRead,
Function(numRead)
If numRead < 0 Then Throw New ArgumentException("Invalid number read")
Console.WriteLine("Position after read: " & stream.Position.ToString())
Dim ret(numRead - 1) As Byte
Array.Copy(buffer, ret, numRead)
Return ret
End Function,
Function(numRead) numRead <= 0)
'this will be an observable of the chunk size you specify
Dim obs = obsFac(buffer, 0, buffer.Length)
From there, you will need some sort of accumulator function that takes byte arrays and outputs complete messages when they are found. The skeleton of such a function might look like:
Public Function Accumulate(source As IObservable(Of Byte())) As IObservable(Of Message)
Return Observable.Create(Of message)(
Function(obs)
Dim accumulator As New List(Of Byte)
Return source.Subscribe(
Sub(buffer)
'do some logic to build a packet here
accumulator.AddRange(buffer)
If True Then
obs.OnNext(New message())
'reset accumulator
End If
End Sub,
AddressOf obs.OnError,
AddressOf obs.OnCompleted)
End Function)
End Function
Related
I want to create an extension method of the form:
IObservable<bool> CancellableTimer( this IObservable source, TimeSpan delay )
{
...
}
which produces a sequence which is always false when the source is, but will go true when the source sequence has stayed true for a period defined by a delay, t:
source: 0---1---------0--1-0-1-0-1-0-1----------0
t------> t------>
result: 0----------1--0---------------------1---0
I'm sure there must be a way to do this using Rx primitives but I'm new to Rx and having trouble getting my head round it. Any ideas please?
Okay so this is what I came up with. I also renamed the method to AsymetricDelay() as it seems like a more appropriate name:
static public IObservable<bool> AsymetricDelay(this IObservable<bool> source, TimeSpan delay, IScheduler scheduler)
{
var distinct = source.DistinctUntilChanged();
return distinct.
Throttle(delay, scheduler) // Delay both trues and falses
.Where(x => x) // But we only want trues to be delayed
.Merge( // Merge the trues with...
distinct.Where(x=>!x) // non delayed falses
)
.DistinctUntilChanged(); // Get rid of any repeated values
}
And here is a unit test to confirm its operation:
[Fact]
public static void Test_AsymetricDelay()
{
var scheduler = new TestScheduler();
var xs = scheduler.CreateHotObservable(
new Recorded<Notification<bool>>(10000000, Notification.CreateOnNext(true)),
new Recorded<Notification<bool>>(60000000, Notification.CreateOnNext(false)),
new Recorded<Notification<bool>>(70000000, Notification.CreateOnNext(true)),
new Recorded<Notification<bool>>(80000000, Notification.CreateOnNext(false)),
new Recorded<Notification<bool>>(100000000, Notification.CreateOnCompleted<bool>())
);
var dest = xs.DelayOn( TimeSpan.FromSeconds(2), scheduler);
var testObserver = scheduler.Start(
() => dest,
0,
0,
TimeSpan.FromSeconds(10).Ticks);
testObserver.Messages.AssertEqual(
new Recorded<Notification<bool>>(30000000, Notification.CreateOnNext(true)),
new Recorded<Notification<bool>>(60000000, Notification.CreateOnNext(false)),
new Recorded<Notification<bool>>(100000000, Notification.CreateOnCompleted<bool>())
);
}
I'm using reactive programming to do a bunch of calculations. Here is a simple example that tracks two numbers and their sum:
static void Main(string[] args) {
BehaviorSubject<int> x = new BehaviorSubject<int>(1);
BehaviorSubject<int> y = new BehaviorSubject<int>(2);
var sum = Observable.CombineLatest(x, y, (num1, num2) => num1 + num2);
Observable
.CombineLatest(x, y, sum, (xx, yy, sumsum) => new { X = xx, Y = yy, Sum = sumsum })
.Subscribe(i => Console.WriteLine($"X:{i.X} Y:{i.Y} Sum:{i.Sum}"));
x.OnNext(3);
Console.ReadLine();
}
This generates the following output:
X:1 Y:2 Sum:3
X:3 Y:2 Sum:3
X:3 Y:2 Sum:5
Notice how second output result is "incorrect" because it is showing that 3+2=3. I understand why this is happening (x is updated before the sum is updated) but I want my output calculations to be atomic/consistent - no value should be emitted until all dependent calculations are complete. My first approach was this...
Observable.When(sum.And(Observable.CombineLatest(x, y)).Then((s, xy) => new { Sum = s, X = xy[0], Y = xy[1] } ));
This seems to work for my simple example. But my actual code has LOTS of calculated values and I couldn't figure out how to scale it. For example, if there was a sum and squaredSum, I don't know how to wait for each of these to emit something before taking action.
One method that should work (in-theory) is to timestamp all the values I care about, as shown below.
Observable
.CombineLatest(x.Timestamp(), y.Timestamp(), sum.Timestamp(), (xx, yy, sumsum) => new { X = xx, Y = yy, Sum = sumsum })
.Where(i=>i.Sum.Timestamp>i.X.Timestamp && i.Sum.Timestamp>i.Y.Timestamp)
// do the calculation and subscribe
This method could work for very complicated models. All I have to do is ensure that no calculated value is emitted that is older than any core data value. I find this to be a bit of a kludge. It didn't actually work in my console app. When I replaced Timestamp with a custom extension that assigned a sequential int64 it did work.
What is a simple, clean way to handle this kind of thing in general?
=======
I'm making some progress here. This waits for a sum and sumSquared to emit a value before grabbing the data values that triggered the calculation.
var all = Observable.When(sum.And(sumSquared).And(Observable.CombineLatest(x, y)).Then((s, q, data)
=> new { Sum = s, SumSquared = q, X = data[0], Y = data[1] }));
This should do what you want:
Observable.CombineLatest(x, y, sum)
.DistinctUntilChanged(list => list[2])
.Subscribe(list => Console.WriteLine("{0}+{1}={2}", list[0], list[1], list[2]));
It waits until the sum has been updated, which means that all its sources must have been updated too.
You problem isn't because x is updated before the sum is updated per se. It's really about the way that you've constructed your query.
You've effectively created two queries: Observable.CombineLatest(x, y, (num1, num2) => num1 + num2) & Observable.CombineLatest(x, y, sum, (xx, yy, sumsum) => new { X = xx, Y = yy, Sum = sumsum }). Since in each you're subscribing to x then you've create two subscriptions. Meaning that when x updates then two lots of updates occur.
You need to avoid creating two subscriptions.
If you write your code like this:
BehaviorSubject<int> x = new BehaviorSubject<int>(1);
BehaviorSubject<int> y = new BehaviorSubject<int>(2);
Observable
.CombineLatest(x, y, (num1, num2) => new
{
X = num1,
Y = num2,
Sum = num1 + num2
})
.Subscribe(i => Console.WriteLine($"X:{i.X} Y:{i.Y} Sum:{i.Sum}"));
x.OnNext(3);
...then you correctly get this output:
X:1 Y:2 Sum:3
X:3 Y:2 Sum:5
I've started to get my head around this some more. Here is a more detailed example of what I'm trying to accomplish. This is some code that validates a first and last name, and should only generate a whole name when both parts are valid. As you can see I'm trying to use a bunch of small independently defined functions, like "firstIsValid", and then compose them together to calculate something more complex.
It seems like the challenge I'm facing here is trying to correlate inputs and outputs in my functions. For example, "firstIsValid" generates an output that says some first name was valid, but doesn't tell you which one. In option 2 below, I'm able to correlate them using Zip.
This strategy won't work if a validation function does not generate one output for each input. For example, if the user is typing web addresses and we're trying to validate them on the web, maybe we'd do a Throttle and/or Switch. There might be 10 web addresses for a single "webAddressIsValid". In that situation, I think I have to include the output with the input. Maybe have an IObservable> where the string is the web address and the bool is whether it is valid or not.
static void Main(string[] args) {
var first = new BehaviorSubject<string>(null);
var last = new BehaviorSubject<string>(null);
var firstIsValid = first.Select(i => string.IsNullOrEmpty(i) || i.Length < 3 ? false : true);
var lastIsValid = last.Select(i => string.IsNullOrEmpty(i) || i.Length < 3 ? false : true);
// OPTION 1 : Does not work
// Output: bob smith, bob, bob roberts, roberts
// firstIsValid and lastIsValid are not in sync with first and last
//var whole = Observable
// .CombineLatest(first, firstIsValid, last, lastIsValid, (f, fv, l, lv) => new {
// First = f,
// Last = l,
// FirstIsValid = fv,
// LastIsValid = lv
// })
// .Where(i => i.FirstIsValid && i.LastIsValid)
// .Select(i => $"{i.First} {i.Last}");
// OPTION 2 : Works as long as every change in a core data value generates one calculated value
// Output: bob smith, bob robert
var firstValidity = Observable.Zip(first, firstIsValid, (f, fv) => new { Name = f, IsValid = fv });
var lastValidity = Observable.Zip(last, lastIsValid, (l, lv) => new { Name = l, IsValid = lv });
var whole =
Observable.CombineLatest(firstValidity, lastValidity, (f, l) => new { First = f, Last = l })
.Where(i => i.First.IsValid && i.Last.IsValid)
.Select(i => $"{i.First.Name} {i.Last.Name}");
whole.Subscribe(i => Console.WriteLine(i));
first.OnNext("bob");
last.OnNext("smith");
last.OnNext(null);
last.OnNext("roberts");
first.OnNext(null);
Console.ReadLine();
}
Another approach here. Each value gets a version number (like a timestamp). Any time a calculated value is older than the data (or other calculated values it relies upon) we can ignore it.
public class VersionedValue {
static long _version;
public VersionedValue() { Version = Interlocked.Increment(ref _version); }
public long Version { get; }
}
public class VersionedValue<T> : VersionedValue {
public VersionedValue(T value) { Value = value; }
public T Value { get; }
public override string ToString() => $"{Value} {Version}";
}
public static class ExtensionMethods {
public static IObservable<VersionedValue<T>> Versioned<T>(this IObservable<T> values) => values.Select(i => new VersionedValue<T>(i));
public static VersionedValue<T> AsVersionedValue<T>(this T obj) => new VersionedValue<T>(obj);
}
static void Main(string[] args) {
// same as before
//
var whole = Observable
.CombineLatest(first.Versioned(), firstIsValid.Versioned(), last.Versioned(), lastIsValid.Versioned(), (f, fv, l, lv) => new {
First = f,
Last = l,
FirstIsValid = fv,
LastIsValid = lv
})
.Where(i => i.FirstIsValid.Version > i.First.Version && i.LastIsValid.Version > i.Last.Version)
.Where(i => i.FirstIsValid.Value && i.LastIsValid.Value)
.Select(i => $"{i.First.Value} {i.Last.Value}");
I 've removed the boilerplate to get to the point
// a.js
// My observables from stream and event
this.a = Rx.Node.fromStream(this.aStream());
this.itemSource = Rx.Observable.fromEvent(ee, 'addItem');
// Zip 'em
this.itemcombo = Rx.Observable.zip(this.a, this.itemSource, function (s1, s2) {
return {item: s2, a: s1.toString()};
});
// Streams the lowercase alphabet
rb.prototype.aStream = function aStream() {
var rs = Readable();
var c = 97;
rs._read = function () {
rs.push(String.fromCharCode(c++));
console.log('Hit!');
if (c > 'z'.charCodeAt(0)) {
rs.push(null);
}
};
return rs;
};
// b.js (requires the module exported above)
rb.enqueue('a'); // The method simply does an ee.emit('addItem', ...) in the module to trigger the itemSource observable
What I expected to see:
{item: 'a', a: 'a'} printed in the console
What happened:
Hit! was printed 24 times before {item: 'a', a: 'a'}. This means that zip took all the values from aStream, buffered them and then did what it was supposed to do.
How do I get the same functionality zip offers but lazily? My goal is to use an infinite stream/observable and zip it with a finite (async) one.
Edit
See/Edit it via runnable: RX Zip test Edit 2 Code updated based on answer -> no output now.
zip is indeed lazy. It just subscribes to a and b and does its work whenever either produces a new value.
Your problem is that fromStream is emitting all of its values synchronously as soon as zip subscribes to it. This is happening because your custom Readable is constantly saying "There is more data available!"
Make your Readable asynchronous and you'll get the desired behavior.
Try something like this (untested)
var rs = Readable();
var subscription = null;
rs._read = function () {
if (!subscription) {
// produce the values once per second
subscription = Rx.Observable
.generateWithRelativeTime(
97, // start value
function (c) { return c > 'z'.charCodeAt(0); }, // end condition
function (c) { return c + 1; }, // step function
function (c) { return String.fromCharCode(c); }, // result selector
function () { return 1000; }) // 1000ms between values
.subscribe(
function (s) {
rs.push(s);
console.log("Hit!");
},
function (error) { rs.push(null); },
function () { rs.push(null); });
}
};
I'm playing with using the Rx in an XNA proof-of-concept, and I've run into a bit of an obstacle composing some queries that I'm hoping you folks can assist me with understanding how some of these operators work.
In my POC, I would like the player's score to increment only while there is not an active drag operation occurring. In addition, there is a 'grab gauge' that I would like to deplete whenever there is an ongoing drag, and fill whenever there isn't. Finally, if a drag operation is under way and the grab gauge drops below 0, I want to cancel the drag operation.
I've got the score incrementing working just fine with this:
IObservable<bool> PrincessGrabbed; // e.g., OnGrabbedBegin
_playerScoreChanged = IObservable<Unit>
// ... //
// In the initialization method
_playerScoreChanged = from startTrigger in PrincessGrabbed.StartWith(false)
.Where(x => !x)
from i in Observable.Interval(TargetElapsedTime)
.TakeUntil(PrincessGrabbed
.Where(x => x)
select new Unit();
_playerScoreChanged.Subscribe(unit => PlayerScore += 1);
The score will increment when it is supposed to, and stop when the character is picked up. Getting the gauge behavior to work correctly has been troublesome however. I've tried a ton of variations using Window, Generate, etc... but what seems to end up happening is that either it doesn't work at all, or the increment/decrement gauge operations end up fighting each other, or it will all seem to work properly, but continue to subtract or add points/gauge in the background. Here's the gauge implementation (extremely poor performance, crashes after about 10-15s, doesn't work properly):
var a = from startTrigger in PrincessGrabbed.StartWith(false).Where(x => x)
from i in Observable.Interval(TargetElapsedTime)
.Where(x => GrabGaugeFillAmount > 0)
.TakeUntil(PrincessGrabbed.Where(x => !x))
select new Unit();
a.TimeInterval().Subscribe(unit =>
GrabGaugeFillAmount -= (float)unit.Interval.TotalSeconds *
GrabGaugeDepletionPerSecond);
I have no doubts that my lack of understanding with Rx is at fault in some way, shape, or form, but I've reached the limit of experimenting with different operators/queries. Any insights?
EPILOGUE: Gideon Engelberth's answer fit my needs spot-on - I wish I could upvote it 10x! Here's the quick C# representation of his answer (not 100% on the IDisposable.Dispose(), but should be close):
public class AlternatingSubject : IDisposable
{
private readonly object _lockObj = new object();
private int _firstTriggered;
private readonly ISubject<Unit> _first = new Subject<Unit>();
public ISubject<Unit> First { get { return _first; }}
private readonly ISubject<Unit> _second = new Subject<Unit>();
public ISubject<Unit> Second { get { return _second; }}
public void TriggerFirst()
{
if (System.Threading.Interlocked.Exchange(ref _firstTriggered, 1) == 1)
return;
First.OnNext(Unit.Default);
}
public void TriggerSecond()
{
if (System.Threading.Interlocked.Exchange(ref _firstTriggered, 0) == 0)
return;
Second.OnNext(Unit.Default);
}
#region Implementation of IDisposable
public void Dispose()
{
lock (_lockObj)
{
First.OnCompleted();
Second.OnCompleted();
}
}
#endregion
}
And the logic to hook up the events in the game class (there are some refactoring opportunities). Summary: works like a charm! Thanks!
public class PrincessCatcherGame : Game
{
// ... //
public IObservable<bool> PrincessGrabbed // external source fires these events
{
get
{
return princessGrabbed.AsObservable();
}
}
// ... //
private readonly ISubject<bool> _princessGrabbed = new Subject<bool>();
private readonly ISubject<Unit> _grabGaugeEmptied = new Subject<Unit>();
private readonly ISubject<Unit> _grabGaugeFull = new Subject<Unit>();
private readonly AlternatingSubject _alternatingSubject = new AlternatingSubject();
private ISubject<Unit> _grabs;
private ISubject<Unit> _releases;
// ... //
private void SubscribeToGrabbedEvents()
{
var decrements = from g in _grabs
from tick in Observable.Interval(TargetElapsedTime).TakeUntil(_releases)
select Unit.Default;
decrements.Subscribe(x =>
{
Debug.Assert(GrabGaugeFillAmount >= 0);
GrabGaugeFillAmount -= (GrabGaugeDepletionPerSecond/30f);
if (GrabGaugeFillAmount <= 1)
{
GrabGaugeFillAmount = 0;
_alternatingSubject.TriggerSecond();
_grabGaugeEmptied.OnNext(Unit.Default);
}
});
decrements.Subscribe(x => PlayerScore += 1);
var increments = from r in _releases
from tick in Observable.Interval(TargetElapsedTime).TakeUntil(_grabs.Merge(_grabGaugeFull))
select Unit.Default;
increments.Subscribe(x =>
{
Debug.Assert(GrabGaugeFillAmount <= 100);
GrabGaugeFillAmount += (GrabGaugeFillPerSecond/30f);
if (GrabGaugeFillAmount >= 100)
{
GrabGaugeFillAmount = 100;
_grabGaugeFull.OnNext(Unit.Default);
}
});
}
You are definitely on the right track. I would start by making grabs and releases observables of their own and then making PrincessGrabbed based on those two observables. For a case like this, I use a class I call AlternatingSubject.
Public NotInheritable Class AlternatingSubject
Implements IDisposable
'IDisposable implementation left out for sample
Private _firstTriggered As Integer
Private ReadOnly _first As New Subject(Of Unit)()
Public ReadOnly Property First As IObservable(Of Unit)
Get
Return _first
End Get
End Property
Private ReadOnly _second As New Subject(Of Unit)()
Public ReadOnly Property Second As IObservable(Of Unit)
Get
Return _second
End Get
End Property
Public Sub TriggerFirst()
If System.Threading.Interlocked.Exchange(_firstTriggered, 1) = 1 Then Exit Sub
_first.OnNext(Unit.Default)
End Sub
Public Sub TriggerSecond()
If System.Threading.Interlocked.Exchange(_firstTriggered, 0) = 0 Then Exit Sub
_second.OnNext(Unit.Default)
End Sub
End Class
Along with that, you will probably want to add a "gague full" observable you can trigger from the incrementing method. The "gague empty" will trigger the release portion of the AlternatingSubject.
Sub Main()
Dim alt As New AlternatingSubject
Dim grabs = alt.First
Dim releases = alt.Second
Dim isGrabbed As New Subject(Of Boolean)()
'I assume you have these in your real app,
'simulate them with key presses here
Dim mouseDowns As New Subject(Of Unit)
Dim mouseUps As New Subject(Of Unit)
Dim gagueFulls As New Subject(Of Unit)()
'the TakeUntils ensure that the timers stop ticking appropriately
Dim decrements = From g In grabs
From tick In Observable.Interval(TargetElapsedTime) _
.TakeUntil(releases)
Select Unit.Default
'this TakeUnitl watches for either a grab or a gague full
Dim increments = From r In releases
From tick In Observable.Interval(TargetElapsedTime) _
.TakeUntil(grabs.Merge(gagueFulls))
Select Unit.Default
'simulated values for testing, you may just have
'these be properties on an INotifyPropertyChanged object
'rather than having a PlayerScoreChanged observable.
Const GagueMax As Integer = 20
Const GagueMin As Integer = 0
Const GagueStep As Integer = 1
Dim gagueValue As Integer = GagueMax
Dim playerScore As Integer
Dim disp As New CompositeDisposable()
'hook up IsGrabbed to the grabs and releases
disp.Add(grabs.Subscribe(Sub(v) isGrabbed.OnNext(True)))
disp.Add(releases.Subscribe(Sub(v) isGrabbed.OnNext(False)))
'output grabbed state to the console for testing
disp.Add(isGrabbed.Subscribe(Sub(v) Console.WriteLine("Grabbed: " & v)))
disp.Add(gagueFulls.Subscribe(Sub(v) Console.WriteLine("Gague full")))
disp.Add(decrements.Subscribe(Sub(v)
'testing use only
If gagueValue <= GagueMin Then
Console.WriteLine("Should not get here, decrement below min!!!")
End If
'do the decrement
gagueValue -= GagueStep
Console.WriteLine("Gague value: " & gagueValue.ToString())
If gagueValue <= GagueMin Then
gagueValue = GagueMin
Console.WriteLine("New gague value: " & gagueValue)
alt.TriggerSecond() 'trigger a release when the gague empties
End If
End Sub))
disp.Add(decrements.Subscribe(Sub(v)
'based on your example, it seems you score just for grabbing
playerScore += 1
Console.WriteLine("Player Score: " & playerScore)
End Sub))
disp.Add(increments.Subscribe(Sub(v)
'testing use only
If gagueValue >= GagueMax Then
Console.WriteLine("Should not get here, increment above max!!!")
End If
'do the increment
gagueValue += GagueStep
Console.WriteLine("Gague value: " & gagueValue.ToString())
If gagueValue >= GagueMax Then
gagueValue = GagueMax
Console.WriteLine("New gague value: " & gagueValue)
gagueFulls.OnNext(Unit.Default) 'trigger a full
End If
End Sub))
'hook the "mouse" to the grab/release subject
disp.Add(mouseDowns.Subscribe(Sub(v) alt.TriggerFirst()))
disp.Add(mouseUps.Subscribe(Sub(v) alt.TriggerSecond()))
'mouse simulator
Dim done As Boolean
Do
done = False
Dim key = Console.ReadKey()
If key.Key = ConsoleKey.G Then
mouseDowns.OnNext(Unit.Default)
ElseIf key.Key = ConsoleKey.R Then
mouseUps.OnNext(Unit.Default)
Else
done = True
End If
Loop While Not done
'shutdown
disp.Dispose()
Console.ReadKey()
End Sub
For the sake of the test app, everything is in one function. In your real app, you should of course consider what to expose and how.
I want to stop stream A for exactly one notification whenever stream B fires. Both streams will stay online and won't ever complete.
A: o--o--o--o--o--o--o--o--o
B: --o-----o--------o-------
R: o-----o-----o--o-----o--o
or
A: o--o--o--o--o--o--o--o--o
B: -oo----oo-------oo-------
R: o-----o-----o--o-----o--o
Here's a version of my SkipWhen operator I did for a similar question (the difference is that, in the original, multiple "B's" would skip multiple "A's"):
public static IObservable<TSource> SkipWhen<TSource, TOther>(this IObservable<TSource> source,
IObservable<TOther> other)
{
return Observable.Create<TSource>(observer =>
{
object lockObject = new object();
bool shouldSkip = false;
var otherSubscription = new MutableDisposable();
var sourceSubscription = new MutableDisposable();
otherSubscription.Disposable = other.Subscribe(
x => { lock(lockObject) { shouldSkip = true; } });
sourceSubscription.Disposable = source.Where(_ =>
{
lock(lockObject)
{
if (shouldSkip)
{
shouldSkip = false;
return false;
}
else
{
return true;
}
}
}).Subscribe(observer);
return new CompositeDisposable(
sourceSubscription, otherSubscription);
});
}
If the current implementation becomes a bottleneck, consider changing the lock implementation to use a ReaderWriterLockSlim.
This solution will work when the observable is hot (and without refCount):
streamA
.takeUntil(streamB)
.skip(1)
.repeat()
.merge(streamA.take(1))
.subscribe(console.log);
.takeUntil(streamB): make stream A complete upon stream B producing a value.
.skip(1): make stream A skip one value upon starting (or as a result of .repeat()).
.repeat(): make stream A repeat (reconnect) indefinitely.
.merge(streamA.take(1)): offset the effect of .skip(1) at the beginning of the stream.
Example of making A stream skip every 5 seconds:
var streamA,
streamB;
streamA = Rx.Observable
.interval(1000)
.map(function (x) {
return 'A:' + x;
}).publish();
streamB = Rx.Observable
.interval(5000);
streamA
.takeUntil(streamB)
.skip(1)
.repeat()
.merge(streamA.take(1))
.subscribe(console.log);
streamA.connect();
You can also use this sandbox http://jsbin.com/gijorid/4/edit?js,console to execute BACTION() in the console log at the time of running the code to manually push a value to streamB (which is helpful for analysing the code).