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 "world.hpp"
|
|
|
|
|
#include "drivenengine.hpp"
|
2021-11-11 13:56:49 -05:00
|
|
|
#include "util.hpp"
|
|
|
|
|
#include "printbuffer.hpp"
|
2025-12-03 19:55:53 -05:00
|
|
|
#include "luastack.hpp"
|
2021-10-14 15:51:38 -04:00
|
|
|
|
2022-02-25 19:57:23 -05:00
|
|
|
#include <memory>
|
|
|
|
|
|
2022-02-28 21:57:54 -05:00
|
|
|
class Client : public eng::opnew {
|
2021-10-14 15:51:38 -04:00
|
|
|
public:
|
|
|
|
|
int64_t actor_id_;
|
2022-01-05 12:50:16 -05:00
|
|
|
SharedChannel channel_;
|
2021-10-15 13:51:32 -04:00
|
|
|
UniqueWorld sync_;
|
2025-12-02 18:06:23 -05:00
|
|
|
double last_full_diff_;
|
|
|
|
|
double last_mini_diff_;
|
2024-02-26 15:19:15 -05:00
|
|
|
bool async_diff_;
|
2021-10-14 15:51:38 -04:00
|
|
|
};
|
2022-02-25 19:57:23 -05:00
|
|
|
using UniqueClient = std::unique_ptr<Client>;
|
2022-02-24 02:17:41 -05:00
|
|
|
using ClientVector = eng::vector<UniqueClient>;
|
2021-10-14 15:51:38 -04:00
|
|
|
|
2025-12-16 00:34:30 -05:00
|
|
|
class LpxServer : public DrivenEngine {
|
2021-10-14 15:51:38 -04:00
|
|
|
public:
|
2021-11-11 16:23:11 -05:00
|
|
|
ClientVector clients_;
|
2021-11-11 13:56:49 -05:00
|
|
|
PrintChanneler print_channeler_;
|
2022-05-17 15:00:20 -04:00
|
|
|
HttpChannelMap http_client_channels_;
|
|
|
|
|
HttpChannelVec http_server_channels_;
|
2024-09-04 21:00:47 -04:00
|
|
|
eng::vector<Invocation> delayed_invocations_;
|
2025-06-16 19:58:26 -04:00
|
|
|
double next_tick_ = 0;
|
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
|
|
|
LpxServer()
|
|
|
|
|
{
|
2025-12-03 19:55:53 -05:00
|
|
|
// Create a little lua interpreter for syntax checking only.
|
|
|
|
|
lua_syntax_checker_ = LuaCoreStack::newstate(eng::l_alloc);
|
|
|
|
|
|
2021-11-11 13:56:49 -05:00
|
|
|
// Create the master world model.
|
2026-02-21 19:27:42 -05:00
|
|
|
world_.reset(new World(WORLD_TYPE_MASTER));
|
2021-11-11 13:56:49 -05:00
|
|
|
|
2025-06-16 19:58:26 -04:00
|
|
|
// Create the admin actor. Note: there isn't any 'init' function yet.
|
2026-02-21 19:27:42 -05:00
|
|
|
actor_id_ = world_->create_login_actor();
|
2021-11-11 13:56:49 -05:00
|
|
|
|
|
|
|
|
// Print out admin ID for debugging purposes.
|
2026-02-21 19:27:42 -05:00
|
|
|
util::dprint("Admin actor id = ", actor_id_);
|
2021-11-11 13:56:49 -05:00
|
|
|
|
2022-05-17 15:00:20 -04:00
|
|
|
// Enable listening on port 8085 (client connections)
|
2021-11-11 13:56:49 -05:00
|
|
|
listen_port(8085);
|
|
|
|
|
|
2022-05-17 15:00:20 -04:00
|
|
|
// Enable listening on port 8080 (http server connections)
|
|
|
|
|
listen_port(8080);
|
|
|
|
|
|
2025-12-15 22:22:03 -05:00
|
|
|
// Reset the print channeler.
|
|
|
|
|
print_channeler_.reset();
|
2025-12-15 22:42:02 -05:00
|
|
|
|
2025-06-16 19:58:26 -04:00
|
|
|
// Trigger the loading of the lua source.
|
2025-06-27 20:03:38 -04:00
|
|
|
rescan_lua_source(true);
|
2021-11-11 13:56:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-03 19:55:53 -05:00
|
|
|
~LpxServer() {
|
|
|
|
|
lua_close(lua_syntax_checker_);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 00:34:30 -05:00
|
|
|
void slash_command(std::string_view command)
|
|
|
|
|
{
|
|
|
|
|
util::dprint("Slash Command:", command);
|
2021-11-11 13:56:49 -05:00
|
|
|
}
|
|
|
|
|
|
2021-11-11 16:23:11 -05:00
|
|
|
void delete_client(UniqueClient &client) {
|
2025-12-15 22:42:02 -05:00
|
|
|
util::dprint("Client closed: actor id=", client->actor_id_);
|
2026-02-21 19:27:42 -05:00
|
|
|
world_->disconnected(client->actor_id_);
|
2021-11-11 16:23:11 -05:00
|
|
|
client.reset();
|
2021-10-14 15:51:38 -04:00
|
|
|
}
|
|
|
|
|
|
2025-12-02 18:06:23 -05:00
|
|
|
void send_diffs(UniqueClient &client, bool full) {
|
2021-11-11 16:23:11 -05:00
|
|
|
StreamBuffer *sb = client->channel_->out();
|
|
|
|
|
sb->write_uint8(util::MSG_DIFF);
|
|
|
|
|
sb->write_uint32(0);
|
|
|
|
|
int64_t tw_1 = sb->total_writes();
|
2025-12-15 22:42:02 -05:00
|
|
|
// util::dprint("Sending diffs to client ", client->actor_id_);
|
2026-02-21 19:27:42 -05:00
|
|
|
client->sync_->diff(client->actor_id_, full, world_.get(), sb);
|
2021-11-11 16:23:11 -05:00
|
|
|
int64_t tw_2 = sb->total_writes();
|
|
|
|
|
sb->overwrite_int32(tw_1, tw_2 - tw_1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool handle_invocation(UniqueClient &client) {
|
|
|
|
|
StreamBuffer *sb = client->channel_->in();
|
|
|
|
|
int64_t tr_before = sb->total_reads();
|
|
|
|
|
Invocation inv;
|
|
|
|
|
try {
|
|
|
|
|
uint8_t msg_type = sb->read_uint8();
|
|
|
|
|
if (msg_type != util::MSG_INVOKE) {
|
|
|
|
|
delete_client(client);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
inv.deserialize(sb);
|
2023-10-18 17:23:05 -04:00
|
|
|
} catch (const StreamEofOnRead &seof) {
|
2021-11-11 16:23:11 -05:00
|
|
|
sb->unread_to(tr_before);
|
|
|
|
|
return false;
|
2023-10-23 21:22:48 -04:00
|
|
|
} catch (const StreamException &sexcept) {
|
2021-11-11 16:23:11 -05:00
|
|
|
delete_client(client);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2023-10-23 21:22:48 -04:00
|
|
|
// Security check.
|
2021-11-11 16:23:11 -05:00
|
|
|
if (inv.actor() != client->actor_id_) {
|
2023-10-23 21:22:48 -04:00
|
|
|
delete_client(client);
|
|
|
|
|
return false;
|
2021-11-11 16:23:11 -05:00
|
|
|
}
|
2025-12-15 22:42:02 -05:00
|
|
|
// util::dprint("Invoking: ", inv.debug_string());
|
2026-02-21 19:27:42 -05:00
|
|
|
world_->invoke(inv);
|
2021-11-11 16:23:11 -05:00
|
|
|
client->channel_->out()->write_uint8(util::MSG_ACK);
|
|
|
|
|
client->channel_->out()->write_uint32(0);
|
|
|
|
|
client->sync_->invoke(inv);
|
2024-02-26 15:19:15 -05:00
|
|
|
client->async_diff_ = true;
|
2021-11-11 16:23:11 -05:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
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 {
|
2026-02-21 19:27:42 -05: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: {
|
2026-02-21 19:27:42 -05:00
|
|
|
delayed_invocations_.emplace_back(kind, actor_id_, place_id, datapk);
|
2025-06-16 19:58:26 -04:00
|
|
|
break;
|
|
|
|
|
}
|
2025-06-13 21:03:13 -04:00
|
|
|
case AccessKind::PROBE_LUA_CALL: {
|
2026-02-21 19:27:42 -05:00
|
|
|
world_->snapshot();
|
|
|
|
|
world_->probe_lua_call(actor_id_, place_id, datapk, retpk);
|
|
|
|
|
world_->rollback();
|
2024-09-05 01:33:14 -04:00
|
|
|
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.
|
2026-02-21 19:27:42 -05:00
|
|
|
if (print_channeler_.channel(world_->get_printbuffer(actor_id_), retpk)) {
|
|
|
|
|
world_->invoke(print_channeler_.invocation(actor_id_));
|
2025-12-15 22:22:03 -05:00
|
|
|
}
|
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-12-15 22:42:02 -05:00
|
|
|
util::dprint("Invalid event_access: ", int(kind));
|
2024-09-05 01:33:14 -04:00
|
|
|
}
|
|
|
|
|
}
|
2024-09-03 22:44:22 -04:00
|
|
|
}
|
|
|
|
|
|
2023-10-23 20:57:47 -04:00
|
|
|
virtual void event_update() override {
|
2024-02-26 15:19:15 -05:00
|
|
|
// Get the clock.
|
|
|
|
|
double clock = get_clock();
|
|
|
|
|
|
2023-10-23 20:57:47 -04:00
|
|
|
// Execute any queued invocations.
|
|
|
|
|
// We just feed these directly into the master model.
|
2024-09-04 21:00:47 -04:00
|
|
|
for (const Invocation &inv : delayed_invocations_) {
|
2026-02-21 19:27:42 -05:00
|
|
|
world_->invoke(inv);
|
2023-10-23 20:57:47 -04:00
|
|
|
}
|
2024-09-03 22:44:22 -04:00
|
|
|
delayed_invocations_.clear();
|
2021-12-15 14:18:19 -05:00
|
|
|
|
2021-11-11 13:56:49 -05:00
|
|
|
// Check for new incoming channels, set up client structures.
|
|
|
|
|
while (true) {
|
2022-01-05 12:50:16 -05:00
|
|
|
SharedChannel chan = new_incoming_channel();
|
2021-11-11 13:56:49 -05:00
|
|
|
if (chan == nullptr) break;
|
2022-05-17 15:00:20 -04:00
|
|
|
if (chan->port() == 8085) {
|
|
|
|
|
Client *client = new Client;
|
2026-02-21 19:27:42 -05:00
|
|
|
client->actor_id_ = world_->create_login_actor();
|
2024-02-22 17:30:24 -05:00
|
|
|
// TODO: initialize the login actor on the master.
|
2022-05-17 15:00:20 -04:00
|
|
|
client->channel_ = std::move(chan);
|
2024-02-26 15:19:15 -05:00
|
|
|
client->async_diff_ = true;
|
2025-12-02 18:06:23 -05:00
|
|
|
client->last_full_diff_ = -100.0;
|
|
|
|
|
client->last_mini_diff_ = -100.0;
|
2023-03-01 16:07:13 -05:00
|
|
|
client->sync_.reset(new World(WORLD_TYPE_PREDICTIVE));
|
2024-02-22 17:30:24 -05:00
|
|
|
// This login actor is never used, it is just to preserve the invariant that
|
|
|
|
|
// the client model and the server synchronous model are identical.
|
2024-03-04 16:33:23 -05:00
|
|
|
client->sync_->create_login_actor();
|
2022-05-17 15:00:20 -04:00
|
|
|
clients_.emplace_back(client);
|
2025-12-15 22:42:02 -05:00
|
|
|
util::dprint("New client: actor id=", client->actor_id_);
|
2022-05-17 15:00:20 -04:00
|
|
|
} else if (chan->port() == 8080) {
|
|
|
|
|
HttpChannel htchan;
|
|
|
|
|
htchan.channel_ = chan;
|
|
|
|
|
http_server_channels_.push_back(htchan);
|
|
|
|
|
}
|
2021-11-11 13:56:49 -05:00
|
|
|
}
|
|
|
|
|
|
2024-02-26 15:19:15 -05:00
|
|
|
// If the clock has advanced far enough, tick the master model.
|
|
|
|
|
if (clock >= next_tick_) {
|
2026-02-21 19:27:42 -05:00
|
|
|
world_->invoke(Invocation(AccessKind::INVOKE_TICK, 0, 0, ""));
|
2024-02-26 15:20:47 -05:00
|
|
|
for (UniqueClient &client : clients_) {
|
|
|
|
|
client->async_diff_ = true;
|
|
|
|
|
}
|
2024-02-26 15:19:15 -05:00
|
|
|
next_tick_ += 1.0;
|
|
|
|
|
if (next_tick_ < clock + 0.3) next_tick_ = clock + 0.3;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-11 13:56:49 -05:00
|
|
|
// Traverse all existing channels, process any communication.
|
2021-11-11 16:23:11 -05:00
|
|
|
for (UniqueClient &client : clients_) {
|
2021-11-11 13:56:49 -05:00
|
|
|
if (client->channel_->closed()) {
|
2021-11-11 16:23:11 -05:00
|
|
|
delete_client(client);
|
2021-11-11 13:56:49 -05:00
|
|
|
continue;
|
|
|
|
|
}
|
2021-11-11 16:23:11 -05:00
|
|
|
|
2021-11-11 13:56:49 -05:00
|
|
|
// Check for received invocations.
|
2021-11-11 16:23:11 -05:00
|
|
|
while (handle_invocation(client));
|
2025-06-27 19:58:39 -04:00
|
|
|
if (client == nullptr) continue;
|
2024-02-26 15:19:15 -05:00
|
|
|
|
|
|
|
|
// Possibly send a diff.
|
2025-12-02 18:06:23 -05:00
|
|
|
// Currently, it's configured to send about
|
|
|
|
|
// ten mini-diffs per second, and two full diffs
|
|
|
|
|
// per second. It actually only considers sending
|
|
|
|
|
// a full diff if it already has decided to send
|
|
|
|
|
// a mini diff.
|
|
|
|
|
double mini_delay = 0.1;
|
|
|
|
|
double full_delay = 0.45;
|
|
|
|
|
if (client->async_diff_) {
|
|
|
|
|
mini_delay = 0.05;
|
|
|
|
|
full_delay = 0.1;
|
|
|
|
|
}
|
|
|
|
|
if (clock >= client->last_mini_diff_ + mini_delay) {
|
|
|
|
|
if (clock >= client->last_full_diff_ + full_delay) {
|
|
|
|
|
send_diffs(client, true);
|
|
|
|
|
client->last_full_diff_ = clock;
|
|
|
|
|
client->last_mini_diff_ = clock;
|
|
|
|
|
} else {
|
|
|
|
|
send_diffs(client, false);
|
|
|
|
|
client->last_mini_diff_ = clock;
|
|
|
|
|
}
|
2024-02-26 15:19:15 -05:00
|
|
|
client->async_diff_ = false;
|
|
|
|
|
}
|
2021-11-11 13:56:49 -05:00
|
|
|
}
|
2021-11-11 16:23:11 -05:00
|
|
|
util::remove_nullptrs(clients_);
|
2022-05-06 13:16:27 -04:00
|
|
|
|
|
|
|
|
// Look for new outgoing HTTP client requests.
|
2026-02-21 19:27:42 -05:00
|
|
|
for (const auto &pair : world_->http_requests()) {
|
2022-05-06 13:16:27 -04:00
|
|
|
const HttpClientRequest &request = pair.second;
|
2022-05-17 15:00:20 -04:00
|
|
|
HttpChannel &channel = http_client_channels_[request.request_id()];
|
2022-05-06 13:16:27 -04:00
|
|
|
if (channel.channel_ == nullptr) {
|
|
|
|
|
channel.channel_ = new_outgoing_channel(request.target());
|
2022-06-07 01:54:08 -04:00
|
|
|
channel.method_ = request.method();
|
2022-05-06 13:16:27 -04:00
|
|
|
channel.parsed_bytes_ = 0;
|
|
|
|
|
request.send(channel.channel_->out());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Maintain existing outgoing HTTP client requests.
|
2022-05-16 17:16:42 -04:00
|
|
|
HttpParserVec http_responses;
|
2022-05-06 13:16:27 -04:00
|
|
|
for (auto &pair : http_client_channels_) {
|
2022-05-17 15:00:20 -04:00
|
|
|
HttpChannel &htchan = pair.second;
|
2022-05-06 13:16:27 -04:00
|
|
|
Channel &channel = *htchan.channel_;
|
|
|
|
|
if (channel.closed() || (channel.in()->fill() > htchan.parsed_bytes_)) {
|
2022-05-16 17:16:42 -04:00
|
|
|
HttpParser response;
|
2022-05-06 13:16:27 -04:00
|
|
|
if (!channel.error().empty()) {
|
|
|
|
|
response.fail(503, util::ss("Service Unavailable: ", channel.error()));
|
|
|
|
|
} else {
|
|
|
|
|
htchan.parsed_bytes_ = channel.in()->fill();
|
2022-06-07 01:54:08 -04:00
|
|
|
response.parse_response(channel.in()->view(), channel.closed(), htchan.method_);
|
2022-05-06 13:16:27 -04:00
|
|
|
}
|
|
|
|
|
if (response.complete()) {
|
|
|
|
|
response.set_request_id(pair.first);
|
|
|
|
|
http_responses.push_back(response);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-16 17:16:42 -04:00
|
|
|
for (const HttpParser &response : http_responses) {
|
2022-05-06 13:16:27 -04:00
|
|
|
http_client_channels_.erase(response.request_id());
|
|
|
|
|
}
|
2026-02-21 19:27:42 -05:00
|
|
|
world_->http_responses(http_responses);
|
2022-05-17 15:00:20 -04:00
|
|
|
|
|
|
|
|
// Maintain incoming HTTP server channels.
|
|
|
|
|
for (HttpChannel &htchan : http_server_channels_) {
|
|
|
|
|
SharedChannel &chan = htchan.channel_;
|
|
|
|
|
if (chan->in()->fill() > htchan.parsed_bytes_) {
|
|
|
|
|
HttpParser parser;
|
|
|
|
|
parser.parse_request(chan->in()->view(), chan->closed());
|
|
|
|
|
htchan.parsed_bytes_ = chan->in()->fill();
|
|
|
|
|
if (parser.complete()) {
|
|
|
|
|
StreamBuffer *sb = chan->out();
|
2026-02-21 19:27:42 -05:00
|
|
|
HttpServerResponse resp = world_->http_serve(parser);
|
2022-05-20 17:12:58 -04:00
|
|
|
resp.send(sb);
|
2022-05-17 15:00:20 -04:00
|
|
|
htchan.channel_ = nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
util::remove_marked_items(http_server_channels_);
|
2025-12-15 23:35:47 -05:00
|
|
|
|
|
|
|
|
// Notify the driver if there are any prints.
|
2026-02-21 19:27:42 -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("lpxserver", LpxServer);
|
2021-10-14 15:51:38 -04:00
|
|
|
|