diff --git a/luprex/core/cpp/globaldb.cpp b/luprex/core/cpp/globaldb.cpp index 89020293..c85aa72f 100644 --- a/luprex/core/cpp/globaldb.cpp +++ b/luprex/core/cpp/globaldb.cpp @@ -45,14 +45,15 @@ LuaDefine(global_table, "globalname", "get a table where global data can be stor LuaRet globaltab; LuaVar globaldb; LuaStack LS(L, globalname, globaltab, globaldb); + LS.checkstring(globalname); // Get a pointer to the globaldb. LS.rawget(globaldb, LuaRegistry, "globaldb"); - if (!LS.istable(globaldb)) { - return lua_yield(L, 0); // donotpredict - } - LS.checkstring(globalname); + // nopredict + if (lua_isyieldable(L) && (!LS.istable(globaldb))) { + return lua_yield(L, 0); + } // Get the globaltab from the globaldb, sanity check it. LS.rawget(globaltab, globaldb, globalname); diff --git a/luprex/core/cpp/lpxserver.cpp b/luprex/core/cpp/lpxserver.cpp index 0ef4e595..0095ece3 100644 --- a/luprex/core/cpp/lpxserver.cpp +++ b/luprex/core/cpp/lpxserver.cpp @@ -20,11 +20,13 @@ using ClientVector = eng::vector; class LpxServer : public DrivenEngine { public: + using StringVec = LuaConsole::StringVec; UniqueWorld master_; LuaConsole console_; ClientVector clients_; PrintChanneler print_channeler_; int64_t admin_id_; + Gui gui_; public: virtual void event_init(int argc, char *argv[]) { @@ -61,6 +63,22 @@ public: stdostream() << "Syntax Error: " << words[1] << std::endl; } + void do_menu_command(const StringVec &cmd) { + int64_t place = sv::to_int64(cmd[1], admin_id_); + master_->update_gui(admin_id_, place, &gui_); + stdostream() << gui_.menu_debug_string(); + } + + void do_choose_command(const StringVec &cmd) { + eng::string action = gui_.get_action(sv::to_int64(cmd[1])); + if (action == "") { + stdostream() << "Invalid menu item #" << std::endl; + return; + } + stdostream() << "Invoking plan: " << action << std::endl; + master_->invoke(Invocation(Invocation::KIND_PLAN, admin_id_, gui_.place(), action)); + } + void do_tick_command(const util::StringVec &words) { master_->invoke(Invocation(Invocation::KIND_TICK, admin_id_, admin_id_, "")); } @@ -78,6 +96,8 @@ public: else if (words[0] == "luainvoke") do_luainvoke_command(words); else if (words[0] == "luaprobe") do_luaprobe_command(words); else if (words[0] == "syntax") do_syntax_command(words); + else if (words[0] == "menu") do_menu_command(words); + else if (words[0] == "choose") do_choose_command(words); else if (words[0] == "tick") do_tick_command(words); else if (words[0] == "cpl") do_cpl_command(words); else if (words[0] == "quit") do_quit_command(words); diff --git a/luprex/core/cpp/pprint.cpp b/luprex/core/cpp/pprint.cpp index 2b4d22f4..eada8c22 100644 --- a/luprex/core/cpp/pprint.cpp +++ b/luprex/core/cpp/pprint.cpp @@ -120,8 +120,8 @@ static void tabify(Inspector &insp, int level) { static void pprint_r(Inspector &insp, int level, LuaSlot root) { lua_checkstack(insp.L, 20); - LuaVar idv, pairs, key, val; - LuaStack LS(insp.L, idv, pairs, key, val); + LuaVar idv, pairs, key, val, nextseq; + LuaStack LS(insp.L, idv, pairs, key, val, nextseq); // If it's anything but a table, use 'atomic_print'. if (!LS.istable(root)) { @@ -175,9 +175,9 @@ static void pprint_r(Inspector &insp, int level, LuaSlot root) { } // State variables. - int nextseq = 1; bool needcomma = false; bool multiline = false; + LS.set(nextseq, 1); // Open the brackets. (*insp.stream) << "{"; @@ -190,10 +190,10 @@ static void pprint_r(Inspector &insp, int level, LuaSlot root) { LS.rawget(val, pairs, i+1); if (needcomma) (*insp.stream) << ","; needcomma = true; - if (LS.isnumber(key) && (LS.ckint(key) == nextseq)) { + if (LS.rawequal(key, nextseq)) { (*insp.stream) << " "; pprint_r(insp, level + 1, val); - nextseq = nextseq + 1; + LS.set(nextseq, LS.ckinteger(nextseq) + 1); } else { multiline = true; tabify(insp, level + 1); @@ -227,7 +227,7 @@ static void pprint_r(Inspector &insp, int level, LuaSlot root) { // Close the brackets. if (multiline) { tabify(insp, level); - } else if (nextseq > 1) { + } else if (LS.ckinteger(nextseq) > 1) { (*insp.stream) << " "; } (*insp.stream) << "}"; diff --git a/luprex/core/cpp/sched.cpp b/luprex/core/cpp/sched.cpp index dbdaaaf8..6075668b 100644 --- a/luprex/core/cpp/sched.cpp +++ b/luprex/core/cpp/sched.cpp @@ -24,6 +24,8 @@ eng::string SchedEntry::debug_string() const { } void Schedule::add(int64_t clk, int64_t thid, int64_t plid) { + assert(plid != 0); + assert(thid != 0); schedule_.insert(SchedEntry(clk, thid, plid)); } diff --git a/luprex/core/cpp/source.cpp b/luprex/core/cpp/source.cpp index fede292a..2ea0f15c 100644 --- a/luprex/core/cpp/source.cpp +++ b/luprex/core/cpp/source.cpp @@ -328,9 +328,10 @@ static eng::string source_load_lfunctions(lua_State *L) { // Call the closure. If there's an error, collect it. lua_pushvalue(L, closure.index()); - if (traceback_pcall(L, 0, 0) != 0) { - lua_replace(L, err.index()); - errss << LS.ckstring(err); + eng::string msg = traceback_pcall(L, 0, 0); + if (!msg.empty()) { + LS.set(err, msg); + errss << msg << std::endl; } } LS.result(); @@ -380,9 +381,10 @@ void SourceDB::run_unittests() { LS.rawget(func, unittests, name); lua_pushvalue(L, func.index()); - if (traceback_pcall(L, 0, 0) != 0) { - lua_replace(L, err.index()); - std::cerr << LS.ckstring(err); + eng::string msg = traceback_pcall(L, 0, 0); + if (!msg.empty()) { + LS.set(err, msg); + std::cerr << msg << std::endl; any = true; } } diff --git a/luprex/core/cpp/traceback.cpp b/luprex/core/cpp/traceback.cpp index f5fd9d3e..3f454acd 100644 --- a/luprex/core/cpp/traceback.cpp +++ b/luprex/core/cpp/traceback.cpp @@ -76,12 +76,19 @@ int traceback_coroutine(lua_State *L) { } -int traceback_pcall(lua_State *L, int narg, int nret) { +eng::string traceback_pcall(lua_State *L, int narg, int nret) { int status; int base = lua_gettop(L) - narg; /* function index */ lua_pushcfunction(L, traceback_coroutine); /* push traceback function */ lua_insert(L, base); /* put it under chunk and args */ status = lua_pcall(L, narg, nret, base); lua_remove(L, base); /* remove traceback function */ - return status; + if (status != LUA_OK) { + const char *msg = lua_tostring(L, -1); + if ((msg == NULL) || (msg[0] == 0)) { + msg = "unknown error"; + } + return msg; + } + return ""; } diff --git a/luprex/core/cpp/traceback.hpp b/luprex/core/cpp/traceback.hpp index 20d58f8e..fb6e6bbb 100644 --- a/luprex/core/cpp/traceback.hpp +++ b/luprex/core/cpp/traceback.hpp @@ -22,9 +22,11 @@ int traceback_coroutine(lua_State *L); // traceback_pcall // -// same as lua_pcall, except that it automatically supplies -// traceback_coroutine as a message handler. +// Similar to lua_pcall, except that it automatically supplies +// traceback_coroutine as a message handler. It also automatically +// returns any error message. Returns empty string if there's +// no error. // -int traceback_pcall(lua_State *L, int narg, int nret); +eng::string traceback_pcall(lua_State *L, int narg, int nret); #endif // TRACEBACK_HPP diff --git a/luprex/core/cpp/util.cpp b/luprex/core/cpp/util.cpp index baf21132..ccb3df7a 100644 --- a/luprex/core/cpp/util.cpp +++ b/luprex/core/cpp/util.cpp @@ -510,10 +510,6 @@ double distance_squared(double x1, double y1, double x2, double y2) { return dx*dx + dy*dy; } -bool world_type_authoritative(util::WorldType wt) { - return (wt == WORLD_TYPE_MASTER) || (wt == WORLD_TYPE_STANDALONE); -} - LuaSourcePtr make_lua_source(const eng::string &code) { LuaSourcePtr result(new LuaSourceVec); eng::string fn = "file.lua"; diff --git a/luprex/core/cpp/util.hpp b/luprex/core/cpp/util.hpp index ff7e9fb7..33e15d28 100644 --- a/luprex/core/cpp/util.hpp +++ b/luprex/core/cpp/util.hpp @@ -212,9 +212,6 @@ eng::string toupper(eng::string input); // Calculate distance between two points double distance_squared(double x1, double y1, double x2, double y2); -// Return true if a world type is authoritative. -bool world_type_authoritative(util::WorldType wt); - // Make a LuaSourceVec with one element, for unit testing. LuaSourcePtr make_lua_source(const eng::string &code); diff --git a/luprex/core/cpp/world-accessor.cpp b/luprex/core/cpp/world-accessor.cpp index 7f58bda7..25436c2c 100644 --- a/luprex/core/cpp/world-accessor.cpp +++ b/luprex/core/cpp/world-accessor.cpp @@ -294,23 +294,40 @@ LuaDefine(tangible_scan, "plane,x,y,radius,omit_nowhere", LuaDefine(wait, "nticks", "|Wait the specified number of ticks.") { - if ((lua_gettop(L) != 1) || (lua_type(L, -1) != LUA_TNUMBER)) { - luaL_error(L, "Argument to wait must be a number."); + World *w = World::fetch_global_pointer(L); + LuaArg seconds; + LuaStack LS(L, seconds); + int64_t n = LS.ckinteger(seconds); + if ((n < 0) || (n > 1000000)) { + luaL_error(L, "Argument to wait must be between 0 and 1000000"); + return LS.result(); + } + if (!lua_isyieldable(L)) { + // in a probe, wait throws an error. + luaL_error(L, "cannot wait in a probe."); + return LS.result(); + } else if (!w->is_authoritative()) { + // in a nonauth model, yield is converted to nopredict. + lua_yield(L, 0); + return LS.result(); + } else { + // in an authoritative model, wait schedules a continuation. + w->thread_sched_.add(w->clock_ + n, w->lthread_thread_id_, w->lthread_place_id_); + lua_yield(L, 0); + return LS.result(); } - return lua_yield(L, 1); } -LuaDefine(tangible_nopredict, "", +LuaDefine(nopredict, "", "|Stop predictive execution of this thread.") { if (lua_gettop(L) != 0) { luaL_error(L, "tangible.nopredict takes no arguments"); } World *w = World::fetch_global_pointer(L); - if (util::world_type_authoritative(w->world_type_)) { - return 0; - } else { + if (lua_isyieldable(L) && !w->is_authoritative()) { return lua_yield(L, 0); } + return 0; } LuaDefine(math_random, "(args...)", diff --git a/luprex/core/cpp/world-core.cpp b/luprex/core/cpp/world-core.cpp index 149e25ca..e53d206f 100644 --- a/luprex/core/cpp/world-core.cpp +++ b/luprex/core/cpp/world-core.cpp @@ -64,7 +64,7 @@ World::World(util::WorldType wt) { LS.rawset(LuaRegistry, "tangibles", LuaNewTable); // Create the globaldb and oncedb in the registry. - if (util::world_type_authoritative(wt)) { + if ((wt == util::WORLD_TYPE_MASTER) || (wt == util::WORLD_TYPE_STANDALONE)) { LS.rawset(LuaRegistry, "globaldb", LuaNewTable); LS.rawset(LuaRegistry, "oncedb", LuaNewTable); } @@ -291,23 +291,20 @@ eng::string World::probe_lua(int64_t actor_id, const eng::string &lua) { // Call the closure. int top = lua_gettop(L); lua_pushvalue(L, closure.index()); - open_lthread_state(actor_id, actor_id, false, true); - status = traceback_pcall(L, 0, LUA_MULTRET); + open_lthread_state(actor_id, actor_id, 0, false, true); + eng::string msg = traceback_pcall(L, 0, LUA_MULTRET); // If there's an error message, print it. // Otherwise, pretty-print the results. std::ostream *ostream = lthread_print_stream(); - if (status == LUA_OK) { + if (msg.empty()) { for (int i = top + 1; i <= lua_gettop(L); i++) { LuaSpecial root(i); pprint(LS, root, true, ostream); (*ostream) << std::endl; } } else { - const char *msg = lua_tostring(L, -1); - if (msg == NULL) { - msg = "(error object is not a string)"; - } + assert(msg != "attempt to yield from outside a coroutine"); (*ostream) << msg << std::endl; } @@ -363,13 +360,14 @@ void World::update_gui(int64_t actor_id, int64_t place_id, Gui *gui) { lua_pushvalue(L, actor.index()); lua_pushvalue(L, place.index()); Gui::store_global_pointer(L, gui); - open_lthread_state(actor_id, place_id, false, false); - int status = traceback_pcall(L, 2, 0); + open_lthread_state(actor_id, place_id, 0, false, false); + eng::string msg = traceback_pcall(L, 2, 0); close_lthread_state(); Gui::store_global_pointer(L, nullptr); - if (status != 0) { + if (!msg.empty()) { gui->clear(0); - std::cerr << lua_tostring(L, -1); + assert(msg != "attempt to yield from outside a coroutine"); + std::cerr << msg << std::endl; LS.result(); return; } @@ -503,7 +501,7 @@ void World::invoke_lua(int64_t actor_id, int64_t place_id, const eng::string &ac LS.newtable(thinfo); LS.rawset(thinfo, "thread", thread); LS.rawset(thinfo, "actorid", actor_id); - LS.rawset(thinfo, "nargs", 0); + LS.rawset(thinfo, "isnew", true); LS.rawset(thinfo, "useppool", true); LS.rawset(thinfo, "print", true); @@ -596,7 +594,7 @@ void World::invoke_plan(int64_t actor_id, int64_t place_id, const eng::string &a LS.newtable(thinfo); LS.rawset(thinfo, "thread", thread); LS.rawset(thinfo, "actorid", actor_id); - LS.rawset(thinfo, "nargs", 3); // actor, place, invdata + LS.rawset(thinfo, "isnew", true); LS.rawset(thinfo, "useppool", true); LS.rawset(thinfo, "print", false); @@ -617,7 +615,7 @@ void World::invoke_plan(int64_t actor_id, int64_t place_id, const eng::string &a } void World::invoke_tick(int64_t actor_id, int64_t place_id, const eng::string &action, const InvocationData &data) { - if (!util::world_type_authoritative(world_type_)) { + if (!is_authoritative()) { return; } clock_ += 1; @@ -625,7 +623,7 @@ void World::invoke_tick(int64_t actor_id, int64_t place_id, const eng::string &a } void World::invoke_lua_source(int64_t actor_id, int64_t place_id, const eng::string &action, const InvocationData &data) { - if (!util::world_type_authoritative(world_type_)) { + if (!is_authoritative()) { return; } // We need some kind of authentication here. @@ -642,8 +640,8 @@ void World::invoke_lua_source(int64_t actor_id, int64_t place_id, const eng::str void World::run_scheduled_threads() { assert(stack_is_clear()); lua_State *L = state(); - LuaVar tangibles, place, mt, threads, thinfo, actorid, nargs, useppool, thread, print; - LuaStack LS(L, tangibles, place, mt, threads, thinfo, actorid, nargs, useppool, thread, print); + LuaVar tangibles, place, mt, threads, thinfo, actorid, isnew, useppool, thread, print; + LuaStack LS(L, tangibles, place, mt, threads, thinfo, actorid, isnew, useppool, thread, print); LS.rawget(tangibles, LuaRegistry, "tangibles"); while (thread_sched_.ready(clock_)) { @@ -668,8 +666,8 @@ void World::run_scheduled_threads() { if (!LS.isnumber(actorid)) { continue; } - LS.rawget(nargs, thinfo, "nargs"); - if (!LS.isnumber(nargs)) { + LS.rawget(isnew, thinfo, "isnew"); + if (!LS.isboolean(isnew)) { continue; } LS.rawget(useppool, thinfo, "useppool"); @@ -683,35 +681,16 @@ void World::run_scheduled_threads() { // Resume the coroutine. lua_State *CO = LS.ckthread(thread); - open_lthread_state(LS.ckinteger(actorid), sched.place_id(), LS.ckboolean(useppool), true); - int status = lua_resume(CO, nullptr, LS.ckint(nargs)); + open_lthread_state(LS.ckinteger(actorid), sched.place_id(), sched.thread_id(), LS.ckboolean(useppool), true); + int nargs = LS.ckboolean(isnew) ? (lua_gettop(CO) - 1) : lua_gettop(CO); + int status = lua_resume(CO, nullptr, nargs); std::ostream *ostream = lthread_print_stream(); - // Three possible outcomes: finished, yielded, or errored. - if (!util::world_type_authoritative(world_type_)) { - LS.rawset(threads, sched.thread_id(), LuaNil); - } else if (status == LUA_YIELD) { - // If there's nothing on the stack, infer that tangible.nopredict yielded. - if (lua_gettop(CO) == 0) { - LS.rawset(threads, sched.thread_id(), LuaNil); - } - // If there's a single number on the stack, infer that 'wait' yielded. - else if ((lua_gettop(CO) == 1) && (lua_isnumber(CO, 1))) { - lua_Number delay = lua_tonumber(CO, 1); - lua_settop(CO, 0); - LS.rawset(thinfo, "nargs", 0); - LS.rawset(thinfo, "useppool", false); - thread_sched_.add(clock_ + int64_t(delay), sched.thread_id(), sched.place_id()); - } - // In any other case, generate an error and kill the coroutine. - else { - std::cerr << "Thread yielded incorrectly. Killing it." << std::endl; - LS.rawset(threads, sched.thread_id(), LuaNil); - } - } else if (status == LUA_OK) { + if (status == LUA_OK) { // Successfully ran to completion. Print any return values. // Remove from thread table. LS.rawget(print, thinfo, "print"); + LS.rawset(threads, sched.thread_id(), LuaNil); LuaStack LSCO(CO); if (LS.ckboolean(print)) { for (int i = 1; i <= lua_gettop(CO); i++) { @@ -719,12 +698,21 @@ void World::run_scheduled_threads() { (*ostream) << std::endl; } } - LS.rawset(threads, sched.thread_id(), LuaNil); + } else if (status == LUA_YIELD) { + if (is_authoritative()) { + LS.rawset(thinfo, "isnew", false); + LS.rawset(thinfo, "useppool", false); + } else { + // In a nonauth model, a yield is converted to a 'nopredict'. + LS.rawset(threads, sched.thread_id(), LuaNil); + } } else { // Generated an error. Add a traceback, print, and kill the coroutine. // Currently, the error is sent to the actor. That seems... not right in the long run. - traceback_coroutine(CO); - (*ostream) << lua_tostring(CO, -1); + if (is_authoritative()) { + traceback_coroutine(CO); + (*ostream) << lua_tostring(CO, -1); + } LS.rawset(threads, sched.thread_id(), LuaNil); } close_lthread_state(); @@ -757,12 +745,14 @@ void World::clear_lthread_state() { lthread_prints_.reset(); lthread_actor_id_ = 0; lthread_place_id_ = 0; + lthread_thread_id_ = 0; lthread_use_ppool_ = false; } -void World::open_lthread_state(int64_t actor, int64_t place, bool ppool, bool prints) { +void World::open_lthread_state(int64_t actor, int64_t place, int64_t thread, bool ppool, bool prints) { lthread_actor_id_ = actor; lthread_place_id_ = place; + lthread_thread_id_ = thread; lthread_use_ppool_ = ppool; if (prints) { lthread_prints_.reset(new eng::ostringstream); @@ -780,8 +770,7 @@ void World::close_lthread_state() { const eng::string &output = lthread_prints_->str(); Tangible *actor = tangible_get(lthread_actor_id_); if (actor != nullptr) { - bool auth = util::world_type_authoritative(world_type_); - actor->print_buffer_.add_string(output, auth); + actor->print_buffer_.add_string(output, is_authoritative()); } } // Now clean up everything. diff --git a/luprex/core/cpp/world.hpp b/luprex/core/cpp/world.hpp index d9c64c7a..8a9a1fbd 100644 --- a/luprex/core/cpp/world.hpp +++ b/luprex/core/cpp/world.hpp @@ -212,6 +212,10 @@ public: // static World *fetch_global_pointer(lua_State *L); + // Check if the world is authoritative. + // + bool is_authoritative() const { return (world_type_ == util::WORLD_TYPE_MASTER) || (world_type_ == util::WORLD_TYPE_STANDALONE); } + // Serialize and deserialize. // void serialize(StreamBuffer *sb); @@ -245,7 +249,7 @@ public: // cleared. // void clear_lthread_state(); - void open_lthread_state(int64_t actor_id, int64_t place_id, bool ppool, bool prints); + void open_lthread_state(int64_t actor_id, int64_t place_id, int64_t thread_id, bool ppool, bool prints); void close_lthread_state(); std::ostream *lthread_print_stream() const; @@ -260,7 +264,6 @@ public: int64_t alloc_id_predictable(); private: - // Store a pointer to a world model into a lua registry. // static void store_global_pointer(lua_State *L, World *w); @@ -489,6 +492,7 @@ private: // int64_t lthread_actor_id_; int64_t lthread_place_id_; + int64_t lthread_thread_id_; int64_t lthread_use_ppool_; std::unique_ptr lthread_prints_; @@ -503,6 +507,8 @@ private: friend int lfn_tangible_scan(lua_State *L); friend int lfn_math_random(lua_State *L); friend int lfn_math_randomstate(lua_State *L); + friend int lfn_wait(lua_State *L); + friend int lfn_nopredict(lua_State *L); }; using UniqueWorld = std::unique_ptr; diff --git a/luprex/core/lua/login.lua b/luprex/core/lua/login.lua index 08180efa..04b9bd0a 100644 --- a/luprex/core/lua/login.lua +++ b/luprex/core/lua/login.lua @@ -3,6 +3,7 @@ makeclass('login') function login.interface(actor, place) gui.menu_item("cb_becomeplayer", "Become a Player") gui.menu_item("cb_p123", "Print 1, 2, 3") + gui.menu_item("cb_p123_nopredict", "Print 1, 2, 3 nopredict") end function login.cb_becomeplayer(actor, place, dialog) @@ -20,9 +21,21 @@ function login.cb_p123(actor, place, dialog) print(3) end +function login.cb_p123_nopredict(actor, place, dialog) + nopredict() + print(1) + wait(1) + print(2) + wait(1) + print(3) + end + -- this is function documentation for setfoo. function setfoo(n) - tangible.nopredict() + if (n == nil) then + error("setfoo(n) - n must be an integer") + end + nopredict() tangible.actor().inventory.foo = n end diff --git a/luprex/eris-master/src/ldo.c b/luprex/eris-master/src/ldo.c index e9dd5fa9..1117d8f4 100644 --- a/luprex/eris-master/src/ldo.c +++ b/luprex/eris-master/src/ldo.c @@ -591,6 +591,9 @@ LUA_API int lua_yieldk (lua_State *L, int nresults, int ctx, lua_CFunction k) { return 0; /* return to 'luaD_hook' */ } +LUA_API int lua_isyieldable (lua_State *L) { + return (L->nny == 0); +} int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t old_top, ptrdiff_t ef) { diff --git a/luprex/eris-master/src/lua.h b/luprex/eris-master/src/lua.h index a78a5e1b..165d685d 100644 --- a/luprex/eris-master/src/lua.h +++ b/luprex/eris-master/src/lua.h @@ -277,6 +277,7 @@ LUA_API int (lua_yieldk) (lua_State *L, int nresults, int ctx, #define lua_yield(L,n) lua_yieldk(L, (n), 0, NULL) LUA_API int (lua_resume) (lua_State *L, lua_State *from, int narg); LUA_API int (lua_status) (lua_State *L); +LUA_API int (lua_isyieldable) (lua_State *L); /* ** garbage-collection function and options