【问题标题】:Streaming data for state machine playback用于状态机回放的流数据
【发布时间】:2010-09-01 21:03:16
【问题描述】:

我有一个需要支持播放的状态机设计。我们有执行动作的状态,有时需要生成随机数。如果程序在 FSM 执行过程中关闭,程序需要使用与之前相同的随机数播放整个 FSM。

举个基本的例子,假设我有三个状态:A、B 和 C。FSM 将调用一个状态的 Execute() 函数。在函数结束时,状态将发布一个事件,FSM 将确定接下来要进入哪个状态。在状态 A 中,它将调用 rand()。如果数字是偶数,它会发布一个事件去状态 B,否则状态 C 应该是下一个状态。

void StateA::Execute(IEventQueue& rQueue)
{
    int num = rand();
    if( num % 2 == 0 )
    {
        rQueue.PostEvent("GoToStateB");
    }
    else
    {
        rQueue.PostEvent("GoToStateC");
    }
}

如果随机数是 69,那么它应该进入状态 C。当处于状态 C 时,程序可能会退出。当程序再次启动时,它应该播放状态机。显然,要让它正常工作,它不能生成一个全新的随机数,它需要再次使用 69 才能准确播放。

我有一个文件流接口,可以用来将数据保存到文件中,但是代码有点难看:

void StateA::Execute(IEventQueue& rQueue, IFileStream& rStream)
{

    int num = 0;

    // fails if there's no more data to read
    bool bSuccess = rStream.ReadInt(num);
    if (!bSucess)
    {
        num = rand();
        rStream.WriteInt(num);
    }

    // same code as before
}

我对这个解决方案的唯一问题是我不关心必须先检查流中的数据,然后有条件地写入同一个流。

我想过这样隐藏它:

void StateA::Execute(IEventQueue& rQueue, IStream& rStream)
{

    int num = 0;

    num = rand();
    rStream & num;

    // same code as before
}

在 IStream 内部,operator&(可能不是重载的最佳用途)实际上会尝试从流中读取一个 int。如果该流是空的,它将改为写入它。和以前一样,行为是:先读取直到流结束,然后开始追加。

所以我想我的问题是:对于这种类型的播放是否有一个我可能会忽略的常见习语?有没有人有其他建议?我觉得我的设计有点过于复杂了。

谢谢!

【问题讨论】:

  • 你说得对,重载真的不是很好用。不要那样做!
  • 另外,使用固定随机种子是不可能的吗?
  • 种子是不可能的。我们实际上使用了一个不同的 RNG 系统,它有点复杂。另外,我只以RNG为例。理想情况下,我希望能够将任何数据写入流并使其可用于播放。

标签: c++ stream state-machine


【解决方案1】:

为什么状态直接与文件流交互? Single Responsibility 说我们应该有一个类,它的工作是根据某种逻辑提供正确的数字。

struct INumberSource {
    virtual int GenNextNumber() = 0;
}

// My job is to provide numbers from an RNG
struct RNGNumberSource : public INumberSource {
    virtual int GenNextNumber() {
        return rand();
    }
}

// My job is to write any numbers sourced through me to a file
// I delegate to another source to get an actual number
class FileStreamTrackingNumberSource : INumberSource {
public:
    FileStreamTrackingNumberSource(INumberSource& source, IFileStream& stream)
        : altSource(source), fileStream(stream) { }

    virtual int GenNextNumber() {
        int num = altSource.GenNextNumber();
        fileStream.WriteInt(num);
        return num;
    }
private:
    INumberSource altSource;
    IFileStream& fileStream;
}

// My job is to source numbers from a file stream delegating to an
// alternate source when I run out
class FileStreamNumberSource : public INumberSource {
public:
    FileStreamNumberSource(INumberSource& source, IFileStream& stream)
        : altSource(source), fileStream(stream), failedRead(false) { }

    virtual int GenNextNumber() {
        int num = 0;

        if(failedRead || !(failedRead = fileStream.ReadInt(num))) {
            num = altSource.GenNextNumber();
        }

        return num;
    }

private:
    INumberSource& altSource;
    IFileStream& fileStream;
    bool failedRead;
}

因此,在您的情况下,您将向 FileStreamTrackingNumberSource 提供 IFileStreamRNGNumberSource,并将其和相同的 IFileStream 提供给 FileStreamNumberSourceFileStreamNumberSource 是您将提供给您所在州的 INumberSource 参数的值。

假设您只需要数字来选择下一个状态,那么您的状态代码可能如下所示:

void StateA::Execute(IEventQueue& rQueue, INumberSource& numberSource)
{
    if( numberSource.GenNextNumber() % 2 == 0 )
    {
        rQueue.PostEvent("GoToStateB");
    }
    else
    {
        rQueue.PostEvent("GoToStateC");
    }
}

【讨论】:

  • 相当漂亮!我喜欢这个。直接与文件流交互是不可取的。试图正确地把它抽象出来让我很难过。这给了我一些思考。感谢您的反馈。
【解决方案2】:

我怀疑您应该有两个文件:一个记录您正在播放的事件,另一个是您从中读取“重播”事件的文件。如果重播文件比“录音”文件长,那么这就是您用于重播的文件。

我也不会像你建议的那样使用运算符重载。也许只是使用三元运算符。

【讨论】:

    【解决方案3】:

    我不确定我是否理解“播放”背后的基本原理,但您不能简单地将整个“随机数或从文件读取”逻辑包装在类或函数后面吗?

    更新

    关于“播放”主题和您的总体设计,我不确定 FSM 生成自己的刺激(即触发状态转换的随机数)是否正常。通常,刺激是从外部提供的。如果您考虑到这一点重新考虑,那么您将不再有这个混乱的问题!

    【讨论】:

    • 您能否详细说明一下您所说的外部提供是什么意思?
    • @lhumongous:通常情况下,一个有输入->FSM->输出,其中输入是触发FSM改变状态的刺激。在您的设计中,似乎没有任何输入(尽管我很欣赏您可能提供了一个精简的示例)。尽管如此,我确实质疑一个国家提供自己的刺激意味着什么。
    • 这是一个事件驱动的 FSM,事件可以是内部的也可以是外部的。这是我遇到的非常常见的设计。在为 AI 代理设计 FSM 时经常使用它。
    • 但是除了随机输入之外,还有什么其他的内部刺激来源呢?即除了使用 RNG 之外,您的 FSM 是否具有确定性?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-18
    相关资源
    最近更新 更多