#include "wrap-string.hpp" #include "wrap-vector.hpp" #include "util.hpp" #include "drivenengine.hpp" #include "world.hpp" #include "base-buffer.hpp" #include #include #include #include #include #include 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 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 == reg->name) { UniqueDrivenEngine result = reg->maker(); return result.release(); } } return nullptr; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // // The stdostream // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// 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(DrivenEngine *de, int chid, int port, const eng::string &target, bool stop) { chid_ = chid; port_ = port; closed_ = false; target_ = target; stop_driver_ = stop; sb_in_ = eng::make_shared(); sb_out_ = eng::make_shared(); } 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(this, 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; } } SharedChannel DrivenEngine::get_stdio_channel() { return stdio_channel_; } void DrivenEngine::set_console_prompt(const eng::string &prompt) { console_prompt_ = prompt; } eng::vector DrivenEngine::get_queued_invocations() { eng::vector result = std::move(queued_invocations_); queued_invocations_.clear(); return result; } void DrivenEngine::rescan_lua_source() { rescan_lua_source_ = true; } void DrivenEngine::set_visible_world_and_actor(World *w, int64_t id) { visible_world_ = w; visible_actor_id_ = id; } 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() { next_unused_chid_ = 1; stdio_channel_ = eng::make_shared(this, 0, 0, "", false); stdostream_.reset(new StreamBufferOStream(stdio_channel_->out())); channels_[0] = stdio_channel_; rescan_lua_source_ = false; clock_ = 0.0; stop_driver_ = false; } 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_CALL_EVENT_UPDATE, PLAY_INVOKE_PLAYER, PLAY_INVOKE_CHOOSE, PLAY_INVOKE_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_CALL_EVENT_UPDATE: return "PLAY_CALL_EVENT_UPDATE"; case PLAY_INVOKE_PLAYER: return "PLAY_INVOKE_PLAYER"; case PLAY_INVOKE_CHOOSE: return "PLAY_INVOKE_CHOOSE"; case PLAY_INVOKE_LUA_SOURCE: return "PLAY_INVOKE_LUA_SOURCE"; 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, 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, 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); } void DrivenEngine::drv_get_console_prompt(uint32_t *len, const char **data) const { *len = console_prompt_.size(); *data = console_prompt_.c_str(); } double DrivenEngine::drv_get_clock() const { return clock_; } 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 visible_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 ((visible_world_ != 0) && (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); visible_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 (visible_world_ == nullptr) { util::SharedStdString empty = std::make_shared(""); for (int i = 0; i < int(count); i++) { anim_queues_[i] = empty; } } else { visible_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_initialize(uint32_t srcpklen, const char *srcpk, int argc, char **argv) { event_init(std::string_view(srcpk, srcpklen), argc, argv); } 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(this, chid, port, "", stop_driver_); accepted_channels_.push_back(channels_[chid]); return chid; } void DrivenEngine::drv_call_event_update(double clock) { clock_ = clock; event_update(); } void DrivenEngine::drv_invoke_engio(int64_t place, uint32_t datapklen, const char *datapk) { Invocation *inv = new Invocation(Invocation::KIND_ENGIO, visible_actor_id_, place, std::string_view(datapk, datapklen)); queued_invocations_.emplace_back(inv); } void DrivenEngine::drv_invoke_choose(int64_t place, uint32_t datapklen, const char *datapk) { Invocation *inv = new Invocation(Invocation::KIND_CHOOSE, visible_actor_id_, place, std::string_view(datapk, datapklen)); queued_invocations_.emplace_back(inv); } void DrivenEngine::drv_invoke_lua_source(uint32_t srcpklen, const char *srcpk) { Invocation *inv = new Invocation(Invocation::KIND_LUA_SOURCE, visible_actor_id_, visible_actor_id_, std::string_view(srcpk, srcpklen)); queued_invocations_.emplace_back(inv); 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 void drv_get_console_prompt(EngineWrapper *w, uint32_t *len, const char **data) { return w->engine->drv_get_console_prompt(len, data); } 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(); } 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, 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) { w->wlog->write_cmd_hash(PLAY_INITIALIZE, eng::memhash()); w->wlog->write_uint32(argc); for (uint32_t i = 0; i < argc; i++) { w->wlog->write_string(argv[i]); } w->wlog->write_string(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 argvstr; uint32_t argc = w->rlog->read_uint32(); for (uint32_t i = 0; i < argc; i++) { argvstr.push_back(w->rlog->read_string()); } std::string srcpk = w->rlog->read_string(); 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 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) { 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_invoke_event_update(EngineWrapper *w, double clock) { assert(w->rlog == nullptr); if (w->wlog != nullptr) { w->wlog->write_cmd_hash(PLAY_CALL_EVENT_UPDATE, eng::memhash()); w->wlog->write_double(clock); w->wlog->flush(); } w->engine->drv_call_event_update(clock); } static void replay_invoke_event_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_call_event_update(clock); } //////////////////////// void play_invoke_engio(EngineWrapper *w, int64_t place, uint32_t datapklen, const char *datapk) { assert(w->rlog == nullptr); if (w->wlog != nullptr) { w->wlog->write_cmd_hash(PLAY_INVOKE_PLAYER, eng::memhash()); w->wlog->write_int64(place); w->wlog->write_string(std::string_view(datapk, datapklen)); w->wlog->flush(); } w->engine->drv_invoke_engio(place, datapklen, datapk); } void replay_invoke_engio(EngineWrapper *w) { 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_invoke_engio"); } w->engine->drv_invoke_engio(place, srcpack.size(), srcpack.c_str()); } //////////////////////// void play_invoke_choose(EngineWrapper *w, int64_t place, uint32_t datapklen, const char *datapk) { assert(w->rlog == nullptr); if (w->wlog != nullptr) { w->wlog->write_cmd_hash(PLAY_INVOKE_CHOOSE, eng::memhash()); w->wlog->write_int64(place); w->wlog->write_string(std::string_view(datapk, datapklen)); w->wlog->flush(); } w->engine->drv_invoke_choose(place, datapklen, datapk); } void replay_invoke_choose(EngineWrapper *w) { 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_invoke_choose"); } w->engine->drv_invoke_choose(place, srcpack.size(), srcpack.c_str()); } //////////////////////// void play_invoke_lua_source(EngineWrapper *w, uint32_t srcpklen, const char *srcpk) { assert(w->rlog == nullptr); if (w->wlog != nullptr) { w->wlog->write_cmd_hash(PLAY_INVOKE_LUA_SOURCE, eng::memhash()); w->wlog->write_string(std::string_view(srcpk, srcpklen)); w->wlog->flush(); } w->engine->drv_invoke_lua_source(srcpklen, srcpk); } void replay_invoke_lua_source(EngineWrapper *w) { std::string srcpack = w->rlog->read_string(); if (!w->rlog->good()) { return reset_wrapper(w, "replay log corrupt in replay_invoke_lua_source"); } w->engine->drv_invoke_lua_source(srcpack.size(), srcpack.c_str()); } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // // 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_CALL_EVENT_UPDATE: replay_invoke_event_update(w); return; case PLAY_INVOKE_PLAYER: replay_invoke_lua_source(w); return; case PLAY_INVOKE_CHOOSE: replay_invoke_lua_source(w); return; case PLAY_INVOKE_LUA_SOURCE: replay_invoke_lua_source(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. // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// #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_console_prompt = drv_get_console_prompt; 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->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_invoke_event_update = play_invoke_event_update; w->play_invoke_engio = play_invoke_engio; w->play_invoke_choose = play_invoke_choose; w->play_invoke_lua_source = play_invoke_lua_source; w->replay_initialize = replaycore_initialize; w->replay_step = replaycore_step; w->hook_dprint = util::hook_dprint; w->release = release; }; extern "C" { DLLEXPORT void init_engine_wrapper(EngineWrapper *w) { init_engine_wrapper_helper(w); } }