代码之家  ›  专栏  ›  技术社区  ›  lhumongous

用于状态机回放的流数据

  •  0
  • lhumongous  · 技术社区  · 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中,操作符&(可能不是重载的最佳用法)实际上会尝试从流中读取int。如果该流是空的,那么它将改为写入它。像以前一样,行为是:先读取,直到流结束,然后开始追加。

    谢谢!

    3 回复  |  直到 13 年前
        1
  •  1
  •   joshperry    16 年前

    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 RNGNumberSource 到 FileStreamTrackingNumberSource 提供同样的信息 如果流 到 FileStreamNumberSource . 那个 文件流编号源 你会给你所在州的 INumberSource

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

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

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

        3
  •  0
  •   Oliver Charlesworth    16 年前

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

    更新