Progress on mingw driver

This commit is contained in:
2021-10-07 14:58:20 -04:00
parent bce756d1fd
commit e7f55a2411
16 changed files with 405 additions and 172 deletions

View File

@@ -5,7 +5,7 @@ CPP_FILES=\
cpp/invocation.cpp\
cpp/spookyv2.cpp\
cpp/drivenengine.cpp\
cpp/driver.cpp\
cpp/driver-mingw.cpp\
cpp/util.cpp\
cpp/luastack.cpp\
cpp/traceback.cpp\
@@ -36,6 +36,6 @@ obj/%.o: cpp/%.cpp
$(CXX) -c -MMD $< -o $@
main.exe: $(OBJ_FILES)
$(CXX) -o main.exe $(OBJ_FILES) -Llib lib/liblua-dbg.a
$(CXX) -o main.exe $(OBJ_FILES) -lws2_32 -Llib lib/liblua-dbg.a
-include $(OBJ_FILES:%.o=%.d)

View File

@@ -15,10 +15,26 @@ Channel::Channel(DrivenEngine *de, int chid, int port, const std::string &target
Channel::~Channel() {
assert(driven_->channels_[chid_] == this);
driven_->channels_.erase(chid_);
if (driven_->recent_channel_ == this) {
driven_->recent_channel_ = driven_->stdio_channel_.get();
driven_->new_closed_.insert(chid_);
driven_->new_outgoing_.erase(chid_);
driven_->channels_[chid_] = nullptr;
}
int DrivenEngine::find_unused_chid() {
// Note: channel ID zero is special, it is never reused.
for (int i = 0; i < MAX_CHAN; i++) {
int id = next_unused_chid_++;
if (next_unused_chid_ == MAX_CHAN) next_unused_chid_ = 1;
if (channels_[id] == nullptr) return id;
}
assert(false);
return 0;
}
Channel *DrivenEngine::get_chid(int chid) {
assert(unsigned(chid) < MAX_CHAN);
assert(channels_[chid] != nullptr);
return channels_[chid];
}
double DrivenEngine::get_clock() {
@@ -26,7 +42,9 @@ double DrivenEngine::get_clock() {
}
std::unique_ptr<Channel> DrivenEngine::new_outgoing_channel(const std::string &target) {
return std::unique_ptr<Channel>(new Channel(this, next_channel_id_++, 0, target));
int chid = find_unused_chid();
new_outgoing_.insert(chid);
return std::unique_ptr<Channel>(new Channel(this, chid, 0, target));
}
std::unique_ptr<Channel> DrivenEngine::new_incoming_channel() {
@@ -55,42 +73,24 @@ void DrivenEngine::stop_driver() {
stop_driver_ = true;
}
void DrivenEngine::drv_logmode_write(const std::string &filename, int64_t maxsize) {
// NOT IMPLEMENTED YET, but it's okay as a stub.
void DrivenEngine::drv_get_new_closed(std::set<int> &channels) {
channels = std::move(new_closed_);
new_closed_.clear();
}
void DrivenEngine::drv_logmode_replay(const std::string &filename) {
// NOT IMPLEMENTED YET.
assert(false);
}
void DrivenEngine::drv_logmode_none() {
// NOT IMPLEMENTED YET, but it's okay as a stub.
}
Channel *DrivenEngine::get_chid(int chid) {
// We cache the most recently used channel.
if (recent_channel_->chid_ != chid) {
auto iter = channels_.find(chid);
assert(iter != channels_.end());
recent_channel_ = iter->second;
}
return recent_channel_;
}
void DrivenEngine::drv_list_channels(std::vector<int> &channels) {
channels.clear();
for (const auto &p : channels_) {
if (!p.second->closed_) {
channels.push_back(p.first);
}
}
void DrivenEngine::drv_get_new_outgoing(std::set<int> &channels) {
channels = std::move(new_outgoing_);
new_outgoing_.clear();
}
const std::string &DrivenEngine::drv_get_target(int chid) {
return get_chid(chid)->target_;
}
bool DrivenEngine::drv_outgoing_empty(int chid) {
return get_chid(chid)->sb_out_->empty();
}
void DrivenEngine::drv_peek_outgoing(int chid, int *nbytes, const char **bytes) {
Channel *ch = get_chid(chid);
*nbytes = ch->sb_out_->fill();
@@ -101,7 +101,7 @@ void DrivenEngine::drv_sent_outgoing(int chid, int nbytes) {
get_chid(chid)->sb_out_->read_bytes(nbytes);
}
void DrivenEngine::drv_recv_incoming(int chid, int nbytes, char *bytes) {
void DrivenEngine::drv_recv_incoming(int chid, int nbytes, const char *bytes) {
if (nbytes > 0) {
get_chid(chid)->sb_in_->write_bytes(bytes, nbytes);
}
@@ -112,7 +112,7 @@ void DrivenEngine::drv_notify_close(int chid) {
}
int DrivenEngine::drv_notify_accept(int port) {
int chid = next_channel_id_++;
int chid = find_unused_chid();
accepted_channels_.emplace_back(new Channel(this, chid, port, ""));
return chid;
}
@@ -138,16 +138,12 @@ bool DrivenEngine::drv_get_stop_driver() {
return stop_driver_;
}
bool DrivenEngine::drv_step_logfile() {
// NOT IMPLEMENTED
assert(false);
return false;
}
DrivenEngine::DrivenEngine() {
next_channel_id_ = 1;
for (int i = 0; i < MAX_CHAN; i++) {
channels_[i] = nullptr;
}
next_unused_chid_ = 1;
stdio_channel_.reset(new Channel(this, 0, 0, ""));
recent_channel_ = stdio_channel_.get();
rescan_lua_source_ = true;
clock_ = 0.0;
stop_driver_ = false;
@@ -158,7 +154,9 @@ DrivenEngine::~DrivenEngine() {
stdio_channel_.reset();
accepted_channels_.clear();
// At this point, all channels should be gone.
assert(channels_.empty());
for (int i = 0; i < MAX_CHAN; i++) {
assert(channels_[i] == nullptr);
}
}
static DrivenEngine *engine_;

View File

@@ -2,26 +2,16 @@
//
// DrivenEngine
//
// This module achieves two goals:
// This module embodies the idea of an "event-driven game engine." We want the
// engine to be event-driven because an event-driven engine is a deterministic
// state machine. That, in turn, makes it possible to do replay logging.
//
// * Makes it possible to do replay logging.
// * Makes the engine act like a deterministic state machine.
// The DrivenEngine module provides two APIs: the 'engine-side' API, and the
// 'driver-side' API.
//
// We want to implement replay logging. That means we want to be able to run
// the engine, keeping a log, then "replay" that execution later.
//
// Unfortunately, I/O makes this difficult. Suppose that during the execution
// of the server, the server reads a lua source file. Later, we want to replay
// that execution, but the lua source file has changed on disk. When the engine
// reads the lua source file, we need it to "read" the data that *used* to be
// there, not the data that's currently there.
//
// To make it work, all I/O has to operate in one of two modes: either it can be
// real I/O, or it can be simulated I/O that retrieves data from the logfile.
//
// This module, DrivenEngine, provides an API for the engine to do all its I/O.
// It provides a variety of methods to open sockets, read and write sockets,
// read lua source, get the clock, and so forth.
// The engine-side API looks like a typical collection of I/O primitives. It
// includes methods to open sockets, read and write sockets, read lua source,
// get the clock, and so forth.
//
// But in reality, these I/O functions don't ever call operating system
// functions like "read" or "write" or "connect." They don't call the operating
@@ -71,15 +61,11 @@
// - If the engine asked that the lua source be refreshed, read the source
// from disk and call 'drv_set_lua_source'.
//
// - List all existing channels using drv_list_channels.
// - Get a list of recently-closed channels using drv_get_closed_channels.
// Close any socket associated with these channels and free all resources.
//
// - If there are any new channels in the channel list, use
// drv_get_channel_target to fetch the target host, then create a socket
// and open the connection. Associate the socket with the channel.
//
// - If the channel list is missing a previously-known channel, then that
// channel was deleted by the engine. Clean up the channel's data and
// close the socket, if any.
// - Get a list of recently-opened channels using drv_get_opened_channels.
// Open new outgoing connections for these channels.
//
// - Do an OS 'poll'. The poll should include the sockets for all channels
// in the channel list, all listening ports, and stdio.
@@ -232,32 +218,23 @@ public:
//
//////////////////////////////////////////////////////////////
// Run in logging mode. In this mode, the 'drv' methods will write records
// into the logfile, making it possible to replay the exact sequence of drv
// methods that were invoked. The replay log will be flushed and closed
// automatically in the event of a crash, or if it exceeds the maximum size.
// The maximum channel ID plus one.
//
void drv_logmode_write(const std::string &filename, int64_t maxsize);
static const int MAX_CHAN = 256;
// Run in replay mode. In this mode, the only function the driver may call
// is 'drv_step_logfile.' Each time this method is called, a single drv_xxx
// method will be called, as requested by the logfile.
// Get a list of all recently-closed channels. The driver should
// discard all socket information associated with these channels.
// Caution: this may contain channels that the driver has never
// heard of. In that case, just ignore the close-request.
//
void drv_logmode_replay(const std::string &filename);
// Run in non-logging mode. In this mode, no logfile will be read or written
// in this mode.
//
void drv_logmode_none();
// Get a list of all non-closed existing channels. This may include new
// channels that were created using 'new_outgoing_channel'. It may also be
// missing channels that were deleted. It is up to the driver to update its
// internal data structures to 'match' this list. Channel number zero is
// always present, it is the stdio channel.
//
void drv_list_channels(std::vector<int> &channels);
void drv_get_new_closed(std::set<int> &opened);
// Get a list of all recently-opened channels that were created using
// drv_new_outgoing_channel. The driver should initiate outgoing
// connections for these channels.
//
void drv_get_new_outgoing(std::set<int> &closed);
// Get the target of a channel. A target is a string like
// "www.whatever.com:80". It indicates the host and port that the channel
// is supposed to be talking to. Non-socket channels and incoming channels
@@ -265,6 +242,10 @@ public:
//
const std::string &drv_get_target(int chid);
// Return true if the outgoing buffer is empty.
//
bool drv_outgoing_empty(int chid);
// Get a pointer to the bytes in the outgoing buffer. The pointer returned
// here is naturally only valid until the buffer is changed. This function
// is used for all channels, including sockets and stdio.
@@ -281,7 +262,7 @@ public:
// bytes to be appended to the incoming buffer. This function is used for
// all channels, including sockets and stdio.
//
void drv_recv_incoming(int chid, int nbytes, char *bytes);
void drv_recv_incoming(int chid, int nbytes, const char *bytes);
// Notify the channel that the connection was closed. This includes all
// sorts of closes, including friendly termination, all the way to network
@@ -325,11 +306,6 @@ public:
//
bool drv_get_stop_driver();
// In replay mode, perform a single step of the logfile. Returns true
// if the logfile was not empty.
//
bool drv_step_logfile();
//////////////////////////////////////////////////////////////
//
// Creation and Destruction.
@@ -360,15 +336,20 @@ public:
static DrivenEngine *get();
private:
// Get a channel by channel ID.
// Find a currently-unused channel ID. Channel IDs
// are small integers that are reused.
int find_unused_chid();
// Get the channel associated with the specified channel ID.
Channel *get_chid(int chid);
private:
Channel *channels_[MAX_CHAN];
int next_unused_chid_;
std::unique_ptr<Channel> stdio_channel_;
int next_channel_id_;
std::map<int, Channel *> channels_;
Channel *recent_channel_;
std::vector<std::unique_ptr<Channel>> accepted_channels_;
std::set<int> new_closed_;
std::set<int> new_outgoing_;
util::LuaSourcePtr lua_source_;
bool rescan_lua_source_;
double clock_;

View File

@@ -0,0 +1,261 @@
#include "driver.hpp"
#include <map>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cassert>
#include <ws2tcpip.h>
#include <winsock2.h>
#include <synchapi.h>
class Driver {
public:
static const int MAX_CHAN = DrivenEngine::MAX_CHAN;
static const int CONSOLE_MAX = 512;
DrivenEngine *driven_;
SOCKET socket_[MAX_CHAN];
bool connected_[MAX_CHAN];
bool engine_wakeup_;
char console_line_[CONSOLE_MAX + 1];
int console_len_;
std::unique_ptr<char> chbuf;
static PADDRINFOA find_good_addr(PADDRINFOA addrinfo) {
for (PADDRINFOA addr = addrinfo; addr != nullptr; addr = addr->ai_next) {
if ((addr->ai_family == AF_INET) || (addr->ai_family == AF_INET6)) {
return addr;
}
}
return nullptr;
}
static SOCKET open_connection(const std::string &target) {
std::string host, port;
util::split_host_port(target, host, port);
PADDRINFOA addrs;
int status = getaddrinfo(host.c_str(), port.c_str(), nullptr, &addrs);
assert(status == 0);
PADDRINFOA goodaddr = find_good_addr(addrs);
assert(goodaddr != nullptr);
freeaddrinfo(addrs);
SOCKET sock = socket(goodaddr->ai_family, SOCK_STREAM, IPPROTO_TCP);
assert(sock != INVALID_SOCKET);
u_long mode = 1; // 1 to enable non-blocking socket
status = ioctlsocket(sock, FIONBIO, &mode);
assert(status == 0);
status = connect(sock, goodaddr->ai_addr, goodaddr->ai_addrlen);
assert(status == 0);
return sock;
}
void init(DrivenEngine *de) {
driven_ = de;
for (int i = 0; i < MAX_CHAN; i++) {
socket_[i] = INVALID_SOCKET;
connected_[i] = false;
}
console_len_ = 0;
engine_wakeup_ = false;
chbuf.reset(new char[65536]);
}
void handle_lua_source() {
if (driven_->drv_get_rescan_lua_source()) {
driven_->drv_set_lua_source(util::read_lua_source("lua"));
engine_wakeup_ = true;
}
}
void handle_new_closed_sockets() {
std::set<int> chans;
driven_->drv_get_new_closed(chans);
for (int chid : chans) {
if (socket_[chid] != INVALID_SOCKET) {
assert(closesocket(socket_[chid] == 0));
socket_[chid] = INVALID_SOCKET;
connected_[chid] = false;
}
}
}
void handle_new_outgoing_sockets() {
std::set<int> chans;
driven_->drv_get_new_outgoing(chans);
for (int chid : chans) {
assert(socket_[chid] == INVALID_SOCKET);
SOCKET sock = open_connection(driven_->drv_get_target(chid));
socket_[chid] = sock;
connected_[chid] = false;
}
}
void handle_console_output() {
int nbytes; const char *bytes;
driven_->drv_peek_outgoing(0, &nbytes, &bytes);
if (nbytes > 0) {
HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
assert(hstdout != INVALID_HANDLE_VALUE);
DWORD nwrote;
if (nbytes > 10000) nbytes = 10000;
assert(WriteConsoleA(hstdout, bytes, nbytes, &nwrote, nullptr));
assert(nwrote > 0);
driven_->drv_sent_outgoing(0, nwrote);
}
}
// This is painful. Win32 allows nonblocking read of keyboard events. But
// it doesn't have any way to do nonblocking read of processed lines. So we
// have to read individual events and do the line processing ourselves.
void handle_console_input() {
HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
assert(hstdin != INVALID_HANDLE_VALUE);
assert(hstdout != INVALID_HANDLE_VALUE);
INPUT_RECORD inrecords[512];
DWORD nread, nevents, nwrote;
if (GetNumberOfConsoleInputEvents(hstdin, &nevents)) {
if (nevents > 512) nevents = 512;
ReadConsoleInputA(hstdin, inrecords, nevents, &nread);
for (int i = 0; i < int(nread); i++) {
const INPUT_RECORD &inr = inrecords[i];
if (inr.EventType != KEY_EVENT) continue;
const KEY_EVENT_RECORD &key = inr.Event.KeyEvent;
if (!key.bKeyDown) continue;
char c = key.uChar.AsciiChar;
if ((c == '\r') || (c == '\n')) {
console_line_[console_len_++] = '\n';
assert(WriteConsoleA(hstdout, " \r\n", 3, &nwrote, nullptr));
assert(nwrote==3);
driven_->drv_recv_incoming(0, console_len_, console_line_);
console_len_ = 0;
engine_wakeup_ = true;
} else if (c == '\b') {
if (console_len_ > 0) {
assert(WriteConsoleA(hstdout, "\b \b", 3, &nwrote, nullptr));
assert(nwrote==3);
console_len_ -= 1;
}
} else if (c >= 32) {
if (console_len_ < CONSOLE_MAX) {
console_line_[console_len_++] = c;
assert(WriteConsoleA(hstdout, &c, 1, &nwrote, nullptr));
assert(nwrote==1);
}
}
}
}
}
void handle_clock() {
}
bool calc_select_sets(fd_set &rfds, fd_set &wfds, fd_set &efds) const {
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&efds);
bool any = false;
for (int chid = 1; chid < MAX_CHAN; chid++) {
SOCKET sock = socket_[chid];
if (sock == INVALID_SOCKET) continue;
any = true;
FD_SET(sock, &rfds);
if ((!connected_[chid]) || (!driven_->drv_outgoing_empty(chid))) {
FD_SET(sock, &wfds);
}
}
return any;
}
void close_socket(int chid) {
assert(socket_[chid] != INVALID_SOCKET);
assert(closesocket(socket_[chid]) == 0);
driven_->drv_notify_close(chid);
socket_[chid] = INVALID_SOCKET;
connected_[chid] = false;
engine_wakeup_ = true;
}
void handle_socket_input_output(int mstimeout) {
fd_set rfds, wfds, efds;
int nbytes; const char *bytes;
bool any = calc_select_sets(rfds, wfds, efds);
if (!any) {
if (mstimeout>0) Sleep(mstimeout);
return;
}
TIMEVAL timeout;
timeout.tv_sec = mstimeout / 1000;
timeout.tv_usec = (mstimeout - (timeout.tv_sec*1000)) * 1000;
int status = select(1, &rfds, &wfds, &efds, &timeout);
if (status == SOCKET_ERROR) {
int err = WSAGetLastError();
std::cerr << "ERR:" << err << std::endl;
}
assert(status != SOCKET_ERROR);
for (int chid = 1; chid < MAX_CHAN; chid++) {
SOCKET sock = socket_[chid];
if (sock == INVALID_SOCKET) continue;
if (FD_ISSET(sock, &wfds)) {
connected_[chid] = true;
driven_->drv_peek_outgoing(chid, &nbytes, &bytes);
if (nbytes > 0) {
int wbytes = send(sock, bytes, nbytes, 0);
if (wbytes == SOCKET_ERROR) {
close_socket(chid);
continue;
} else {
driven_->drv_sent_outgoing(chid, wbytes);
}
}
}
if (FD_ISSET(sock, &rfds)) {
int nrecv = recv(sock, chbuf.get(), 65536, 0);
if (nrecv == SOCKET_ERROR) {
close_socket(chid);
continue;
} else {
driven_->drv_recv_incoming(chid, nrecv, chbuf.get());
engine_wakeup_ = true;
}
}
}
}
void drive(DrivenEngine *de) {
WSADATA whocares;
assert(WSAStartup(MAKEWORD(2,2), &whocares) == 0);
HANDLE hconsole = GetStdHandle(STD_INPUT_HANDLE);
assert(hconsole != INVALID_HANDLE_VALUE);
init(de);
DrivenEngine::set(de);
driven_->drv_set_lua_source(util::read_lua_source("lua"));
driven_->drv_invoke_event_update();
while (!de->drv_get_stop_driver()) {
engine_wakeup_ = false;
handle_lua_source();
handle_new_closed_sockets();
handle_new_outgoing_sockets();
handle_console_output();
handle_console_input();
int mstimeout = engine_wakeup_ ? 0 : 100;
handle_socket_input_output(mstimeout);
handle_clock();
if (engine_wakeup_) {
de->drv_invoke_event_update();
}
}
DrivenEngine::set(nullptr);
}
};
void driver_drive(DrivenEngine *de) {
Driver driver;
driver.drive(de);
}

View File

@@ -1,31 +0,0 @@
#include "driver.hpp"
#include <map>
#include <iostream>
#include <cstdio>
#include <cstring>
void driver_drive(DrivenEngine *de) {
const int MAXINPUT = 1000;
char buf[MAXINPUT];
int nbytes; const char *bytes;
DrivenEngine::set(de);
de->drv_logmode_none();
while (!de->drv_get_stop_driver()) {
if (de->drv_get_rescan_lua_source()) {
de->drv_set_lua_source(util::read_lua_source("lua"));
de->drv_invoke_event_update();
}
de->drv_peek_outgoing(0, &nbytes, &bytes);
if (nbytes > 0) {
fwrite(bytes, 1, nbytes, stdout);
}
if (fgets(buf, MAXINPUT, stdin)) {
de->drv_recv_incoming(0, strlen(buf), buf);
de->drv_invoke_event_update();
}
}
DrivenEngine::set(nullptr);
}

View File

@@ -1,5 +1,4 @@
#include "idalloc.hpp"
#include <iostream>
#include <map>
#include <sstream>
#include <ostream>

View File

@@ -2,7 +2,7 @@
#include <string.h>
#include "luaconsole.hpp"
#include "util.hpp"
#include <iostream>
static bool is_single_letter(const std::string &s) {
return ((s.size() == 1) && (s[0] >= 'a') && (s[0] <= 'z'));
@@ -24,13 +24,14 @@ void LuaConsole::clear() {
words_.clear();
syntax_ = "";
action_ = DO_NOTHING;
prompt_ = "> ";
}
void LuaConsole::split_words() {
words_.clear();
std::string acc;
for (char c : raw_input_) {
if ((c == ' ')||(c == '\n')) {
if ((c == ' ')||(c == '\n')||(c == '\r')) {
if (!acc.empty()) {
words_.push_back(acc);
acc = "";
@@ -44,10 +45,13 @@ void LuaConsole::split_words() {
}
}
std::string LuaConsole::get_prompt() {
std::string result = prompt_;
prompt_ = "";
return result;
}
void LuaConsole::add(std::string line) {
if (action_ != DO_NOTHING) {
clear();
}
for (int i = 0; i < int(line.size()); i++) {
if (line[i] == '\n') line[i] = ' ';
}
@@ -96,20 +100,9 @@ void LuaConsole::add(std::string line) {
lua_expression_ = partial;
}
lua_settop(lua_state_, top);
if (action_ == DO_NOTHING) {
prompt_ = ">> ";
}
}
void LuaConsole::add_stdin() {
if (action_ != DO_NOTHING) {
clear();
}
const int MAXINPUT = 1000;
char buf[MAXINPUT];
fputs(raw_input_.empty() ? "> " : ">> ", stdout);
fflush(stdout);
if (fgets(buf, MAXINPUT, stdin)) {
size_t len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n')
buf[len - 1] = '\0';
add(buf);
}
}

View File

@@ -46,6 +46,7 @@ private:
std::string lua_expression_;
StringVec words_;
std::string syntax_;
std::string prompt_;
void split_words();
@@ -56,6 +57,10 @@ public:
// Get the recommended action.
int action() const { return action_; }
// Fetch the stored prompt. Also clears the stored prompt. You should fetch
// and print the prompt after 'add' or 'clear'.
std::string get_prompt();
// When action is DO_COMMAND, get the command words.
const StringVec &words() const { return words_; }
@@ -66,12 +71,11 @@ public:
const std::string &syntax() const { return syntax_; }
// Add a line of text that was just read from the console.
// If more input is needed, stores the ">> " prompt.
void add(std::string line);
// Read a line of text from stdin and add it.
void add_stdin();
// Clear the state.
// Call 'clear' after executing an action.
// Stores the "> " prompt.
void clear();
};

View File

@@ -1,7 +1,6 @@
#include "luasnap.hpp"
#include "luastack.hpp"
#include <iostream>
#include <cassert>
#include <sstream>
@@ -71,7 +70,6 @@ void LuaSnap::serialize(StreamBuffer *sb) {
int64_t pos2 = sb->total_writes();
sb->overwrite_int64(pos1, pos2 - pos1);
lua_settop(state_, 0);
// std::cerr << "Eris dump is " << pos2-pos1 << " bytes." << std::endl;
}
void LuaSnap::deserialize(StreamBuffer *sb) {

View File

@@ -166,6 +166,9 @@ void StreamBuffer::write_bytes(const char *s, int64_t len) {
write_cursor_ += len;
}
void StreamBuffer::write_bytes(const std::string &s) {
write_bytes(s.c_str(), s.size());
}
const char *StreamBuffer::read_bytes(int64_t bytes) {
check_available(bytes);
@@ -395,11 +398,11 @@ void StreamBuffer::overwrite_uint64(int64_t write_count_after, uint64_t vv) {
memcpy(target, &v, 8);
}
bool StreamBuffer::at_eof() {
bool StreamBuffer::empty() {
return (read_cursor_ == write_cursor_);
}
void StreamBuffer::verify_eof() {
void StreamBuffer::verify_empty() {
if (read_cursor_ != write_cursor_) {
throw StreamCorruption();
}

View File

@@ -277,7 +277,8 @@ public:
// It just writes the bytes.
//
void write_bytes(const char *bytes, int64_t len);
void write_bytes(const std::string &bytes);
// Read a block of bytes from the buffer.
//
// Throws StreamEof if the specified number of bytes aren't present.
@@ -350,10 +351,10 @@ public:
void overwrite_uint64(int64_t write_count_after, uint64_t v);
// This function checks to see if the buffer is empty.
bool at_eof();
bool empty();
// Verify that the buffer is empty, if not, throw StreamCorruption.
void verify_eof();
void verify_empty();
// Rewind the read cursor to a previous position.
void unread_to(int64_t total_reads);

View File

@@ -178,6 +178,7 @@ void TextGame::event_update()
world_->run_unittests();
actor_id_ = world_->create_login_actor();
std::cerr << "Login actor ID: " << actor_id_ << std::endl;
get_stdio_channel()->out()->write_bytes(console_.get_prompt());
}
world_->update_source(get_lua_source());
@@ -188,16 +189,20 @@ void TextGame::event_update()
int action = console_.action();
if (action == LuaConsole::DO_LUA) {
do_lua(console_.lua_expression());
console_.clear();
} else if (action == LuaConsole::DO_COMMAND) {
do_command(console_.words());
console_.clear();
} else if (action == LuaConsole::DO_SYNTAX) {
std::cerr << console_.syntax() << std::endl;
console_.clear();
}
check_redirects();
if (actor_id_ == 0) {
stop_driver();
return;
}
get_stdio_channel()->out()->write_bytes(console_.get_prompt());
}
}

View File

@@ -6,7 +6,6 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <cmath>
#include <iostream>
#include <iomanip>
#include <cassert>
@@ -185,6 +184,18 @@ double strtodouble(const std::string &value) {
}
}
void split_host_port(const std::string &target, std::string &host, std::string &port) {
size_t lastcolon = target.rfind(':');
if (lastcolon == std::string::npos) {
host = ""; port = ""; return;
}
host = target.substr(0, lastcolon);
port = target.substr(lastcolon + 1);
if ((host == "") || (port == "")) {
host = ""; port = ""; return;
}
}
std::string ltrim(std::string s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(),
std::not1(std::ptr_fun<int, int>(std::isspace))));
@@ -311,6 +322,12 @@ LuaDefine(unittests_util, "c") {
LuaAssert(L, std::isnan(util::strtodouble("12ab")));
LuaAssert(L, std::isnan(util::strtodouble("")));
// Test split_host_port
std::string host, port;
util::split_host_port("stanford.edu:80", host, port);
LuaAssertStrEq(L, host, "stanford.edu");
LuaAssertStrEq(L, port, "80");
// Test trim, ltrim, rtrim
LuaAssert(L, util::ltrim(" foo ") == "foo ");
LuaAssert(L, util::rtrim(" foo ") == " foo");

View File

@@ -69,6 +69,10 @@ int64_t strtoint(const std::string &value, int64_t errval);
// String to double. Returns NAN if the number is not parseable.
double strtodouble(const std::string &value);
// Split a string of the form "hostname:port" into a hostname and a port.
// On failure, returns empty strings.
void split_host_port(const std::string &target, std::string &host, std::string &port);
// Trim strings: left end, right end, both ends.
std::string ltrim(std::string s);
std::string rtrim(std::string s);

View File

@@ -540,7 +540,7 @@ void World::snapshot() {
}
void World::rollback() {
assert(!snapshot_.at_eof());
assert(!snapshot_.empty());
deserialize(&snapshot_);
}

View File

@@ -42,7 +42,7 @@ void World::diff_actor(int64_t actor_id, World *master, StreamBuffer *xsb) {
// Forward to client, and apply to server-synchronous.
tsb.copy_into(xsb);
patch_actor(&tsb);
assert(tsb.at_eof());
assert(tsb.empty());
}
void World::patch_visible(StreamBuffer *sb) {
@@ -133,7 +133,7 @@ void World::diff_visible(const util::IdVector &visible, World *master, StreamBuf
// Forward to client, and apply to server-synchronous.
tsb.copy_into(xsb);
patch_visible(&tsb);
assert(tsb.at_eof());
assert(tsb.empty());
// Copy the version number from master animqueue to synch animqueue.
for (int i = 0; i < int(mvis.size()); i++) {
@@ -191,7 +191,7 @@ void World::diff_luatabs(int64_t actor_id, World *master, StreamBuffer *xsb) {
patch_tangible_databases(&tsb);
patch_numbered_tables(&tsb);
unnumber_lua_tables();
assert(tsb.at_eof());
assert(tsb.empty());
// Unnumber tables in both models.
unnumber_lua_tables();
@@ -259,7 +259,7 @@ void World::diff_tanclass(int64_t actor_id, World *master, StreamBuffer *xsb) {
// Forward to client, and apply to server-synchronous.
tsb.copy_into(xsb);
patch_tanclass(&tsb);
assert(tsb.at_eof());
assert(tsb.empty());
}
void World::patch_source(StreamBuffer *sb) {