2022-02-23 23:08:28 -05:00
|
|
|
#include "wrap-string.hpp"
|
|
|
|
|
#include "wrap-vector.hpp"
|
2021-10-14 15:51:38 -04:00
|
|
|
|
|
|
|
|
#include "drivenengine.hpp"
|
|
|
|
|
#include "world.hpp"
|
|
|
|
|
#include "luaconsole.hpp"
|
|
|
|
|
#include "invocation.hpp"
|
2021-11-03 11:11:31 -04:00
|
|
|
#include "util.hpp"
|
2021-11-11 13:56:49 -05:00
|
|
|
#include "printbuffer.hpp"
|
2021-10-14 15:51:38 -04:00
|
|
|
|
2022-02-25 19:57:23 -05:00
|
|
|
#include <memory>
|
|
|
|
|
|
2024-02-28 18:24:36 -05:00
|
|
|
class LpxClient : public DrivenEngine, public CommonCommands {
|
2021-10-14 15:51:38 -04:00
|
|
|
public:
|
|
|
|
|
using StringVec = LuaConsole::StringVec;
|
2021-10-15 13:51:32 -04:00
|
|
|
UniqueWorld world_;
|
2021-10-14 15:51:38 -04:00
|
|
|
int64_t actor_id_;
|
|
|
|
|
InvocationQueue unack_;
|
2022-01-05 12:50:16 -05:00
|
|
|
SharedChannel channel_;
|
2021-10-14 15:51:38 -04:00
|
|
|
LuaConsole console_;
|
2021-11-11 13:56:49 -05:00
|
|
|
PrintChanneler print_channeler_;
|
2021-10-14 15:51:38 -04:00
|
|
|
|
|
|
|
|
public:
|
2024-02-22 17:30:24 -05:00
|
|
|
void set_initial_state_connect(const eng::string &hostspec) {
|
2021-10-15 13:51:32 -04:00
|
|
|
// Create the world model.
|
2023-03-01 16:07:13 -05:00
|
|
|
world_.reset(new World(WORLD_TYPE_PREDICTIVE));
|
2021-10-15 13:51:32 -04:00
|
|
|
|
2024-02-22 17:30:24 -05:00
|
|
|
// Create the communication channel.
|
|
|
|
|
channel_ = new_outgoing_channel(hostspec);
|
|
|
|
|
|
2021-10-15 13:51:32 -04:00
|
|
|
// This is a temporary actor that will be used only until the server sends
|
|
|
|
|
// us the first difference transmission. We do this only to establish
|
|
|
|
|
// the invariant that there's always an actor. When the first difference
|
|
|
|
|
// transmission arrives, this actor may be deleted, or it may just be
|
|
|
|
|
// ignored, at the server's discretion.
|
2024-03-04 16:33:23 -05:00
|
|
|
actor_id_ = world_->create_login_actor();
|
2021-12-21 15:22:26 -05:00
|
|
|
|
|
|
|
|
// Clear the unack command queue.
|
|
|
|
|
unack_.clear();
|
2023-10-23 20:57:47 -04:00
|
|
|
|
|
|
|
|
// Export stuff to the graphics engine.
|
|
|
|
|
set_visible_world_and_actor(world_.get(), actor_id_);
|
2024-02-22 17:30:24 -05:00
|
|
|
|
|
|
|
|
// Reset the print channeler
|
|
|
|
|
print_channeler_.reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void set_initial_state_standalone(std::string_view srcpk) {
|
|
|
|
|
// Create the world model.
|
|
|
|
|
world_.reset(new World(WORLD_TYPE_MASTER));
|
|
|
|
|
|
|
|
|
|
// Update the source code of the master model.
|
|
|
|
|
world_->update_source(srcpk);
|
|
|
|
|
|
|
|
|
|
// Make sure the channel is empty.
|
|
|
|
|
channel_.reset();
|
|
|
|
|
|
|
|
|
|
// Create the standalone actor.
|
2024-03-04 16:33:23 -05:00
|
|
|
actor_id_ = world_->create_login_actor();
|
2024-02-22 17:30:24 -05:00
|
|
|
|
|
|
|
|
// TODO: initialize the standalone actor.
|
|
|
|
|
|
|
|
|
|
// Clear the unack command queue.
|
|
|
|
|
unack_.clear();
|
|
|
|
|
|
|
|
|
|
// Export stuff to the graphics engine.
|
|
|
|
|
set_visible_world_and_actor(world_.get(), actor_id_);
|
|
|
|
|
|
|
|
|
|
// Reset the print channeler
|
|
|
|
|
print_channeler_.reset();
|
2021-11-09 16:27:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// When the world is in synchronous mode, there's no
|
|
|
|
|
// snapshot, and the commands in the unack queue are not
|
|
|
|
|
// reflected in the world. In asynchronous mode, there is
|
|
|
|
|
// a snapshot that allows return to the synchronous mode,
|
|
|
|
|
// and the unack commands are in the world model.
|
|
|
|
|
|
|
|
|
|
void world_to_synchronous() {
|
|
|
|
|
if (!world_->snapshot_empty()) {
|
|
|
|
|
world_->rollback();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void world_to_asynchronous() {
|
2024-02-22 17:30:24 -05:00
|
|
|
if (!world_->is_authoritative()) {
|
|
|
|
|
if (world_->snapshot_empty()) {
|
|
|
|
|
world_->snapshot();
|
|
|
|
|
for (const Invocation &inv : unack_) {
|
|
|
|
|
world_->invoke(inv);
|
|
|
|
|
}
|
2021-11-09 16:27:39 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void abandon_server() {
|
2024-02-22 17:30:24 -05:00
|
|
|
// When we abandon the server, we leave the world model
|
|
|
|
|
// hanging around to preserve the invariant that there
|
|
|
|
|
// is always a world model. Then, we trigger a rescan
|
|
|
|
|
// of the lua source. When the lua source shows up, then
|
|
|
|
|
// we will create a standalone model to replace the client
|
|
|
|
|
// model.
|
2021-11-11 16:23:11 -05:00
|
|
|
|
2021-11-09 16:27:39 -05:00
|
|
|
channel_.reset();
|
2024-02-22 17:30:24 -05:00
|
|
|
rescan_lua_source();
|
2021-11-09 16:27:39 -05:00
|
|
|
}
|
|
|
|
|
|
2023-10-23 20:19:36 -04:00
|
|
|
virtual void event_init(std::string_view srcpk, int argc, char *argv[]) {
|
2021-11-09 16:27:39 -05:00
|
|
|
// Put the world into the starting state.
|
2024-02-22 17:30:24 -05:00
|
|
|
set_initial_state_standalone(srcpk);
|
2021-11-04 14:49:25 -04:00
|
|
|
|
|
|
|
|
// Set the console prompt
|
2023-05-18 22:09:54 -04:00
|
|
|
set_console_prompt(console_.get_prompt());
|
2021-10-14 15:51:38 -04:00
|
|
|
}
|
|
|
|
|
|
2021-11-09 16:27:39 -05:00
|
|
|
void send_invocation(const Invocation &inv) {
|
|
|
|
|
if (channel_ == nullptr) {
|
2024-02-22 17:30:24 -05:00
|
|
|
if ((!world_->is_authoritative()) && (inv.kind() == Invocation::KIND_LUA_SOURCE)) {
|
|
|
|
|
// We have a client model, but no client connection. That means we're
|
|
|
|
|
// in the process of shutting down a client model. The client model
|
|
|
|
|
// is supposed to linger until the lua source is reread. Once we have
|
|
|
|
|
// the lua source, we're supposed to throw out the client model and
|
|
|
|
|
// create a standalone model.
|
|
|
|
|
set_initial_state_standalone(inv.datapack());
|
|
|
|
|
} else {
|
|
|
|
|
world_->invoke(inv);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
world_to_asynchronous();
|
|
|
|
|
world_->invoke(inv);
|
|
|
|
|
unack_.push_back(inv);
|
|
|
|
|
StreamBuffer *sb = channel_->out();
|
|
|
|
|
sb->write_uint8(util::MSG_INVOKE);
|
|
|
|
|
inv.serialize(sb);
|
2021-11-09 16:27:39 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-28 18:24:36 -05:00
|
|
|
virtual void do_syntax_error(std::string_view error) override {
|
|
|
|
|
stdostream() << "Syntax error: " << error << std::endl;
|
2021-11-09 16:27:39 -05:00
|
|
|
}
|
|
|
|
|
|
2024-02-28 18:24:36 -05:00
|
|
|
virtual void do_unknown_command(std::string_view name) override {
|
|
|
|
|
stdostream() << "Unknown command: " << name << std::endl;
|
2021-11-26 13:56:24 -05:00
|
|
|
}
|
|
|
|
|
|
2024-02-28 18:24:36 -05:00
|
|
|
virtual void do_view_command() override {
|
2024-02-28 15:35:47 -05:00
|
|
|
stdostream() << world_->tangibles_near_debug_string(actor_id_, 1000);
|
2021-11-09 16:27:39 -05:00
|
|
|
}
|
|
|
|
|
|
2024-02-28 18:24:36 -05:00
|
|
|
virtual void do_moveto_command(int x, int y) override {
|
|
|
|
|
do_unknown_command("moveto");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual void do_quit_command() override {
|
|
|
|
|
abandon_server();
|
|
|
|
|
stop_driver();
|
2021-11-09 16:27:39 -05:00
|
|
|
}
|
|
|
|
|
|
2024-02-28 18:24:36 -05:00
|
|
|
virtual void do_cpl_command() override {
|
|
|
|
|
rescan_lua_source();
|
2021-11-26 15:45:36 -05:00
|
|
|
}
|
|
|
|
|
|
2024-02-28 18:24:36 -05:00
|
|
|
virtual void do_work_command() override {
|
|
|
|
|
do_unknown_command("work");
|
2021-12-15 14:18:19 -05:00
|
|
|
}
|
|
|
|
|
|
2024-02-28 18:24:36 -05:00
|
|
|
virtual void do_display_command() override {
|
|
|
|
|
do_unknown_command("display");
|
2021-12-17 16:21:56 -05:00
|
|
|
}
|
|
|
|
|
|
2024-02-28 18:24:36 -05:00
|
|
|
virtual void do_aborthttp_command() override {
|
|
|
|
|
do_unknown_command("aborthttp");
|
2021-11-09 16:27:39 -05:00
|
|
|
}
|
|
|
|
|
|
2024-02-28 18:24:36 -05:00
|
|
|
virtual void do_connect_command(std::string_view hostname) override {
|
|
|
|
|
set_initial_state_connect(util::ss("nocert:", hostname, ":8085"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual void do_luainvoke_command(std::string_view cmd) override {
|
|
|
|
|
send_invocation(Invocation(Invocation::KIND_LUA, actor_id_, actor_id_, cmd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual void do_luaprobe_command(std::string_view cmd) override {
|
|
|
|
|
world_to_asynchronous();
|
2024-09-01 19:43:00 -04:00
|
|
|
stdostream() << world_->probe_lua_expr(actor_id_, cmd);
|
2024-02-28 18:24:36 -05:00
|
|
|
world_to_synchronous();
|
2021-11-09 16:27:39 -05:00
|
|
|
}
|
|
|
|
|
|
2021-11-11 13:56:49 -05:00
|
|
|
void change_actor_id(int64_t actor_id) {
|
2021-11-11 16:23:11 -05:00
|
|
|
stdostream() << "Actor ID changing: " << actor_id << std::endl;
|
2021-11-11 13:56:49 -05:00
|
|
|
print_channeler_.reset();
|
|
|
|
|
actor_id_ = actor_id;
|
2023-10-23 20:57:47 -04:00
|
|
|
set_visible_world_and_actor(world_.get(), actor_id_);
|
2021-11-11 13:56:49 -05:00
|
|
|
}
|
2021-11-11 16:23:11 -05:00
|
|
|
|
2021-11-09 16:27:39 -05:00
|
|
|
void receive_ack_from_server(StreamBuffer *sb) {
|
|
|
|
|
// An ack is just a single byte, so there's nothing left to read.
|
|
|
|
|
if (unack_.empty()) {
|
|
|
|
|
// Invalid acknowledgement when theres' nothing in the unack queue.
|
|
|
|
|
abandon_server();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
world_to_synchronous();
|
|
|
|
|
world_->invoke(unack_.front());
|
|
|
|
|
unack_.pop_front();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void receive_diff_from_server(StreamBuffer *sb) {
|
|
|
|
|
world_to_synchronous();
|
|
|
|
|
try {
|
2024-02-26 15:19:15 -05:00
|
|
|
DebugCollector dbc("");
|
2021-11-21 13:35:39 -05:00
|
|
|
int64_t nactor = world_->patch_everything(sb, &dbc);
|
2021-11-11 13:56:49 -05:00
|
|
|
if (nactor != actor_id_) change_actor_id(nactor);
|
2021-11-21 13:35:39 -05:00
|
|
|
dbc.dump(stdostream());
|
2023-10-18 17:23:05 -04:00
|
|
|
} catch (const StreamException &sexcept) {
|
2021-11-09 16:27:39 -05:00
|
|
|
abandon_server();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool receive_message_from_server(StreamBuffer *sb) {
|
|
|
|
|
if (sb->fill() < 5) return false;
|
|
|
|
|
int64_t tr_before = sb->total_reads();
|
|
|
|
|
uint8_t message_type = sb->read_uint8();
|
|
|
|
|
uint32_t message_body_len = sb->read_uint32();
|
|
|
|
|
if (sb->fill() < message_body_len) {
|
|
|
|
|
sb->unread_to(tr_before);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2023-10-18 17:23:05 -04:00
|
|
|
const char *message_body = sb->read_bytes(message_body_len);
|
|
|
|
|
StreamBuffer body(std::string_view(message_body, message_body_len));
|
2021-11-09 16:27:39 -05:00
|
|
|
if (message_type == util::MSG_ACK) {
|
|
|
|
|
receive_ack_from_server(&body);
|
|
|
|
|
} else if (message_type == util::MSG_DIFF) {
|
|
|
|
|
receive_diff_from_server(&body);
|
|
|
|
|
} else {
|
|
|
|
|
abandon_server();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (!body.empty()) {
|
|
|
|
|
abandon_server();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2021-11-03 11:11:31 -04:00
|
|
|
}
|
|
|
|
|
|
2021-10-14 15:51:38 -04:00
|
|
|
virtual void event_update() {
|
2023-10-23 20:57:47 -04:00
|
|
|
// Send invocations. We execute these using predictive execution.
|
|
|
|
|
eng::vector<UniqueInvocation> invocations = get_queued_invocations();
|
|
|
|
|
for (const UniqueInvocation &inv : invocations) {
|
|
|
|
|
send_invocation(*inv);
|
2021-12-15 14:18:19 -05:00
|
|
|
}
|
|
|
|
|
|
2021-11-03 11:11:31 -04:00
|
|
|
// Check for keyboard input on stdin.
|
|
|
|
|
while (true) {
|
2022-02-24 02:17:41 -05:00
|
|
|
eng::string line = get_stdio_channel()->in()->readline();
|
2021-11-03 11:11:31 -04:00
|
|
|
if (line == "") break;
|
2021-11-04 11:20:29 -04:00
|
|
|
console_.add(line);
|
2023-05-18 22:09:54 -04:00
|
|
|
set_console_prompt(console_.get_prompt());
|
2021-11-04 14:49:25 -04:00
|
|
|
do_command(console_.get_command());
|
2021-11-03 11:11:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for communication from server..
|
|
|
|
|
if (channel_ != nullptr) {
|
|
|
|
|
if (channel_->closed()) {
|
2022-03-18 16:25:20 -04:00
|
|
|
stdostream() << "server closed connection: " << channel_->error() << std::endl;
|
2021-11-09 16:27:39 -05:00
|
|
|
abandon_server();
|
2021-11-03 11:11:31 -04:00
|
|
|
} else {
|
2021-11-11 16:23:11 -05:00
|
|
|
while (true) {
|
|
|
|
|
if (!receive_message_from_server(channel_->in())) break;
|
|
|
|
|
if (channel_ == nullptr) break;
|
|
|
|
|
}
|
2021-11-09 16:27:39 -05:00
|
|
|
world_to_asynchronous();
|
2021-11-03 11:11:31 -04:00
|
|
|
}
|
|
|
|
|
}
|
2021-11-11 16:23:11 -05:00
|
|
|
|
|
|
|
|
// Channel print statements.
|
|
|
|
|
if (print_channeler_.channel(world_->get_printbuffer(actor_id_), stdostream())) {
|
2024-02-22 17:30:24 -05:00
|
|
|
send_invocation(print_channeler_.invocation(actor_id_));
|
2021-11-11 16:23:11 -05:00
|
|
|
}
|
2021-10-14 15:51:38 -04:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-02-25 19:57:23 -05:00
|
|
|
DrivenEngineDefine("lpxclient", LpxClient);
|
2021-10-14 15:51:38 -04:00
|
|
|
|