Progress on mingw driver
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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,31 +218,22 @@ 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);
|
||||
void drv_get_new_closed(std::set<int> &opened);
|
||||
|
||||
// Run in non-logging mode. In this mode, no logfile will be read or written
|
||||
// in this mode.
|
||||
// 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_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_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
|
||||
@@ -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_;
|
||||
|
||||
261
luprex/core/cpp/driver-mingw.cpp
Normal file
261
luprex/core/cpp/driver-mingw.cpp
Normal 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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "idalloc.hpp"
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <ostream>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -277,6 +277,7 @@ 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.
|
||||
//
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -540,7 +540,7 @@ void World::snapshot() {
|
||||
}
|
||||
|
||||
void World::rollback() {
|
||||
assert(!snapshot_.at_eof());
|
||||
assert(!snapshot_.empty());
|
||||
deserialize(&snapshot_);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user