Client can now connect to servers interactively
This commit is contained in:
@@ -22,10 +22,13 @@ public:
|
|||||||
Gui gui_;
|
Gui gui_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void set_initial_state() {
|
void set_initial_state_connect(const eng::string &hostspec) {
|
||||||
// Create the world model.
|
// Create the world model.
|
||||||
world_.reset(new World(WORLD_TYPE_PREDICTIVE));
|
world_.reset(new World(WORLD_TYPE_PREDICTIVE));
|
||||||
|
|
||||||
|
// Create the communication channel.
|
||||||
|
channel_ = new_outgoing_channel(hostspec);
|
||||||
|
|
||||||
// This is a temporary actor that will be used only until the server sends
|
// 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
|
// us the first difference transmission. We do this only to establish
|
||||||
// the invariant that there's always an actor. When the first difference
|
// the invariant that there's always an actor. When the first difference
|
||||||
@@ -38,6 +41,34 @@ public:
|
|||||||
|
|
||||||
// Export stuff to the graphics engine.
|
// Export stuff to the graphics engine.
|
||||||
set_visible_world_and_actor(world_.get(), actor_id_);
|
set_visible_world_and_actor(world_.get(), actor_id_);
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
actor_id_ = world_->create_login_actor();
|
||||||
|
|
||||||
|
// 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -54,6 +85,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void world_to_asynchronous() {
|
void world_to_asynchronous() {
|
||||||
|
if (!world_->is_authoritative()) {
|
||||||
if (world_->snapshot_empty()) {
|
if (world_->snapshot_empty()) {
|
||||||
world_->snapshot();
|
world_->snapshot();
|
||||||
for (const Invocation &inv : unack_) {
|
for (const Invocation &inv : unack_) {
|
||||||
@@ -61,23 +93,23 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void abandon_server() {
|
void abandon_server() {
|
||||||
stdostream() << "Abandoning server." << std::endl;
|
// 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.
|
||||||
|
|
||||||
// Put the world model back into a known-good state.
|
|
||||||
set_initial_state();
|
|
||||||
|
|
||||||
// Disconnect from the server.
|
|
||||||
channel_.reset();
|
channel_.reset();
|
||||||
|
rescan_lua_source();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void event_init(std::string_view srcpk, int argc, char *argv[]) {
|
virtual void event_init(std::string_view srcpk, int argc, char *argv[]) {
|
||||||
// Put the world into the starting state.
|
// Put the world into the starting state.
|
||||||
set_initial_state();
|
set_initial_state_standalone(srcpk);
|
||||||
|
|
||||||
// Establish a connection to the server.
|
|
||||||
channel_ = new_outgoing_channel("nocert:localhost:8085");
|
|
||||||
|
|
||||||
// Set the console prompt
|
// Set the console prompt
|
||||||
set_console_prompt(console_.get_prompt());
|
set_console_prompt(console_.get_prompt());
|
||||||
@@ -85,9 +117,17 @@ public:
|
|||||||
|
|
||||||
void send_invocation(const Invocation &inv) {
|
void send_invocation(const Invocation &inv) {
|
||||||
if (channel_ == nullptr) {
|
if (channel_ == nullptr) {
|
||||||
stdostream() << "Cannot invoke any actions, not connected." << std::endl;
|
if ((!world_->is_authoritative()) && (inv.kind() == Invocation::KIND_LUA_SOURCE)) {
|
||||||
return;
|
// 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_to_asynchronous();
|
||||||
world_->invoke(inv);
|
world_->invoke(inv);
|
||||||
unack_.push_back(inv);
|
unack_.push_back(inv);
|
||||||
@@ -95,6 +135,7 @@ public:
|
|||||||
sb->write_uint8(util::MSG_INVOKE);
|
sb->write_uint8(util::MSG_INVOKE);
|
||||||
inv.serialize(sb);
|
inv.serialize(sb);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void do_luainvoke_command(const StringVec &words) {
|
void do_luainvoke_command(const StringVec &words) {
|
||||||
send_invocation(Invocation(Invocation::KIND_LUA, actor_id_, actor_id_, words[1]));
|
send_invocation(Invocation(Invocation::KIND_LUA, actor_id_, actor_id_, words[1]));
|
||||||
@@ -149,6 +190,10 @@ public:
|
|||||||
stop_driver();
|
stop_driver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void do_connect_command(const util::StringVec &words) {
|
||||||
|
set_initial_state_connect(util::ss("nocert:", words[1], ":8085"));
|
||||||
|
}
|
||||||
|
|
||||||
void do_command(const util::StringVec &words) {
|
void do_command(const util::StringVec &words) {
|
||||||
if (words.empty()) return;
|
if (words.empty()) return;
|
||||||
else if (words[0] == "luainvoke") do_luainvoke_command(words);
|
else if (words[0] == "luainvoke") do_luainvoke_command(words);
|
||||||
@@ -161,6 +206,7 @@ public:
|
|||||||
else if (words[0] == "cpl") do_cpl_command(words);
|
else if (words[0] == "cpl") do_cpl_command(words);
|
||||||
else if (words[0] == "work") do_work_command(words);
|
else if (words[0] == "work") do_work_command(words);
|
||||||
else if (words[0] == "quit") do_quit_command(words);
|
else if (words[0] == "quit") do_quit_command(words);
|
||||||
|
else if (words[0] == "connect") do_connect_command(words);
|
||||||
else {
|
else {
|
||||||
stdostream() << "Unsupported command: " << words[0] << std::endl;
|
stdostream() << "Unsupported command: " << words[0] << std::endl;
|
||||||
}
|
}
|
||||||
@@ -256,11 +302,9 @@ public:
|
|||||||
|
|
||||||
// Channel print statements.
|
// Channel print statements.
|
||||||
if (print_channeler_.channel(world_->get_printbuffer(actor_id_), stdostream())) {
|
if (print_channeler_.channel(world_->get_printbuffer(actor_id_), stdostream())) {
|
||||||
if (channel_ != nullptr) {
|
|
||||||
send_invocation(print_channeler_.invocation(actor_id_));
|
send_invocation(print_channeler_.invocation(actor_id_));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DrivenEngineDefine("lpxclient", LpxClient);
|
DrivenEngineDefine("lpxclient", LpxClient);
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ public:
|
|||||||
// Create an actor for administrative commands.
|
// Create an actor for administrative commands.
|
||||||
admin_id_ = master_->create_login_actor();
|
admin_id_ = master_->create_login_actor();
|
||||||
|
|
||||||
|
// TODO: initialize the admin actor.
|
||||||
|
|
||||||
// Print out admin ID for debugging purposes.
|
// Print out admin ID for debugging purposes.
|
||||||
stdostream() << "Admin actor id = " << admin_id_ << std::endl;
|
stdostream() << "Admin actor id = " << admin_id_ << std::endl;
|
||||||
|
|
||||||
@@ -196,8 +198,11 @@ public:
|
|||||||
if (chan->port() == 8085) {
|
if (chan->port() == 8085) {
|
||||||
Client *client = new Client;
|
Client *client = new Client;
|
||||||
client->actor_id_ = master_->create_login_actor();
|
client->actor_id_ = master_->create_login_actor();
|
||||||
|
// TODO: initialize the login actor on the master.
|
||||||
client->channel_ = std::move(chan);
|
client->channel_ = std::move(chan);
|
||||||
client->sync_.reset(new World(WORLD_TYPE_PREDICTIVE));
|
client->sync_.reset(new World(WORLD_TYPE_PREDICTIVE));
|
||||||
|
// This login actor is never used, it is just to preserve the invariant that
|
||||||
|
// the client model and the server synchronous model are identical.
|
||||||
client->sync_->create_login_actor();
|
client->sync_->create_login_actor();
|
||||||
clients_.emplace_back(client);
|
clients_.emplace_back(client);
|
||||||
stdostream() << "New client: actor id=" << client->actor_id_ << std::endl;
|
stdostream() << "New client: actor id=" << client->actor_id_ << std::endl;
|
||||||
|
|||||||
@@ -102,6 +102,12 @@ void LuaConsole::simplify(const StringVec &words) {
|
|||||||
if (words.size() != 1) {
|
if (words.size() != 1) {
|
||||||
synerr("/aborthttp takes no arguments");
|
synerr("/aborthttp takes no arguments");
|
||||||
}
|
}
|
||||||
|
} else if (words[0] == "connect") {
|
||||||
|
if ((words.size() == 2)&&(sv::valid_hostname(words[1]))) {
|
||||||
|
// OK
|
||||||
|
} else {
|
||||||
|
synerr("/connect [hostname]");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
synerr("unrecognized command");
|
synerr("unrecognized command");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ struct PrintBufferCore : public eng::opnew {
|
|||||||
|
|
||||||
// Line number of the first unchecked line in the buffer. All line
|
// Line number of the first unchecked line in the buffer. All line
|
||||||
// numbers including this one and beyond are unchecked.
|
// numbers including this one and beyond are unchecked.
|
||||||
|
// "Unchecked" lines are lines in a non-authoritative model
|
||||||
|
// that haven't been verified through difference transmission.
|
||||||
|
|
||||||
int first_unchecked_;
|
int first_unchecked_;
|
||||||
|
|
||||||
// Constructor.
|
// Constructor.
|
||||||
|
|||||||
@@ -104,6 +104,8 @@ public:
|
|||||||
int last_line() const;
|
int last_line() const;
|
||||||
|
|
||||||
// Get the current first unchecked line.
|
// Get the current first unchecked line.
|
||||||
|
// "Unchecked" lines are lines in a non-authoritative model
|
||||||
|
// that haven't been verified through difference transmission.
|
||||||
// Guaranteed to be between first_line and last_line inclusive.
|
// Guaranteed to be between first_line and last_line inclusive.
|
||||||
int first_unchecked() const;
|
int first_unchecked() const;
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,30 @@ bool valid_double(string_view value) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool valid_hostname(string_view value) {
|
||||||
|
bool start_word = true;
|
||||||
|
bool last_dash = false;
|
||||||
|
for (char c : value) {
|
||||||
|
if (c == '.') {
|
||||||
|
if (start_word) return false;
|
||||||
|
if (last_dash) return false;
|
||||||
|
start_word = true;
|
||||||
|
last_dash = false;
|
||||||
|
} else if (c == '-') {
|
||||||
|
if (start_word) return false;
|
||||||
|
start_word = false;
|
||||||
|
last_dash = true;
|
||||||
|
} else if (ascii_isalnum(c)) {
|
||||||
|
start_word = false;
|
||||||
|
last_dash = false;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (start_word || last_dash) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int64_t to_int64(string_view value, int64_t errval) {
|
int64_t to_int64(string_view value, int64_t errval) {
|
||||||
int64_t result;
|
int64_t result;
|
||||||
const char *p = value.data();
|
const char *p = value.data();
|
||||||
@@ -810,6 +834,17 @@ static std::string_view read_number_x(const char *p, bool plus, bool minus, bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
LuaDefine(unittests_util, "", "some unit tests") {
|
LuaDefine(unittests_util, "", "some unit tests") {
|
||||||
|
// Test valid_hostname
|
||||||
|
LuaAssert(L, sv::valid_hostname("foo123"));
|
||||||
|
LuaAssert(L, !sv::valid_hostname("foo%123"));
|
||||||
|
LuaAssert(L, sv::valid_hostname("foo-bar"));
|
||||||
|
LuaAssert(L, !sv::valid_hostname("-foo"));
|
||||||
|
LuaAssert(L, !sv::valid_hostname("foo-"));
|
||||||
|
LuaAssert(L, sv::valid_hostname("foo.bar.baz"));
|
||||||
|
LuaAssert(L, sv::valid_hostname("foo-bar.baz"));
|
||||||
|
LuaAssert(L, !sv::valid_hostname("foo-bar-.baz"));
|
||||||
|
LuaAssert(L, !sv::valid_hostname("foo.-bar-baz"));
|
||||||
|
|
||||||
// test str_to_int64, str_to_double
|
// test str_to_int64, str_to_double
|
||||||
LuaAssert(L, sv::to_int64("123") == 123);
|
LuaAssert(L, sv::to_int64("123") == 123);
|
||||||
LuaAssert(L, sv::to_int64("123.4") == INT64_MAX);
|
LuaAssert(L, sv::to_int64("123.4") == INT64_MAX);
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ bool valid_double(string_view v);
|
|||||||
bool valid_int64(string_view v);
|
bool valid_int64(string_view v);
|
||||||
bool valid_hex64(string_view v);
|
bool valid_hex64(string_view v);
|
||||||
|
|
||||||
|
// Check if a hostname is a valid DNS (ascii) hostname.
|
||||||
|
bool valid_hostname(string_view v);
|
||||||
|
|
||||||
// Convert strings to numbers. Returns errval on failure.
|
// Convert strings to numbers. Returns errval on failure.
|
||||||
//
|
//
|
||||||
// The integer parser accepts a sequence of digits,
|
// The integer parser accepts a sequence of digits,
|
||||||
|
|||||||
@@ -1083,11 +1083,13 @@ void World::close_lthread_state() {
|
|||||||
// send the output to std::cerr.
|
// send the output to std::cerr.
|
||||||
if (lthread_prints_ != nullptr) {
|
if (lthread_prints_ != nullptr) {
|
||||||
const eng::string &output = lthread_prints_->str();
|
const eng::string &output = lthread_prints_->str();
|
||||||
|
if (output.size() > 0) {
|
||||||
Tangible *actor = tangible_get(lthread_actor_id_);
|
Tangible *actor = tangible_get(lthread_actor_id_);
|
||||||
if (actor != nullptr) {
|
if (actor != nullptr) {
|
||||||
actor->print_buffer_.add_string(output, is_authoritative());
|
actor->print_buffer_.add_string(output, is_authoritative());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Now clean up everything.
|
// Now clean up everything.
|
||||||
clear_lthread_state();
|
clear_lthread_state();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user