//////////////////////////////////////////////////////////////////////////////// // // PRINTBUFFER // // Lua has a 'print' statement - in client-server mode, where does the output of // the 'print' statement go? We need to be able to handle print-statements on // the server (and forward them to the client), and we need for predictive // prints to be handled gracefully. // // Class PrintBuffer is a buffer for storing the output of the print statement. // It is part of the actor tangible. When a lua thread calls 'print', the print // goes into stringstream lthread_prints. When the thread stops or yields, the // contents of lthread_prints are converted to lines and transferred to the // PrintBuffer of the actor. From there, it gets difference transmitted, and the // client can probe it. // // Suppose, for example, that the player logs in and invokes a plan that prints // six lines from a Robert Frost poem. That plan is executed by both the master // model and the synchronous model. When the thread finishes, the PrintBuffer // in the actor in the master model will contain this: // // * Line 0: Whose woods these are I think I know. [authoritative] // * Line 1: His house is in the village though; [authoritative] // * Line 2: He will not see me stopping here [authoritative] // * Line 3: To watch his woods fill up with snow. [authoritative] // * Line 4: My little horse must think it queer [authoritative] // * Line 5: To stop without a farmhouse near. [authoritative] // // Note that the buffer stores line numbers, which start from zero the moment // the player logs in. In the master model, all lines are always authoritative // because everything in the master model is authoritative. In the synchronous // model, the PrintBuffer is likely to contain the same strings, but the lines // will all be marked as [prediction] instead of [authoritative]. // // When the server difference transmits, the difference transmission will fix up // any mistakes in the PrintBuffer in the synchronous model. In the process, it // will also fix up any [prediction] changing it to [authoritative]. // // Periodically, the oldest lines in the buffer will get discarded. More on // this later. When lines get discarded, the line numbers don't change. For // example, if we were to discard three lines from the buffer above, this is // what would remain: // // * Line 3: To watch his woods fill up with snow. [authoritative] // * Line 4: My little horse must think it queer [authoritative] // * Line 5: To stop without a farmhouse near. [authoritative] // // The client will periodically probe the PrintBuffer. Suppose it sees all six // lines of the Robert Frost poem. The next time it probes the buffer, it will // still see those same six lines. The client will continue to see those six // lines until it takes explicit steps. // // Here's how we keep the buffer from growing forever. The client probes the // PrintBuffer, and sees some authoritative lines. The client downloads and // stores those lines locally. Once the lines are stored locally in the client, // the client injects a command into the command stream: "delete from // PrintBuffer up to line N" into the command stream. This will cause the // engine to discard the lines that the client no longer needs. // // Note that when the client injects a "delete from PrintBuffer" into the // command stream, that's an invoke-command that has to follow the same process // as any other client invoke command. It can take time for it to get // processed. Therefore, the client must be prepared that it might see some // redundant lines for a little while. // // MEMORY EFFICIENCY. // // Every tangible will contain a printbuffer. To avoid memory waste, the // implementation of PrintBuffer stores everything inside a dynamically // allocated "core" struct. All printbuffers that contain nothing share a // common empty core. That way, the empty printbuffers that will exist in 99% of // all tangibles will take up very little space. // //////////////////////////////////////////////////////////////////////////////// #ifndef PRINTBUFFER_HPP #define PRINTBUFFER_HPP #include "wrap-deque.hpp" #include "wrap-string.hpp" #include #include "streambuffer.hpp" #include "util.hpp" #include "invocation.hpp" #include "debugcollector.hpp" #include struct PrintBufferCore; class PrintBuffer : public eng::nevernew { private: PrintBufferCore *core_; public: PrintBuffer(); ~PrintBuffer(); // Get the current first line. int first_line() const; // Return the line number beyond the end of the buffer. int last_line() const; // Get the current first unchecked line. // "Unchecked" lines are lines in a non-authoritative model // that haven't been verified through difference transmission. // Guaranteed to be between first_line and last_line inclusive. int first_unchecked() const; // Return true if the printbuffer is in the initial state. // Note: if you clear the buffer, it's back in the initial state. bool never_printed() const; // Get the specified line number. const eng::string &nth(int n) const; // Print the entire contents of the buffer to a string (for unit testing). eng::string debug_string() const; // Clear the buffer void clear(); // Add a string. If the string doesn't end in a newline, a newline // is added. The string is broken into lines, and the lines are added // to the PrintBuffer. void add_string(const eng::string &s, bool auth); // Discard lines up to but not including line N. void discard_upto(int n); // Serialization and deserialization void serialize(StreamBuffer *sb) const; void deserialize(StreamBuffer *sb); // Difference transmission void diff(const PrintBuffer &auth, StreamBuffer *sb) const; void patch(StreamBuffer *sb, DebugCollector *dbc); }; class PrintChanneler : public eng::nevernew { private: int64_t line_; public: PrintChanneler() { line_ = 0; } // Reset the print channeler. void reset() { line_ = 0; } // Copy any new lines from the printbuffer to the stdostream. // Update the current line number. Return true if the printbuffer // contains any lines that have already been channeled. bool channel(const PrintBuffer *pb, std::ostream &ostream); // Generate an invocation that removes unnecessary lines from the // printbuffer. Invocation invocation(int64_t actor_id); }; #endif // PRINTBUFFER_HPP