On server, channel dprint through the readline-device. Also some refactors and quality improvements.

This commit is contained in:
2026-01-09 14:28:58 -05:00
parent 1087d18a2e
commit 7fa3f39d72
15 changed files with 258 additions and 253 deletions

View File

@@ -1,7 +1,6 @@
#include "drvutil.hpp"
#include "osdrvutil.hpp"
#include "sslutil.hpp"
#include "readline.hpp"
#include "../core/enginewrapper.hpp"
@@ -220,21 +219,8 @@ static int socket_poll(struct pollfd *pollvec, int pollcount, int mstimeout, std
}
// Write unicode onto the console.
static void console_write(const std::u32string &cps) {
std::string utf8 = drvutil::utf32_to_utf8(cps);
write(1, utf8.c_str(), utf8.size());
}
static std::u32string console_read() {
std::u32string result;
char buffer[512];
int nread = read(0, buffer, 512);
if (nread > 0) {
std::string_view s(buffer, nread);
result = drvutil::utf8_to_utf32(s, nullptr);
}
return result;
}
static void call_init_engine_wrapper(const std::filesystem::path &luprexroot, EngineWrapper *w) {
using InitFn = void (*)(EngineWrapper *);

View File

@@ -2,7 +2,6 @@
#define _WIN32_WINNT 0x0600
#include "drvutil.hpp"
#include "osdrvutil.hpp"
#include "sslutil.hpp"
#include "readline.hpp"
#include "../core/enginewrapper.hpp"
@@ -232,52 +231,6 @@ static void init_winsock() {
}
static void console_write(const std::u32string &cps) {
if (cps.size() == 0) return;
// Convert to wstring. Any character not representable as a single wchar_t
// is replaced with a box. It's not ideal, but it's pretty good.
std::wstring ws(cps.size(), 0);
for (int i = 0; i < int(cps.size()); i++) {
char32_t c = cps[i];
if (drvutil::is_single_wchar_t(c)) ws[i] = (wchar_t)c;
else ws[i] = 0x2610;
}
HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
assert(hstdout != INVALID_HANDLE_VALUE);
DWORD nwrote;
std::wstring_view v(ws);
while (v.size() > 0) {
int nwrite = v.size();
if (nwrite > 10000) nwrite = 10000;
assert(WriteConsoleW(hstdout, v.data(), nwrite, &nwrote, nullptr));
assert(nwrote > 0);
v.remove_prefix(nwrote);
}
}
static std::u32string console_read() {
HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
assert(hstdin != INVALID_HANDLE_VALUE);
INPUT_RECORD inrecords[512];
DWORD nread, nevents;
if (GetNumberOfConsoleInputEvents(hstdin, &nevents)) {
if (int(nevents) > 0) {
if (int(nevents) > 512) nevents = 512;
ReadConsoleInputW(hstdin, inrecords, nevents, &nread);
std::u32string result(nread, 0);
int len = 0;
for (int i = 0; i < int(nread); i++) {
const INPUT_RECORD &inr = inrecords[i];
if (inr.EventType != KEY_EVENT) continue;
const KEY_EVENT_RECORD &key = inr.Event.KeyEvent;
if (!key.bKeyDown) continue;
result[len++] = key.uChar.UnicodeChar;
}
return result.substr(0, len);
}
}
return std::u32string();
}
static void ssl_load_certificate_authorities(SSL_CTX *ctx) {
HCERTSTORE hStore = CertOpenSystemStoreW(0, L"ROOT");

View File

@@ -11,6 +11,11 @@
#define POLLVEC_SIZE (DRV_MAX_CHAN + 1)
#define MAX_BIO_BUFFER (128 * 1024)
static ReadlineDevice readline_device;
static void dprint_callback(const char *oneline, size_t size) {
readline_device.printline(std::string_view(oneline, size));
}
static void if_error_print_and_exit(const std::string_view str) {
if (!str.empty()) {
@@ -19,16 +24,6 @@ static void if_error_print_and_exit(const std::string_view str) {
}
}
// DPrints are currently not going through the readline device.
// doing so would not currently be thread-safe. Do I care about
// that? I'm not sure.
static void dprint_callback(const char *oneline, size_t size) {
fwrite("**", 1, 2, stderr);
fwrite(oneline, 1, size, stderr);
fwrite("\n", 1, 1, stderr);
fflush(stderr);
}
inline bool file_exists(const std::filesystem::path &name) {
std::ifstream f(name);
return f.good();
@@ -96,7 +91,6 @@ class Driver {
bool read_console_recently_;
std::unique_ptr<struct pollfd[]> pollvec_;
std::unique_ptr<char[]> chbuf_;
ReadlineDevice readline_device_;
std::string console_command_;
sslutil::UniqueCTX ssl_server_ctx_;
@@ -209,7 +203,7 @@ class Driver {
engw.play_access(&engw, AccessKind::CHANNEL_PRINTS, 0, 0, "", &ndata, &data);
if (ndata > 0) {
if (ndata > DRV_SHORTSTRING_SIZE) ndata = DRV_SHORTSTRING_SIZE;
readline_device_.printline(std::string_view(data, ndata));
readline_device.printline(std::string_view(data, ndata));
}
}
}
@@ -224,7 +218,7 @@ class Driver {
inject_lua_source();
}
else {
readline_device_.printline(parser.Error());
readline_device.printline(parser.Error());
}
}
@@ -249,24 +243,24 @@ class Driver {
engw.play_access(&engw, AccessKind::INVOKE_LUA_EXPR,
0, cmd.size(), cmd.c_str(), nullptr, nullptr);
} else {
readline_device_.printline(message);
readline_device.printline(message);
}
if (console_command_.empty()) {
readline_device_.set_prompt(">");
readline_device.set_prompt(">");
} else {
readline_device_.set_prompt(">>");
readline_device.set_prompt(">>");
}
}
void handle_console_input() {
read_console_recently_ = false;
while (true) {
std::u32string cps = console_read();
std::u32string cps = drvutil::console_read();
if (cps.size() == 0) break;
read_console_recently_ = true;
for (char32_t c : cps) {
std::string line = readline_device_.putcode(c);
std::string line = readline_device.putcode(c);
if (!line.empty()) {
add_console_command(line);
}
@@ -631,8 +625,7 @@ class Driver {
int drive(int argc, char *argv[]) {
// Set up the console readline device.
readline_device_.set_print_callback(console_write);
readline_device_.set_prompt(">");
readline_device.set_prompt(">");
console_command_.clear();
// Remove the program name from argv.

View File

@@ -0,0 +1,83 @@
#include <time.h>
#include <string>
#include <cstring>
#include <cassert>
#include <cstdio>
#include <pthread.h>
namespace drvutil {
// strerror has to be the most overcomplicated function imaginable. The simple
// version, 'strerror', is not thread-safe, and the improved versions are all
// incompatible from OS to OS. Even different versions of linux aren't
// compatible. A lot of conditional compilation is needed.
inline static void strerror_helper(int status, int errnum, char errbuf[256]) {
if (status != 0) {
snprintf(errbuf, 256, "unknown errno %d", errnum);
}
}
inline static void strerror_helper(const char *result, int errnum, char errbuf[256]) {
if (result != errbuf) {
snprintf(errbuf, 256, "%s", result);
}
}
void strerror_safe(int errnum, char errbuf[256]) {
auto rval = strerror_r(errnum, errbuf, 256);
strerror_helper(rval, errnum, errbuf);
}
std::string strerror_str(int errnum) {
char buf[256];
strerror_safe(errnum, buf);
return buf;
}
class MonoClock {
private:
struct timespec base_;
public:
MonoClock() {
int status = clock_gettime(CLOCK_MONOTONIC, &base_);
assert(status == 0);
}
double get() {
struct timespec t;
int status = clock_gettime(CLOCK_MONOTONIC, &t);
assert(status == 0);
double tv_sec = t.tv_sec - base_.tv_sec;
double tv_nsec = t.tv_nsec - base_.tv_nsec;
return tv_sec + (tv_nsec * 1.0E-9);
}
};
static MonoClock monoclock;
double get_monotonic_clock() {
return monoclock.get();
}
void console_write(const std::u32string &cps) {
std::string utf8 = drvutil::utf32_to_utf8(cps);
write(1, utf8.c_str(), utf8.size());
}
std::u32string console_read() {
std::u32string result;
char buffer[512];
int nread = read(0, buffer, 512);
if (nread > 0) {
std::string_view s(buffer, nread);
result = drvutil::utf8_to_utf32(s, nullptr);
}
return result;
}
} // namespace drvutil

View File

@@ -0,0 +1,94 @@
#include <windows.h>
#include <profileapi.h>
namespace drvutil {
static void strerror_safe(int errnum, char errbuf[256]) {
int status = strerror_s(errbuf, 256, errnum);
if (status != 0) {
snprintf(errbuf, 256, "unknown errno %d", errnum);
}
}
std::string strerror_str(int errnum) {
char buf[256];
strerror_safe(errnum, buf);
return buf;
}
class MonoClock {
public:
double freq_;
LONGLONG base_;
inline LONGLONG qpc() {
LARGE_INTEGER x;
BOOL status = QueryPerformanceCounter(&x);
assert(status != 0);
return x.QuadPart;
}
MonoClock() {
LARGE_INTEGER x;
BOOL status = QueryPerformanceFrequency(&x);
assert(status != 0);
freq_ = 1.0 / double(x.QuadPart);
base_ = qpc();
}
double get() {
return (qpc() - base_) * freq_;
}
};
static MonoClock monoclock;
double get_monotonic_clock() {
return monoclock.get();
}
void console_write(const std::u32string &cps) {
if (cps.size() == 0) return;
// Convert to wstring. Any character not representable as a single wchar_t
// is replaced with a box. It's not ideal, but it's pretty good.
std::wstring ws(cps.size(), 0);
for (int i = 0; i < int(cps.size()); i++) {
char32_t c = cps[i];
if (drvutil::is_single_wchar_t(c)) ws[i] = (wchar_t)c;
else ws[i] = 0x2610;
}
HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
assert(hstdout != INVALID_HANDLE_VALUE);
DWORD nwrote;
std::wstring_view v(ws);
while (v.size() > 0) {
int nwrite = v.size();
if (nwrite > 10000) nwrite = 10000;
assert(WriteConsoleW(hstdout, v.data(), nwrite, &nwrote, nullptr));
assert(nwrote > 0);
v.remove_prefix(nwrote);
}
}
std::u32string console_read() {
HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
assert(hstdin != INVALID_HANDLE_VALUE);
INPUT_RECORD inrecords[512];
DWORD nread, nevents;
if (GetNumberOfConsoleInputEvents(hstdin, &nevents)) {
if (int(nevents) > 0) {
if (int(nevents) > 512) nevents = 512;
ReadConsoleInputW(hstdin, inrecords, nevents, &nread);
std::u32string result(nread, 0);
int len = 0;
for (int i = 0; i < int(nread); i++) {
const INPUT_RECORD &inr = inrecords[i];
if (inr.EventType != KEY_EVENT) continue;
const KEY_EVENT_RECORD &key = inr.Event.KeyEvent;
if (!key.bKeyDown) continue;
result[len++] = key.uChar.UnicodeChar;
}
return result.substr(0, len);
}
}
return std::u32string();
}
} // namespace drvutil

View File

@@ -354,4 +354,17 @@ std::string package_lua_source(const std::filesystem::path &base, std::ostream *
}
} // namespace drv
} // namespace drvutil
// Include the system-dependent part of drvutil.
#if defined(__linux__)
#include "drvutil-linux.cpp"
#elif defined(_WIN32)
#include "drvutil-windows.cpp"
#else
#error "Only support __linux__ or _WIN32"
#endif

View File

@@ -19,6 +19,14 @@
namespace drvutil {
// Read/write the console.
//
// These only work when the program is being run from an actual
// text-based console. Otherwise, they may go to the bit-bucket.
//
std::u32string console_read();
void console_write(const std::u32string &cps);
// Read the lua source from disk into an ostringstream.
//
// To pass the lua source into the DLL, here is what you do: Construct an

View File

@@ -1,116 +0,0 @@
#include "osdrvutil.hpp"
#if defined(__linux__)
#include <time.h>
#elif defined(_WIN32)
#include <windows.h>
#include <profileapi.h>
#else
#error "Only support __linux__ or _WIN32"
#endif
#include <string>
#include <cstring>
#include <cassert>
#include <cstdio>
// strerror has to be the most overcomplicated function imaginable. The simple
// version, 'strerror', is not thread-safe, and the improved versions are all
// incompatible from OS to OS. Even different versions of linux aren't
// compatible. A lot of conditional compilation is needed.
#if defined(__linux__)
inline static void strerror_helper(int status, int errnum, char errbuf[256]) {
if (status != 0) {
snprintf(errbuf, 256, "unknown errno %d", errnum);
}
}
inline static void strerror_helper(const char *result, int errnum, char errbuf[256]) {
if (result != errbuf) {
snprintf(errbuf, 256, "%s", result);
}
}
static void strerror_safe(int errnum, char errbuf[256]) {
auto rval = strerror_r(errnum, errbuf, 256);
strerror_helper(rval, errnum, errbuf);
}
#elif defined(_WIN32)
static void strerror_safe(int errnum, char errbuf[256]) {
int status = strerror_s(errbuf, 256, errnum);
if (status != 0) {
snprintf(errbuf, 256, "unknown errno %d", errnum);
}
}
#endif
// The monotonic clock is required to start at zero at initialization time,
// advance steadily, and never go backwards. It is okay, however, if it is a
// little inaccurate, or if it drifts a little over time.
#if defined(__linux__)
class MonoClock {
private:
struct timespec base_;
public:
MonoClock() {
int status = clock_gettime(CLOCK_MONOTONIC, &base_);
assert(status == 0);
}
double get() {
struct timespec t;
int status = clock_gettime(CLOCK_MONOTONIC, &t);
assert(status == 0);
double tv_sec = t.tv_sec - base_.tv_sec;
double tv_nsec = t.tv_nsec - base_.tv_nsec;
return tv_sec + (tv_nsec * 1.0E-9);
}
};
#elif defined(_WIN32)
class MonoClock {
public:
double freq_;
LONGLONG base_;
inline LONGLONG qpc() {
LARGE_INTEGER x;
BOOL status = QueryPerformanceCounter(&x);
assert(status != 0);
return x.QuadPart;
}
MonoClock() {
LARGE_INTEGER x;
BOOL status = QueryPerformanceFrequency(&x);
assert(status != 0);
freq_ = 1.0 / double(x.QuadPart);
base_ = qpc();
}
double get() {
return (qpc() - base_) * freq_;
}
};
#endif
namespace drvutil {
static MonoClock monoclock;
double get_monotonic_clock() {
return monoclock.get();
}
std::string strerror_str(int errnum) {
char buf[256];
strerror_safe(errnum, buf);
return buf;
}
} // namespace drvutil

View File

@@ -1,23 +0,0 @@
#ifndef OSDRVUTIL_HPP
#define OSDRVUTIL_HPP
#include <string>
namespace drvutil {
// Get a system error message, in an OS-independent manner.
//
std::string strerror_str(int errnum);
// Get the amount of time elapsed since program start.
//
// This is guaranteed to be monotonically increasing. It is not
// guaranteed to be accurate. Error could gradually accumulate over
// time.
//
double get_monotonic_clock();
} // namespace drvutil
#endif // OSDRVUTIL_HPP

View File

@@ -24,11 +24,8 @@ static int common_prefix_length(const std::u32string &a, const std::u32string &b
return minlen;
}
void ReadlineDevice::set_print_callback(print_callback cb) {
print_cb_ = cb;
}
void ReadlineDevice::set_prompt(std::string_view prompt) {
std::scoped_lock lock(mutex_);
desired_prompt_ = drvutil::utf8_to_utf32(prompt, nullptr);
echo_command();
}
@@ -36,7 +33,7 @@ void ReadlineDevice::set_prompt(std::string_view prompt) {
void ReadlineDevice::erase_command() {
int ccsize = current_prompt_.size() + current_command_.size();
if (ccsize > 0) {
print_cb_(n_backspaces(ccsize));
drvutil::console_write(n_backspaces(ccsize));
current_prompt_.clear();
current_command_.clear();
}
@@ -46,8 +43,8 @@ void ReadlineDevice::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();
print_cb_(n_backspaces(ccsize));
print_cb_(desired_prompt_);
drvutil::console_write(n_backspaces(ccsize));
drvutil::console_write(desired_prompt_);
current_command_.clear();
current_prompt_ = desired_prompt_;
}
@@ -58,31 +55,31 @@ void ReadlineDevice::echo_command() {
// Echo backspaces to remove the non-matching part.
int remove = current_command_.size() - match;
if (remove > 0) {
print_cb_(n_backspaces(remove));
drvutil::console_write(n_backspaces(remove));
current_command_ = current_command_.substr(0, match);
}
// Echo the new part.
std::u32string newpart = desired_command_.substr(current_command_.size());
if (!newpart.empty()) {
print_cb_(newpart);
drvutil::console_write(newpart);
current_command_ = desired_command_;
}
}
std::string ReadlineDevice::putcode(char32_t c) {
std::scoped_lock lock(mutex_);
if ((c == '\n') && (readline_lastc_ == '\r')) {
// Ignore newline immediately after carriage return.
// Otherwise, crlf produces two newlines.
return "";
} else if ((c == '\r') || (c == '\n')) {
echo_command();
print_cb_(white_ + newline_);
drvutil::console_write(white_ + newline_);
std::u32string result = desired_command_ + newline_;
desired_command_.clear();
current_prompt_.clear();
current_command_.clear();
echo_command();
return drvutil::utf32_to_utf8(result);
} else if ((c == '\b') || (c == 127)) {
int len = desired_command_.size();
@@ -105,11 +102,12 @@ std::string ReadlineDevice::putcode(char32_t c) {
void ReadlineDevice::printline(std::string_view s) {
std::scoped_lock lock(mutex_);
bool missing_newline = ((s.size() == 0) || (s[s.size() - 1] != '\n'));
std::u32string utf32 = drvutil::utf8_to_utf32(s, nullptr);
erase_command();
print_cb_(utf32);
if (missing_newline) print_cb_(newline_);
drvutil::console_write(utf32);
if (missing_newline) drvutil::console_write(newline_);
echo_command();
}

View File

@@ -4,6 +4,7 @@
#include <string>
#include <string_view>
#include <mutex>
#include "drvutil.hpp"
@@ -12,7 +13,7 @@ public:
using print_callback = void (*)(const std::u32string &text);
private:
print_callback print_cb_;
std::mutex mutex_;
std::u32string desired_command_;
std::u32string current_command_;
std::u32string desired_prompt_;
@@ -24,9 +25,6 @@ private:
public:
// The callback must be set before using the readline device.
void set_print_callback(print_callback cb);
// change the prompt.
void set_prompt(std::string_view prompt);

View File

@@ -1,5 +1,4 @@
#include "drvutil.hpp"
#include "osdrvutil.hpp"
#include "sslutil.hpp"
#include <iostream>
#include <cassert>