diff --git a/luprex/core/cpp/driver-mingw.cpp b/luprex/core/cpp/driver-mingw.cpp index 9a223ce8..c2ea0423 100644 --- a/luprex/core/cpp/driver-mingw.cpp +++ b/luprex/core/cpp/driver-mingw.cpp @@ -33,7 +33,7 @@ public: static PADDRINFOA find_good_addr(PADDRINFOA addrinfo) { for (PADDRINFOA addr = addrinfo; addr != nullptr; addr = addr->ai_next) { - if ((addr->ai_family == AF_INET) || (addr->ai_family == AF_INET6)) { + if (addr->ai_family == AF_INET) { return addr; } } diff --git a/luprex/core/cpp/lpxclient.cpp b/luprex/core/cpp/lpxclient.cpp index ece1f075..06427d6a 100644 --- a/luprex/core/cpp/lpxclient.cpp +++ b/luprex/core/cpp/lpxclient.cpp @@ -5,6 +5,7 @@ #include "luaconsole.hpp" #include "invocation.hpp" #include "util.hpp" +#include "printbuffer.hpp" #include class LpxClient : public DrivenEngine { @@ -15,6 +16,7 @@ public: InvocationQueue unack_; UniqueChannel channel_; LuaConsole console_; + PrintChanneler print_channeler_; Gui gui_; int64_t gui_place_; @@ -186,6 +188,11 @@ public: } } + void change_actor_id(int64_t actor_id) { + print_channeler_.reset(); + actor_id_ = actor_id; + } + void receive_ack_from_server(StreamBuffer *sb) { // An ack is just a single byte, so there's nothing left to read. if (unack_.empty()) { @@ -201,7 +208,8 @@ public: void receive_diff_from_server(StreamBuffer *sb) { world_to_synchronous(); try { - actor_id_ = world_->patch_everything(sb); + int nactor = world_->patch_everything(sb); + if (nactor != actor_id_) change_actor_id(nactor); } catch (const StreamEof &seof) { abandon_server(); return; diff --git a/luprex/core/cpp/lpxserver.cpp b/luprex/core/cpp/lpxserver.cpp index e1ae68bd..d9d04878 100644 --- a/luprex/core/cpp/lpxserver.cpp +++ b/luprex/core/cpp/lpxserver.cpp @@ -2,25 +2,128 @@ #include "world.hpp" #include "drivenengine.hpp" +#include "luaconsole.hpp" +#include "util.hpp" +#include "printbuffer.hpp" #include class ServerClient { public: int64_t actor_id_; - Channel *channel_; + UniqueChannel channel_; UniqueWorld sync_; }; +using UniqueServerClient = std::unique_ptr; +using ServerClientVector = std::vector; class LpxServer : public DrivenEngine { public: UniqueWorld master_; - std::vector> clients_; + LuaConsole console_; + ServerClientVector clients_; + PrintChanneler print_channeler_; + int64_t admin_id_; public: virtual void event_init(int argc, char *argv[]) { + // Create the master world model. + master_.reset(new World(util::WORLD_TYPE_MASTER)); + + // Create an actor for administrative commands. + admin_id_ = master_->create_login_actor(); + + // Print out admin ID for debugging purposes. + stdostream() << "Admin actor id = " << admin_id_ << std::endl; + + // Enable listening on port 8085. + listen_port(8085); + + // Set the console prompt. + get_stdio_channel()->set_prompt(console_.get_prompt()); + } + + void do_lua_command(const util::StringVec &words) { + if (words.size() != 2) { + stdostream() << "lua command (lua) takes a single string" << std::endl; + return; + } + const std::string &exp = words[1]; + InvocationData dummyresult; + Invocation inv(Invocation::KIND_LUA, admin_id_, admin_id_, exp, dummyresult); + master_->invoke(inv); + } + + void do_syntax_command(const util::StringVec &words) { + stdostream() << "Syntax Error: "; + for (int i = 1; i < int(words.size()); i++) { + stdostream() << words[i] << " "; + } + stdostream() << std::endl; + } + + void do_quit_command(const util::StringVec &words) { + if (words.size() != 1) { + stdostream() << "quit command takes no arguments" << std::endl; + return; + } + stop_driver(); + } + + void do_command(const util::StringVec &words) { + if (words.empty()) return; + else if (words[0] == "lua") do_lua_command(words); + else if (words[0] == "syntax") do_syntax_command(words); + else if (words[0] == "quit") do_quit_command(words); + else { + stdostream() << "Unknown command: " << words[0] << std::endl; + } + } + + bool handle_invocation(ServerClient *sclient) { + return false; } virtual void event_update() { + // Check for keyboard input on stdin. + while (true) { + std::string line = get_stdio_channel()->in()->readline(); + if (line == "") break; + console_.add(line); + get_stdio_channel()->set_prompt(console_.get_prompt()); + do_command(console_.get_command()); + } + + // Anything in the administrator printbuffer should go to stdostream. + if (print_channeler_.channel(master_->get_printbuffer(admin_id_), stdostream())) { + master_->invoke(print_channeler_.invocation(admin_id_)); + } + + // Check for new incoming channels, set up client structures. + while (true) { + UniqueChannel chan = new_incoming_channel(); + if (chan == nullptr) break; + ServerClient *client = new ServerClient; + client->actor_id_ = master_->create_login_actor(); + client->channel_ = std::move(chan); + client->sync_.reset(new World(util::WORLD_TYPE_S_SYNC)); + client->sync_->create_login_actor(); + clients_.emplace_back(client); + stdostream() << "New client: actor id=" << client->actor_id_ << std::endl; + } + + // Traverse all existing channels, process any communication. + ServerClientVector keep; + for (UniqueServerClient &client : clients_) { + if (client->channel_->closed()) { + stdostream() << "Client closed: actor id=" << client->actor_id_ << std::endl; + continue; + } + // Check for received invocations. + while (handle_invocation(client.get())); + // Transfer the client to the keep vector. + keep.emplace_back(std::move(client)); + } + clients_ = std::move(keep); } }; diff --git a/luprex/core/cpp/printbuffer.cpp b/luprex/core/cpp/printbuffer.cpp index 56d70311..52ef15e6 100644 --- a/luprex/core/cpp/printbuffer.cpp +++ b/luprex/core/cpp/printbuffer.cpp @@ -97,6 +97,22 @@ void PrintBuffer::patch(StreamBuffer *sb) { } } +bool PrintChanneler::channel(const PrintBuffer *printbuffer, std::ostream &ostream) { + if (printbuffer == nullptr) return false; + if (printbuffer->first_line() > line_) { + line_ = printbuffer->first_line(); + } + while (line_ < printbuffer->first_unchecked()) { + ostream << printbuffer->nth(line_) << std::endl; + line_ += 1; + } + return line_ > printbuffer->first_line(); +} + +Invocation PrintChanneler::invocation(int64_t actor_id) { + return Invocation(Invocation::KIND_FLUSH_PRINTS, actor_id, actor_id, std::to_string(line_), InvocationData()); +} + LuaDefine(unittests_printbuffer, "c") { PrintBuffer pbm(util::WORLD_TYPE_MASTER); PrintBuffer pbs(util::WORLD_TYPE_S_SYNC); diff --git a/luprex/core/cpp/printbuffer.hpp b/luprex/core/cpp/printbuffer.hpp index a9e87148..b43cf3a3 100644 --- a/luprex/core/cpp/printbuffer.hpp +++ b/luprex/core/cpp/printbuffer.hpp @@ -71,9 +71,11 @@ #include "streambuffer.hpp" #include "util.hpp" +#include "invocation.hpp" #include #include #include +#include class PrintBuffer { private: @@ -136,5 +138,25 @@ public: using UniquePrintBuffer = std::unique_ptr; +class PrintChanneler { +private: + int64_t line_; +public: + PrintChanneler() { line_ = 0; } + + // Reset the print channeler. + void reset() { line_ = 0; } + + // Copy any new lines from the printbuffer to the stdostream. + // Update the current line number. Return true if the printbuffer + // contains any lines that have already been channeled. + bool channel(const PrintBuffer *pb, std::ostream &ostream); + + // Generate an invocation that removes unnecessary lines from the + // printbuffer. + Invocation invocation(int64_t actor_id); +}; + + #endif // PRINTBUFFER_HPP diff --git a/luprex/core/cpp/textgame.cpp b/luprex/core/cpp/textgame.cpp index d9f65044..72ca4b32 100644 --- a/luprex/core/cpp/textgame.cpp +++ b/luprex/core/cpp/textgame.cpp @@ -14,6 +14,7 @@ #include "textgame.hpp" #include "luaconsole.hpp" #include "pprint.hpp" +#include "printbuffer.hpp" #include #include @@ -23,213 +24,178 @@ private: using StringVec = LuaConsole::StringVec; UniqueWorld world_; LuaConsole console_; + PrintChanneler print_channeler_; Gui gui_; int64_t gui_place_; int64_t actor_id_; - int64_t printbuffer_line_; - - void do_lua_command(const StringVec &cmd); - void do_syntax_command(const StringVec &cmd); - void do_view_command(const StringVec &cmd); - void do_menu_command(const StringVec &cmd); - void do_choose_command(const StringVec &cmd); - void do_quit_command(const StringVec &cmd); - void do_snapshot_command(const StringVec &cmd); - void do_rollback_command(const StringVec &cmd); - void do_tick_command(const StringVec &cmd); - void do_command(const StringVec &exp); - void check_redirects(); - void channel_printbuffer(); - -public: - virtual void event_init(int argc, char *argv[]); - virtual void event_update(); -}; - -void TextGame::do_lua_command(const StringVec &words) { - assert(world_->stack_is_clear()); - if (words.size() != 2) { - stdostream() << "lua command (lua) takes a single string" << std::endl; - return; - } - const std::string &exp = words[1]; - InvocationData dummyresult; - Invocation inv(Invocation::KIND_LUA, actor_id_, actor_id_, exp, dummyresult); - world_->invoke(inv); -} - -void TextGame::do_syntax_command(const StringVec &words) { - stdostream() << "Syntax Error: "; - for (int i = 1; i < int(words.size()); i++) { - stdostream() << words[i] << " "; - } - stdostream() << std::endl; -} - -void TextGame::do_view_command(const StringVec &cmd) { - if (cmd.size() != 1) { - stdostream() << "v command (view) takes no arguments" << std::endl; - return; - } - for (int64_t id : world_->get_near(actor_id_, 100, true)) { - const Tangible *tan = world_->tangible_get(id); - const AnimStep &aqback = tan->anim_queue_.back(); - stdostream() << id << ": " << aqback.graphic() << " " << aqback.plane() << " " << aqback.xyz().debug_string() << std::endl; - } -} - -void TextGame::do_menu_command(const StringVec &cmd) { - int64_t id; - if (cmd.size() == 1) { - id = actor_id_; - } else if (cmd.size() == 2) { - id = util::strtoint(cmd[1], -1); - } else { - stdostream() << "m command (menu) expects a tangible ID or defaults to actor_id" << std::endl; - return; - } - gui_place_ = id; - world_->update_gui(actor_id_, id, &gui_); - int index = 0; - for (const GuiElt &elt : gui_.elts()) { - stdostream() << index << " " << elt.label() << std::endl; - index += 1; - } -} - -void TextGame::do_choose_command(const StringVec &cmd) { - int64_t index; - if (cmd.size() == 1) { - index = util::strtoint(cmd[0], -1); - } else { - stdostream() << "c command (choose) expects a menu line number" << std::endl; - return; - } - const Gui::EltVec &elts = gui_.elts(); - if ((index < 0) || (index >= int(elts.size()))) { - stdostream() << "No menu item #" << index << std::endl; - return; - } - std::string action = elts[index].action(); - stdostream() << "Invoking plan: " << action << std::endl; - InvocationData dummyresult; - dummyresult["flavor"] = "chocolate"; - dummyresult["color"] = "blue"; - Invocation inv(Invocation::KIND_PLAN, actor_id_, gui_place_, action, dummyresult); - world_->invoke(inv); -} - - -void TextGame::do_snapshot_command(const StringVec &cmd) { - if (cmd.size() != 1) { - stdostream() << "s command (snapshot) takes no arguments" << std::endl; - return; - } - world_->snapshot(); -} - -void TextGame::do_rollback_command(const StringVec &cmd) { - if (cmd.size() != 1) { - stdostream() << "r command (rollback) takes no arguments" << std::endl; - return; - } - world_->rollback(); -} - -void TextGame::do_tick_command(const StringVec &cmd) { - int64_t clock; - if (cmd.size() == 2) { - clock = util::strtoint(cmd[1], -1); - } else { - stdostream() << "t command (tick) expects a time value" << std::endl; - return; - } - world_->run_scheduled_threads(clock); -} - -void TextGame::do_quit_command(const StringVec &cmd) { - if (cmd.size() != 1) { - stdostream() << "q command (quit) takes no arguments" << std::endl; - return; - } - actor_id_ = 0; -} - -void TextGame::do_command(const StringVec &words) { - if (words.empty()) return; - else if (words[0] == "lua") do_lua_command(words); - else if (words[0] == "syntax") do_syntax_command(words); - else if (words[0] == "view") do_view_command(words); - else if (words[0] == "menu") do_menu_command(words); - else if (words[0] == "quit") do_quit_command(words); - else if (words[0] == "snap") do_snapshot_command(words); - else if (words[0] == "roll") do_rollback_command(words); - else if (words[0] == "tick") do_tick_command(words); - else if (util::validinteger(words[0])) do_choose_command(words); - else { - stdostream() << "Unknown command: " << words[0] << std::endl; - } -} - -void TextGame::check_redirects() { - World::Redirects redir = world_->fetch_redirects(); - for (const auto &p : redir) { - if (p.first == actor_id_) { - actor_id_ = p.second; - stdostream() << "Login actor ID: " << actor_id_ << std::endl; - gui_.clear(); + void do_lua_command(const StringVec &words) { + assert(world_->stack_is_clear()); + if (words.size() != 2) { + stdostream() << "lua command (lua) takes a single string" << std::endl; + return; } - } -} - -void TextGame::channel_printbuffer() { - const PrintBuffer *printbuffer = world_->get_printbuffer(actor_id_); - if (printbuffer == nullptr) return; - if (printbuffer->first_line() > printbuffer_line_) { - printbuffer_line_ = printbuffer->first_line(); - } - while (printbuffer_line_ < printbuffer->first_unchecked()) { - stdostream() << printbuffer->nth(printbuffer_line_) << std::endl; - printbuffer_line_ += 1; - } - if (printbuffer_line_ > printbuffer->first_line()) { - InvocationData data; - Invocation inv(Invocation::KIND_FLUSH_PRINTS, actor_id_, actor_id_, std::to_string(printbuffer_line_), data); + const std::string &exp = words[1]; + InvocationData dummyresult; + Invocation inv(Invocation::KIND_LUA, actor_id_, actor_id_, exp, dummyresult); world_->invoke(inv); } -} + void do_syntax_command(const StringVec &words) { + stdostream() << "Syntax Error: "; + for (int i = 1; i < int(words.size()); i++) { + stdostream() << words[i] << " "; + } + stdostream() << std::endl; + } -void TextGame::event_init(int argc, char *argv[]) -{ - world_.reset(new World(util::WORLD_TYPE_STANDALONE)); - world_->update_source(get_lua_source()); - world_->run_unittests(); - actor_id_ = world_->create_login_actor(); - printbuffer_line_ = 0; - stdostream() << "Login actor ID: " << actor_id_ << std::endl; - get_stdio_channel()->set_prompt(console_.get_prompt()); -} - -void TextGame::event_update() -{ - world_->update_source(get_lua_source()); - while (true) { - std::string line = get_stdio_channel()->in()->readline(); - if (line == "") break; - console_.add(line); - get_stdio_channel()->set_prompt(console_.get_prompt()); - do_command(console_.get_command()); - channel_printbuffer(); - check_redirects(); - if (actor_id_ == 0) { - stop_driver(); - break; + void do_view_command(const StringVec &cmd) { + if (cmd.size() != 1) { + stdostream() << "v command (view) takes no arguments" << std::endl; + return; + } + for (int64_t id : world_->get_near(actor_id_, 100, true)) { + const Tangible *tan = world_->tangible_get(id); + const AnimStep &aqback = tan->anim_queue_.back(); + stdostream() << id << ": " << aqback.graphic() << " " << aqback.plane() << " " << aqback.xyz().debug_string() << std::endl; } } -} + + void do_menu_command(const StringVec &cmd) { + int64_t id; + if (cmd.size() == 1) { + id = actor_id_; + } else if (cmd.size() == 2) { + id = util::strtoint(cmd[1], -1); + } else { + stdostream() << "m command (menu) expects a tangible ID or defaults to actor_id" << std::endl; + return; + } + gui_place_ = id; + world_->update_gui(actor_id_, id, &gui_); + int index = 0; + for (const GuiElt &elt : gui_.elts()) { + stdostream() << index << " " << elt.label() << std::endl; + index += 1; + } + } + + void do_choose_command(const StringVec &cmd) { + int64_t index; + if (cmd.size() == 1) { + index = util::strtoint(cmd[0], -1); + } else { + stdostream() << "c command (choose) expects a menu line number" << std::endl; + return; + } + const Gui::EltVec &elts = gui_.elts(); + if ((index < 0) || (index >= int(elts.size()))) { + stdostream() << "No menu item #" << index << std::endl; + return; + } + std::string action = elts[index].action(); + stdostream() << "Invoking plan: " << action << std::endl; + InvocationData dummyresult; + dummyresult["flavor"] = "chocolate"; + dummyresult["color"] = "blue"; + Invocation inv(Invocation::KIND_PLAN, actor_id_, gui_place_, action, dummyresult); + world_->invoke(inv); + } + + + void do_snapshot_command(const StringVec &cmd) { + if (cmd.size() != 1) { + stdostream() << "s command (snapshot) takes no arguments" << std::endl; + return; + } + world_->snapshot(); + } + + void do_rollback_command(const StringVec &cmd) { + if (cmd.size() != 1) { + stdostream() << "r command (rollback) takes no arguments" << std::endl; + return; + } + world_->rollback(); + } + + void do_tick_command(const StringVec &cmd) { + int64_t clock; + if (cmd.size() == 2) { + clock = util::strtoint(cmd[1], -1); + } else { + stdostream() << "t command (tick) expects a time value" << std::endl; + return; + } + world_->run_scheduled_threads(clock); + } + + void do_quit_command(const StringVec &cmd) { + if (cmd.size() != 1) { + stdostream() << "q command (quit) takes no arguments" << std::endl; + return; + } + actor_id_ = 0; + } + + void do_command(const StringVec &words) { + if (words.empty()) return; + else if (words[0] == "lua") do_lua_command(words); + else if (words[0] == "syntax") do_syntax_command(words); + else if (words[0] == "view") do_view_command(words); + else if (words[0] == "menu") do_menu_command(words); + else if (words[0] == "quit") do_quit_command(words); + else if (words[0] == "snap") do_snapshot_command(words); + else if (words[0] == "roll") do_rollback_command(words); + else if (words[0] == "tick") do_tick_command(words); + else if (util::validinteger(words[0])) do_choose_command(words); + else { + stdostream() << "Unknown command: " << words[0] << std::endl; + } + } + + void check_redirects() { + World::Redirects redir = world_->fetch_redirects(); + for (const auto &p : redir) { + if (p.first == actor_id_) { + actor_id_ = p.second; + stdostream() << "Login actor ID: " << actor_id_ << std::endl; + gui_.clear(); + } + } + } + + void event_init(int argc, char *argv[]) + { + world_.reset(new World(util::WORLD_TYPE_STANDALONE)); + world_->update_source(get_lua_source()); + world_->run_unittests(); + actor_id_ = world_->create_login_actor(); + stdostream() << "Login actor ID: " << actor_id_ << std::endl; + get_stdio_channel()->set_prompt(console_.get_prompt()); + } + + void event_update() + { + world_->update_source(get_lua_source()); + while (true) { + std::string line = get_stdio_channel()->in()->readline(); + if (line == "") break; + console_.add(line); + get_stdio_channel()->set_prompt(console_.get_prompt()); + do_command(console_.get_command()); + if (print_channeler_.channel(world_->get_printbuffer(actor_id_), stdostream())) { + world_->invoke(print_channeler_.invocation(actor_id_)); + } + check_redirects(); + if (actor_id_ == 0) { + stop_driver(); + break; + } + } + } +}; UniqueDrivenEngine make_TextGame() { return UniqueDrivenEngine(new TextGame);