Files
integration/luprex/cpp/core/drivenengine.cpp

927 lines
29 KiB
C++

#include "wrap-string.hpp"
#include "wrap-vector.hpp"
#include "util.hpp"
#include "drivenengine.hpp"
#include "world.hpp"
#include "base-buffer.hpp"
#include <string_view>
#include <utility>
#include <cstring>
#include <cstdio>
#include <fstream>
#include <cassert>
DrivenEngineReg *DrivenEngineReg::All;
DrivenEngineReg::DrivenEngineReg(const char *n, DrivenEngineMaker fn) {
name = n;
maker = fn;
next = All;
All = this;
}
DrivenEngineInitializer DrivenEngineInitializerReg::func;
DrivenEngineInitializerReg::DrivenEngineInitializerReg(DrivenEngineInitializer fn) {
assert(func == nullptr);
func = fn;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// DrivenEngine private methods
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
int DrivenEngine::find_unused_chid() {
// Note: channel ID zero is never used. Channel ID 0 used to be
// reserved for the stdio channel. When we eliminated the stdio
// channel, we blocked off ID 0 permanently.
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(chid != 0);
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 == reg->name) {
UniqueDrivenEngine result = reg->maker();
return result.release();
}
}
return nullptr;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// A std::ostream that sends its output to a StreamBuffer.
//
// This is not currently used so it's commented out. But if it's
// needed, it works fine: it can be ressurected.
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
// class StreamBufferWriter : public std::streambuf, public eng::opnew {
// private:
// StreamBuffer *target_;
// public:
// StreamBufferWriter(StreamBuffer *t) : target_(t) {}
// virtual int_type overflow(int_type c) {
// if (c != EOF) {
// target_->write_uint8(c);
// }
// return c;
// }
// };
// class StreamBufferOStream : public std::ostream, public eng::opnew {
// private:
// StreamBufferWriter writer_;
// public:
// StreamBufferOStream(StreamBuffer *t) : std::ostream(nullptr), writer_(t) {
// rdbuf(&writer_);
// }
// virtual ~StreamBufferOStream() {
// }
// };
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// Class Channel
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
Channel::Channel(int chid, int port, const eng::string &target, bool stop) {
chid_ = chid;
port_ = port;
closed_ = false;
target_ = target;
stop_driver_ = stop;
}
std::string_view Channel::peek_outgoing() const {
return sb_out_.view();
}
void Channel::sent_outgoing(int nbytes) {
sb_out_.read_bytes(nbytes);
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// DrivenEngine Client-Side API
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
void DrivenEngine::listen_port(int port) {
assert(listen_ports_.size() < DRV_MAX_LISTEN_PORTS);
listen_ports_.push_back(port);
}
double DrivenEngine::get_clock() {
return clock_;
}
SharedChannel DrivenEngine::new_outgoing_channel(const eng::string &target) {
int chid = find_unused_chid();
new_outgoing_.push_back(chid);
SharedChannel result = eng::make_shared<Channel>(chid, 0, target, stop_driver_);
channels_[chid] = result;
return result;
}
SharedChannel DrivenEngine::new_incoming_channel() {
if (accepted_channels_.empty()) {
return nullptr;
} else {
SharedChannel result = std::move(accepted_channels_.back());
accepted_channels_.pop_back();
return result;
}
}
void DrivenEngine::stop_driver() {
stop_driver_ = true;
for (int i = 0; i < DRV_MAX_CHAN; i++) {
if (channels_[i] != nullptr) {
channels_[i]->stop_driver_ = true;
}
}
}
DrivenEngine::DrivenEngine() {
}
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_UPDATE,
PLAY_ACCESS,
PLAY_RELEASE,
};
[[maybe_unused]] 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_UPDATE: return "PLAY_UPDATE";
case PLAY_ACCESS: return "PLAY_ACCESS";
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 BaseWriter<PlayLogfile>, public std::ofstream {
using std::ofstream::ofstream;
public:
void write_bytes(const char *n, size_t size) { write(n, size); }
void raise_truncated() {
fprintf(stderr, "number exceeds allowable size\n");
std::abort();
}
void write_short_string(std::string_view v) {
assert(v.size() < DRV_SHORTSTRING_SIZE);
write_string(v);
}
void write_cmd_hash(DrvAction act, uint32_t hash) {
write_uint8(act);
write_uint32(hash);
}
};
class ReplayLogfile : public BaseReader<ReplayLogfile>, public std::ifstream {
using std::ifstream::ifstream;
public:
using read_string_type = std::string;
void read_bytes_into(char *n, size_t size) {
read(n, size);
if (!good()) {
memset(n, 0, size);
}
}
void raise_string_too_long() {
fprintf(stderr, "string in logfile is too long");
std::abort();
}
std::string_view read_short_string(EngineWrapper *w) {
size_t size = read_length();
assert(size <= DRV_SHORTSTRING_SIZE);
if (size > 0) read(w->databuffer, size);
if (!good()) return std::string_view();
return std::string_view(w->databuffer, size);
}
};
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// 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) {
w->wlog->write_cmd_hash(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;
}
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();
}
bool DrivenEngine::drv_get_outgoing_empty(uint32_t chid) const {
std::string_view v = get_chid(chid)->peek_outgoing();
return (v.size() == 0);
}
double DrivenEngine::drv_get_clock() const {
return clock_;
}
bool DrivenEngine::drv_get_have_prints() const {
return have_prints_;
}
bool DrivenEngine::drv_get_rescan_lua_source() const {
return rescan_lua_source_;
}
bool DrivenEngine::drv_get_stop_driver() const {
return stop_driver_;
}
int64_t DrivenEngine::drv_get_actor_id() const {
return actor_id_;
}
void DrivenEngine::drv_get_tangibles_near(int64_t tanid, double rx, double ry, double rz, uint32_t *count, int64_t **ids) {
uint32_t hash1 = eng::memhash();
scan_result_.clear();
if (world_ && (tanid != 0)) {
PlaneScan scan;
scan.set_near(tanid, true);
scan.set_omit_nowhere(true);
scan.set_sorted(false);
scan.set_radius(util::XYZ(rx, ry, rz));
scan.set_shape(PlaneScan::CYLINDER);
world_->get_near(scan, &scan_result_);
}
*count = scan_result_.size();
if (*count > 0) {
*ids = &scan_result_[0];
} else {
*ids = nullptr;
}
uint32_t hash2 = eng::memhash();
assert(hash1 == hash2);
}
void DrivenEngine::drv_get_animation_queues(uint32_t count, const int64_t *ids, uint32_t *lengths, const char **strings) {
anim_queues_.resize(count);
if (!world_) {
for (int i = 0; i < int(count); i++) {
anim_queues_[i] = AnimQueue::get_encoded_blank_queue();
}
} else {
world_->get_encoded_animation_queues(count, ids, anim_queues_);
}
for (int i = 0; i < int(count); i++) {
lengths[i] = anim_queues_[i]->size();
strings[i] = anim_queues_[i]->c_str();
}
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// DRIVER Methods: Mutators
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
void DrivenEngine::drv_clear_new_outgoing() {
new_outgoing_.clear();
}
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) {
std::string_view sbytes(bytes, nbytes);
Channel *ch = get_chid(chid);
ch->sb_in_.write_bytes(sbytes);
}
}
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>(chid, port, "", stop_driver_);
accepted_channels_.push_back(channels_[chid]);
return chid;
}
void DrivenEngine::drv_update(double clock) {
clock_ = clock;
event_update();
}
void DrivenEngine::drv_access(AccessKind kind, int64_t place, uint32_t datapklen, const char *datapk, uint32_t *retpklen, const char **retpk) {
// This next line is a hack, because the DrivenEngine is not supposed to care about 'kind'.
if (kind == AccessKind::INVOKE_LUA_SOURCE) rescan_lua_source_ = false;
call_function_retpk_.clear();
event_access(kind, place, std::string_view(datapk, datapklen), &call_function_retpk_);
if (retpklen != nullptr) {
std::string_view view = call_function_retpk_.view();
*retpklen = view.size();
*retpk = view.data();
}
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// 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_have_prints(EngineWrapper *w) {
return w->engine->drv_get_have_prints();
}
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();
}
static uint64_t drv_get_actor_id(EngineWrapper *w) {
return w->engine->drv_get_actor_id();
}
static void drv_get_tangibles_near(EngineWrapper *w, uint64_t tanid, double rx, double ry, double rz, uint32_t *count, int64_t **ids) {
return w->engine->drv_get_tangibles_near(tanid, rx, ry, rz, count, ids);
}
static void drv_get_animation_queues(EngineWrapper *w, uint32_t count, const int64_t *ids, uint32_t *lengths, const char **strings) {
return w->engine->drv_get_animation_queues(count, ids, lengths, strings);
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// 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, const char *engtype, 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) {
w->wlog->write_cmd_hash(PLAY_INITIALIZE, eng::memhash());
w->wlog->write_string(engtype);
w->wlog->flush();
}
// Create the engine of the appropriate type.
w->engine = make_engine(engtype);
if (w->engine == nullptr) {
return reset_wrapper(w, "No such driven engine type: %s", engtype);
}
}
static void replay_initialize(EngineWrapper *w) {
assert(w->rlog != nullptr);
std::string engtype = w->rlog->read_string();
if (!w->rlog->good()) {
return reset_wrapper(w, "replay log corrupt in replay_initialize");
}
// Create the engine.
w->engine = make_engine(engtype.c_str());
if (w->engine == nullptr) {
return reset_wrapper(w, "No such driven engine type: %s", engtype.c_str());
}
}
////////////////////////
static void play_clear_new_outgoing(EngineWrapper *w) {
assert(w->rlog == nullptr);
if (w->wlog != nullptr) {
w->wlog->write_cmd_hash(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);
w->wlog->write_cmd_hash(PLAY_SENT_OUTGOING, eng::memhash());
w->wlog->write_uint32(chid);
w->wlog->write_uint32(nbytes);
w->wlog->write_uint64(SpookyHash::QkHash64(data, nbytes));
w->wlog->flush();
}
w->engine->drv_sent_outgoing(chid, nbytes);
}
static void replay_sent_outgoing(EngineWrapper *w) {
uint32_t chid = w->rlog->read_uint32();
uint32_t nbytes = w->rlog->read_uint32();
uint64_t hash = w->rlog->read_uint64();
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");
}
if (w->replay_cb_sent_outgoing != nullptr) {
w->replay_cb_sent_outgoing(w->replay_cb_vp, chid, ndata, data);
}
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) {
w->wlog->write_cmd_hash(PLAY_RECV_INCOMING, eng::memhash());
w->wlog->write_uint32(chid);
w->wlog->write_short_string(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 = w->rlog->read_uint32();
std::string_view data = w->rlog->read_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) {
w->wlog->write_cmd_hash(PLAY_NOTIFY_CLOSE, eng::memhash());
w->wlog->write_uint32(chid);
w->wlog->write_string(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 = w->rlog->read_uint32();
std::string message = w->rlog->read_string();
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) {
w->wlog->write_cmd_hash(PLAY_NOTIFY_ACCEPT, eng::memhash());
w->wlog->write_uint32(port);
w->wlog->flush();
}
return w->engine->drv_notify_accept(port);
}
static void replay_notify_accept(EngineWrapper *w) {
uint32_t port = w->rlog->read_uint32();
if (!w->rlog->good()) {
return reset_wrapper(w, "replay log corrupt in replay_notify_accept");
}
w->engine->drv_notify_accept(port);
}
////////////////////////
static void play_update(EngineWrapper *w, double clock) {
assert(w->rlog == nullptr);
if (w->wlog != nullptr) {
w->wlog->write_cmd_hash(PLAY_UPDATE, eng::memhash());
w->wlog->write_double(clock);
w->wlog->flush();
}
w->engine->drv_update(clock);
}
static void replay_update(EngineWrapper *w) {
double clock = w->rlog->read_double();
if (!w->rlog->good()) {
return reset_wrapper(w, "replay log corrupt in replay_event_update");
}
w->engine->drv_update(clock);
}
////////////////////////
void play_access(EngineWrapper *w, AccessKind kind, int64_t place, uint32_t datapklen, const char *datapk, uint32_t *retpklen, const char **retpk) {
assert(w->rlog == nullptr);
if (w->wlog != nullptr) {
w->wlog->write_cmd_hash(PLAY_ACCESS, eng::memhash());
w->wlog->write_uint8(int64_t(kind));
w->wlog->write_int64(place);
w->wlog->write_string(std::string_view(datapk, datapklen));
w->wlog->flush();
}
w->engine->drv_access(kind, place, datapklen, datapk, retpklen, retpk);
}
void replay_access(EngineWrapper *w) {
AccessKind kind = AccessKind(w->rlog->read_uint8());
int64_t place = w->rlog->read_int64();
std::string srcpack = w->rlog->read_string();
if (!w->rlog->good()) {
return reset_wrapper(w, "replay log corrupt in replay_access");
}
w->engine->drv_access(kind, place, srcpack.size(), srcpack.c_str(), nullptr, nullptr);
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// Replay Core
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
static void replaycore_initialize(EngineWrapper *w, const char *logfn) {
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 = w->rlog->read_uint8();
int hash = w->rlog->read_uint32();
if (!w->rlog->good()) {
return reset_wrapper(w, "logfile corrupt in initial step");
}
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 = w->rlog->read_uint8();
if (w->rlog->eof()) {
return reset_wrapper(w, "logfile terminated abruptly");
}
int hash = w->rlog->read_uint32();
if (!w->rlog->good()) {
return reset_wrapper(w, "logfile corrupt in replay step");
}
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_UPDATE: replay_update(w); return;
case PLAY_ACCESS: replay_access(w); return;
case PLAY_RELEASE: release(w); return;
default: return reset_wrapper(w, "Replay log corrupt in command dispatcher");
}
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// 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.
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
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_have_prints = drv_get_have_prints;
w->get_rescan_lua_source = drv_get_rescan_lua_source;
w->get_stop_driver = drv_get_stop_driver;
w->get_actor_id = drv_get_actor_id;
w->get_tangibles_near = drv_get_tangibles_near;
w->get_animation_queues = drv_get_animation_queues;
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_update = play_update;
w->play_access = play_access;
w->replay_initialize = replaycore_initialize;
w->replay_step = replaycore_step;
w->hook_dprint = util::hook_dprint;
w->release = release;
};
#if defined(__linux__)
#define DLLEXPORT __attribute__((visibility("default")))
#elif defined(_WIN32)
#define DLLEXPORT __declspec(dllexport)
#endif
extern "C" {
DLLEXPORT void init_engine_wrapper(EngineWrapper *w) {
init_engine_wrapper_helper(w);
}
}