From e7f55a24112cbf5d1a733f50dd269373c373fab8 Mon Sep 17 00:00:00 2001 From: Josh Yelon Date: Thu, 7 Oct 2021 14:58:20 -0400 Subject: [PATCH] Progress on mingw driver --- luprex/core/Makefile | 4 +- luprex/core/cpp/drivenengine.cpp | 84 +++++----- luprex/core/cpp/drivenengine.hpp | 97 +++++------ luprex/core/cpp/driver-mingw.cpp | 261 +++++++++++++++++++++++++++++ luprex/core/cpp/driver.cpp | 31 ---- luprex/core/cpp/idalloc.cpp | 1 - luprex/core/cpp/luaconsole.cpp | 33 ++-- luprex/core/cpp/luaconsole.hpp | 12 +- luprex/core/cpp/luasnap.cpp | 2 - luprex/core/cpp/streambuffer.cpp | 7 +- luprex/core/cpp/streambuffer.hpp | 7 +- luprex/core/cpp/textgame.cpp | 5 + luprex/core/cpp/util.cpp | 19 ++- luprex/core/cpp/util.hpp | 4 + luprex/core/cpp/world-core.cpp | 2 +- luprex/core/cpp/world-diffxmit.cpp | 8 +- 16 files changed, 405 insertions(+), 172 deletions(-) create mode 100644 luprex/core/cpp/driver-mingw.cpp delete mode 100644 luprex/core/cpp/driver.cpp diff --git a/luprex/core/Makefile b/luprex/core/Makefile index 196af6b4..3ab3fe2b 100644 --- a/luprex/core/Makefile +++ b/luprex/core/Makefile @@ -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) diff --git a/luprex/core/cpp/drivenengine.cpp b/luprex/core/cpp/drivenengine.cpp index b6701c66..2596338a 100644 --- a/luprex/core/cpp/drivenengine.cpp +++ b/luprex/core/cpp/drivenengine.cpp @@ -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 DrivenEngine::new_outgoing_channel(const std::string &target) { - return std::unique_ptr(new Channel(this, next_channel_id_++, 0, target)); + int chid = find_unused_chid(); + new_outgoing_.insert(chid); + return std::unique_ptr(new Channel(this, chid, 0, target)); } std::unique_ptr 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 &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 &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 &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_; diff --git a/luprex/core/cpp/drivenengine.hpp b/luprex/core/cpp/drivenengine.hpp index c7033851..c628c717 100644 --- a/luprex/core/cpp/drivenengine.hpp +++ b/luprex/core/cpp/drivenengine.hpp @@ -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 &channels); + void drv_get_new_closed(std::set &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 &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 stdio_channel_; - int next_channel_id_; - std::map channels_; - Channel *recent_channel_; std::vector> accepted_channels_; + std::set new_closed_; + std::set new_outgoing_; util::LuaSourcePtr lua_source_; bool rescan_lua_source_; double clock_; diff --git a/luprex/core/cpp/driver-mingw.cpp b/luprex/core/cpp/driver-mingw.cpp new file mode 100644 index 00000000..fb8e12f1 --- /dev/null +++ b/luprex/core/cpp/driver-mingw.cpp @@ -0,0 +1,261 @@ + +#include "driver.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + + + + +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 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 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 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); +} + diff --git a/luprex/core/cpp/driver.cpp b/luprex/core/cpp/driver.cpp deleted file mode 100644 index 2a567034..00000000 --- a/luprex/core/cpp/driver.cpp +++ /dev/null @@ -1,31 +0,0 @@ - -#include "driver.hpp" -#include -#include -#include -#include - - -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); -} - diff --git a/luprex/core/cpp/idalloc.cpp b/luprex/core/cpp/idalloc.cpp index 698e5de5..3feed430 100644 --- a/luprex/core/cpp/idalloc.cpp +++ b/luprex/core/cpp/idalloc.cpp @@ -1,5 +1,4 @@ #include "idalloc.hpp" -#include #include #include #include diff --git a/luprex/core/cpp/luaconsole.cpp b/luprex/core/cpp/luaconsole.cpp index 4f17136d..44b7f823 100644 --- a/luprex/core/cpp/luaconsole.cpp +++ b/luprex/core/cpp/luaconsole.cpp @@ -2,7 +2,7 @@ #include #include "luaconsole.hpp" #include "util.hpp" - +#include 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); - } -} diff --git a/luprex/core/cpp/luaconsole.hpp b/luprex/core/cpp/luaconsole.hpp index 0af201e4..7058fa90 100644 --- a/luprex/core/cpp/luaconsole.hpp +++ b/luprex/core/cpp/luaconsole.hpp @@ -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(); }; diff --git a/luprex/core/cpp/luasnap.cpp b/luprex/core/cpp/luasnap.cpp index 719a7bd1..db9f49de 100644 --- a/luprex/core/cpp/luasnap.cpp +++ b/luprex/core/cpp/luasnap.cpp @@ -1,7 +1,6 @@ #include "luasnap.hpp" #include "luastack.hpp" -#include #include #include @@ -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) { diff --git a/luprex/core/cpp/streambuffer.cpp b/luprex/core/cpp/streambuffer.cpp index 86538a12..b9518808 100644 --- a/luprex/core/cpp/streambuffer.cpp +++ b/luprex/core/cpp/streambuffer.cpp @@ -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(); } diff --git a/luprex/core/cpp/streambuffer.hpp b/luprex/core/cpp/streambuffer.hpp index a1163dd4..b63d8dbc 100644 --- a/luprex/core/cpp/streambuffer.hpp +++ b/luprex/core/cpp/streambuffer.hpp @@ -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); diff --git a/luprex/core/cpp/textgame.cpp b/luprex/core/cpp/textgame.cpp index af01e778..f8a9491a 100644 --- a/luprex/core/cpp/textgame.cpp +++ b/luprex/core/cpp/textgame.cpp @@ -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()); } } diff --git a/luprex/core/cpp/util.cpp b/luprex/core/cpp/util.cpp index c729e985..7a6b29bc 100644 --- a/luprex/core/cpp/util.cpp +++ b/luprex/core/cpp/util.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include @@ -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(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"); diff --git a/luprex/core/cpp/util.hpp b/luprex/core/cpp/util.hpp index cfb02962..47017a24 100644 --- a/luprex/core/cpp/util.hpp +++ b/luprex/core/cpp/util.hpp @@ -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); diff --git a/luprex/core/cpp/world-core.cpp b/luprex/core/cpp/world-core.cpp index 9d03eeee..1cc30926 100644 --- a/luprex/core/cpp/world-core.cpp +++ b/luprex/core/cpp/world-core.cpp @@ -540,7 +540,7 @@ void World::snapshot() { } void World::rollback() { - assert(!snapshot_.at_eof()); + assert(!snapshot_.empty()); deserialize(&snapshot_); } diff --git a/luprex/core/cpp/world-diffxmit.cpp b/luprex/core/cpp/world-diffxmit.cpp index 958e67b3..b33aa610 100644 --- a/luprex/core/cpp/world-diffxmit.cpp +++ b/luprex/core/cpp/world-diffxmit.cpp @@ -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) {