2022-02-23 23:08:28 -05:00
|
|
|
#include "wrap-sstream.hpp"
|
|
|
|
|
|
2021-10-20 14:05:09 -04:00
|
|
|
#include "printbuffer.hpp"
|
|
|
|
|
|
2022-02-25 19:57:23 -05:00
|
|
|
#include <algorithm>
|
2022-02-24 02:17:41 -05:00
|
|
|
#include <cstdio>
|
2022-03-17 23:28:25 -04:00
|
|
|
#include <cinttypes>
|
2022-02-24 02:17:41 -05:00
|
|
|
|
2022-02-28 21:57:54 -05:00
|
|
|
struct PrintBufferCore : public eng::opnew {
|
2021-11-14 15:57:18 -05:00
|
|
|
// The most recent lines printed.
|
2022-02-24 02:17:41 -05:00
|
|
|
eng::deque<eng::string> lines_;
|
2021-11-14 15:57:18 -05:00
|
|
|
|
|
|
|
|
// 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.
|
2024-02-22 17:30:24 -05:00
|
|
|
// "Unchecked" lines are lines in a non-authoritative model
|
|
|
|
|
// that haven't been verified through difference transmission.
|
|
|
|
|
|
2021-11-14 15:57:18 -05:00
|
|
|
int first_unchecked_;
|
|
|
|
|
|
|
|
|
|
// Constructor.
|
|
|
|
|
PrintBufferCore() : first_line_(0), first_unchecked_(0) {}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static PrintBufferCore shared_core;
|
|
|
|
|
|
|
|
|
|
PrintBuffer::PrintBuffer() {
|
|
|
|
|
core_ = &shared_core;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PrintBuffer::~PrintBuffer() {
|
|
|
|
|
if (core_ != &shared_core) delete core_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int PrintBuffer::first_line() const {
|
|
|
|
|
return core_->first_line_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int PrintBuffer::last_line() const {
|
|
|
|
|
return core_->first_line_ + core_->lines_.size();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int PrintBuffer::first_unchecked() const {
|
|
|
|
|
return core_->first_unchecked_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool PrintBuffer::never_printed() const {
|
|
|
|
|
return (core_->first_line_==0) && (core_->first_unchecked_==0) && (core_->lines_.size()==0);
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-24 02:17:41 -05:00
|
|
|
const eng::string &PrintBuffer::nth(int n) const {
|
2021-11-14 15:57:18 -05:00
|
|
|
return core_->lines_[n - core_->first_line_];
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-24 02:17:41 -05:00
|
|
|
eng::string PrintBuffer::debug_string() const {
|
|
|
|
|
eng::ostringstream oss;
|
2021-11-14 15:57:18 -05:00
|
|
|
oss << core_->first_line_ << "," << core_->first_unchecked_ << ":";
|
|
|
|
|
for (int i = 0; i < int(core_->lines_.size()); i++) {
|
|
|
|
|
oss << core_->lines_[i] << ";";
|
|
|
|
|
}
|
|
|
|
|
return oss.str();
|
2021-10-20 14:05:09 -04:00
|
|
|
}
|
|
|
|
|
|
2021-11-14 15:57:18 -05:00
|
|
|
void PrintBuffer::clear() {
|
|
|
|
|
if (core_ != &shared_core) {
|
|
|
|
|
delete core_;
|
|
|
|
|
core_ = &shared_core;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-20 14:05:09 -04:00
|
|
|
static int first_line_len(const char *text, int len) {
|
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
|
if (text[i] == '\n') return i;
|
|
|
|
|
}
|
|
|
|
|
return len;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-14 15:57:18 -05:00
|
|
|
static void add_line(PrintBufferCore *core, const char *text, int len, bool auth) {
|
|
|
|
|
assert(core != &shared_core);
|
|
|
|
|
core->lines_.emplace_back(text, len);
|
|
|
|
|
if (auth) {
|
|
|
|
|
core->first_unchecked_ = core->first_line_ + int(core->lines_.size());
|
2021-10-20 14:05:09 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-24 02:17:41 -05:00
|
|
|
void PrintBuffer::add_string(const eng::string &s, bool auth) {
|
2021-11-14 15:57:18 -05:00
|
|
|
const char *text = s.c_str();
|
|
|
|
|
int len = s.size();
|
|
|
|
|
if (core_ == &shared_core) core_ = new PrintBufferCore;
|
2021-10-20 14:05:09 -04:00
|
|
|
while (true) {
|
|
|
|
|
int fll = first_line_len(text, len);
|
|
|
|
|
if (fll == len) {
|
|
|
|
|
if (len > 0) {
|
2021-11-14 15:57:18 -05:00
|
|
|
add_line(core_, text, len, auth);
|
2021-10-20 14:05:09 -04:00
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
2021-11-14 15:57:18 -05:00
|
|
|
add_line(core_, text, fll, auth);
|
2021-10-20 14:05:09 -04:00
|
|
|
text += (fll + 1);
|
|
|
|
|
len -= (fll + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PrintBuffer::discard_upto(int n) {
|
2021-11-14 15:57:18 -05:00
|
|
|
if (core_ == &shared_core) return;
|
|
|
|
|
while ((!core_->lines_.empty()) && (core_->first_line_ < n)) {
|
|
|
|
|
core_->lines_.pop_front();
|
|
|
|
|
core_->first_line_ += 1;
|
2021-10-20 14:05:09 -04:00
|
|
|
}
|
2021-11-14 15:57:18 -05:00
|
|
|
if (core_->first_unchecked_ < core_->first_line_) {
|
|
|
|
|
core_->first_unchecked_ = core_->first_line_;
|
2021-10-20 14:05:09 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-14 15:57:18 -05:00
|
|
|
void PrintBuffer::serialize(StreamBuffer *sb) const {
|
|
|
|
|
if (never_printed()) {
|
|
|
|
|
sb->write_bool(true);
|
|
|
|
|
} else {
|
|
|
|
|
sb->write_bool(false);
|
|
|
|
|
sb->write_uint32(core_->first_line_);
|
|
|
|
|
sb->write_uint32(core_->first_unchecked_);
|
|
|
|
|
sb->write_uint32(core_->lines_.size());
|
2022-02-24 02:17:41 -05:00
|
|
|
for (const eng::string &s : core_->lines_) {
|
2021-11-14 15:57:18 -05:00
|
|
|
sb->write_string(s);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-10-20 14:05:09 -04:00
|
|
|
}
|
|
|
|
|
|
2021-11-14 15:57:18 -05:00
|
|
|
void PrintBuffer::deserialize(StreamBuffer *sb) {
|
|
|
|
|
bool never_printed = sb->read_bool();
|
|
|
|
|
if (never_printed) {
|
|
|
|
|
clear();
|
|
|
|
|
} else {
|
|
|
|
|
if (core_ == &shared_core) core_ = new PrintBufferCore;
|
|
|
|
|
core_->first_line_ = sb->read_uint32();
|
|
|
|
|
core_->first_unchecked_ = sb->read_uint32();
|
|
|
|
|
int nlines = sb->read_uint32();
|
|
|
|
|
core_->lines_.clear();
|
|
|
|
|
for (int i = 0; i < nlines; i++) {
|
|
|
|
|
core_->lines_.push_back(sb->read_string());
|
|
|
|
|
}
|
2021-10-20 14:05:09 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PrintBuffer::diff(const PrintBuffer &auth, StreamBuffer *sb) const {
|
|
|
|
|
// Currently, the implementation is simple. The synchronous model discards
|
|
|
|
|
// all prediction lines from its buffer, then the server retransmits all
|
|
|
|
|
// those lines. So this barely counts as difference transmission - it's
|
|
|
|
|
// just transmission, regardless of what was in the synchronous model. I
|
|
|
|
|
// think that's okay for the text console.
|
|
|
|
|
// Ask the client to discard anything that precedes auth_first.
|
2021-11-14 15:57:18 -05:00
|
|
|
sb->write_int32(auth.first_line());
|
2021-10-20 14:05:09 -04:00
|
|
|
sb->write_int32(auth.last_line());
|
2021-11-14 15:57:18 -05:00
|
|
|
for (int i = core_->first_unchecked_; i < auth.last_line(); i++) {
|
2022-02-24 02:17:41 -05:00
|
|
|
eng::string line;
|
2021-11-14 15:57:18 -05:00
|
|
|
if (i >= auth.first_line()) line = auth.nth(i);
|
2021-10-20 14:05:09 -04:00
|
|
|
sb->write_string(line);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-21 13:35:39 -05:00
|
|
|
void PrintBuffer::patch(StreamBuffer *sb, DebugCollector *dbc) {
|
|
|
|
|
DebugBlock dbb(dbc, "PrintBuffer::patch");
|
2021-11-14 15:57:18 -05:00
|
|
|
if (core_ == &shared_core) core_ = new PrintBufferCore;
|
2021-10-20 14:05:09 -04:00
|
|
|
int auth_first = sb->read_int32();
|
|
|
|
|
int auth_last = sb->read_int32();
|
2021-11-14 15:57:18 -05:00
|
|
|
core_->lines_.resize(core_->first_unchecked_ - core_->first_line_);
|
2021-11-21 13:35:39 -05:00
|
|
|
int nlines = auth_last - core_->first_unchecked_;
|
|
|
|
|
if (nlines > 0) DebugLine(dbc) << "PrintBuffer received " << nlines << " lines.";
|
2021-11-14 15:57:18 -05:00
|
|
|
for (int i = core_->first_unchecked_; i < auth_last; i++) {
|
|
|
|
|
core_->lines_.emplace_back(sb->read_string());
|
2021-10-20 14:05:09 -04:00
|
|
|
}
|
2021-11-14 15:57:18 -05:00
|
|
|
core_->first_unchecked_ = core_->first_line_ + core_->lines_.size();
|
2021-10-20 14:05:09 -04:00
|
|
|
discard_upto(auth_first);
|
2021-11-14 15:57:18 -05:00
|
|
|
if (core_->first_line_ < auth_first) {
|
|
|
|
|
core_->first_line_ = auth_first;
|
2021-10-25 14:47:37 -04:00
|
|
|
}
|
2021-10-20 14:05:09 -04:00
|
|
|
}
|
|
|
|
|
|
2022-02-25 19:57:23 -05:00
|
|
|
bool PrintChanneler::channel(const PrintBuffer *printbuffer, std::ostream &ostream) {
|
2021-11-11 13:56:49 -05:00
|
|
|
if (printbuffer == nullptr) return false;
|
|
|
|
|
if (printbuffer->first_line() > line_) {
|
|
|
|
|
line_ = printbuffer->first_line();
|
|
|
|
|
}
|
|
|
|
|
while (line_ < printbuffer->first_unchecked()) {
|
2021-11-23 13:18:23 -05:00
|
|
|
ostream << "|" << printbuffer->nth(line_) << std::endl;
|
2021-11-11 13:56:49 -05:00
|
|
|
line_ += 1;
|
|
|
|
|
}
|
|
|
|
|
return line_ > printbuffer->first_line();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Invocation PrintChanneler::invocation(int64_t actor_id) {
|
2022-02-24 02:17:41 -05:00
|
|
|
char buf[80];
|
2022-03-22 12:24:02 -04:00
|
|
|
sprintf(buf, "%" PRId64, line_);
|
2024-09-02 21:48:24 -04:00
|
|
|
return Invocation(InvocationKind::FLUSH_PRINTS, actor_id, actor_id, buf);
|
2021-11-11 13:56:49 -05:00
|
|
|
}
|
|
|
|
|
|
2021-12-15 23:03:43 -05:00
|
|
|
LuaDefine(unittests_printbuffer, "", "some unit tests") {
|
2021-11-14 15:57:18 -05:00
|
|
|
PrintBuffer pbm;
|
|
|
|
|
PrintBuffer pbs;
|
2021-10-20 14:05:09 -04:00
|
|
|
StreamBuffer sb;
|
|
|
|
|
|
2021-11-14 15:57:18 -05:00
|
|
|
// Test authoritative add_string and discard_upto.
|
2021-10-20 14:05:09 -04:00
|
|
|
LuaAssertStrEq(L, pbm.debug_string(), "0,0:");
|
2021-11-14 15:57:18 -05:00
|
|
|
pbm.add_string("foo\nbar\nbaz\n", true);
|
2021-10-20 14:05:09 -04:00
|
|
|
LuaAssertStrEq(L, pbm.debug_string(), "0,3:foo;bar;baz;");
|
2021-11-14 15:57:18 -05:00
|
|
|
pbm.add_string("a\nb\nc", true);
|
2021-10-20 14:05:09 -04:00
|
|
|
LuaAssertStrEq(L, pbm.debug_string(), "0,6:foo;bar;baz;a;b;c;");
|
|
|
|
|
pbm.discard_upto(2);
|
|
|
|
|
LuaAssertStrEq(L, pbm.debug_string(), "2,6:baz;a;b;c;");
|
|
|
|
|
pbm.discard_upto(8);
|
2021-10-25 14:47:37 -04:00
|
|
|
LuaAssertStrEq(L, pbm.debug_string(), "6,6:");
|
2021-10-20 14:05:09 -04:00
|
|
|
|
2021-11-14 15:57:18 -05:00
|
|
|
// Test nonauthoritative add_string and discard_upto.
|
2021-10-20 14:05:09 -04:00
|
|
|
LuaAssertStrEq(L, pbs.debug_string(), "0,0:");
|
2021-11-14 15:57:18 -05:00
|
|
|
pbs.add_string("foo\nbar\nbaz\n", false);
|
2021-10-20 14:05:09 -04:00
|
|
|
LuaAssertStrEq(L, pbs.debug_string(), "0,0:foo;bar;baz;");
|
2021-11-14 15:57:18 -05:00
|
|
|
pbs.add_string("a\nb\nc", false);
|
2021-10-20 14:05:09 -04:00
|
|
|
LuaAssertStrEq(L, pbs.debug_string(), "0,0:foo;bar;baz;a;b;c;");
|
|
|
|
|
pbs.discard_upto(2);
|
|
|
|
|
LuaAssertStrEq(L, pbs.debug_string(), "2,2:baz;a;b;c;");
|
|
|
|
|
pbs.discard_upto(8);
|
2021-10-25 14:47:37 -04:00
|
|
|
LuaAssertStrEq(L, pbs.debug_string(), "6,6:");
|
2021-10-20 14:05:09 -04:00
|
|
|
|
2021-11-14 15:57:18 -05:00
|
|
|
// Test serialization and deserialization of a nonempty printbuffer.
|
|
|
|
|
pbm.clear();
|
|
|
|
|
sb.clear();
|
|
|
|
|
pbm.add_string("boy\nhowdy\nyeah\n", true);
|
|
|
|
|
pbm.add_string("baby\nget\ndown\n", false);
|
|
|
|
|
LuaAssertStrEq(L, pbm.debug_string(), "0,3:boy;howdy;yeah;baby;get;down;");
|
|
|
|
|
pbm.serialize(&sb);
|
|
|
|
|
pbs.deserialize(&sb);
|
|
|
|
|
LuaAssertStrEq(L, pbs.debug_string(), "0,3:boy;howdy;yeah;baby;get;down;");
|
|
|
|
|
|
|
|
|
|
// Test serialization and deserialization of an empty printbuffer.
|
|
|
|
|
pbm.clear();
|
|
|
|
|
sb.clear();
|
|
|
|
|
LuaAssert(L, pbm.never_printed());
|
|
|
|
|
pbm.serialize(&sb);
|
|
|
|
|
pbs.deserialize(&sb);
|
|
|
|
|
LuaAssert(L, pbs.never_printed());
|
|
|
|
|
|
2021-10-20 14:05:09 -04:00
|
|
|
pbm.clear();
|
|
|
|
|
pbs.clear();
|
|
|
|
|
sb.clear();
|
2021-11-14 15:57:18 -05:00
|
|
|
pbm.add_string("foo\nbar\n", true);
|
2021-10-20 14:05:09 -04:00
|
|
|
pbs.diff(pbm, &sb);
|
2021-11-21 13:35:39 -05:00
|
|
|
pbs.patch(&sb, nullptr);
|
2021-10-20 14:05:09 -04:00
|
|
|
LuaAssertStrEq(L, pbs.debug_string(), "0,2:foo;bar;");
|
|
|
|
|
pbm.clear();
|
2021-11-14 15:57:18 -05:00
|
|
|
pbm.add_string("foo\nyow\nding\ndong\n", true);
|
2021-10-20 14:05:09 -04:00
|
|
|
pbs.diff(pbm, &sb);
|
2021-11-21 13:35:39 -05:00
|
|
|
pbs.patch(&sb, nullptr);
|
2021-10-20 14:05:09 -04:00
|
|
|
LuaAssertStrEq(L, pbs.debug_string(), "0,4:foo;bar;ding;dong;");
|
|
|
|
|
pbs.discard_upto(2);
|
|
|
|
|
LuaAssertStrEq(L, pbs.debug_string(), "2,4:ding;dong;");
|
|
|
|
|
pbs.diff(pbm, &sb);
|
2021-11-21 13:35:39 -05:00
|
|
|
pbs.patch(&sb, nullptr);
|
2021-10-20 14:05:09 -04:00
|
|
|
LuaAssertStrEq(L, pbs.debug_string(), "2,4:ding;dong;");
|
2021-11-14 15:57:18 -05:00
|
|
|
pbs.add_string("boy\nhowdy\n", false);
|
2021-10-20 14:05:09 -04:00
|
|
|
LuaAssertStrEq(L, pbs.debug_string(), "2,4:ding;dong;boy;howdy;");
|
|
|
|
|
pbs.diff(pbm, &sb);
|
2021-11-21 13:35:39 -05:00
|
|
|
pbs.patch(&sb, nullptr);
|
2021-10-20 14:05:09 -04:00
|
|
|
LuaAssertStrEq(L, pbs.debug_string(), "2,4:ding;dong;");
|
2021-11-14 15:57:18 -05:00
|
|
|
pbs.add_string("boy\nhowdy\nyeah\nbaby\nget\ndown\n", false);
|
2021-10-20 14:05:09 -04:00
|
|
|
LuaAssertStrEq(L, pbs.debug_string(), "2,4:ding;dong;boy;howdy;yeah;baby;get;down;");
|
|
|
|
|
pbs.discard_upto(5);
|
|
|
|
|
LuaAssertStrEq(L, pbs.debug_string(), "5,5:howdy;yeah;baby;get;down;");
|
|
|
|
|
pbs.diff(pbm, &sb);
|
2021-11-21 13:35:39 -05:00
|
|
|
pbs.patch(&sb, nullptr);
|
2021-10-20 14:05:09 -04:00
|
|
|
LuaAssertStrEq(L, pbs.debug_string(), "5,5:");
|
|
|
|
|
|
2021-11-14 15:57:18 -05:00
|
|
|
|
2021-10-20 14:05:09 -04:00
|
|
|
return 0;
|
|
|
|
|
}
|