Client can now connect to servers interactively
This commit is contained in:
@@ -22,10 +22,13 @@ public:
|
||||
Gui gui_;
|
||||
|
||||
public:
|
||||
void set_initial_state() {
|
||||
void set_initial_state_connect(const eng::string &hostspec) {
|
||||
// Create the world model.
|
||||
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
|
||||
// us the first difference transmission. We do this only to establish
|
||||
// the invariant that there's always an actor. When the first difference
|
||||
@@ -38,6 +41,34 @@ public:
|
||||
|
||||
// Export stuff to the graphics engine.
|
||||
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,30 +85,31 @@ public:
|
||||
}
|
||||
|
||||
void world_to_asynchronous() {
|
||||
if (world_->snapshot_empty()) {
|
||||
world_->snapshot();
|
||||
for (const Invocation &inv : unack_) {
|
||||
world_->invoke(inv);
|
||||
if (!world_->is_authoritative()) {
|
||||
if (world_->snapshot_empty()) {
|
||||
world_->snapshot();
|
||||
for (const Invocation &inv : unack_) {
|
||||
world_->invoke(inv);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
rescan_lua_source();
|
||||
}
|
||||
|
||||
virtual void event_init(std::string_view srcpk, int argc, char *argv[]) {
|
||||
// Put the world into the starting state.
|
||||
set_initial_state();
|
||||
|
||||
// Establish a connection to the server.
|
||||
channel_ = new_outgoing_channel("nocert:localhost:8085");
|
||||
set_initial_state_standalone(srcpk);
|
||||
|
||||
// Set the console prompt
|
||||
set_console_prompt(console_.get_prompt());
|
||||
@@ -85,15 +117,24 @@ public:
|
||||
|
||||
void send_invocation(const Invocation &inv) {
|
||||
if (channel_ == nullptr) {
|
||||
stdostream() << "Cannot invoke any actions, not connected." << std::endl;
|
||||
return;
|
||||
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);
|
||||
}
|
||||
world_to_asynchronous();
|
||||
world_->invoke(inv);
|
||||
unack_.push_back(inv);
|
||||
StreamBuffer *sb = channel_->out();
|
||||
sb->write_uint8(util::MSG_INVOKE);
|
||||
inv.serialize(sb);
|
||||
}
|
||||
|
||||
void do_luainvoke_command(const StringVec &words) {
|
||||
@@ -149,6 +190,10 @@ public:
|
||||
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) {
|
||||
if (words.empty()) return;
|
||||
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] == "work") do_work_command(words);
|
||||
else if (words[0] == "quit") do_quit_command(words);
|
||||
else if (words[0] == "connect") do_connect_command(words);
|
||||
else {
|
||||
stdostream() << "Unsupported command: " << words[0] << std::endl;
|
||||
}
|
||||
@@ -256,9 +302,7 @@ public:
|
||||
|
||||
// Channel print statements.
|
||||
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_));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -41,6 +41,8 @@ public:
|
||||
// Create an actor for administrative commands.
|
||||
admin_id_ = master_->create_login_actor();
|
||||
|
||||
// TODO: initialize the admin actor.
|
||||
|
||||
// Print out admin ID for debugging purposes.
|
||||
stdostream() << "Admin actor id = " << admin_id_ << std::endl;
|
||||
|
||||
@@ -196,8 +198,11 @@ public:
|
||||
if (chan->port() == 8085) {
|
||||
Client *client = new Client;
|
||||
client->actor_id_ = master_->create_login_actor();
|
||||
// TODO: initialize the login actor on the master.
|
||||
client->channel_ = std::move(chan);
|
||||
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();
|
||||
clients_.emplace_back(client);
|
||||
stdostream() << "New client: actor id=" << client->actor_id_ << std::endl;
|
||||
|
||||
@@ -102,6 +102,12 @@ void LuaConsole::simplify(const StringVec &words) {
|
||||
if (words.size() != 1) {
|
||||
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 {
|
||||
synerr("unrecognized command");
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ struct PrintBufferCore : public eng::opnew {
|
||||
|
||||
// Line number of the first unchecked line in the buffer. All line
|
||||
// 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_;
|
||||
|
||||
// Constructor.
|
||||
|
||||
@@ -104,6 +104,8 @@ public:
|
||||
int last_line() const;
|
||||
|
||||
// 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.
|
||||
int first_unchecked() const;
|
||||
|
||||
|
||||
@@ -55,6 +55,30 @@ bool valid_double(string_view value) {
|
||||
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 result;
|
||||
const char *p = value.data();
|
||||
@@ -810,7 +834,18 @@ static std::string_view read_number_x(const char *p, bool plus, bool minus, bool
|
||||
}
|
||||
|
||||
LuaDefine(unittests_util, "", "some unit tests") {
|
||||
// test str_to_int64, str_to_double
|
||||
// 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
|
||||
LuaAssert(L, sv::to_int64("123") == 123);
|
||||
LuaAssert(L, sv::to_int64("123.4") == INT64_MAX);
|
||||
LuaAssert(L, sv::to_int64("12ab") == INT64_MAX);
|
||||
|
||||
@@ -62,6 +62,9 @@ bool valid_double(string_view v);
|
||||
bool valid_int64(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.
|
||||
//
|
||||
// The integer parser accepts a sequence of digits,
|
||||
|
||||
@@ -1083,9 +1083,11 @@ void World::close_lthread_state() {
|
||||
// send the output to std::cerr.
|
||||
if (lthread_prints_ != nullptr) {
|
||||
const eng::string &output = lthread_prints_->str();
|
||||
Tangible *actor = tangible_get(lthread_actor_id_);
|
||||
if (actor != nullptr) {
|
||||
actor->print_buffer_.add_string(output, is_authoritative());
|
||||
if (output.size() > 0) {
|
||||
Tangible *actor = tangible_get(lthread_actor_id_);
|
||||
if (actor != nullptr) {
|
||||
actor->print_buffer_.add_string(output, is_authoritative());
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now clean up everything.
|
||||
|
||||
Reference in New Issue
Block a user