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

327 lines
12 KiB
C++
Raw Normal View History

//////////////////////////////////////////////////////////////
//
// DrivenEngine
//
2023-02-14 13:14:18 -05:00
// 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.
//
2021-10-07 14:58:20 -04:00
// 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)
2021-09-30 13:45:37 -04:00
// 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.
//
2021-09-30 13:45:37 -04:00
// So the upshot of all this is that the DrivenEngine is a deterministic state
// machine, free of all OS-specific code.
//
//////////////////////////////////////////////////////////////
2026-02-25 01:58:19 -05:00
#pragma once
#include "wrap-string.hpp"
#include "wrap-vector.hpp"
#include <ostream>
#include <memory>
#include <string_view>
#include "util.hpp"
#include "streambuffer.hpp"
2023-02-14 13:14:18 -05:00
#include "enginewrapper.hpp"
#include "invocation.hpp"
class DrivenEngine;
using UniqueDrivenEngine = std::unique_ptr<DrivenEngine>;
using DrivenEngineMaker = UniqueDrivenEngine (*)(EngineWrapper *);
2023-02-14 13:14:18 -05:00
using DrivenEngineInitializer = void (*)();
2022-03-02 14:52:51 -05:00
class Channel : public eng::opnew {
public:
// Get the buffers associated with this channel.
//
StreamBuffer *out() { return &sb_out_; }
StreamBuffer *in() { return &sb_in_; }
2021-10-08 21:03:52 -04:00
// The channel ID. These are reused.
//
int chid() const { return chid_; }
2021-10-08 21:03:52 -04:00
// 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_; }
2021-10-12 13:54:08 -04:00
// 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_; }
2021-10-12 13:54:08 -04:00
// 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(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.
//
2022-03-04 16:45:47 -05:00
std::string_view peek_outgoing() const;
void sent_outgoing(int nbytes);
private:
int chid_;
// These are the in/out buffers presented to the user.
StreamBuffer sb_in_;
StreamBuffer sb_out_;
int port_;
bool closed_;
eng::string error_;
eng::string target_;
bool stop_driver_;
friend class DrivenEngine;
};
using SharedChannel = std::shared_ptr<Channel>;
2022-03-02 14:52:51 -05:00
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) {};
2021-10-05 12:54:37 -04:00
// The update callback. You may override this in a subclass.
// This will be called whenever anything changes.
//
virtual void event_update() = 0;
2021-10-08 21:03:52 -04:00
// 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();
// 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) { rescan_lua_source_ = b; }
// Set the flag to indicate that you have something you want to
// print. This will trigger the driver to access CHANNEL_PRINTS.
//
void set_have_prints(bool b) { have_prints_ = b; }
// 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.
//
//////////////////////////////////////////////////////////////
// Reset the wrapper's world-related function pointers back to
// defaults that return empty results. Call this before destroying
// a World that was previously exposed via World::setup_wrapper.
//
static void unexpose_world_to_driver(EngineWrapper *w);
// 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();
2023-02-14 13:14:18 -05:00
//////////////////////////////////////////////////////////////
//
2023-02-14 13:14:18 -05:00
// The following accessors are for use by PlayWrapper and ReplayWrapper.
//
2023-02-14 13:14:18 -05:00
// 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_have_prints() const;
2023-02-14 13:14:18 -05:00
bool drv_get_rescan_lua_source() const;
bool drv_get_stop_driver() const;
int64_t drv_get_actor_id() const;
2023-02-14 13:14:18 -05:00
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);
2024-09-03 21:56:53 -04:00
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:
2021-10-07 14:58:20 -04:00
// 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;
protected:
// When the Driver calls get_actor_id,
// we return this value. This is initialized
// to zero, but classes that derive from
// DrivenEngine can store an actor_id here.
//
int64_t actor_id_ = 0;
private:
2023-02-14 13:14:18 -05:00
SharedChannel channels_[DRV_MAX_CHAN];
int next_unused_chid_ = 1;
eng::vector<SharedChannel> accepted_channels_;
2023-02-14 13:14:18 -05:00
eng::vector<uint32_t> new_outgoing_;
eng::vector<uint32_t> listen_ports_;
StreamBuffer call_function_retpk_;
bool rescan_lua_source_ = false;
double clock_ = 0.0;
bool have_prints_ = false;
bool stop_driver_ = false;
friend class Channel;
};
2023-02-14 13:14:18 -05:00
//////////////////////////////////////////////////////////////////////////////////
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(EngineWrapper *w) { \
return UniqueDrivenEngine(new cname(w)); \
} \
DrivenEngineReg dengreg_##cname(name, dengmake_##cname);
2023-02-14 13:14:18 -05:00
struct DrivenEngineInitializerReg {
static DrivenEngineInitializer func;
DrivenEngineInitializerReg(DrivenEngineInitializer f);
};