2

I'm currently building a wizard system for an application, and we're using ReactiveUI and as a result Rx.

Each step in the wizard implements IWizardStep<T> where T is just the data type that the wizard ultimately produces.

Each step has the capability of surfacing which step should be available for the user to move to next, so as to enable branching based on data entry.

The step can be thought of having a similar structure to:

public interface IWizardStep<T>
{
    IObservable<IStepChoice<T>> NextStepChoice {get;}
}

With IStepChoice<T> simply being:

public interface IStepChoice<T>
{
    IWizardStep<T> Step {get;}
    string Reason {get;}
}

In order to calculate the current path from the start to the end, for display to the user, I need to be able to go from the starting step, and walk along the NextStepChoice chain recursively until it hits a null (it's valid behaviour for the NextStepChoice observable to emit a null to indicate the end of the wizard).

I've taken a look at Observable.Scan but can't for the life of me work out how to get this working properly recursively.

I've also taken a look at Observable.Generate which looks promising, as this is a classic unfold-esque problem; the only issue is that Generate takes a function to determine when to break the loop, but I need to evaluate the inner observable to work this out.

Observable.Generate(
    new WizardStepChoice<T>(start, null),
    choice => choice != null,
    choice => choice.ChosenStep.NextStepChoice,
    choice => choice);

This would be ideal, and produce the output I'm after, but the NextStepChoice selector there obviously doesn't compile because it's an IObservable<IWizardStepChoice<T>> rather than an IWizardStepChoice<T>.

I've looked at using Aggregate and Scan but as these are more fold-driven operations, and I've only got the starting element, it's an unfold I'm looking for ala Generate, but I need it to be able to evaluate the nested observable.

Would Observable.Create perhaps be something I could utilise? I've tried it and come up with:

Path = CurrentStep.Select(_ => Observable.Create<IWizardStep<T>>(async observer =>
 {
     IWizardStepChoice<T> next = new WizardStepChoice<T>(start, null);
     observer.OnNext(next.ChosenStep);
     while (next != null)
     {
         next = await next.ChosenStep.NextStepChoice;
         observer.OnNext(next.ChosenStep);
     }
     observer.OnCompleted();
     return Disposable.Empty;
 }).Aggregate(new List<IWizardStep<T>>(),
 (l, s) =>
 {
     l.Add(s);
     return l;
 })).Switch().Publish().RefCount();

Which has all the right signature I want IWizardStep<T>->IReadOnlyList<IWizardStep<T>>, so at first glance it looks right, but it doesn't work; it fires, and I can step through, but it hangs once it hits the await and doesn't come back.

I've got a feeling I'm close, and this is a scheduling issue, so my question really is this:

  1. What is the best approach to solve this, am I close?
  2. If this is right, why is there an issue with the await, and how might I solve it?

Update

After a little bit of tinkering I noticed that the await was likely hanging as that observable hadn't yet (and wasn't going to) emit a value (duh), which I've now resolved by initialising each step with a value at the beginning of the wizard.

I've even sanity-checked this by adding a property to IWizardStep<T> - IWizardStepChoice<T> LatestStepChoice {get;} which is just hooked up with:

NextStepChoice.Subscribe(c => _latestStepChoice = c);

This is done on the step class itself, and I can confirm it's working just fine.

Yet still the await hangs, so I tried:

  1. Making it Replay(1) so the await calling .Subscribe() would get the value - this didn't work
  2. Making it Repeat() so even if something is subscribed it'll see the new value - this just made the whole thing hang.

Clearly I'm missing something here, I want it so that when the await queries the observable, it will be given the latest value seen, which is what I thought Replay(1) would achieve; I've also tried PublishLast() but then future updates don't get honoured because of the AsyncSubject<T> behaviour.

For now I've switched to using the self-subscribed property, but it's not ideal, I'd rather not have to break out of querying the observables if I can help it, it feels "hacky".

Clint
  • 6,133
  • 2
  • 27
  • 48
  • Can you post the signature of what you're looking for? Are the observables in wizard step hot? – Shlomo Feb 23 '17 at 14:15
  • @Shlomo I did, I want to be able to go IWizardStep->IReadOnlyList> as it happens I've gotten around it, by just having the step class self-subscribe to the choice observable and shove it into a property that I can then access normally. – Clint Feb 23 '17 at 16:33
  • What's the point of having a function go from a recursive data structure to a list of recursive data structures? – Shlomo Feb 23 '17 at 16:49
  • @Shlomo because each one offers up a pointer to a potential next link in the chain, so, knowing the starting link, I need to be able to traverse the chain and produce a list of each step from the start to the end. – Clint Feb 23 '17 at 17:09
  • 1
    Maybe provide an example? A function that transforms a Tree -> List seems valuable. A function that transforms a Tree -> List> seems less so – Shlomo Feb 23 '17 at 17:30
  • 1
    Just a side-note: if ever you do a `return Disposable.Empty;` inside an `Observable.Create` you are probably doing something wrong. – Enigmativity Feb 24 '17 at 02:31
  • @Clint - Can you please post a [mcve]? Something that we can cop-paste-and-run in a console app. Then we can run and test. I'm wondering if the fact that your `Observable.Create` is run synchronously is the issue, but I can't test without runable code. – Enigmativity Feb 24 '17 at 02:34
  • @Enigmativity I took the `Disposable.Empty` from here: http://www.introtorx.com/content/v1.0.10621.0/04_CreatingObservableSequences.html as my observable doesn't observe anything else, It wasn't really needed to dispose an inner sequence. As it stands I've gotten it working, and yes, it probably was because it was running synchronously on the main thread, but as it stands my current solution is proving functional enough; this was more a weird "How do I do this?" sort of thing. – Clint Feb 24 '17 at 09:17
  • @Clint - Then show us your running code. In full so that we can also run it and see what's happening. – Enigmativity Feb 24 '17 at 11:13
  • @Enigmativity unfortunately there's a fair bit, and much of it is bound together so would be hard to get it all together here. Really my question was just how to do a recursive unfold with an observable, and I kinda managed to solve it in the end. – Clint Feb 24 '17 at 11:21
  • @Clint - In the future you should make the effort to put the code together in as a [mcve]. It would certainly help us answer the question. – Enigmativity Feb 24 '17 at 23:18

1 Answers1

2

A recursive walk can transform the tree of observables into a single observable:

    static IObservable<IWizardStep<T>> Walk<T>(IWizardStep<T> step)
    {
        if (step?.NextStepChoice == null)
            return Observable.Return(step);

        return step.NextStepChoice.SelectMany(choice => Walk(choice.Step)).StartWith(step);
    }

Usage:

var steps = await Walk(step).ToArray();
Asti
  • 12,447
  • 29
  • 38
  • This is pretty much what I ended up doing (kind of), in the end I settled on consolidating all the disparate state into a single immutable state object and then I observe on that, it means at construction time I can perform my imperative enumerable-based recursive walks, as well as extract other information. It's also greatly simplified the scheduling concerns and made the code easier to reason about. – Clint Feb 27 '17 at 09:49