1

I am using RxCPP and have difficulties to understand its behavior.

Here are two programs, one in Rx.Net and the other one in RxCPP. They suppose to output the same prints, but they don't.
the program take points from a mouse stream, and calculate a stream of deltas between the points.
the mouse is a stream of stream of points, every stroke - from down to up pressing is one stream. the mouse gives such streams one after the other.

In these test the expected output is:
Delta no 0 is: 0,0
Delta no 1 is: 5,0
Delta no 2 is: 0,5
Delta no 3 is: 2,3
Which is what the Rx.Net outputs.
The Rx.Cpp output only the first line: Delta no 0 is: 0,0

Any idea?

Rx.Cpp example:

  #include <rx.hpp>
  namespace rx = rxcpp;
  namespace rxsub = rxcpp::subjects;
  using rxob = rx::observable<>;

    struct Point
    {
        Point(int x, int y) : x(x), y(y) {}

        int x = 0, y = 0;
        Point operator-() const { return {-x, -y}; }
        Point operator+(const Point& other) const { return Point{x + other.x, y + other.y}; }
        Point operator-(const Point& other) const { return operator+(-other); }
    };

    std::ostream& operator<<(std::ostream& o, const Point& p)
    {
        return o << "(" << p.x << ", " << p.y << ")";
    }

    void TestRxCPP()
    {
      using RxPoint = rx::observable<Point>;
      using Strokes = rx::observable<RxPoint>;
      using StrokesSubject = rxsub::subject<RxPoint>;

      StrokesSubject mouseSource;
      auto strokes = mouseSource.get_observable();

      auto deltaVectors = [](Strokes strokes) {
        auto deltas = strokes.flat_map([=](RxPoint stroke) {
            auto points = stroke;
            // create stream of delta vectors from start point
            auto firstPoint = points.take(1);
            auto delta =
                points.combine_latest([](Point v0, Point v1) { return v0 - v1; }, firstPoint);
            return delta;
        });

        return deltas;
      };

      auto delta = deltaVectors(strokes);
      int n = 0;
      delta.subscribe(
        [&](const Point& d) { std::cout << "Delta no. " << n++ << " is: " << d << std::endl; });

      auto testMouse = rxob::from(Point{3 + 0, 4 + 0}, Point{3 + 5, 4 + 0}, Point{3 + 0, 4 + 5}, Point{3 + 2, 4 + 3});
      mouseSource.get_subscriber().on_next(testMouse);
    }

Rx.Net example:

    void RxNET()
    {
        var strokesS = new Subject<IObservable<Point>>();

        Func<IObservable<IObservable<Point>>, IObservable<Point>> 
        deltaVectors = strokes =>
        {
            var deltas = strokes.SelectMany(stroke =>
            {
                var points = stroke;
                // create stream of delta vectors from start point
                var firstPoint = points.Take(1);
                var deltaP =
                    points.CombineLatest(firstPoint, (v0, v1) => new Point(v0.X - v1.X, v0.Y - v1.Y));
                return deltaP;
            });

            return deltas;
        };

        var delta = deltaVectors(strokesS);
        var n = 0;
        delta.Subscribe(d => { Console.WriteLine($"Delta no {n++} is: {d}\n"); });

        var testMouse = new List<Point>
        {
            new Point(3 + 0, 4 + 0),
            new Point(3 + 5, 4 + 0),
            new Point(3 + 0, 4 + 5),
            new Point(3 + 2, 4 + 3)
        }.ToObservable();
        strokesS.OnNext(testMouse);
    }
ShaulF
  • 841
  • 10
  • 17

1 Answers1

1

Thanks to @Kirk Shoop at the rxcpp github :-)
this is a HOTvCOLD behavior.

the strokes are COLD and are being shared and only one thread is used. the points.combine_latest(..., firstPoint) means that all the points are sent before firstPoint is subscribed. thus only the last delta is emitted.

COLD and HOT sources will work if you reverse the combine_latest

auto delta =
    firstPoint.combine_latest([](Point v0, Point v1) { return v1 - v0; }, points);
ShaulF
  • 841
  • 10
  • 17