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 "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>
|
|
|
|
|
|
2025-12-16 00:34:30 -05:00
|
|
|
class LpxClient : public DrivenEngine {
|
2021-10-14 15:51:38 -04:00
|
|
|
public:
|
|
|
|
|
InvocationQueue unack_;
|
2022-01-05 12:50:16 -05:00
|
|
|
SharedChannel channel_;
|
2021-11-11 13:56:49 -05:00
|
|
|
PrintChanneler print_channeler_;
|
2024-09-04 21:00:47 -04:00
|
|
|
eng::vector<Invocation> delayed_invocations_;
|
2025-12-03 19:55:53 -05:00
|
|
|
lua_State *lua_syntax_checker_;
|
2021-10-14 15:51:38 -04:00
|
|
|
|
|
|
|
|
public:
|
2025-06-16 19:58:26 -04:00
|
|
|
LpxClient() {
|
2025-12-03 19:55:53 -05:00
|
|
|
lua_syntax_checker_ = LuaCoreStack::newstate(eng::l_alloc);
|
|
|
|
|
|
2025-06-16 19:58:26 -04:00
|
|
|
set_initial_state_standalone();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-03 19:55:53 -05:00
|
|
|
~LpxClient() {
|
|
|
|
|
lua_close(lua_syntax_checker_);
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
2024-02-22 17:30:24 -05:00
|
|
|
// Reset the print channeler
|
|
|
|
|
print_channeler_.reset();
|
2025-06-27 20:03:38 -04:00
|
|
|
|
|
|
|
|
// Do not trigger lua source loading.
|
|
|
|
|
rescan_lua_source(false);
|
|
|
|
|
|
|
|
|
|
// Clear any saved invocations
|
|
|
|
|
delayed_invocations_.clear();
|
2024-02-22 17:30:24 -05:00
|
|
|
}
|
|
|
|
|
|
2025-06-16 19:58:26 -04:00
|
|
|
void set_initial_state_standalone() {
|
2024-02-22 17:30:24 -05:00
|
|
|
// Create the world model.
|
|
|
|
|
world_.reset(new World(WORLD_TYPE_MASTER));
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
// Clear the unack command queue.
|
|
|
|
|
unack_.clear();
|
|
|
|
|
|
|
|
|
|
// Reset the print channeler
|
|
|
|
|
print_channeler_.reset();
|
2021-11-09 16:27:39 -05:00
|
|
|
|
2025-06-16 19:58:26 -04:00
|
|
|
// Trigger lua source loading.
|
2025-06-27 20:03:38 -04:00
|
|
|
rescan_lua_source(true);
|
|
|
|
|
|
|
|
|
|
// Clear any saved invocations
|
|
|
|
|
delayed_invocations_.clear();
|
2025-06-16 19:58:26 -04:00
|
|
|
}
|
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() {
|
2025-06-16 19:58:26 -04:00
|
|
|
if (channel_)
|
|
|
|
|
{
|
|
|
|
|
set_initial_state_standalone();
|
|
|
|
|
}
|
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) {
|
2025-06-16 19:58:26 -04:00
|
|
|
world_->invoke(inv);
|
2024-02-22 17:30:24 -05:00
|
|
|
} 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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 00:34:30 -05:00
|
|
|
void slash_command(std::string_view command) {
|
|
|
|
|
util::dprint("Slash Command: ", command);
|
|
|
|
|
//
|
|
|
|
|
// set_initial_state_connect(util::ss("nocert:", hostname, ":8085"));
|
2021-11-09 16:27:39 -05:00
|
|
|
}
|
|
|
|
|
|
2021-11-11 13:56:49 -05:00
|
|
|
void change_actor_id(int64_t actor_id) {
|
2025-07-02 16:01:18 -04:00
|
|
|
util::dprint("Actor ID changing: ", actor_id);
|
2021-11-11 13:56:49 -05:00
|
|
|
print_channeler_.reset();
|
|
|
|
|
actor_id_ = actor_id;
|
|
|
|
|
}
|
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("");
|
2025-12-02 18:06:23 -05:00
|
|
|
int64_t nactor = world_->patch(sb, &dbc);
|
2021-11-11 13:56:49 -05:00
|
|
|
if (nactor != actor_id_) change_actor_id(nactor);
|
2025-12-15 22:42:02 -05:00
|
|
|
// dbc.dump(...);
|
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
|
|
|
}
|
|
|
|
|
|
2025-06-13 21:03:13 -04:00
|
|
|
virtual void event_access(AccessKind kind, int64_t place_id, std::string_view datapk, StreamBuffer *retpk) override {
|
2025-06-16 19:58:26 -04:00
|
|
|
if (place_id == 0) place_id = actor_id_;
|
2024-09-05 01:33:14 -04:00
|
|
|
switch (kind) {
|
2025-06-16 19:58:26 -04:00
|
|
|
case AccessKind::INVOKE_LUA_CALL:
|
|
|
|
|
case AccessKind::INVOKE_LUA_EXPR:
|
|
|
|
|
case AccessKind::INVOKE_FLUSH_PRINTS:
|
|
|
|
|
case AccessKind::INVOKE_TICK:
|
|
|
|
|
case AccessKind::INVOKE_LUA_SOURCE: {
|
|
|
|
|
delayed_invocations_.emplace_back(kind, actor_id_, place_id, datapk);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-06-13 21:03:13 -04:00
|
|
|
case AccessKind::PROBE_LUA_CALL: {
|
2024-09-05 01:33:14 -04:00
|
|
|
world_to_asynchronous();
|
|
|
|
|
world_->probe_lua_call(actor_id_, place_id, datapk, retpk);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-06-16 19:58:26 -04:00
|
|
|
case AccessKind::CONNECT_TO_SERVER: {
|
|
|
|
|
set_initial_state_connect(util::ss("nocert:", datapk, ":8085"));
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-12-09 15:51:35 -05:00
|
|
|
case AccessKind::VALIDATE_LUA_EXPR: {
|
2025-12-03 19:55:53 -05:00
|
|
|
LuaVar closure;
|
2025-12-09 02:42:13 -05:00
|
|
|
LuaExtStack LS(lua_syntax_checker_, closure);
|
2025-12-03 19:55:53 -05:00
|
|
|
eng::string errmsg = LS.load(closure, datapk, "stdin");
|
|
|
|
|
retpk->write_bytes(errmsg);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-12-15 22:22:03 -05:00
|
|
|
case AccessKind::CHANNEL_PRINTS: {
|
2025-12-15 22:42:02 -05:00
|
|
|
// If there's nothing new in the printbuffer, this is very fast.
|
2025-12-15 22:22:03 -05:00
|
|
|
world_to_asynchronous();
|
|
|
|
|
if (print_channeler_.channel(world_->get_printbuffer(actor_id_), retpk)) {
|
|
|
|
|
send_invocation(print_channeler_.invocation(actor_id_));
|
|
|
|
|
}
|
2025-12-15 23:35:47 -05:00
|
|
|
set_have_prints(false);
|
2025-12-15 22:22:03 -05:00
|
|
|
break;
|
|
|
|
|
}
|
2025-12-16 00:34:30 -05:00
|
|
|
case AccessKind::SLASH_COMMAND: {
|
|
|
|
|
slash_command(datapk);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-09-05 01:33:14 -04:00
|
|
|
default: {
|
2025-07-02 16:01:18 -04:00
|
|
|
util::dprint("Invalid event_access: ", int(kind));
|
2024-09-05 01:33:14 -04:00
|
|
|
}
|
|
|
|
|
}
|
2024-09-03 22:44:22 -04:00
|
|
|
}
|
|
|
|
|
|
2024-09-04 21:00:47 -04:00
|
|
|
virtual void event_update() override {
|
2023-10-23 20:57:47 -04:00
|
|
|
// Send invocations. We execute these using predictive execution.
|
2024-09-04 21:00:47 -04:00
|
|
|
for (const Invocation &inv : delayed_invocations_) {
|
|
|
|
|
send_invocation(inv);
|
2021-12-15 14:18:19 -05:00
|
|
|
}
|
2024-09-03 22:44:22 -04:00
|
|
|
delayed_invocations_.clear();
|
2021-12-15 14:18:19 -05:00
|
|
|
|
2021-11-03 11:11:31 -04:00
|
|
|
// Check for communication from server..
|
|
|
|
|
if (channel_ != nullptr) {
|
|
|
|
|
if (channel_->closed()) {
|
2025-07-02 16:01:18 -04:00
|
|
|
util::dprint("server closed connection: ", channel_->error());
|
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
|
|
|
}
|
|
|
|
|
}
|
2025-12-15 23:35:47 -05:00
|
|
|
|
|
|
|
|
set_have_prints(print_channeler_.have_prints(world_->get_printbuffer(actor_id_)));
|
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
|
|
|
|