////////////////////////////////////////////////////////////// // // DrivenEngine // // 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, // get the clock, and so forth. // // But in reality, these I/O functions don't ever call operating system // functions like "read" or "write" or "connect." They don't call the operating // system at all - not even indirectly, through a wrapper. Therefore, they // can't really do any I/O. When you use one of these I/O functions to (say) // write some data to a communication channel, the only thing that happens is // that the data is put into a buffer. The actual transmission of the data // happens elsewhere, in what is called the "Driver." Likewise, when you use // one of these I/O functions to read data, it only returns data that was // previously stored by the "Driver." // // The "Driver" is a module that implements the actual I/O. It is highly // OS-dependent code, because it contains code to manipulate sockets, time // clocks, and the like. // // From the perspective of the driver, the DrivenEngine is a C++ object that // acts like a state machine. This state machine is driven forward by I/O // events. The DrivenEngine provides an API where the driver can feed in these // I/O events. // // Notice that the usual call graph is inverted: in most application programs, // the application calls the operating system to do I/O. But when using class // DrivenEngine, it's the other way around: the driver calls into class // DrivenEngine to drive it forward. I/O routines drive computation. // // So the upshot of all this is that the DrivenEngine is a deterministic state // machine, free of all OS-specific code. // ////////////////////////////////////////////////////////////// #ifndef DRIVENENGINE_HPP #define DRIVENENGINE_HPP #include "wrap-string.hpp" #include "wrap-vector.hpp" #include #include #include #include "util.hpp" #include "streambuffer.hpp" #include "enginewrapper.hpp" #include "planemap.hpp" #include "invocation.hpp" class DrivenEngine; class World; using UniqueDrivenEngine = std::unique_ptr; using DrivenEngineMaker = UniqueDrivenEngine (*)(); using DrivenEngineInitializer = void (*)(); class Channel : public eng::opnew { public: // Get the buffers associated with this channel. // StreamBuffer *out() { return sb_out_.get(); } StreamBuffer *in() { return sb_in_.get(); } // The channel ID. These are reused. // int chid() const { return chid_; } // If this is a socket connection, the receiver's port number. // int port() const { return port_; } // If this is an outgoing socket connection, get the target host. // const eng::string &target() const { return target_; } // True if the remote has closed the connection. // bool closed() const { return closed_; } // Get the channel's error message. // // If this is an empty string, there is no error. If this is set, // then the channel is also closed. // const eng::string &error() const { return error_; } // Do not construct your own Channels. Instead, // use methods of class DrivenEngine like new_outgoing_channel. // Channels are referenced by shared_ptr. You can // release your shared_ptr at any time. // Channel(DrivenEngine *de, int chid, int port, const eng::string &target, bool stop); ~Channel() {}; private: // Constructor is deliberately private. Use // DrivenEngine::new_outgoing_channel to create outgoing socket channels. // std::string_view peek_outgoing() const; void sent_outgoing(int nbytes); private: int chid_; // These are the in/out buffers presented to the user. std::shared_ptr sb_in_; std::shared_ptr sb_out_; int port_; bool closed_; eng::string error_; eng::string target_; bool stop_driver_; friend class DrivenEngine; }; using SharedChannel = std::shared_ptr; class DrivenEngine : public eng::opnew { public: ////////////////////////////////////////////////////////////// // // Build the named engine // ////////////////////////////////////////////////////////////// static UniqueDrivenEngine make(std::string_view name); static void print_usage(std::ostream &strm, std::string_view progname); ////////////////////////////////////////////////////////////// // // The following methods are the 'engine' side of the pipe. // ////////////////////////////////////////////////////////////// // The call-function callback. This is invoked whenever drv_access // is called. This is the main entry point for "general" access into the // DrivenEngine. The datapk parameter can contain any arbitrary data needed // by the call. If the call wants to return anything, it can write the // return data into the retpk datapack. // virtual void event_access(AccessKind kind, int64_t place, std::string_view datapk, StreamBuffer *retpk) {}; // The update callback. You may override this in a subclass. // This will be called whenever anything changes. // virtual void event_update() = 0; // Specify the set of listening ports. // This can only be used during the init routine. // void listen_port(int port); // Get the current time. // // DRIVER: This returns the time most recently stored by the driver // using drv_set_clock. // double get_clock(); // Create a channel and open an outgoing connection. The channel creation // always succeeds. You can write to the channel immediately. You can // read, too, but of course there won't be anything in the incoming buffer // yet. In future update events, data will show up in the incoming buffer, // and will have been sent from the outgoing buffer. In future update // events, the channel may get closed by the remote. If the connection // fails (say, the remote host doesn't exist), then the Channel will get // closed with an error. // // DRIVER: The channel object is created instantly, but it does nothing // until the driver notices the new channel. The driver is responsible for // actually opening the connection and relaying data into the channel using // drv_get_target, drv_peek_outgoing, drv_sent_outgoing, drv_recv_incoming. // SharedChannel new_outgoing_channel(const eng::string &target); // Create a new channel from any pending incoming connection. If there is no // incoming connection, returns nullptr. // // DRIVER: The driver must be hardwired to know what ports to listen on. // When the driver notices a new incoming connection, it calls // drv_notify_accept, which triggers the creation of the channel. The // channel is put into the incoming channel queue, which is fetched by this // method. The driver is responsible for relaying data into the channel // using drv_get_target, drv_peek_outgoing, drv_sent_outgoing, // drv_recv_incoming. // SharedChannel new_incoming_channel(); // Obtain the stdio channel. There is only one stdio channel. // // DRIVER: the stdio channel is created automatically when the DrivenEngine // is created. Stdio should be connected to a console which is in // line-at-a-time mode. The driver is responsible for relaying data from // the console into the stdio channel using drv_peek_outgoing, // drv_sent_outgoing, drv_recv_incoming. // SharedChannel get_stdio_channel(); // Set the prompt for the console. // void set_console_prompt(const eng::string &prompt); // Set the flag to rescan the lua source directory. The lua source // directory is read once, automatically, at engine creation time. // If you want to read it again, you must trigger a rescan. // The rescan is not instantaneous. // // DRIVER: this merely sets a flag, which the driver will notice later, // causing the driver to update the lua source. // void rescan_lua_source(bool b); // Set the world pointer and the actor ID. // // This allows the graphics engine to query the DrivenEngine // about the state of the world and the player. It is legal to set these // to zero, in which case queries will return null results. // void set_visible_world_and_actor(World *w, int64_t actor); // Stop the driver. The engine should call this when it's done // and there's nothing left to do. // void stop_driver(); ////////////////////////////////////////////////////////////// // // Creation and Destruction. // ////////////////////////////////////////////////////////////// // Constructor. // // Most initialization is achieved by 'drv_xxx' functions, so // this constructor takes no arguments. // DrivenEngine(); // Destructor. // // It is necessary to delete all channels before deleting the // DrivenEngine. The destructor will verify that this has been done. // virtual ~DrivenEngine(); ////////////////////////////////////////////////////////////// // // The following accessors are for use by PlayWrapper and ReplayWrapper. // // 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; void drv_get_console_prompt(uint32_t *len, const char **data) const; double drv_get_clock() const; bool drv_get_rescan_lua_source() const; bool drv_get_stop_driver() const; int64_t drv_get_actor_id() const; void drv_get_tangibles_near(int64_t tanid, double rx, double ry, double rz, uint32_t *count, int64_t **ids); void drv_get_animation_queues(uint32_t count, const int64_t *ids, uint32_t *lengths, const char **strings); 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_update(double clock); void drv_access(AccessKind kind, int64_t place, uint32_t datapklen, const char *datapk, uint32_t *retpklen, const char **retpk); private: // Find a currently-unused channel ID. Channel IDs // are small integers that are reused. int find_unused_chid(); // Get the channel associated with the specified channel ID. Channel *get_chid(int chid) const; private: SharedChannel channels_[DRV_MAX_CHAN]; int next_unused_chid_; SharedChannel stdio_channel_; eng::vector accepted_channels_; eng::vector new_outgoing_; eng::vector listen_ports_; World *visible_world_; int64_t visible_actor_id_; util::IdVector scan_result_; std::vector anim_queues_; StreamBuffer call_function_retpk_; bool rescan_lua_source_; double clock_; bool stop_driver_; eng::string console_prompt_; friend class Channel; }; ////////////////////////////////////////////////////////////////////////////////// struct DrivenEngineReg { const char *name; DrivenEngineMaker maker; DrivenEngineReg *next; static DrivenEngineReg *All; DrivenEngineReg(const char *name, DrivenEngineMaker f); }; #define DrivenEngineDefine(name, cname) \ UniqueDrivenEngine dengmake_##cname() { \ return UniqueDrivenEngine(new cname); \ } \ DrivenEngineReg dengreg_##cname(name, dengmake_##cname); struct DrivenEngineInitializerReg { static DrivenEngineInitializer func; DrivenEngineInitializerReg(DrivenEngineInitializer f); }; #endif // DRIVENENGINE_HPP