用于状态机播放的流数据

时间:2010-09-01 21:03:17

标签: c++ stream state-machine

我有一个需要支持播放的状态机设计。我们有执行操作的状态,有时需要生成随机数。如果程序在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,运营商& (可能不是最好的重载使用)实际上会尝试从流中读取一个int。如果该流为空,则将其写入。像以前一样,行为将是:首先读取直到流结束,然后开始追加。

所以我想我的问题是:这种类型的播放有一个常见的习惯用法,我可能会忽略它吗?有人有其他建议吗?我觉得我开始过度复杂化设计了。

谢谢!

3 个答案:

答案 0 :(得分:1)

为什么状态直接与文件流交互?单一职责说我们应该有一个班级,根据一些逻辑提供正确的数字。

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

因此,在您的情况下,您会向IFileStream提供RNGNumberSourceFileStreamTrackingNumberSource,并向IFileStream提供相同的FileStreamNumberSourceFileStreamNumberSource就是您对州的INumberSource参数所提供的内容。

假设您只需要该号码来选择下一个州,那么您的州代码可能如下所示:

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

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

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

<强>更新

关于“回放”的主题和你的设计一般来说,我不确定FSM产生自己的刺激(即随后触发状态转换的随机数)是正常的。通常,刺激是在外部提供的。如果你考虑到这一点,那么你就不再有这个混乱的问题!