Move readline functionality into device-independent code
This commit is contained in:
@@ -9,6 +9,10 @@ Channel::Channel(DrivenEngine *de, int chid, int port, const std::string &target
|
||||
port_ = port;
|
||||
closed_ = false;
|
||||
target_ = target;
|
||||
readline_enabled_ = (chid == 0);
|
||||
readline_len_ = 0;
|
||||
readline_lastc_ = 0;
|
||||
echo_len_ = 0;
|
||||
assert(driven_->channels_[chid_] == nullptr);
|
||||
driven_->channels_[chid_] = this;
|
||||
}
|
||||
@@ -20,6 +24,50 @@ Channel::~Channel() {
|
||||
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() {
|
||||
// Note: channel ID zero is special, it is never reused.
|
||||
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) {
|
||||
Channel *ch = get_chid(chid);
|
||||
*nbytes = ch->sb_out_->fill();
|
||||
*bytes = ch->sb_out_->data();
|
||||
if (ch->echo_len_ > 0) {
|
||||
*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) {
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,20 @@ public:
|
||||
//
|
||||
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
|
||||
// the channel.
|
||||
//
|
||||
@@ -134,7 +148,13 @@ private:
|
||||
//
|
||||
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:
|
||||
static const int READLINE_MAX=512;
|
||||
DrivenEngine *driven_;
|
||||
int chid_;
|
||||
std::unique_ptr<StreamBuffer> sb_in_;
|
||||
@@ -142,6 +162,12 @@ private:
|
||||
int port_;
|
||||
bool closed_;
|
||||
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;
|
||||
};
|
||||
using UniqueChannel = std::unique_ptr<Channel>;
|
||||
|
||||
@@ -20,8 +20,6 @@ public:
|
||||
SOCKET socket_[MAX_CHAN];
|
||||
bool connected_[MAX_CHAN];
|
||||
bool engine_wakeup_;
|
||||
char console_line_[CONSOLE_MAX + 1];
|
||||
int console_len_;
|
||||
std::map<int, SOCKET> listen_sockets_;
|
||||
std::unique_ptr<char> chbuf;
|
||||
|
||||
@@ -113,7 +111,6 @@ public:
|
||||
socket_[i] = INVALID_SOCKET;
|
||||
connected_[i] = false;
|
||||
}
|
||||
console_len_ = 0;
|
||||
engine_wakeup_ = false;
|
||||
chbuf.reset(new char[65536]);
|
||||
}
|
||||
@@ -182,16 +179,16 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// We're feeding raw console characters to the DrivenEngine layer.
|
||||
// The DrivenEngine channel is expected to be in readline mode,
|
||||
// which will handle echoing and line processing.
|
||||
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;
|
||||
DWORD nread, nevents;
|
||||
char ascii[512];
|
||||
int nascii = 0;
|
||||
if (GetNumberOfConsoleInputEvents(hstdin, &nevents)) {
|
||||
if (nevents > 512) nevents = 512;
|
||||
ReadConsoleInputA(hstdin, inrecords, nevents, &nread);
|
||||
@@ -201,28 +198,11 @@ public:
|
||||
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);
|
||||
}
|
||||
}
|
||||
ascii[nascii++] = c;
|
||||
}
|
||||
driven_->drv_recv_incoming(0, nascii, ascii);
|
||||
}
|
||||
engine_wakeup_ = true;
|
||||
}
|
||||
|
||||
void handle_clock() {
|
||||
|
||||
@@ -27,6 +27,7 @@ public:
|
||||
while (true) {
|
||||
UniqueChannel ch = new_incoming_channel();
|
||||
if (ch == nullptr) break;
|
||||
ch->set_readline(true);
|
||||
channels_.emplace_back(std::move(ch));
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
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());
|
||||
|
||||
while (true) {
|
||||
std::string line = get_stdio_channel()->in()->readline();
|
||||
if (line == "") break;
|
||||
|
||||
@@ -29,6 +29,7 @@ private:
|
||||
void check_redirects();
|
||||
|
||||
public:
|
||||
virtual void event_init();
|
||||
virtual void event_update();
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user