组合依赖于其他可观察量的可观察量



我正在使用高度的可观察量模拟无人机的飞行。高度应根据此方案而有所不同:

  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; }
}

你真的应该尝试制作可观察量,以便它模拟随时选择起飞或着陆 - 就像无人机的用户可能会做的那样。

如果您像这样编写代码,这将变得非常简单:

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;

使用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()...那么它将尝试在时间 11 订阅cruise并获取最后一个值。

  • 如果你cruise定义原样,它将尝试重新订阅一个新的冷可观察量,这将导致 0 个元素,因为isLanding现在是假的。
  • 如果将.Publish().RefCount()添加到cruise定义中,它将尝试订阅已完成的上一个可观察量,这也将导致 0 个元素。
  • .Replay(1).Refcount()缓存最后一个值,因此在可观察量完成后订阅的任何订阅者仍将获得最后一个值(这是您想要的值(。

相关内容

  • 没有找到相关文章

最新更新