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;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user