2021-10-20 14:05:09 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
//
|
|
|
|
|
// 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
|
2021-11-14 15:57:18 -05:00
|
|
|
// goes into stringstream lthread_prints. When the thread stops or yields, the
|
|
|
|
|
// contents of lthread_prints are converted to lines and transferred to the
|
2021-10-20 14:05:09 -04:00
|
|
|
// 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:
|
|
|
|
|
//
|
2021-11-14 15:57:18 -05:00
|
|
|
// * 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]
|
2021-10-20 14:05:09 -04:00
|
|
|
//
|
|
|
|
|
// 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.
|
|
|
|
|
//
|
2021-11-14 15:57:18 -05:00
|
|
|
// 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.
|
|
|
|
|
//
|
2021-10-20 14:05:09 -04:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef PRINTBUFFER_HPP
|
|
|
|
|
#define PRINTBUFFER_HPP
|
|
|
|
|
|
|
|
|
|
#include "streambuffer.hpp"
|
|
|
|
|
#include "util.hpp"
|
2021-11-11 13:56:49 -05:00
|
|
|
#include "invocation.hpp"
|
2021-10-20 14:05:09 -04:00
|
|
|
#include <deque>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <memory>
|
2021-11-11 13:56:49 -05:00
|
|
|
#include <ostream>
|
2021-10-20 14:05:09 -04:00
|
|
|
|
2021-11-14 15:57:18 -05:00
|
|
|
struct PrintBufferCore;
|
|
|
|
|
|
2021-10-20 14:05:09 -04:00
|
|
|
class PrintBuffer {
|
|
|
|
|
private:
|
2021-11-14 15:57:18 -05:00
|
|
|
PrintBufferCore *core_;
|
2021-10-20 14:05:09 -04:00
|
|
|
|
|
|
|
|
public:
|
2021-11-14 15:57:18 -05:00
|
|
|
PrintBuffer();
|
|
|
|
|
~PrintBuffer();
|
2021-10-20 14:05:09 -04:00
|
|
|
|
2021-10-21 13:15:04 -04:00
|
|
|
// Get the current first line.
|
2021-11-14 15:57:18 -05:00
|
|
|
int first_line() const;
|
|
|
|
|
|
2021-10-21 13:15:04 -04:00
|
|
|
// Return the line number beyond the end of the buffer.
|
2021-11-14 15:57:18 -05:00
|
|
|
int last_line() const;
|
|
|
|
|
|
2021-10-21 13:15:04 -04:00
|
|
|
// Get the current first unchecked line.
|
2021-11-14 15:57:18 -05:00
|
|
|
// 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;
|
2021-10-21 13:15:04 -04:00
|
|
|
|
|
|
|
|
// Get the specified line number.
|
2021-11-14 15:57:18 -05:00
|
|
|
const std::string &nth(int n) const;
|
2021-10-21 13:15:04 -04:00
|
|
|
|
2021-11-14 15:57:18 -05:00
|
|
|
// Print the entire contents of the buffer to a string (for unit testing).
|
|
|
|
|
std::string debug_string() const;
|
|
|
|
|
|
|
|
|
|
// Clear the buffer
|
|
|
|
|
void clear();
|
|
|
|
|
|
2021-10-20 14:05:09 -04:00
|
|
|
// 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.
|
2021-11-14 15:57:18 -05:00
|
|
|
void add_string(const std::string &s, bool auth);
|
2021-10-21 13:15:04 -04:00
|
|
|
|
2021-10-20 14:05:09 -04:00
|
|
|
// Discard lines up to but not including line N.
|
|
|
|
|
void discard_upto(int n);
|
|
|
|
|
|
2021-11-14 15:57:18 -05:00
|
|
|
// Serialization and deserialization
|
|
|
|
|
void serialize(StreamBuffer *sb) const;
|
|
|
|
|
void deserialize(StreamBuffer *sb);
|
|
|
|
|
|
2021-10-20 14:05:09 -04:00
|
|
|
// Difference transmission
|
|
|
|
|
void diff(const PrintBuffer &auth, StreamBuffer *sb) const;
|
|
|
|
|
void patch(StreamBuffer *sb);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2021-11-11 13:56:49 -05:00
|
|
|
class PrintChanneler {
|
|
|
|
|
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);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2021-10-20 14:05:09 -04:00
|
|
|
#endif // PRINTBUFFER_HPP
|
|
|
|
|
|