我正在开发一个 Java 桌面飞行模拟。我需要记录驾驶舱内发生的所有飞行员动作,例如油门控制、转向、武器部署等,以便稍后查看这些事件(或实时流式传输)。
我想在事件的回放上添加一个视觉回放功能,这样当我及时向前和向后移动时,我可以直观地看到驾驶舱。只要我按时间顺序回放事件,回放就没有问题,但是倒带有点棘手。
您将如何实现倒带功能?
我会使用修改后的Memento 模式。
不同之处在于我将让 Memento 对象存储所有试点操作的列表。
Memento 模式通常用于回滚(撤消),但是在您的情况下,我也可以看到它也适用。您还需要将试点操作设为可存储状态。
您可以使用命令模式的一种变体,并让您的每个试点操作都实施撤消操作。
例如,如果您的飞行员做出了向左转向的动作(我知道很简单),那么它的反面将是向右转向。
public interface IPilotAction {
void doAction(CockpitState state);
void undoAction(CockpitState state);
}
public class ThrottleControl implement IPilotAction {
private boolean increase;
private int speedAmount;
public ThrottleControl(boolean increase, int speedAmount) {
this.increase = increase;
this.speedAmount = speedAmount;
}
public void doAction(CockpitState state) {
if (increase) {
state.speed += speedAmount;
} else {
state.speed -= speedAmount;
}
}
public void undoAction(CockpitState state) {
if (increase {
state.speed -= speedAmount;
} else {
state.speed += speedAmount;
}
}
您正在寻找的实际上是Command和Memento模式的混合。每个试点操作都应该是您可以记录的命令。如果需要,每个记录的命令都有一个备忘录,记录了(A)不在命令中的任何附加状态,并且(B)不能可靠地重建。“B”很重要,在几乎任何重要的领域中都有一些这种状态。它需要被存储以恢复准确的重建。
如果您合并这些概念,实质上为每个命令附加一个备忘录,您将拥有一系列完整记录的确定性事件。
我在另一个答案中更详细地讨论了这个问题。不要害怕根据您的特定需求大幅调整设计模式。:)
RE 性能问题:
如果您预计跳跃几分钟是一种常见情况,并且在实施后您表明这是一个不可行的性能瓶颈,我建议您实施一个偶尔的“快照”以及日志记录机制。基本上每隔几分钟保存一次整个应用程序状态,以最大限度地减少您需要执行的日志滚动量。然后,您可以从最近的保存状态访问所需的时间范围。这类似于动画和媒体中的关键帧。
不是直接的答案,但请查看有关实施撤消的讨论。大多数情况下,它们将与文本编辑器有关,但应该适用相同的原则。
如果您更喜欢不变性,它会有所帮助。撤消复杂的更改很困难。即使是自动化系统也存在性能问题(软件事务内存,STM)。
您可以在每个实例中存储状态。1kb 用于状态(风速、物体速度 + 方向/控制输入状态,x 30fps x 20 分钟 ~ 36megs。1kb 的状态可以让您记录大约 16 个物体(位置/速度/角速度/方向/和 5 个控制轴/影响)
这对你来说可能太多了,但它最容易实现。根本不需要做任何工作来重新创建状态(即时 acecss),并且您可以很容易地在状态之间进行插值(为了更快/更慢的播放)。对于磁盘空间,您可以将其压缩,这可以在录制时完成,因此在播放时不会占用内存。
节省空间的一种快速方法是对录制文件进行分页,并分别压缩每个 bin。即每分钟一个压缩流。这样,您只需解压缩当前的 bin,节省大量内存,但这取决于您的状态数据压缩的程度。
录制命令并让你的类文件实现多个播放方向需要大量的调试工作。减慢/加速播放也将更加计算密集。您唯一节省的就是空间。
如果那是溢价,还有其他方法可以节省。
确保您已经以模拟的“状态”是一个函数的方式实现了模拟。也就是时间的函数。
给定时间的初始状态T0
,您应该能够Tn
为任何时间构建模拟框架n
。例如,初始静止状态和没有事件(还)可能等于恒等函数,所以Tn == Tn+1
.
给定一些试验性的动作事件Ta
,你应该能够Ta+n
为任何事件构建一个框架n
。因此,您可以将事件视为修改函数,该函数将时间值作为参数并返回该时间的模拟帧。
我会将事件历史实现为(时间,函数)对的拉链,表示模拟的控制状态。“当前”状态将成为焦点,右侧是未来状态列表,左侧是过去状态。像这样:
([past], present, [future])
每次模拟状态变化时,记录一个新的状态函数在future
. 然后运行模拟就变成了从列表中取出函数future
并将当前时间传递给它们的问题。向后运行它完全相同,只是您将事件从past
列表中取出。
因此,如果您在 timeTn
并且想要倒退到 time Tn-1
,请查看属性小于past
的最新状态的列表。传入它的属性,你就有了 time 的模拟状态。time
n-1
n-1
function
Tn-1