Move readline functionality into device-independent code

This commit is contained in:
2021-10-12 12:46:11 -04:00
parent 315bf6e3b1
commit 995219d965
6 changed files with 122 additions and 42 deletions

View File

@@ -9,6 +9,10 @@ Channel::Channel(DrivenEngine *de, int chid, int port, const std::string &target
port_ = port; port_ = port;
closed_ = false; closed_ = false;
target_ = target; target_ = target;
readline_enabled_ = (chid == 0);
readline_len_ = 0;
readline_lastc_ = 0;
echo_len_ = 0;
assert(driven_->channels_[chid_] == nullptr); assert(driven_->channels_[chid_] == nullptr);
driven_->channels_[chid_] = this; driven_->channels_[chid_] = this;
} }
@@ -20,6 +24,50 @@ Channel::~Channel() {
driven_->channels_[chid_] = nullptr; driven_->channels_[chid_] = nullptr;
} }
void Channel::feed_readline(int nbytes, const char *bytes) {
for (int i = 0; i < nbytes; i++) {
char c = bytes[i];
if ((c == '\n') && (readline_lastc_ == '\r')) {
// Ignore newline immediately after carriage return.
// Otherwise, crlf produces two newlines.
} else if ((c == '\r') || (c == '\n')) {
if ((echo_space() >= 3) && (readline_space() >= 1)) {
echo_buf_[echo_len_++] = ' ';
echo_buf_[echo_len_++] = '\r';
echo_buf_[echo_len_++] = '\n';
readline_buf_[readline_len_++] = '\n';
sb_in_->write_bytes(readline_buf_, readline_len_);
readline_len_ = 0;
}
} else if (c == '\b') {
if ((readline_len_ >= 1) && (echo_space() >= 3)) {
echo_buf_[echo_len_++] = '\b';
echo_buf_[echo_len_++] = ' ';
echo_buf_[echo_len_++] = '\b';
readline_len_ -= 1;
}
} else if (c >= 32) {
// Don't use up the last character in the readline buffer: save
// it for the newline.
if ((readline_space() >= 2) && (echo_space() >= 1)) {
echo_buf_[echo_len_++] = c;
readline_buf_[readline_len_++] = c;
}
}
readline_lastc_ = c;
}
}
void Channel::set_readline(bool e) {
if (e != readline_enabled_) {
readline_enabled_ = e;
readline_len_ = 0;
readline_lastc_ = 0;
echo_len_ = 0;
}
}
int DrivenEngine::find_unused_chid() { int DrivenEngine::find_unused_chid() {
// Note: channel ID zero is special, it is never reused. // Note: channel ID zero is special, it is never reused.
for (int i = 0; i < MAX_CHAN; i++) { for (int i = 0; i < MAX_CHAN; i++) {
@@ -101,17 +149,40 @@ bool DrivenEngine::drv_outgoing_empty(int chid) {
void DrivenEngine::drv_peek_outgoing(int chid, int *nbytes, const char **bytes) { void DrivenEngine::drv_peek_outgoing(int chid, int *nbytes, const char **bytes) {
Channel *ch = get_chid(chid); Channel *ch = get_chid(chid);
*nbytes = ch->sb_out_->fill(); if (ch->echo_len_ > 0) {
*bytes = ch->sb_out_->data(); *nbytes = ch->echo_len_;
*bytes = ch->echo_buf_;
} else {
*nbytes = ch->sb_out_->fill();
*bytes = ch->sb_out_->data();
}
} }
void DrivenEngine::drv_sent_outgoing(int chid, int nbytes) { void DrivenEngine::drv_sent_outgoing(int chid, int nbytes) {
get_chid(chid)->sb_out_->read_bytes(nbytes); Channel *ch = get_chid(chid);
if (nbytes > 0) {
if (ch->echo_len_ > 0) {
if (nbytes >= ch->echo_len_) {
ch->sb_out_->read_bytes(nbytes - ch->echo_len_);
ch->echo_len_ = 0;
} else {
ch->echo_len_ -= nbytes;
memmove(ch->echo_buf_, ch->echo_buf_ + nbytes, ch->echo_len_);
}
} else {
ch->sb_out_->read_bytes(nbytes);
}
}
} }
void DrivenEngine::drv_recv_incoming(int chid, int nbytes, const char *bytes) { void DrivenEngine::drv_recv_incoming(int chid, int nbytes, const char *bytes) {
if (nbytes > 0) { if (nbytes > 0) {
get_chid(chid)->sb_in_->write_bytes(bytes, nbytes); Channel *ch = get_chid(chid);
if (ch->readline_enabled_) {
ch->feed_readline(nbytes, bytes);
} else {
ch->sb_in_->write_bytes(bytes, nbytes);
}
} }
} }

View File

@@ -123,6 +123,20 @@ public:
// //
bool closed() const { return closed_; } bool closed() const { return closed_; }
// True if the channel is in readline mode.
//
// Stdio always starts with this enabled, other channels always start
// with this disabled.
//
bool readline_enabled() const { return readline_enabled_; }
// Put the channel into readline mode.
//
// Caution: the channel better be coming from a raw tty, otherwise,
// this is going to produce weird results.
//
void set_readline(bool enabled);
// You may delete any channel except for stdio. This closes // You may delete any channel except for stdio. This closes
// the channel. // the channel.
// //
@@ -134,7 +148,13 @@ private:
// //
Channel(DrivenEngine *de, int chid, int port, const std::string &target); Channel(DrivenEngine *de, int chid, int port, const std::string &target);
int readline_space() { return READLINE_MAX - readline_len_; }
int echo_space() { return READLINE_MAX - echo_len_; }
void feed_readline(int nbytes, const char *bytes);
private: private:
static const int READLINE_MAX=512;
DrivenEngine *driven_; DrivenEngine *driven_;
int chid_; int chid_;
std::unique_ptr<StreamBuffer> sb_in_; std::unique_ptr<StreamBuffer> sb_in_;
@@ -142,6 +162,12 @@ private:
int port_; int port_;
bool closed_; bool closed_;
std::string target_; std::string target_;
char readline_buf_[READLINE_MAX];
int readline_len_;
char readline_lastc_;
char echo_buf_[READLINE_MAX];
int echo_len_;
bool readline_enabled_;
friend class DrivenEngine; friend class DrivenEngine;
}; };
using UniqueChannel = std::unique_ptr<Channel>; using UniqueChannel = std::unique_ptr<Channel>;

View File

@@ -20,8 +20,6 @@ public:
SOCKET socket_[MAX_CHAN]; SOCKET socket_[MAX_CHAN];
bool connected_[MAX_CHAN]; bool connected_[MAX_CHAN];
bool engine_wakeup_; bool engine_wakeup_;
char console_line_[CONSOLE_MAX + 1];
int console_len_;
std::map<int, SOCKET> listen_sockets_; std::map<int, SOCKET> listen_sockets_;
std::unique_ptr<char> chbuf; std::unique_ptr<char> chbuf;
@@ -113,7 +111,6 @@ public:
socket_[i] = INVALID_SOCKET; socket_[i] = INVALID_SOCKET;
connected_[i] = false; connected_[i] = false;
} }
console_len_ = 0;
engine_wakeup_ = false; engine_wakeup_ = false;
chbuf.reset(new char[65536]); chbuf.reset(new char[65536]);
} }
@@ -182,16 +179,16 @@ public:
} }
} }
// This is painful. Win32 allows nonblocking read of keyboard events. But // We're feeding raw console characters to the DrivenEngine layer.
// it doesn't have any way to do nonblocking read of processed lines. So we // The DrivenEngine channel is expected to be in readline mode,
// have to read individual events and do the line processing ourselves. // which will handle echoing and line processing.
void handle_console_input() { void handle_console_input() {
HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE); HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
assert(hstdin != INVALID_HANDLE_VALUE); assert(hstdin != INVALID_HANDLE_VALUE);
assert(hstdout != INVALID_HANDLE_VALUE);
INPUT_RECORD inrecords[512]; INPUT_RECORD inrecords[512];
DWORD nread, nevents, nwrote; DWORD nread, nevents;
char ascii[512];
int nascii = 0;
if (GetNumberOfConsoleInputEvents(hstdin, &nevents)) { if (GetNumberOfConsoleInputEvents(hstdin, &nevents)) {
if (nevents > 512) nevents = 512; if (nevents > 512) nevents = 512;
ReadConsoleInputA(hstdin, inrecords, nevents, &nread); ReadConsoleInputA(hstdin, inrecords, nevents, &nread);
@@ -201,28 +198,11 @@ public:
const KEY_EVENT_RECORD &key = inr.Event.KeyEvent; const KEY_EVENT_RECORD &key = inr.Event.KeyEvent;
if (!key.bKeyDown) continue; if (!key.bKeyDown) continue;
char c = key.uChar.AsciiChar; char c = key.uChar.AsciiChar;
if ((c == '\r') || (c == '\n')) { ascii[nascii++] = c;
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);
}
}
} }
driven_->drv_recv_incoming(0, nascii, ascii);
} }
engine_wakeup_ = true;
} }
void handle_clock() { void handle_clock() {

View File

@@ -27,6 +27,7 @@ public:
while (true) { while (true) {
UniqueChannel ch = new_incoming_channel(); UniqueChannel ch = new_incoming_channel();
if (ch == nullptr) break; if (ch == nullptr) break;
ch->set_readline(true);
channels_.emplace_back(std::move(ch)); channels_.emplace_back(std::move(ch));
} }

View File

@@ -170,18 +170,19 @@ void TextGame::check_redirects() {
} }
} }
void TextGame::event_init()
{
world_.reset(new World(util::WORLD_TYPE_STANDALONE));
world_->update_source(get_lua_source());
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());
}
void TextGame::event_update() void TextGame::event_update()
{ {
if (world_ == nullptr) {
world_.reset(new World(util::WORLD_TYPE_STANDALONE));
world_->update_source(get_lua_source());
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()); world_->update_source(get_lua_source());
while (true) { while (true) {
std::string line = get_stdio_channel()->in()->readline(); std::string line = get_stdio_channel()->in()->readline();
if (line == "") break; if (line == "") break;

View File

@@ -29,6 +29,7 @@ private:
void check_redirects(); void check_redirects();
public: public:
virtual void event_init();
virtual void event_update(); virtual void event_update();
}; };