Refactor for DLL

This commit is contained in:
2023-02-14 13:14:18 -05:00
parent fb48329090
commit 569b8aef45
18 changed files with 2821 additions and 383 deletions

View File

@@ -3,9 +3,13 @@
#include "util.hpp"
#include "drivenengine.hpp"
#include <string_view>
#include <utility>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <fstream>
#include <cassert>
DrivenEngineReg *DrivenEngineReg::All;
@@ -16,23 +20,57 @@ DrivenEngineReg::DrivenEngineReg(const char *n, DrivenEngineMaker fn) {
All = this;
}
void DrivenEngine::print_usage(std::ostream &strm, std::string_view progname) {
strm << "Usage: " << progname << " <mode>" << std::endl;
for (auto reg = DrivenEngineReg::All; reg != nullptr; reg=reg->next) {
strm << " Mode can be: " << reg->name << std::endl;
}
DrivenEngineInitializer DrivenEngineInitializerReg::func;
DrivenEngineInitializerReg::DrivenEngineInitializerReg(DrivenEngineInitializer fn) {
assert(func == nullptr);
func = fn;
}
UniqueDrivenEngine DrivenEngine::make(std::string_view kind) {
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// DrivenEngine private methods
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
int DrivenEngine::find_unused_chid() {
// Note: channel ID zero is special, it is never reused.
for (int i = 0; i < DRV_MAX_CHAN; i++) {
int id = next_unused_chid_++;
if (next_unused_chid_ == DRV_MAX_CHAN) next_unused_chid_ = 1;
if (channels_[id] == nullptr) return id;
}
assert(false);
return 0;
}
Channel *DrivenEngine::get_chid(int chid) const {
assert(unsigned(chid) < DRV_MAX_CHAN);
assert(channels_[chid].get() != nullptr);
return channels_[chid].get();
}
static DrivenEngine *make_engine(std::string_view kind) {
for (auto reg = DrivenEngineReg::All; reg != nullptr; reg=reg->next) {
if (kind == std::string_view(reg->name)) {
if (kind == reg->name) {
UniqueDrivenEngine result = reg->maker();
return result;
return result.release();
}
}
return nullptr;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// Class Channel
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
Channel::Channel(DrivenEngine *de, int chid, int port, const eng::string &target, bool stop) {
chid_ = chid;
port_ = port;
@@ -136,24 +174,16 @@ void Channel::sent_outgoing(int nbytes) {
sb_drvout_->read_bytes(nbytes);
}
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) const {
assert(unsigned(chid) < MAX_CHAN);
assert(channels_[chid].get() != nullptr);
return channels_[chid].get();
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// DrivenEngine Client-Side API
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
void DrivenEngine::listen_port(int port) {
assert(listen_ports_.size() < DRV_MAX_LISTEN_PORTS);
listen_ports_.push_back(port);
}
@@ -193,92 +223,255 @@ void DrivenEngine::rescan_lua_source() {
void DrivenEngine::stop_driver() {
stop_driver_ = true;
for (int i = 0; i < MAX_CHAN; i++) {
for (int i = 0; i < DRV_MAX_CHAN; i++) {
if (channels_[i] != nullptr) {
channels_[i]->stop_driver_ = true;
}
}
}
const eng::vector<int> &DrivenEngine::drv_get_listen_ports() const {
return listen_ports_;
DrivenEngine::DrivenEngine() {
next_unused_chid_ = 1;
stdio_channel_ = eng::make_shared<Channel>(this, 0, 0, "", false);
stdio_channel_->sb_drvout_ = eng::make_shared<StreamBuffer>();
channels_[0] = stdio_channel_;
rescan_lua_source_ = false;
clock_ = 0.0;
stop_driver_ = false;
}
const eng::vector<int> &DrivenEngine::drv_get_new_outgoing() const {
return new_outgoing_;
DrivenEngine::~DrivenEngine() {}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// LOGFILE EVENT IDS.
//
// There's one event ID for each mutator, plus one for 'release'.
//
// There are no event IDs for getters, these aren't considered loggable events.
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
enum DrvAction {
PLAY_INITIALIZE,
PLAY_CLEAR_NEW_OUTGOING,
PLAY_SENT_OUTGOING,
PLAY_RECV_INCOMING,
PLAY_NOTIFY_CLOSE,
PLAY_NOTIFY_ACCEPT,
PLAY_INVOKE_EVENT_UPDATE,
PLAY_SET_LUA_SOURCE,
PLAY_RELEASE,
};
inline static const char *action_string(DrvAction act) {
switch(act) {
case PLAY_INITIALIZE: return "PLAY_INITIALIZE";
case PLAY_CLEAR_NEW_OUTGOING: return "PLAY_CLEAR_NEW_OUTGOING";
case PLAY_SENT_OUTGOING: return "PLAY_SENT_OUTGOING";
case PLAY_RECV_INCOMING: return "PLAY_RECV_INCOMING";
case PLAY_NOTIFY_CLOSE: return "PLAY_NOTIFY_CLOSE";
case PLAY_NOTIFY_ACCEPT: return "PLAY_NOTIFY_ACCEPT";
case PLAY_SET_LUA_SOURCE: return "PLAY_SET_LUA_SOURCE";
case PLAY_INVOKE_EVENT_UPDATE: return "PLAY_INVOKE_EVENT_UPDATE";
case PLAY_RELEASE: return "PLAY_RELEASE";
default: return "unknown";
}
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// RLOG and WLOG, functions to read and write binary data to logfiles.
//
// After doing an rlog operation, you should check the stream
// for "good" to find out if there was any error.
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
class PlayLogfile : public std::ofstream { using std::ofstream::ofstream; };
class ReplayLogfile : public std::ifstream { using std::ifstream::ifstream; };
static uint8_t rlog_uint8(EngineWrapper *w) {
uint8_t result;
w->rlog->read((char *)&result, 1);
if (!w->rlog->good()) return 0;
return result;
}
void DrivenEngine::drv_clear_new_outgoing() {
new_outgoing_.clear();
static uint32_t rlog_uint32(EngineWrapper *w) {
uint32_t result;
w->rlog->read((char *)&result, 4);
if (!w->rlog->good()) return 0;
return result;
}
std::string_view DrivenEngine::drv_get_target(int chid) const {
return get_chid(chid)->target_;
static uint64_t rlog_uint64(EngineWrapper *w) {
uint64_t result;
w->rlog->read((char *)&result, 8);
if (!w->rlog->good()) return 0;
return result;
}
bool DrivenEngine::drv_outgoing_empty(int chid) const {
std::string_view view = drv_peek_outgoing(chid);
return (view.size() == 0);
static double rlog_double(EngineWrapper *w) {
double result;
w->rlog->read((char *)&result, 8);
if (!w->rlog->good()) return 0.0;
return result;
}
bool DrivenEngine::drv_get_channel_released(int chid) const {
std::string_view rlog_short_string(EngineWrapper *w) {
uint32_t len = rlog_uint8(w);
if (len == 255) {
len = rlog_uint32(w);
}
assert (len <= DRV_SHORTSTRING_SIZE);
if (len > 0) w->rlog->read(w->databuffer, len);
if (!w->rlog->good()) return std::string_view();
return std::string_view(w->databuffer, len);
}
std::string rlog_string(EngineWrapper *w) {
uint32_t len = rlog_uint8(w);
if (len == 255) {
len = rlog_uint32(w);
}
std::string result(len, ' ');
if (len > 0) w->rlog->read(&result[0], len);
if (!w->rlog->good()) return "";
return result;
}
static void wlog_uint8(EngineWrapper *w, uint8_t v) {
w->wlog->put((char)v);
}
static void wlog_uint32(EngineWrapper *w, uint32_t v) {
w->wlog->write((const char *)&v, 4);
}
static void wlog_uint64(EngineWrapper *w, uint64_t v) {
w->wlog->write((const char *)&v, 8);
}
static void wlog_double(EngineWrapper *w, double v) {
w->wlog->write((const char *)&v, 8);
}
static void wlog_short_string(EngineWrapper *w, std::string_view v) {
assert (v.size() <= DRV_SHORTSTRING_SIZE);
if (v.size() >= 255) {
wlog_uint8(w, 0xFF);
wlog_uint32(w, v.size());
} else {
wlog_uint8(w, v.size());
}
w->wlog->write(v.data(), v.size());
}
static void wlog_string(EngineWrapper *w, std::string_view v) {
if (v.size() >= 255) {
wlog_uint8(w, 0xFF);
wlog_uint32(w, v.size());
} else {
wlog_uint8(w, v.size());
}
w->wlog->write(v.data(), v.size());
}
static void wlog_cmd_hash(EngineWrapper *w, DrvAction act, uint32_t hash) {
wlog_uint8(w, act);
wlog_uint32(w, hash);
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// reset_wrapper
//
// Shut down a EngineWrapper, store an optional error message.
//
// release
//
// Shut down an EngineWrapper cleanly, with no error message, and
// log the step if the logfile is open.
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
static void reset_wrapper(EngineWrapper *w, const char *format, ...) {
va_list argp;
va_start(argp, format);
memset(w->error, 0, DRV_ERRMSG_SIZE);
vsnprintf(w->error, DRV_ERRMSG_SIZE, format, argp);
w->error[DRV_ERRMSG_SIZE - 1] = 0;
if (w->wlog != nullptr) {
w->wlog->close();
delete w->wlog;
w->wlog = nullptr;
}
if (w->rlog != nullptr) {
w->rlog->close();
delete w->rlog;
w->rlog = nullptr;
}
if (w->engine != nullptr) {
delete w->engine;
w->engine = nullptr;
}
}
static void release(EngineWrapper *w) {
if (w->wlog != nullptr) {
wlog_cmd_hash(w, PLAY_RELEASE, eng::memhash());
}
reset_wrapper(w, "");
};
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// DRIVER Methods: Getters
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
void DrivenEngine::drv_get_listen_ports(uint32_t *nports, const uint32_t **ports) const {
*nports = listen_ports_.size();
*ports = &listen_ports_[0];
}
void DrivenEngine::drv_get_new_outgoing(uint32_t *nchids, const uint32_t **chids) const {
*nchids = new_outgoing_.size();
*chids = &new_outgoing_[0];
}
const char *DrivenEngine::drv_get_target(uint32_t chid) const {
return get_chid(chid)->target_.c_str();
}
bool DrivenEngine::drv_get_channel_released(uint32_t chid) const {
return channels_[chid].use_count() == 1;
}
std::string_view DrivenEngine::drv_peek_outgoing(int chid) const {
return get_chid(chid)->peek_outgoing();
void DrivenEngine::drv_get_outgoing(uint32_t chid, uint32_t *len, const char **data) const {
std::string_view v = get_chid(chid)->peek_outgoing();
*len = v.size();
*data = v.data();
}
void DrivenEngine::drv_sent_outgoing(int chid, int nbytes) {
return get_chid(chid)->sent_outgoing(nbytes);
bool DrivenEngine::drv_get_outgoing_empty(uint32_t chid) const {
std::string_view v = get_chid(chid)->peek_outgoing();
return (v.size() == 0);
}
void DrivenEngine::drv_recv_incoming(int chid, std::string_view data) {
if (data.size() > 0) {
Channel *ch = get_chid(chid);
if (ch->sb_drvout_ != ch->sb_out_) {
ch->feed_readline(data);
} else {
ch->sb_in_->write_bytes(data);
}
}
}
void DrivenEngine::drv_notify_close(int chid, std::string_view err) {
Channel *ch = get_chid(chid);
ch->closed_ = true;
ch->error_ = err;
channels_[chid].reset();
}
int DrivenEngine::drv_notify_accept(int port) {
int chid = find_unused_chid();
channels_[chid] = eng::make_shared<Channel>(this, chid, port, "", stop_driver_);
accepted_channels_.push_back(channels_[chid]);
return chid;
}
void DrivenEngine::drv_clear_lua_source() {
lua_source_.reset();
rescan_lua_source_ = false;
}
void DrivenEngine::drv_add_lua_source(std::string_view fn, std::string_view data) {
if (lua_source_ == nullptr) {
lua_source_.reset(new util::LuaSourceVec);
}
lua_source_->emplace_back(eng::string(fn), eng::string(data));
}
void DrivenEngine::drv_invoke_event_init(int argc, char *argv[]) {
event_init(argc, argv);
stdio_channel_->pump_readline();
}
void DrivenEngine::drv_invoke_event_update(double clock) {
clock_ = clock;
event_update();
stdio_channel_->pump_readline();
double DrivenEngine::drv_get_clock() const {
return clock_;
}
bool DrivenEngine::drv_get_rescan_lua_source() const {
@@ -289,25 +482,539 @@ bool DrivenEngine::drv_get_stop_driver() const {
return stop_driver_;
}
DrivenEngine::DrivenEngine() {
next_unused_chid_ = 1;
stdio_channel_ = eng::make_shared<Channel>(this, 0, 0, "", false);
stdio_channel_->sb_drvout_ = eng::make_shared<StreamBuffer>();
channels_[0] = stdio_channel_;
rescan_lua_source_ = true;
clock_ = 0.0;
stop_driver_ = false;
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// DRIVER Methods: Mutators
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
void DrivenEngine::drv_initialize(uint32_t srcpklen, const char *srcpk, int argc, char **argv) {
drv_set_lua_source(srcpklen, srcpk);
event_init(argc, argv);
stdio_channel_->pump_readline();
}
DrivenEngine::~DrivenEngine() {}
static DrivenEngine *engine_;
void DrivenEngine::set(DrivenEngine *de) {
engine_ = de;
void DrivenEngine::drv_clear_new_outgoing() {
new_outgoing_.clear();
}
DrivenEngine *DrivenEngine::get() {
return engine_;
void DrivenEngine::drv_sent_outgoing(uint32_t chid, uint32_t nbytes) {
return get_chid(chid)->sent_outgoing(nbytes);
}
void DrivenEngine::drv_recv_incoming(uint32_t chid, uint32_t nbytes, const char *bytes) {
if (nbytes > 0) {
Channel *ch = get_chid(chid);
if (ch->sb_drvout_ != ch->sb_out_) {
ch->feed_readline(bytes);
} else {
ch->sb_in_->write_bytes(bytes);
}
}
}
void DrivenEngine::drv_notify_close(uint32_t chid, uint32_t len, const char *data) {
Channel *ch = get_chid(chid);
ch->closed_ = true;
ch->error_ = std::string(data, len);
channels_[chid].reset();
}
uint32_t DrivenEngine::drv_notify_accept(uint32_t port) {
int chid = find_unused_chid();
channels_[chid] = eng::make_shared<Channel>(this, chid, port, "", stop_driver_);
accepted_channels_.push_back(channels_[chid]);
return chid;
}
void DrivenEngine::drv_invoke_event_update(double clock) {
clock_ = clock;
event_update();
stdio_channel_->pump_readline();
}
void DrivenEngine::drv_set_lua_source(uint32_t srcpklen, const char *srcpk) {
StreamBuffer sb(srcpk, srcpklen);
uint32_t nfiles = sb.read_uint32();
lua_source_.reset(new util::LuaSourceVec);
lua_source_->resize(nfiles);
for (uint32_t i = 0; i < nfiles; i++) {
(*lua_source_)[i].first = sb.read_string();
}
for (uint32_t i = 0; i < nfiles; i++) {
(*lua_source_)[i].second = sb.read_string();
}
rescan_lua_source_ = false;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// C Wrappers: Getters
//
// These wrappers make it possible to call the drv_get routines using C
// functions instead of methods. This is important if the engine is compiled
// with one C++ compiler, but the driver is compiled with a different C++
// compiler.
//
// Some of these take parameter 'EngineWrapper', some take 'EngineWrapper',
// and some come in two versions. This all depends on whether they are used
// during play, during replay, or both.
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
static void drv_get_listen_ports(EngineWrapper *w, uint32_t *nports, const uint32_t **ports) {
return w->engine->drv_get_listen_ports(nports, ports);
}
static void drv_get_new_outgoing(EngineWrapper *w, uint32_t *nchanids, const uint32_t **chanids) {
return w->engine->drv_get_new_outgoing(nchanids, chanids);
}
static const char *drv_get_target(EngineWrapper *w, uint32_t chid) {
return w->engine->drv_get_target(chid);
}
static bool drv_get_channel_released(EngineWrapper *w, uint32_t chid) {
return w->engine->drv_get_channel_released(chid);
}
static void drv_get_outgoing(EngineWrapper *w, uint32_t chid, uint32_t *len, const char **data) {
return w->engine->drv_get_outgoing(chid, len, data);
}
static bool drv_get_outgoing_empty(EngineWrapper *w, uint32_t chid) {
return w->engine->drv_get_outgoing_empty(chid);
}
static double drv_get_clock(EngineWrapper *w) {
return w->engine->drv_get_clock();
}
static bool drv_get_rescan_lua_source(EngineWrapper *w) {
return w->engine->drv_get_rescan_lua_source();
}
static bool drv_get_stop_driver(EngineWrapper *w) {
return w->engine->drv_get_stop_driver();
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// C Wrappers: Mutators
//
// The wrapper for a mutator consists of two parts: the wrapper which is used at
// 'play' time, and the wrapper which is used at 'replay' time.
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
static void play_initialize(EngineWrapper *w, uint32_t argc, char **argv, uint32_t srcpklen, const char *srcpk, const char *logfn) {
if (w->engine != nullptr) {
return reset_wrapper(w, "Cannot initialize wrapper, it's already initialized.");
}
// Clear the error message.
memset(w->error, 0, DRV_ERRMSG_SIZE);
// Open the logfile, if any is specified.
if ((logfn != nullptr) && (logfn[0] != 0)) {
w->wlog = new PlayLogfile(logfn, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc);
if (!w->wlog->good()) {
return reset_wrapper(w, "Could not open replay log for writing: %s", logfn);
}
} else {
w->wlog = nullptr;
}
// If we have a logfile, then log this initialization.
if (w->wlog != nullptr) {
wlog_cmd_hash(w, PLAY_INITIALIZE, eng::memhash());
wlog_uint32(w, argc);
for (uint32_t i = 0; i < argc; i++) {
wlog_string(w, argv[i]);
}
wlog_string(w, std::string_view(srcpk, srcpklen));
w->wlog->flush();
}
// Create the engine of the appropriate type.
if (argc < 1) {
std::ostringstream oss;
oss << "Must pass an engine type on the command line. Known types:\n";
for (auto reg = DrivenEngineReg::All; reg != nullptr; reg=reg->next) {
oss << " " << reg->name << std::endl;
}
std::string err = oss.str();
return reset_wrapper(w, err.c_str());
}
w->engine = make_engine(argv[0]);
if (w->engine == nullptr) {
return reset_wrapper(w, "No such driven engine type: %s", argv[0]);
}
// Call the engine initialization sequence.
w->engine->drv_initialize(srcpklen, srcpk, argc - 1, argv + 1);
}
static void replay_initialize(EngineWrapper *w) {
assert(w->rlog != nullptr);
std::vector<std::string> argvstr;
uint32_t argc = rlog_uint32(w);
for (uint32_t i = 0; i < argc; i++) {
argvstr.push_back(rlog_string(w));
}
std::string srcpk = rlog_string(w);
if (!w->rlog->good()) {
return reset_wrapper(w, "replay log corrupt in replay_initialize");
}
// We need to convert the argument vector from an array
// of C++ strings into the canonical argc, argv format.
std::vector<char *> argvec;
for (uint32_t i = 0; i < argc; i++) {
argvec.push_back(&argvstr[i][0]);
}
char **argv = &argvec[0];
// Create the engine.
w->engine = make_engine(argv[0]);
if (w->engine == nullptr) {
return reset_wrapper(w, "No such driven engine type: %s", argvstr[0]);
}
w->engine->drv_initialize(srcpk.size(), srcpk.c_str(), argc - 1, argv + 1);
}
////////////////////////
static void play_clear_new_outgoing(EngineWrapper *w) {
assert(w->rlog == nullptr);
if (w->wlog != nullptr) {
wlog_cmd_hash(w, PLAY_CLEAR_NEW_OUTGOING, eng::memhash());
w->wlog->flush();
}
w->engine->drv_clear_new_outgoing();
}
static void replay_clear_new_outgoing(EngineWrapper *w) {
w->engine->drv_clear_new_outgoing();
}
////////////////////////
static void play_sent_outgoing(EngineWrapper *w, uint32_t chid, uint32_t nbytes) {
assert(w->rlog == nullptr);
if (w->wlog != nullptr) {
uint32_t ndata; const char *data;
w->engine->drv_get_outgoing(chid, &ndata, &data);
assert(nbytes <= ndata);
wlog_cmd_hash(w, PLAY_SENT_OUTGOING, eng::memhash());
wlog_uint32(w, chid);
wlog_uint32(w, nbytes);
wlog_uint64(w, SpookyHash::QkHash64(data, nbytes));
w->wlog->flush();
}
w->engine->drv_sent_outgoing(chid, nbytes);
}
static void replay_sent_outgoing(EngineWrapper *w) {
uint32_t chid = rlog_uint32(w);
uint32_t nbytes = rlog_uint32(w);
uint64_t hash = rlog_uint64(w);
if (!w->rlog->good()) {
return reset_wrapper(w, "replay log corrupt in replay_sent_outgoing");
}
uint32_t ndata; const char *data;
w->engine->drv_get_outgoing(chid, &ndata, &data);
if ((nbytes > ndata) || (hash != SpookyHash::QkHash64(data, nbytes))) {
return reset_wrapper(w, "nondeterministic in replay_sent_outgoing");
}
w->engine->drv_sent_outgoing(chid, nbytes);
}
////////////////////////
static void play_recv_incoming(EngineWrapper *w, uint32_t chid, uint32_t len, const char *data) {
assert(w->rlog == nullptr);
if (w->wlog != nullptr) {
wlog_cmd_hash(w, PLAY_RECV_INCOMING, eng::memhash());
wlog_uint32(w, chid);
wlog_short_string(w, std::string_view(data, len));
w->wlog->flush();
}
w->engine->drv_recv_incoming(chid, len, data);
}
static void replay_recv_incoming(EngineWrapper *w) {
uint32_t chid = rlog_uint32(w);
std::string_view data = rlog_short_string(w);
if (!w->rlog->good()) {
return reset_wrapper(w, "replay log corrupt in replay_recv_incoming");
}
w->engine->drv_recv_incoming(chid, data.size(), data.data());
}
////////////////////////
static void play_notify_close(EngineWrapper *w, uint32_t chid, uint32_t len, const char *data) {
assert(w->rlog == nullptr);
if (w->wlog != nullptr) {
wlog_cmd_hash(w, PLAY_NOTIFY_CLOSE, eng::memhash());
wlog_uint32(w, chid);
wlog_string(w, std::string_view(data, len));
w->wlog->flush();
}
w->engine->drv_notify_close(chid, len, data);
}
static void replay_notify_close(EngineWrapper *w) {
uint32_t chid = rlog_uint32(w);
std::string message = rlog_string(w);
if (!w->rlog->good()) {
return reset_wrapper(w, "replay log corrupt in replay_notify_close");
}
w->engine->drv_notify_close(chid, message.size(), message.c_str());
}
////////////////////////
static uint32_t play_notify_accept(EngineWrapper *w, uint32_t port) {
assert(w->rlog == nullptr);
if (w->wlog != nullptr) {
wlog_cmd_hash(w, PLAY_NOTIFY_ACCEPT, eng::memhash());
wlog_uint32(w, port);
w->wlog->flush();
}
return w->engine->drv_notify_accept(port);
}
static void replay_notify_accept(EngineWrapper *w) {
uint32_t port = rlog_uint32(w);
if (!w->rlog->good()) {
return reset_wrapper(w, "replay log corrupt in replay_notify_accept");
}
w->engine->drv_notify_accept(port);
}
////////////////////////
static void play_invoke_event_update(EngineWrapper *w, double clock) {
assert(w->rlog == nullptr);
if (w->wlog != nullptr) {
wlog_cmd_hash(w, PLAY_INVOKE_EVENT_UPDATE, eng::memhash());
wlog_double(w, clock);
w->wlog->flush();
}
w->engine->drv_invoke_event_update(clock);
}
static void replay_invoke_event_update(EngineWrapper *w) {
double clock = rlog_double(w);
if (!w->rlog->good()) {
return reset_wrapper(w, "replay log corrupt in replay_event_update");
}
w->engine->drv_invoke_event_update(clock);
}
////////////////////////
void play_set_lua_source(EngineWrapper *w, uint32_t srcpklen, const char *srcpk) {
assert(w->rlog == nullptr);
if (w->wlog != nullptr) {
wlog_cmd_hash(w, PLAY_SET_LUA_SOURCE, eng::memhash());
wlog_string(w, std::string_view(srcpk, srcpklen));
w->wlog->flush();
}
w->engine->drv_set_lua_source(srcpklen, srcpk);
}
void replay_set_lua_source(EngineWrapper *w) {
std::string srcpack = rlog_string(w);
if (!w->rlog->good()) {
return reset_wrapper(w, "replay log corrupt in replay_set_lua_source");
}
w->engine->drv_set_lua_source(srcpack.size(), srcpack.c_str());
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// Replay Core
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
static void replaycore_initialize(EngineWrapper *w, const char *logfn) {
std::cerr << "Memhash before replaycore_initialize: " << eng::memhash() << std::endl;
if (w->engine != nullptr) {
return reset_wrapper(w, "Cannot initialize wrapper, it's already initialized.");
return;
}
// Clear the error message.
memset(w->error, 0, DRV_ERRMSG_SIZE);
// Open the logfile.
w->rlog = new ReplayLogfile(logfn, std::ios_base::in | std::ios_base::binary);
if (!w->rlog->good()) {
return reset_wrapper(w, "Could not open replay log for reading: %s", logfn);
}
// Read one step from the logfile, and make sure it's an initialize step.
uint8_t code = rlog_uint8(w);
int hash = rlog_uint32(w);
if (!w->rlog->good()) {
return reset_wrapper(w, "logfile corrupt");
}
if (hash != eng::memhash()) {
return reset_wrapper(w, "nondeterminism detected in initial step");
}
if (code != PLAY_INITIALIZE) {
return reset_wrapper(w, "replay log doesn't begin with initialize step");
}
// Replay the initialize step from the logfile.
// Doing this immediately, rather than waiting for the driver
// to call 'step', enforces the invariant that after calling
// initialize, there's an engine.
replay_initialize(w);
}
static void replaycore_step(EngineWrapper *w) {
if (w->rlog == nullptr) {
return;
}
uint8_t code = rlog_uint8(w);
int hash = rlog_uint32(w);
if (!w->rlog->good()) {
return reset_wrapper(w, "logfile corrupt");
}
if (hash != eng::memhash()) {
return reset_wrapper(w, "nondeterminism detected");
}
switch (code) {
case PLAY_CLEAR_NEW_OUTGOING: replay_clear_new_outgoing(w); return;
case PLAY_SENT_OUTGOING: replay_sent_outgoing(w); return;
case PLAY_RECV_INCOMING: replay_recv_incoming(w); return;
case PLAY_NOTIFY_CLOSE: replay_notify_close(w); return;
case PLAY_NOTIFY_ACCEPT: replay_notify_accept(w); return;
case PLAY_SET_LUA_SOURCE: replay_set_lua_source(w); return;
case PLAY_INVOKE_EVENT_UPDATE: replay_invoke_event_update(w); return;
case PLAY_RELEASE: release(w); return;
default: return reset_wrapper(w, "Replay log corrupt in command dispatcher");
}
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// General Mutators
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// Wrapper Initialization
//
// To access the engine across a DLL boundary, you first use
// GetProcAddress or dlsym to fetch the addresses of 'init_play_engine'
// and 'init_replay_engine'. Then, you use those two functions to
// initialize a EngineWrapper or a EngineWrapper, which contain the addresses
// of all the other functions you need. These are the only two functions
// marked 'DLLEXPORT', all other functions are exported from the DLL
// indirectly.
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
#if defined(__linux__)
#define DLLEXPORT __attribute__((visibility("default")))
#elif defined(_WIN32)
#define DLLEXPORT __declspec(dllexport)
#endif
static void init_engine_wrapper_helper(EngineWrapper *w) {
static bool called_initializer;
assert(DrivenEngineInitializerReg::func != nullptr);
if (!called_initializer) {
DrivenEngineInitializerReg::func();
called_initializer = true;
}
memset(w, 0, sizeof(EngineWrapper));
w->get_listen_ports = drv_get_listen_ports;
w->get_new_outgoing = drv_get_new_outgoing;
w->get_target = drv_get_target;
w->get_channel_released = drv_get_channel_released;
w->get_outgoing = drv_get_outgoing;
w->get_outgoing_empty = drv_get_outgoing_empty;
w->get_clock = drv_get_clock;
w->get_rescan_lua_source = drv_get_rescan_lua_source;
w->get_stop_driver = drv_get_stop_driver;
w->play_initialize = play_initialize;
w->play_clear_new_outgoing = play_clear_new_outgoing;
w->play_sent_outgoing = play_sent_outgoing;
w->play_recv_incoming = play_recv_incoming;
w->play_notify_close = play_notify_close;
w->play_notify_accept = play_notify_accept;
w->play_invoke_event_update = play_invoke_event_update;
w->play_set_lua_source = play_set_lua_source;
w->replay_initialize = replaycore_initialize;
w->replay_step = replaycore_step;
w->release = release;
};
extern "C" {
DLLEXPORT void init_engine_wrapper(EngineWrapper *w) {
init_engine_wrapper_helper(w);
}
}

View File

@@ -2,12 +2,9 @@
//
// DrivenEngine
//
// 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.
//
// The DrivenEngine module provides two APIs: the 'engine-side' API, and the
// 'driver-side' API.
// This module embodies the idea of an "event-driven game engine." The
// DrivenEngine module provides two APIs: the engine-side API, and the
// driver-side API.
//
// 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,
@@ -41,53 +38,6 @@
// machine, free of all OS-specific code.
//
//////////////////////////////////////////////////////////////
//
// Here are the rules for what the driver must do:
//
// * Before doing anything else, the driver must select one of the three
// logmodes.
//
// * If 'logmode_replay' is selected, then the driver must proceed to invoke
// 'drv_step_logfile' over and over until it returns false. In replay mode,
// the driver should not do anything else.
//
// * If 'logmode_write' or 'logmode_none' is selected, the driver must proceed
// to drive the application. Follow the remainder of these steps.
//
// * Open a hardwired list of ports for listening.
//
// * Repeat the following steps over and over:
//
// - If the engine asked that the lua source be refreshed, read the source
// from disk and call 'drv_set_lua_source'.
//
// - Get a list of recently-closed channels using drv_get_closed_channels.
// Close any socket associated with these channels and free all resources.
//
// - 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.
//
// - If the poll indicates that a listening port has acceptable
// connections, accept and call drv_notify_accept. Associate the
// accepted socket with the channel.
//
// - If the poll indicates that a connection can accept outgoing data, use
// drv_peek_outgoing to fetch some data to write, and write it. Use
// drv_sent_outgoing_bytes to indicate that the data was sent.
//
// - If the poll indicates that a connection has incoming data, read the
// data then push it into the channel using drv_recv_incoming.
//
// - If the poll indicates that STDIO can be read/written, use
// drv_peek_outgoing, drv_sent_outgoing, and drv_recv_incoming in the
// same manner as you would for a socket.
//
// - Use 'drv_invoke_event_update' to invoke the engine's update callback.
//
//////////////////////////////////////////////////////////////
#ifndef DRIVENENGINE_HPP
#define DRIVENENGINE_HPP
@@ -101,10 +51,12 @@
#include "util.hpp"
#include "streambuffer.hpp"
#include "enginewrapper.hpp"
class DrivenEngine;
using UniqueDrivenEngine = std::unique_ptr<DrivenEngine>;
using DrivenEngineMaker = UniqueDrivenEngine (*)();
using DrivenEngineInitializer = void (*)();
class Channel : public eng::opnew {
public:
@@ -209,10 +161,14 @@ public:
//
//////////////////////////////////////////////////////////////
// The init callback. You may override this in a subclass.
// This will be called once at program initialization.
//
virtual void event_init(int argc, char *argv[]) {}
// The update callback. You may override this in a subclass.
// This will be called whenever anything changes.
//
virtual void event_init(int argc, char *argv[]) {}
virtual void event_update() {}
// Specify the set of listening ports.
@@ -288,111 +244,6 @@ public:
//
void stop_driver();
//////////////////////////////////////////////////////////////
//
// The following methods are the 'driver' side of the pipe.
//
//////////////////////////////////////////////////////////////
// The maximum channel ID plus one.
//
static const int MAX_CHAN = 256;
// Get a list of all the listening ports. The driver is expected
// to fetch this set shortly after the event_init callback is invoked.
//
const eng::vector<int> &drv_get_listen_ports() const;
// 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.
//
const eng::vector<int> &drv_get_new_outgoing() const;
// Clear the list of recently-opened channels that were created using
// drv_new_outgoing_channel.
//
void drv_clear_new_outgoing();
// Get the target of a channel. A target is a string like
// "cert:whatever.com:80" or "nocert:whatever.com:80".
// The first word indicate whether or not a valid SSL certificate
// is required. The second word is the hostname. The third word is
// the port number.
//
std::string_view drv_get_target(int chid) const;
// Return true if the outgoing buffer is empty.
//
bool drv_outgoing_empty(int chid) const;
// Return true if the user has released all references to this channel.
// In this case, the driver should initiate shutdown of the channel,
// and the driver should eventually call drv_notify_close.
//
bool drv_get_channel_released(int chid) const;
// 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.
//
std::string_view drv_peek_outgoing(int chid) const;
// Notifies the channel that some bytes were transmitted. This causes those
// bytes to be removed from the outgoing buffer. This function is used for
// all channels, including sockets and stdio.
//
void drv_sent_outgoing(int chid, int nbytes);
// Notifies the channel that some bytes were received. This causes those
// 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, std::string_view data);
// Notify the channel that the connection was closed. This includes all
// sorts of closes, including friendly termination, all the way to network
// failure. Closing the channel doesn't delete it. The engine is
// responsible for noticing that the channel closed and the engine must
// delete it. Closing a channel prevents it from showing up in
// 'drv_list_channels'.
//
void drv_notify_close(int chid, std::string_view err);
// Notify the DrivenEngine that somebody connected to an incoming port.
// This will cause the DrivenEngine to allocate a new channel and put the
// new channel into the incoming channels queue. Returns the new channel
// ID. The new incoming channel appears in the 'drv_list_channels' list,
// even before the engine pops the channel from the incoming channels queue.
//
int drv_notify_accept(int port);
// Clear the lua source code.
//
void drv_clear_lua_source();
// Set the lua source code. The driver is expected to read the lua source
// code and store it (using this function) once before invoking
//
void drv_add_lua_source(std::string_view fn, std::string_view data);
// Invoke the init or update event.
//
void drv_invoke_event_init(int argc, char *argv[]);
void drv_invoke_event_update(double clock);
// Check the 'rescan_lua_source' flag. If this flag is set, it means
// that the engine wants the driver to rescan the lua source code.
// When the driver sees this flag, it should rescan the source and call
// drv_set_source.
//
bool drv_get_rescan_lua_source() const;
// If true, the engine is done. Stop the driver.
//
bool drv_get_stop_driver() const;
//////////////////////////////////////////////////////////////
//
// Creation and Destruction.
@@ -413,15 +264,38 @@ public:
//
virtual ~DrivenEngine();
// Set/Get Global Pointer.
//////////////////////////////////////////////////////////////
//
// Normally, there is a single global "DrivenEngine" instance.
// We provide a global pointer to store this instance. This is
// a raw pointer, you must manually delete the DrivenEngine.
// The following accessors are for use by PlayWrapper and ReplayWrapper.
//
static void set(DrivenEngine *de);
static DrivenEngine *get();
// The PlayWrapper and ReplayWrapper use C stubs to access
// the engine. The C stubs, in turn, call these C++ methods.
//
// The stubs for the getters are trivial, one-line stubs.
//
// The stubs for the mutators add logging.
//
//////////////////////////////////////////////////////////////
void drv_get_listen_ports(uint32_t *nports, const uint32_t **ports) const;
void drv_get_new_outgoing(uint32_t *nchids, const uint32_t **chids) const;
const char *drv_get_target(uint32_t chid) const;
bool drv_get_channel_released(uint32_t chid) const;
void drv_get_outgoing(uint32_t chid, uint32_t *len, const char **data) const;
bool drv_get_outgoing_empty(uint32_t chid) const;
double drv_get_clock() const;
bool drv_get_rescan_lua_source() const;
bool drv_get_stop_driver() const;
void drv_initialize(uint32_t srcpklen, const char *srcpk, int argc, char **argv);
void drv_clear_new_outgoing();
void drv_sent_outgoing(uint32_t chid, uint32_t nbytes);
void drv_recv_incoming(uint32_t chid, uint32_t nbytes, const char *bytes);
void drv_notify_close(uint32_t chid, uint32_t len, const char *data);
uint32_t drv_notify_accept(uint32_t port);
void drv_invoke_event_update(double clock);
void drv_set_lua_source(uint32_t srcpklen, const char *srcpk);
private:
// Find a currently-unused channel ID. Channel IDs
// are small integers that are reused.
@@ -431,19 +305,23 @@ private:
Channel *get_chid(int chid) const;
private:
SharedChannel channels_[MAX_CHAN];
SharedChannel channels_[DRV_MAX_CHAN];
int next_unused_chid_;
SharedChannel stdio_channel_;
eng::vector<SharedChannel> accepted_channels_;
eng::vector<int> new_outgoing_;
eng::vector<uint32_t> new_outgoing_;
util::LuaSourcePtr lua_source_;
eng::vector<int> listen_ports_;
eng::vector<uint32_t> listen_ports_;
bool rescan_lua_source_;
double clock_;
bool stop_driver_;
friend class Channel;
};
//////////////////////////////////////////////////////////////////////////////////
struct DrivenEngineReg {
const char *name;
DrivenEngineMaker maker;
@@ -458,5 +336,10 @@ struct DrivenEngineReg {
} \
DrivenEngineReg dengreg_##cname(name, dengmake_##cname);
struct DrivenEngineInitializerReg {
static DrivenEngineInitializer func;
DrivenEngineInitializerReg(DrivenEngineInitializer f);
};
#endif // DRIVENENGINE_HPP

View File

@@ -0,0 +1,217 @@
////////////////////////////////////////////////////////////////////////////////
//
// enginewrapper.hpp
//
// This header file contains driver's interface to class DrivenEngine.
// This is meant to be used across a DLL boundary. Since the DLL may have
// been compiled by a different compiler than the driver, we use only simple
// POD types and we only use C calling conventions.
//
// When calling a wrapper function, you must always pass in the wrapper as
// the first parameter.
//
////////////////////////////////////////////////////////////////////////////////
#ifndef ENGINEWRAPPER_H
#define ENGINEWRAPPER_H
#define DRV_MAX_CHAN 256
#define DRV_MAX_LISTEN_PORTS 256
#define DRV_ERRMSG_SIZE 8192
#define DRV_SHORTSTRING_SIZE 65536
class DrivenEngine;
class PlayLogfile;
class ReplayLogfile;
struct EngineWrapper {
char error[DRV_ERRMSG_SIZE];
char databuffer[DRV_SHORTSTRING_SIZE];
DrivenEngine *engine;
PlayLogfile *wlog;
ReplayLogfile *rlog;
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// CONSTRUCTION
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
// Of course, there's no constructor, since this is a C struct.
// To initialize it, you use 'dlsym' or 'GetProcAddress' to get the
// address of the function 'init_engine_wrapper'. Then, you call
// the function init_engine_wrapper(&wrapper).
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// GETTERS
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
// Get a list of all the listening ports. The driver is expected
// to fetch this set shortly after the event_init callback is invoked.
//
void (*get_listen_ports)(EngineWrapper *w, uint32_t *nports, const uint32_t **ports);
// Get a list of all recently-opened channels that were created using
// new_outgoing_channel. The driver should initiate outgoing
// connections for these channels.
//
void (*get_new_outgoing)(EngineWrapper *w, uint32_t *nchanids, const uint32_t **chanids);
// Get a string_view of the target of a channel. A target is a string like
// "cert:whatever.com:80" or "nocert:whatever.com:80".
// The first word indicate whether or not a valid SSL certificate
// is required. The second word is the hostname. The third word is
// the port number. The char string returned here is valid until
// the channel is closed.
//
const char *(*get_target)(EngineWrapper *w, uint32_t chid);
// Return true if the user has released all references to this channel.
// In this case, the driver should initiate shutdown of the channel,
// and the driver should eventually call notify_close.
//
bool (*get_channel_released)(EngineWrapper *w, uint32_t chid);
// Get a pointer to the bytes in the outgoing buffer. The char pointer
// returned here is naturally only valid until the buffer is changed.
// This function is used for all channels, including sockets and stdio.
//
void (*get_outgoing)(EngineWrapper *w, uint32_t chid, uint32_t *len, const char **data);
// Return true if the outgoing buffer is empty.
//
bool (*get_outgoing_empty)(EngineWrapper *w, uint32_t chid);
// Get the clock.
//
// Get the current time. This is equal to the last value passed
// in by invoke_event_update.
//
double (*get_clock)(EngineWrapper *w);
// Check the 'rescan_lua_source' flag. If this flag is set, it means
// that the engine wants the driver to rescan the lua source code.
// When the driver sees this flag, it should rescan the source and call
// set_lua_source.
//
bool (*get_rescan_lua_source)(EngineWrapper *w);
// If true, the engine is done. Stop the driver.
//
bool (*get_stop_driver)(EngineWrapper *w);
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// MUTATORS USED ONLY IN PLAY MODE
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
// Create the driven engine. argc and argv allow you to specify what
// kind of engine you want. You must pass in the initial state of the lua
// source, if you have any. You may optionally also specify a replay log.
// If you don't want to create a replay log, pass a null pointer.
//
// Check to see if the error buffer contains a message after calling
// this function.
//
void (*play_initialize)(EngineWrapper *w, uint32_t argc, char **argv, uint32_t srcpklen, const char *srcpk, const char *logfn);
// Clear the list of recently-opened channels. You are meant to fetch
// new outgoing channels using get_new_outgoing, then you call
// clear_new_outgoing after you've opened those channels.
//
void (*play_clear_new_outgoing)(EngineWrapper *w);
// Notifies the channel that some bytes were transmitted. This causes those
// bytes to be removed from the outgoing buffer. This function is used for
// all channels, including sockets and stdio.
//
void (*play_sent_outgoing)(EngineWrapper *w, uint32_t chid, uint32_t nbytes);
// Notifies the channel that some bytes were received. This causes those
// bytes to be appended to the incoming buffer. This function is used for
// all channels, including sockets and stdio.
//
void (*play_recv_incoming)(EngineWrapper *w, uint32_t chid, uint32_t len, const char *data);
// Notify the channel that the connection was closed. This includes all
// sorts of closes, including friendly termination, all the way to network
// failure. Closing the channel doesn't delete it. The engine is
// responsible for noticing that the channel closed and the engine must
// delete it. Closing a channel prevents it from showing up in
// 'list_channels'.
//
void (*play_notify_close)(EngineWrapper *w, uint32_t chid, uint32_t len, const char *data);
// Notify the DrivenEngine that somebody connected to an incoming port.
// This will cause the DrivenEngine to allocate a new channel and put the
// new channel into the incoming channels queue. Returns the new channel
// ID. The new incoming channel appears in the 'list_channels' list,
// even before the engine pops the channel from the incoming channels queue.
//
uint32_t (*play_notify_accept)(EngineWrapper *w, uint32_t port);
// Invoke the update event.
//
// The clock value must absolutely be monotonically increasing,
// and it should roughly be equal to the number of seconds since
// the program started.
//
void (*play_invoke_event_update)(EngineWrapper *w, double clock);
// Store the lua source code.
//
void (*play_set_lua_source)(EngineWrapper *w, uint32_t srcpklen, const char *srcpk);
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// MUTATORS USED ONLY IN REPLAY MODE
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
// Begin a replay.
//
// Opens the logfile and prepares to replay the log.
// If an error occurs, the error buffer contains a message,
// and the done flag is set to true.
//
void (*replay_initialize)(EngineWrapper *w, const char *logfn);
// Execute a single step from the replay log.
//
// Calling this when 'done' is true is a no-op.
//
void (*replay_step)(EngineWrapper *w);
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// FUNCTIONS THAT CAN BE USED AT ANY TIME
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
// Restore the wrapper to its initial blank state.
//
// Note that the wrapper must have already been initialized using
// init_engine_wrapper. Otherwise, the 'release' function pointer would not
// be initialized. If writing a logfile, this stores a 'clean exit' marker
// in the logfile, indicating that the engine exited cleanly, as opposed to
// crashing.
//
// If the wrapper is already in its clear state, this is a no-op.
//
void (*release)(EngineWrapper *w);
};
#endif // ENGINEWRAPPER_HPP

View File

@@ -151,13 +151,10 @@ public:
void do_work_command(const util::StringVec &words) {
int reps = 10000;
int64_t t1 = util::profiling_clock();
for (int i = 0; i < reps; i++) {
world_to_synchronous();
world_to_asynchronous();
}
int64_t t2 = util::profiling_clock();
stdostream() << "Snapshot/rollback took " << ((t2-t1)/reps) << " nanosec." << std::endl;
}
void do_quit_command(const util::StringVec &words) {

View File

@@ -224,9 +224,6 @@ using IdVector = eng::vector<int64_t>;
eng::string ascii_tolower(std::string_view c);
eng::string ascii_toupper(std::string_view c);
// Return seconds elapsed, for profiling purposes.
double profiling_clock();
// Output a string to a stream using Lua string escaping and quoting.
void quote_string(const eng::string &str, std::ostream *os);

View File

@@ -1033,3 +1033,12 @@ void World::rollback() {
assert(snapshot_.empty());
}
// This is the main routine for the DLL. We have to use a registration device
// to register this main routine with DrivenEngine. DrivenEngine will then call
// it exactly once the first time that the driver initializes an EngineWrapper.
//
void engine_initialization() {
SourceDB::register_lua_builtins();
}
static DrivenEngineInitializerReg eireg(engine_initialization);