Files
integration/luprex/core/cpp/drivenengine.hpp
2021-10-07 14:58:20 -04:00

361 lines
14 KiB
C++

//////////////////////////////////////////////////////////////
//
// 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.
//
// 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.
//
//////////////////////////////////////////////////////////////
//
// 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
#include <memory>
#include <string>
#include <vector>
#include <map>
#include "streambuffer.hpp"
#include "util.hpp"
class DrivenEngine;
class Channel {
public:
// Get the buffers associated with this channel.
//
StreamBuffer *out() { return sb_out_.get(); }
StreamBuffer *in() { return sb_in_.get(); }
// If this is a socket connection, the receiver's port number.
//
int port() { return port_; }
// If this is an outgoing socket connection, get the target host.
const std::string &target() { return target_; }
// True if the remote closed the connection, or a failure occurred.
//
bool closed() const { return closed_; }
// You may delete any channel except for stdio. This closes
// the channel.
//
~Channel();
private:
// Constructor is deliberately private. Use
// DrivenEngine::new_outgoing_channel to create outgoing socket channels.
//
Channel(DrivenEngine *de, int chid, int port, const std::string &target);
private:
DrivenEngine *driven_;
int chid_;
std::unique_ptr<StreamBuffer> sb_in_;
std::unique_ptr<StreamBuffer> sb_out_;
int port_;
bool closed_;
std::string target_;
friend class DrivenEngine;
};
class DrivenEngine {
public:
//////////////////////////////////////////////////////////////
//
// The following methods are the 'engine' side of the pipe.
//
//////////////////////////////////////////////////////////////
// The update callback. You may override this in a subclass.
// This will be called whenever anything changes.
//
virtual void event_update() {}
// 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.
//
// 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.
//
std::unique_ptr<Channel> new_outgoing_channel(const std::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.
//
std::unique_ptr<Channel> new_incoming_channel();
// Obtain the stdio channel. There is only one stdio channel. It is owned
// by the DrivenEngine. It is an error to delete the stdio channel.
//
// DRIVER: the stdio channel is created automatically when the DrivenEngine
// is created. The driver is responsible for relaying data into the channel
// using drv_get_target, drv_peek_outgoing, drv_sent_outgoing,
// drv_recv_incoming.
//
Channel *get_stdio_channel();
// Fetches the lua source, and takes ownership of it. The DrivenEngine
// no longer contains the source after calling this.
//
util::LuaSourcePtr get_lua_source();
// 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();
// Stop the driver. The engine should call this when it's done
// and there's nothing left to do.
//
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 recently-closed channels. The driver should
// discard all socket information associated with these channels.
// Caution: this may contain channels that the driver has never
// heard of. In that case, just ignore the close-request.
//
void drv_get_new_closed(std::set<int> &opened);
// Get a list of all recently-opened channels that were created using
// drv_new_outgoing_channel. The driver should initiate outgoing
// connections for these channels.
//
void drv_get_new_outgoing(std::set<int> &closed);
// Get the target of a channel. A target is a string like
// "www.whatever.com:80". It indicates the host and port that the channel
// is supposed to be talking to. Non-socket channels and incoming channels
// have empty targets.
//
const std::string &drv_get_target(int chid);
// Return true if the outgoing buffer is empty.
//
bool drv_outgoing_empty(int chid);
// Get a pointer to the bytes in the outgoing buffer. The pointer returned
// here is naturally only valid until the buffer is changed. This function
// is used for all channels, including sockets and stdio.
//
void drv_peek_outgoing(int chid, int *nbytes, const char **bytes);
// 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, int nbytes, const char *bytes);
// Notify the channel that the connection was closed. This includes all
// sorts of closes, including friendly termination, all the way to network
// 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);
// 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);
// Set the clock. The driver is expected to periodically check the system
// clock and feed the value into the engine.
//
void drv_set_clock(double t);
// 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_set_lua_source(util::LuaSourcePtr source);
// Invoke the update event.
//
void drv_invoke_event_update();
// 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();
// If true, the engine is done. Stop the driver.
//
bool drv_get_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();
// 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.
//
static void set(DrivenEngine *de);
static DrivenEngine *get();
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);
private:
Channel *channels_[MAX_CHAN];
int next_unused_chid_;
std::unique_ptr<Channel> stdio_channel_;
std::vector<std::unique_ptr<Channel>> accepted_channels_;
std::set<int> new_closed_;
std::set<int> new_outgoing_;
util::LuaSourcePtr lua_source_;
bool rescan_lua_source_;
double clock_;
bool stop_driver_;
friend class Channel;
};
#endif // DRIVENENGINE_HPP