Объединение наблюдаемых, которые зависят от других наблюдаемых - PullRequest
0 голосов
/ 27 сентября 2018

Я моделирую полет беспилотника, используя наблюдаемые для высоты.Высота над уровнем моря должна меняться в соответствии с этой схемой:

  1. Высота увеличивается от 0 до BaseAltitude, то есть является фиксированной высотой.
  2. После достижения BaseAltitude дрон запускаетсякрейсерская, описывающая синусоидальную волну, начиная с BaseAltitude
  3. По сигналу дрон должен начать посадку.Это, начиная с текущей высоты , дрон должен линейно опускаться, пока не достигнет 0 .

Как вы могли заметить, когда начинается посадка, высота неизвестна во время проектирования.Последовательность взлета должна принимать последнюю высоту в качестве начала.Итак, одна последовательность зависит от последнего значения, созданного другой последовательностью.У меня болит мозг!

Ну, я полностью застрял с этим.

Единственный код, который у меня есть на данный момент, ниже.Я положил это, чтобы проиллюстрировать проблему.Вы получите это быстро ...

public class Drone
{
    public Drone()
    {
        var interval = TimeSpan.FromMilliseconds(200);

        var takeOff = Observable.Interval(interval).TakeWhile(h => h < BaseAltitude).Select(t => (double)t);

        var cruise = Observable
            .Interval(interval).Select(t => 100 * Math.Sin(t * 2 * Math.PI / 180) + BaseAltitude)
            .TakeUntil(_ => IsLanding);

        var landing = Observable
            .Interval(interval).Select(t => ??? );

        Altitude = takeOff.Concat(cruise).Concat(landing);
    }

    public bool IsLanding { get; set; }
    public double BaseAltitude { get; set; } = 100;
    public IObservable<double> Altitude { get; }
}

Ответы [ 2 ]

0 голосов
/ 04 октября 2018

Вы действительно должны попытаться создать наблюдаемое так, чтобы оно моделировало выбор взлета или посадки в любое время - так же, как это делает пользователь дрона.

Это становится довольно просто, есливы создаете свой код следующим образом:

public class Drone
{
    public Drone()
    {
        this.Altitude = ...
    }

    private bool _isLanding = true;
    private Subject<bool> _isLandings = new Subject<bool>();

    public bool IsLanding
    {
        get => _isLanding;
        set
        {
            _isLanding = value;
            _isLandings.OnNext(value);
        }
    }

    public double BaseAltitude { get; set; } = 100.0;
    public IObservable<double> Altitude { get; }
}

Каждый раз, когда IsLanding изменяется, частное _isLandings запускает значение, которое можно использовать для изменения режима дрона.

Теперьопределение Altitude начинается с этого базового шаблона:

    this.Altitude =
        _isLandings
            .Select(x => x ? landing : takeOff.Concat(cruise))
            .Switch()
            .StartWith(altitude);

Использование .Switch() здесь является ключевым.Всякий раз, когда _isLandings выдает значение, переключатель выбирает между посадкой или взлетом.Это становится единственной наблюдаемой, которая реагирует на повышение или понижение.

Полный код выглядит следующим образом:

public class Drone
{
    public Drone()
    {
        var altitude = 0.0;
        var interval = TimeSpan.FromMilliseconds(200);

        IObservable<double> landing =
            Observable
                .Interval(interval)
                .TakeWhile(h => altitude > 0.0)
                .Select(t =>
                {
                    altitude -= 10.0;
                    altitude = altitude > 0.0 ? altitude : 0.0;
                    return altitude;
                });

        IObservable<double> takeOff =
            Observable
                .Interval(interval)
                .TakeWhile(h => altitude < BaseAltitude)
                .Select(t =>
                {
                    altitude += 10.0;
                    altitude = altitude < BaseAltitude ? altitude : BaseAltitude;
                    return altitude;
                });

        IObservable<double> cruise =
            Observable
                .Interval(interval)
                .Select(t =>
                {
                    altitude = 10.0 * Math.Sin(t * 2.0 * Math.PI / 180.0) + BaseAltitude;
                    return altitude;
                });

        this.Altitude =
            _isLandings
                .Select(x => x ? landing : takeOff.Concat(cruise))
                .Switch()
                .StartWith(altitude);
    }

    private bool _isLanding = true;
    private Subject<bool> _isLandings = new Subject<bool>();

    public bool IsLanding
    {
        get => _isLanding;
        set
        {
            _isLanding = value;
            _isLandings.OnNext(value);
        }
    }

    public double BaseAltitude { get; set; } = 100.0;
    public IObservable<double> Altitude { get; }
}

Вы можете проверить это с помощью:

var drone = new Drone();

drone.Altitude.Subscribe(x => Console.WriteLine(x));

Thread.Sleep(2000);

drone.IsLanding = false;

Thread.Sleep(4000);

drone.IsLanding = true;
0 голосов
/ 27 сентября 2018

Вы используете LastAsync, чтобы получить последнее значение cruise, затем SelectMany в наблюдаемую область, которую вы хотите.

Вам потребуется немного изменить cruise для обработки нескольких подписок.

    var cruise = Observable.Interval(interval)
        .Select(t => 100 * Math.Sin(t * 2 * Math.PI / 180) + BaseAltitude)
        .TakeUntil(_ => IsLanding)
        .Replay(1)
        .RefCount();

    var landing = cruise
        .LastAsync()
        .SelectMany(maxAlt => Observable.Interval(interval).Select(i => maxAlt - i))
        .TakeWhile(alt => alt >= 0);

    Altitude = takeOff.Concat(cruise).Concat(landing);

Зачем мне нужно .Replay(1).Refcount()?

Здесь все холодно, и никто из них не побежитодновременно.Concat фактически удостоверяется, что они не совпадают.Таким образом, мраморная диаграмма, которую вы хотите, будет выглядеть примерно так:

t        : 1-2-3-4-5-6-7-8-9-0-1-2-3-4-5-6-7-8-...
takeOff  : 1-2-3-4-5-|
cruise   :           6-7-8-7-6-|
isLanding: T-------------------F----------------
landing  :                     5-4-3-2-1-0-|

Если вы определите landing = cruise.LastAsync()..., то он попытается подписаться на cruise в момент времени 11 и получить последнее значение.

  • Если вы оставите cruise определенным так, как оно было у вас, оно попытается повторно подписаться на новую холодную наблюдаемую, что приведет к 0 элементам, потому что isLanding теперь ложно.
  • Если вы добавите .Publish().RefCount() к cruise определению, он попытается подписаться на предыдущую наблюдаемую, которая завершена, и это также приведет к 0 элементам.
  • .Replay(1).Refcount() кэширует последнее значение, поэтому все подписчики, подписавшиеся после завершения наблюдения, по-прежнему будут получать последнее значение (то, что вам нужно).
...