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

311 lines
8.5 KiB
C++

#include "wrap-string.hpp"
#include "wrap-vector.hpp"
#include "wrap-utility.hpp"
#include <iostream>
#include <cstring>
#include "drivenengine.hpp"
static std::vector<std::pair<std::string, DrivenEngineMaker>> makers;
void DrivenEngine::register_maker(const char *kind, DrivenEngineMaker maker) {
std::string skind(kind);
makers.push_back(std::make_pair(skind, maker));
}
void DrivenEngine::print_usage(std::ostream &strm, const char *progname) {
strm << "Usage: " << progname << " <mode>" << std::endl;
for (const auto &mpair : makers) {
strm << " Mode can be: " << mpair.first << std::endl;
}
}
UniqueDrivenEngine DrivenEngine::make(const char *kind) {
for (const auto &mpair : makers) {
if (strcmp(mpair.first.c_str(), kind) == 0) {
return mpair.second();
}
}
return nullptr;
}
Channel::Channel(DrivenEngine *de, int chid, int port, const std::string &target, bool stop) {
chid_ = chid;
port_ = port;
closed_ = false;
target_ = target;
readline_lastc_ = 0;
desired_prompt_ = "";
stop_driver_ = stop;
sb_in_ = std::make_shared<StreamBuffer>();
sb_out_ = std::make_shared<StreamBuffer>();
sb_drvout_ = sb_out_;
}
void Channel::erase_command() {
int ccsize = current_prompt_.size() + current_command_.size();
if (ccsize > 0) {
sb_drvout_->write_bytes(util::repeat_string("\b \b", ccsize));
current_prompt_ = "";
current_command_ = "";
}
}
void Channel::echo_command() {
// If the prompt has changed, erase everything and start over.
if (desired_prompt_ != current_prompt_) {
int ccsize = current_prompt_.size() + current_command_.size();
sb_drvout_->write_bytes(util::repeat_string("\b \b", ccsize));
sb_drvout_->write_bytes(desired_prompt_);
current_command_ = "";
current_prompt_ = desired_prompt_;
}
// Find out how much of the command matches.
int match = util::common_prefix_length(current_command_, desired_command_);
// Echo backspaces to remove the non-matching part.
int remove = current_command_.size() - match;
if (remove > 0) {
sb_drvout_->write_bytes(util::repeat_string("\b \b", remove));
current_command_ = current_command_.substr(0, match);
}
// Echo the new part.
std::string newpart = desired_command_.substr(current_command_.size());
if (newpart != "") {
sb_drvout_->write_bytes(newpart);
current_command_ = desired_command_;
}
}
void Channel::set_prompt(const std::string &p) {
desired_prompt_ = p;
}
void Channel::feed_readline(int nbytes, const char *bytes) {
for (int i = 0; i < nbytes; i++) {
char c = bytes[i];
if ((c == '\n') && (readline_lastc_ == '\r')) {
// Ignore newline immediately after carriage return.
// Otherwise, crlf produces two newlines.
} else if ((c == '\r') || (c == '\n')) {
echo_command();
sb_drvout_->write_bytes(std::string(" \n"));
sb_in_->write_bytes(desired_command_);
sb_in_->write_uint8('\n');
desired_command_ = "";
current_prompt_ = "";
current_command_ = "";
} else if ((c == '\b') || (c == 127)) {
int len = desired_command_.size();
if (len > 0) {
desired_command_ = desired_command_.substr(0, len-1);
}
} else if (c >= 32) {
int len = desired_command_.size();
if (len < READLINE_MAX) {
desired_command_ = desired_command_ + c;
}
}
readline_lastc_ = c;
}
}
void Channel::peek_outgoing(int *nbytes, const char **bytes) const {
*nbytes = sb_drvout_->fill();
*bytes = sb_drvout_->data();
}
void Channel::pump_readline() {
if (sb_drvout_ != sb_out_) {
if (!sb_out_->empty()) {
erase_command();
sb_out_->transfer_into(sb_drvout_.get());
}
echo_command();
}
}
void Channel::sent_outgoing(int nbytes) {
sb_drvout_->read_bytes(nbytes);
}
int DrivenEngine::find_unused_chid() {
// Note: channel ID zero is special, it is never reused.
for (int i = 0; i < MAX_CHAN; i++) {
int id = next_unused_chid_++;
if (next_unused_chid_ == 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) < MAX_CHAN);
assert(channels_[chid].get() != nullptr);
return channels_[chid].get();
}
void DrivenEngine::listen_port(int port) {
listen_ports_.push_back(port);
}
double DrivenEngine::get_clock() {
return clock_;
}
SharedChannel DrivenEngine::new_outgoing_channel(const std::string &target) {
int chid = find_unused_chid();
new_outgoing_.push_back(chid);
SharedChannel result = std::make_shared<Channel>(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_;
}
util::LuaSourcePtr 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;
for (int i = 0; i < MAX_CHAN; i++) {
if (channels_[i] != nullptr) {
channels_[i]->stop_driver_ = true;
}
}
}
const std::vector<int> &DrivenEngine::drv_get_listen_ports() const {
return listen_ports_;
}
const std::vector<int> &DrivenEngine::drv_get_new_outgoing() const {
return new_outgoing_;
}
void DrivenEngine::drv_clear_new_outgoing() {
new_outgoing_.clear();
}
const std::string &DrivenEngine::drv_get_target(int chid) const {
return get_chid(chid)->target_;
}
bool DrivenEngine::drv_outgoing_empty(int chid) const {
int nbytes; const char *bytes;
drv_peek_outgoing(chid, &nbytes, &bytes);
return (nbytes == 0);
}
bool DrivenEngine::drv_get_channel_released(int chid) const {
return channels_[chid].use_count() == 1;
}
void DrivenEngine::drv_peek_outgoing(int chid, int *nbytes, const char **bytes) const {
return get_chid(chid)->peek_outgoing(nbytes, bytes);
}
void DrivenEngine::drv_sent_outgoing(int chid, int nbytes) {
return get_chid(chid)->sent_outgoing(nbytes);
}
void DrivenEngine::drv_recv_incoming(int chid, int nbytes, const char *bytes) {
if (nbytes > 0) {
Channel *ch = get_chid(chid);
if (ch->sb_drvout_ != ch->sb_out_) {
ch->feed_readline(nbytes, bytes);
} else {
ch->sb_in_->write_bytes(bytes, nbytes);
}
}
}
void DrivenEngine::drv_notify_close(int chid, std::string_view err) {
Channel *ch = get_chid(chid);
ch->closed_ = true;
ch->error_ = err;
channels_[chid].reset();
}
int DrivenEngine::drv_notify_accept(int port) {
int chid = find_unused_chid();
channels_[chid] = std::make_shared<Channel>(this, chid, port, "", stop_driver_);
accepted_channels_.push_back(channels_[chid]);
return chid;
}
void DrivenEngine::drv_clear_lua_source() {
lua_source_.reset();
rescan_lua_source_ = false;
}
void DrivenEngine::drv_add_lua_source(std::string_view fn, std::string_view data) {
if (lua_source_ == nullptr) {
lua_source_.reset(new util::LuaSourceVec);
}
lua_source_->emplace_back(std::string(fn), std::string(data));
}
void DrivenEngine::drv_invoke_event_init(int argc, char *argv[]) {
event_init(argc, argv);
stdio_channel_->pump_readline();
}
void DrivenEngine::drv_invoke_event_update(double clock) {
clock_ = clock;
event_update();
stdio_channel_->pump_readline();
}
bool DrivenEngine::drv_get_rescan_lua_source() const {
return rescan_lua_source_;
}
bool DrivenEngine::drv_get_stop_driver() const {
return stop_driver_;
}
DrivenEngine::DrivenEngine() {
next_unused_chid_ = 1;
stdio_channel_ = std::make_shared<Channel>(this, 0, 0, "", false);
stdio_channel_->sb_drvout_ = std::make_shared<StreamBuffer>();
channels_[0] = stdio_channel_;
rescan_lua_source_ = true;
clock_ = 0.0;
stop_driver_ = false;
}
DrivenEngine::~DrivenEngine() {}
static DrivenEngine *engine_;
void DrivenEngine::set(DrivenEngine *de) {
engine_ = de;
}
DrivenEngine *DrivenEngine::get() {
return engine_;
}