Inverted control flow, engine as library

This commit is contained in:
2021-10-04 17:45:18 -04:00
parent e0fdb2d42f
commit bc22dc89af
15 changed files with 387 additions and 136 deletions

View File

@@ -4,6 +4,8 @@ CXX=g++ -std=c++17 -Wall -g -Iinc -Icpp
CPP_FILES=\
cpp/invocation.cpp\
cpp/spookyv2.cpp\
cpp/drivenengine.cpp\
cpp/driver.cpp\
cpp/util.cpp\
cpp/luastack.cpp\
cpp/traceback.cpp\

View File

@@ -1 +1,175 @@
#include "channel.hpp"
#include "drivenengine.hpp"
Channel::Channel(DrivenEngine *de, int chid, int port, const std::string &target) {
driven_ = de;
chid_ = chid;
sb_in_.reset(new StreamBuffer);
sb_out_.reset(new StreamBuffer);
port_ = port;
closed_ = false;
target_ = target;
assert(driven_->channels_[chid_] == nullptr);
driven_->channels_[chid_] = this;
}
Channel::~Channel() {
assert(driven_->channels_[chid_] == this);
driven_->channels_.erase(chid_);
if (driven_->recent_channel_ == this) {
driven_->recent_channel_ = driven_->stdio_channel_.get();
}
}
double DrivenEngine::get_clock() {
return clock_;
}
std::unique_ptr<Channel> DrivenEngine::new_outgoing_channel(const std::string &target) {
return std::unique_ptr<Channel>(new Channel(this, next_channel_id_++, 0, target));
}
std::unique_ptr<Channel> DrivenEngine::new_incoming_channel() {
if (accepted_channels_.empty()) {
return nullptr;
} else {
std::unique_ptr<Channel> result = std::move(accepted_channels_.back());
accepted_channels_.pop_back();
return std::move(result);
}
}
Channel *DrivenEngine::get_stdio_channel() {
return stdio_channel_.get();
}
std::unique_ptr<util::LuaSource> DrivenEngine::get_lua_source() {
return std::move(lua_source_);
}
void DrivenEngine::rescan_lua_source() {
rescan_lua_source_ = true;
}
void DrivenEngine::stop_driver() {
stop_driver_ = true;
}
void DrivenEngine::drv_logmode_write(const std::string &filename, int64_t maxsize) {
// NOT IMPLEMENTED YET, but it's okay as a stub.
}
void DrivenEngine::drv_logmode_replay(const std::string &filename) {
// NOT IMPLEMENTED YET.
assert(false);
}
void DrivenEngine::drv_logmode_none() {
// NOT IMPLEMENTED YET, but it's okay as a stub.
}
void DrivenEngine::drv_invoke_engine_init() {
init();
}
void DrivenEngine::drv_invoke_engine_update() {
update();
}
Channel *DrivenEngine::get_chid(int chid) {
// We cache the most recently used channel.
if (recent_channel_->chid_ != chid) {
auto iter = channels_.find(chid);
assert(iter != channels_.end());
recent_channel_ = iter->second;
}
return recent_channel_;
}
void DrivenEngine::drv_list_channels(std::vector<int> &channels) {
channels.clear();
for (const auto &p : channels_) {
if (!p.second->closed_) {
channels.push_back(p.first);
}
}
}
const std::string &DrivenEngine::drv_get_target(int chid) {
return get_chid(chid)->target_;
}
void DrivenEngine::drv_peek_outgoing(int chid, int *nbytes, const char **bytes) {
Channel *ch = get_chid(chid);
*nbytes = ch->sb_out_->fill();
*bytes = ch->sb_out_->data();
}
void DrivenEngine::drv_sent_outgoing(int chid, int nbytes) {
get_chid(chid)->sb_out_->read_bytes(nbytes);
}
void DrivenEngine::drv_recv_incoming(int chid, int nbytes, char *bytes) {
get_chid(chid)->sb_in_->write_bytes(bytes, nbytes);
}
void DrivenEngine::drv_notify_close(int chid) {
get_chid(chid)->closed_ = true;
}
int DrivenEngine::drv_notify_accept(int port) {
int chid = next_channel_id_++;
accepted_channels_.emplace_back(new Channel(this, chid, port, ""));
return chid;
}
void DrivenEngine::drv_set_clock(double t) {
clock_ = t;
}
void DrivenEngine::drv_set_lua_source(const util::LuaSource &source) {
lua_source_.reset(new util::LuaSource(source));
rescan_lua_source_ = false;
}
bool DrivenEngine::drv_get_rescan_lua_source() {
return rescan_lua_source_;
}
bool DrivenEngine::drv_get_stop_driver() {
return stop_driver_;
}
bool DrivenEngine::drv_step_logfile() {
// NOT IMPLEMENTED
assert(false);
return false;
}
DrivenEngine::DrivenEngine() {
next_channel_id_ = 1;
stdio_channel_.reset(new Channel(this, 0, 0, ""));
recent_channel_ = stdio_channel_.get();
rescan_lua_source_ = true;
clock_ = 0.0;
stop_driver_ = false;
}
DrivenEngine::~DrivenEngine() {
// Delete the channels that we own.
stdio_channel_.reset();
accepted_channels_.clear();
// At this point, all channels should be gone.
assert(channels_.empty());
}
static DrivenEngine *engine_;
void DrivenEngine::set(DrivenEngine *de) {
engine_ = de;
}
DrivenEngine *DrivenEngine::get() {
return engine_;
}

View File

@@ -73,6 +73,9 @@
//
// * 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'.
//
// - List all existing channels using drv_list_channels.
//
// - If there are any new channels in the channel list, use
@@ -101,8 +104,7 @@
// drv_peek_outgoing, drv_sent_outgoing, and drv_recv_incoming in the
// same manner as you would for a socket.
//
// - Use 'drv_invoke_engine_update' to invoke the engine's update
// callback.
// - Use 'drv_invoke_engine_update' to invoke the engine's update callback.
//
//////////////////////////////////////////////////////////////
@@ -111,25 +113,19 @@
#include <memory>
#include <string>
#include <vector>
#include <map>
#include "streambuffer.hpp"
#include "util.hpp"
class Channel {
private:
int chid_;
std::unique_ptr<StreamBuffer> sb_in_;
std::unique_ptr<StreamBuffer> sb_out_;
int port_;
bool remote_closed_;
std::string target_;
class DrivenEngine;
class Channel {
public:
// Get the buffers associated with this channel.
//
StreamBuffer *out();
StreamBuffer *in();
const StreamBuffer *out() const;
const StreamBuffer *in() const;
StreamBuffer *out() { return sb_out_.get(); }
StreamBuffer *in() { return sb_in_.get(); }
// If this is a socket connection, the receiver's port number.
//
@@ -140,30 +136,32 @@ public:
// True if the remote closed the connection, or a failure occurred.
//
bool remote_closed() const { return remote_closed_; }
bool closed() const { return closed_; }
// If communications were closed by the remote, there may
// be an error message.
// You may delete any channel except for stdio. This closes
// the channel.
//
std::string errmsg() const { return errmsg_; }
~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:
// 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.
//
~DrivenEngine();
//////////////////////////////////////////////////////////////
//
// The following methods are the 'engine' side of the pipe.
@@ -174,7 +172,7 @@ public:
// This will be called to initialize the logic engine, shortly after the lua
// source is loaded.
//
virtual void init() { }
virtual void init() {}
// The update function. You should override this in a subclass. This will
// be called to give the engine a chance to respond to new data.
@@ -195,7 +193,7 @@ public:
// 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)
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.
@@ -234,7 +232,7 @@ public:
// periodically poll to see if the engine has called rescan_lua_source,
// using drv_get_rescan
//
std::unique_ptr<util::StringMap> get_lua_source();
std::unique_ptr<util::LuaSource> 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,
@@ -245,6 +243,11 @@ public:
//
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.
@@ -293,7 +296,7 @@ public:
// is supposed to be talking to. Non-socket channels and incoming channels
// have empty targets.
//
std::string drv_get_target(int chid);
const std::string &drv_get_target(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
@@ -338,7 +341,7 @@ public:
// 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(const util::StringMap &source);
void drv_set_lua_source(const util::LuaSource &source);
// 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.
@@ -347,19 +350,59 @@ public:
//
bool drv_get_rescan_lua_source();
// If true, the engine is done. Stop the driver.
//
bool drv_get_stop_driver();
// In replay mode, perform a single step of the logfile. Returns true
// if the logfile was not empty.
//
bool drv_step_logfile();
//////////////////////////////////////////////////////////////
//
// 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:
Channel *stdio_channel_;
// Get a channel by channel ID.
Channel *get_chid(int chid);
private:
std::unique_ptr<Channel> stdio_channel_;
int next_channel_id_;
std::map<int, Channel *> channels_;
Channel *recent_channel_;
std::vector<Channel*> accepted_channels_;
std::vector<std::unique_ptr<Channel>> accepted_channels_;
bool rescan_lua_source_;
std::unique_ptr<util::StringMap> lua_source_;
std::unique_ptr<util::LuaSource> lua_source_;
double clock_;
bool stop_driver_;
friend class Channel;
};
#endif // DRIVENENGINE_HPP

View File

@@ -0,0 +1,32 @@
#include "driver.hpp"
#include <map>
#include <iostream>
#include <cstdio>
#include <cstring>
void driver_drive(DrivenEngine *de) {
const int MAXINPUT = 1000;
char buf[MAXINPUT];
int nbytes; const char *bytes;
DrivenEngine::set(de);
de->drv_logmode_none();
de->drv_set_lua_source(util::read_lua_source("lua"));
de->drv_invoke_engine_init();
while (!de->drv_get_stop_driver()) {
if (de->drv_get_rescan_lua_source()) {
de->drv_set_lua_source(util::read_lua_source("lua"));
}
de->drv_peek_outgoing(0, &nbytes, &bytes);
if (nbytes > 0) {
fwrite(bytes, 1, nbytes, stdout);
}
if (fgets(buf, MAXINPUT, stdin)) {
de->drv_recv_incoming(0, strlen(buf), buf);
}
de->drv_invoke_engine_update();
}
DrivenEngine::set(nullptr);
}

View File

@@ -0,0 +1,8 @@
#ifndef DRIVER_HPP
#define DRIVER_HPP
#include "drivenengine.hpp"
void driver_drive(DrivenEngine *de);
#endif // DRIVER_HPP

View File

@@ -1,8 +1,9 @@
#include "textgame.hpp"
#include "driver.hpp"
int main(int argc, char **argv)
{
TextGame tg;
tg.run();
driver_drive(&tg);
}

View File

@@ -15,24 +15,6 @@
#include "source.hpp"
#include "luasnap.hpp"
// Read control.lst
//
// - trim all lines
// - remove blank lines
// - remove comment lines
//
util::StringVec read_control_lst(const std::string &path) {
util::StringVec lines = util::get_file_lines(path);
util::StringVec result;
for (int i = 0; i < int(lines.size()); i++) {
std::string trimmed = util::trim(lines[i]);
if ((trimmed.size() > 0) && (trimmed[0] != '#')) {
result.push_back(trimmed);
}
}
return result;
}
LuaDefine(source_makeclass, "f") {
LuaArg classname;
LuaRet classtab;
@@ -155,17 +137,7 @@ static void source_updatefile(LuaStack &LS0, LuaSlot source, LuaSlot fn, LuaSlot
old_fingerprint = LS.ckstring(fingerprint);
}
// std::cerr << "Probing " << cfn << std::endl;
std::string new_fingerprint = util::get_file_fingerprint("lua/" + cfn);
if ((old_fingerprint == "") || (old_fingerprint != new_fingerprint)) {
std::cerr << "Rereading " << cfn << std::endl;
std::string code;
if (new_fingerprint != "") code = util::get_file_contents("lua/" + cfn);
LS.rawset(info, "name", fn);
LS.rawset(info, "fingerprint", new_fingerprint);
LS.rawset(info, "code", code);
LS.rawset(info, "hash", util::hash_to_hex(util::hash_string(code)));
calculate_loadresult(LS, info, cfn, code);
}
LS.result();
}
@@ -295,39 +267,31 @@ std::string SourceDB::get(const std::string &fn) {
return oss.str();
}
void SourceDB::update() {
void SourceDB::update(const util::LuaSource &source) {
lua_State *L = lua_state_;
LuaVar sourcedb, newdb, info, fn, seq;
LuaStack LS(L, newdb, sourcedb, info, fn, seq);
LuaVar sourcedb, info;
LuaStack LS(L, sourcedb, info);
// Get the (old) source database.
// Get and clear the source database.
LS.rawget(sourcedb, LuaRegistry, "sourcedb");
if (!LS.istable(sourcedb)) {
LS.newtable(sourcedb);
LS.rawset(LuaRegistry, "sourcedb", sourcedb);
}
LS.cleartable(sourcedb);
// Read the list of filenames.
util::StringVec filenames = read_control_lst("lua/control.lst");
if (filenames.empty()) {
luaL_error(L, "cannot read source database control.lst");
for (int i = 0; i < int(source.size()); i++) {
const std::string &file = source[i].first;
const std::string &code = source[i].second;
std::cerr << "Compiling " << file << std::endl;
LS.newtable(info);
LS.rawset(info, "name", file);
LS.rawset(info, "code", code);
LS.rawset(info, "hash", util::hash_to_hex(util::hash_string(code)));
LS.rawset(info, "sequence", i + 1);
calculate_loadresult(LS, info, file, code);
LS.rawset(sourcedb, file, info);
}
// Process the files one by one.
LS.newtable(newdb);
for (int i = 0; i < int(filenames.size()); i++) {
LS.set(fn, filenames[i]);
// Call source_updatefile to get the updated info for one file.
source_updatefile(LS, sourcedb, fn, info);
// Insert the sequence number and put finalized info into the new database.
LS.set(seq, i + 1);
LS.rawset(info, "sequence", seq);
LS.rawset(newdb, fn, info);
}
// Store the new source db.
LS.rawset(LuaRegistry, "sourcedb", newdb);
LS.result();
}

View File

@@ -132,12 +132,10 @@ public:
// Update
//
// Read all the lua source files from disk and store them in the
// source database. Also compiles these files using lua's "load"
// function. Efficient: if a source file is already in the database
// and hasn't been modified, it is not reloaded.
// Update the database using the specified lua source code.
// Compiles these files using lua's "load" function.
//
void update();
void update(const util::LuaSource &source);
// Rebuild
//

View File

@@ -111,6 +111,19 @@ void StreamBuffer::clear() {
lua_reader_size_ = 0;
}
std::string StreamBuffer::readline() {
char *p = read_cursor_;
while ((p < write_cursor_) && (*p != '\n')) p++;
if (p == write_cursor_) {
return "";
} else {
p++;
std::string result(read_cursor_, p - read_cursor_);
read_cursor_ = p;
return result;
}
}
// These routines return true if you can losslessly cast the
// specified value to the specified type.

View File

@@ -266,6 +266,11 @@ public:
// Frees up as much space as possible.
void clear();
// Attempt to do a "readline". If there is no newline in
// the buffer, returns empty string. If there is a newline,
// returns a block of text that ends in newline.
std::string readline();
// Write block of bytes into the buffer.
//
// Caution: this function doesn't write the length!

View File

@@ -144,6 +144,7 @@ void TextGame::do_quit_command(const StringVec &cmd) {
return;
}
actor_id_ = 0;
stop_driver();
}
void TextGame::do_command(const StringVec &words) {
@@ -169,18 +170,28 @@ void TextGame::check_redirects() {
}
}
void TextGame::run()
void TextGame::init()
{
world_.reset(new World(util::WORLD_TYPE_STANDALONE));
world_->update_source();
std::unique_ptr<util::LuaSource> lsource = get_lua_source();
world_->update_source(*lsource);
world_->run_unittests();
actor_id_ = world_->create_login_actor();
std::cerr << "Login actor ID: " << actor_id_ << std::endl;
console_.clear();
}
void TextGame::update() {
check_redirects();
if (actor_id_ == 0) {
stop_driver();
return;
}
// Process lines from stdin.
while (true) {
check_redirects();
if (actor_id_ == 0) break;
console_.add_stdin();
std::string line = get_stdio_channel()->in()->readline();
if (line == "") break;
console_.add(line);
int action = console_.action();
if (action == LuaConsole::DO_LUA) {
do_lua(console_.lua_expression());
@@ -190,5 +201,8 @@ void TextGame::run()
std::cerr << console_.syntax() << std::endl;
}
}
// Process lua source if available.
std::unique_ptr<util::LuaSource> source = get_lua_source();
if (source != nullptr) world_->update_source(*source);
}

View File

@@ -4,9 +4,10 @@
#include "luaconsole.hpp"
#include "world.hpp"
#include "drivenengine.hpp"
#include <memory>
class TextGame {
class TextGame : public DrivenEngine {
private:
using StringVec = LuaConsole::StringVec;
std::unique_ptr<World> world_;
@@ -27,7 +28,8 @@ private:
void check_redirects();
public:
void run();
virtual void init();
virtual void update();
};
#endif // TEXTGAME_HPP

View File

@@ -8,6 +8,7 @@
#include <cmath>
#include <iostream>
#include <iomanip>
#include <cassert>
#ifndef WIN32
#include <unistd.h>
@@ -206,36 +207,34 @@ double distance_squared(double x1, double y1, double x2, double y2) {
return dx*dx + dy*dy;
}
std::string get_file_contents(const std::string &fn) {
static std::string get_file_contents(const std::string &fn) {
std::ifstream fs(fn);
std::stringstream buffer;
buffer << fs.rdbuf();
return buffer.str();
}
StringVec get_file_lines(const std::string &path) {
StringVec result;
std::ifstream f;
f.open(path);
if (f) {
std::string line;
while (std::getline(f, line)) {
result.push_back(line);
static StringVec read_control_lst(const std::string &path) {
StringVec lines = split(get_file_contents(path), '\n');
util::StringVec result;
for (int i = 0; i < int(lines.size()); i++) {
std::string trimmed = trim(lines[i]);
if ((trimmed.size() > 0) && (trimmed[0] != '#')) {
result.push_back(trimmed);
}
f.close();
}
return result;
}
std::string get_file_fingerprint(const std::string &fn) {
struct stat result;
if(stat(fn.c_str(), &result)==0)
{
std::stringstream ss;
ss << result.st_mtime;
return ss.str();
LuaSource read_lua_source(const std::string &dir) {
StringVec files = read_control_lst(dir + "/control.lst");
assert (!files.empty());
LuaSource result;
for (const std::string &file : files) {
std::string data = get_file_contents(dir + "/" + file);
result.emplace_back(file, data);
}
return "";
return result;
}
std::string XYZ::debug_string() const {

View File

@@ -3,6 +3,7 @@
#include <string>
#include <set>
#include <map>
#include <algorithm>
#include <sstream>
#include <ostream>
@@ -21,7 +22,8 @@ enum WorldType {
};
using StringVec = std::vector<std::string>;
using StringMap = std::map<std::string, std::string>;
using StringPair = std::pair<std::string, std::string>;
using LuaSource = std::vector<StringPair>;
using HashValue = std::pair<uint64_t, uint64_t>;
using IdVector = std::vector<int64_t>;
@@ -73,14 +75,8 @@ std::string trim(std::string s);
// Calculate distance between two points
double distance_squared(double x1, double y1, double x2, double y2);
// Read a file as one big string.
std::string get_file_contents(const std::string &path);
// Read a file as a vector of lines.
StringVec get_file_lines(const std::string &path);
// Get a file's fingerprint - ie, size and modification time.
std::string get_file_fingerprint(const std::string &path);
// Read the lua source code from the specified directory.
LuaSource read_lua_source(const std::string &directory);
// An XYZ coordinate, general purpose.
struct XYZ {

View File

@@ -174,7 +174,7 @@ public:
// Update the source database from disk.
//
void update_source() { source_db_.update(); source_db_.rebuild(true); }
void update_source(const util::LuaSource &source) { source_db_.update(source); source_db_.rebuild(true); }
// Run all unit tests.
//