Implemented PrintBuffer class. (Whew).

This commit is contained in:
2021-10-20 14:05:09 -04:00
parent 547b87d884
commit 458497956d
4 changed files with 297 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
////////////////////////////////////////////////////////////////////////////////
//
// 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_console. When the thread stops or yields, the
// contents of lthread_console 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:
//
// * Block 0: Whose woods these are I think I know. [authoritative]
// * Block 1: His house is in the village though; [authoritative]
// * Block 2: He will not see me stopping here [authoritative]
// * Block 3: To watch his woods fill up with snow. [authoritative]
// * Block 4: My little horse must think it queer [authoritative]
// * Block 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.
//
////////////////////////////////////////////////////////////////////////////////
#ifndef PRINTBUFFER_HPP
#define PRINTBUFFER_HPP
#include "streambuffer.hpp"
#include "util.hpp"
#include <deque>
#include <string>
#include <memory>
class PrintBuffer {
private:
// The most recent lines printed.
std::deque<std::string> lines_;
// Line number of the first line in the buffer. From this, all other
// line numbers can be inferred.
int first_line_;
// Line number of the first unchecked line in the buffer. All line
// numbers including this one and beyond are unchecked.
int first_unchecked_;
// The world type of the enclosing model. This is used to determine
// whether add_string increments the n_checked counter or not.
util::WorldType world_type_;
// Add a line of text (internal). Line is expected to NOT contain
// a newline (or any other weird control characters).
void add_line(const char *line, int len);
// Return the line number beyond the end of the buffer.
int last_line() const { return first_line_ + int(lines_.size()); }
// Get the specified line number.
const std::string &nth(int n) const { return lines_[n - first_line_]; }
public:
PrintBuffer(util::WorldType wt);
// 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 char *text, int len);
void add_string(const std::string &s);
// Discard lines up to but not including line N.
void discard_upto(int n);
// Difference transmission
void diff(const PrintBuffer &auth, StreamBuffer *sb) const;
void patch(StreamBuffer *sb);
// Clear the buffer (for unit testing)
void clear();
// Print the entire contents of the buffer to a string (for unit testing).
std::string debug_string() const;
};
using UniquePrintBuffer = std::unique_ptr<PrintBuffer>;
#endif // PRINTBUFFER_HPP