Merge branch 'main' of https://github.com/jyelon/luprex
This commit is contained in:
@@ -8,4 +8,4 @@ Do something about std::cerr && std::cout once and for all.
|
|||||||
|
|
||||||
Fix math.random (?)
|
Fix math.random (?)
|
||||||
|
|
||||||
|
Do a better job handling 'close' in the driver (need some equivalent of SSL_shutdown)
|
||||||
@@ -323,8 +323,8 @@ bool AnimStep::from_string(const eng::string &config) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimQueue::AnimQueue(util::WorldType wt) {
|
AnimQueue::AnimQueue(WorldType wt) {
|
||||||
version_autoinc_ = (wt == util::WORLD_TYPE_MASTER);
|
version_autoinc_ = (wt == WORLD_TYPE_MASTER);
|
||||||
size_limit_ = 10; // Default size limit.
|
size_limit_ = 10; // Default size limit.
|
||||||
steps_.emplace_back();
|
steps_.emplace_back();
|
||||||
steps_.front().keep_state_only();
|
steps_.front().keep_state_only();
|
||||||
@@ -576,8 +576,8 @@ static bool diff_works(const AnimQueue &master, AnimQueue &sync) {
|
|||||||
LuaDefine(unittests_animqueue, "", "some unit tests") {
|
LuaDefine(unittests_animqueue, "", "some unit tests") {
|
||||||
// Useful objects.
|
// Useful objects.
|
||||||
AnimStep stp;
|
AnimStep stp;
|
||||||
AnimQueue aq(util::WORLD_TYPE_MASTER);
|
AnimQueue aq(WORLD_TYPE_MASTER);
|
||||||
AnimQueue aqds(util::WORLD_TYPE_S_SYNC);
|
AnimQueue aqds(WORLD_TYPE_PREDICTIVE);
|
||||||
StreamBuffer sb;
|
StreamBuffer sb;
|
||||||
|
|
||||||
// Debug string of a newly initialized queue
|
// Debug string of a newly initialized queue
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ private:
|
|||||||
class AnimQueue : public eng::nevernew {
|
class AnimQueue : public eng::nevernew {
|
||||||
public:
|
public:
|
||||||
// World type determines whether versions increment or autozero
|
// World type determines whether versions increment or autozero
|
||||||
AnimQueue(util::WorldType wt);
|
AnimQueue(WorldType wt);
|
||||||
|
|
||||||
// Simple getters.
|
// Simple getters.
|
||||||
const AnimStep &nth(int n) const { return steps_[n]; }
|
const AnimStep &nth(int n) const { return steps_[n]; }
|
||||||
|
|||||||
@@ -505,12 +505,13 @@ void DrivenEngine::drv_sent_outgoing(uint32_t chid, uint32_t nbytes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DrivenEngine::drv_recv_incoming(uint32_t chid, uint32_t nbytes, const char *bytes) {
|
void DrivenEngine::drv_recv_incoming(uint32_t chid, uint32_t nbytes, const char *bytes) {
|
||||||
|
std::string_view sbytes(bytes, nbytes);
|
||||||
if (nbytes > 0) {
|
if (nbytes > 0) {
|
||||||
Channel *ch = get_chid(chid);
|
Channel *ch = get_chid(chid);
|
||||||
if (ch->sb_drvout_ != ch->sb_out_) {
|
if (ch->sb_drvout_ != ch->sb_out_) {
|
||||||
ch->feed_readline(bytes);
|
ch->feed_readline(sbytes);
|
||||||
} else {
|
} else {
|
||||||
ch->sb_in_->write_bytes(bytes);
|
ch->sb_in_->write_bytes(sbytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1015,6 +1016,7 @@ static void init_engine_wrapper_helper(EngineWrapper *w) {
|
|||||||
w->replay_initialize = replaycore_initialize;
|
w->replay_initialize = replaycore_initialize;
|
||||||
w->replay_step = replaycore_step;
|
w->replay_step = replaycore_step;
|
||||||
|
|
||||||
|
w->hook_dprintf = util::hook_dprintf;
|
||||||
w->release = release;
|
w->release = release;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public:
|
|||||||
//
|
//
|
||||||
const eng::string &target() const { return target_; }
|
const eng::string &target() const { return target_; }
|
||||||
|
|
||||||
// True if the remote closed the connection, or a failure occurred.
|
// True if the remote has closed the connection.
|
||||||
//
|
//
|
||||||
bool closed() const { return closed_; }
|
bool closed() const { return closed_; }
|
||||||
|
|
||||||
@@ -318,7 +318,6 @@ private:
|
|||||||
friend class Channel;
|
friend class Channel;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ private:
|
|||||||
|
|
||||||
void event_init(int argc, char *argv[])
|
void event_init(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
world_.reset(new World(util::WORLD_TYPE_STANDALONE));
|
world_.reset(new World(WORLD_TYPE_MASTER));
|
||||||
world_->update_source(get_lua_source());
|
world_->update_source(get_lua_source());
|
||||||
world_->run_unittests();
|
world_->run_unittests();
|
||||||
stop_driver();
|
stop_driver();
|
||||||
|
|||||||
@@ -227,6 +227,23 @@ struct EngineWrapper {
|
|||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Hook dprintf
|
||||||
|
//
|
||||||
|
// The engine provides a function 'util::dprintf' to print debugging
|
||||||
|
// messages. It does not use stderr or std::cerr, because in windows, those
|
||||||
|
// go to the bit-bucket.
|
||||||
|
//
|
||||||
|
// This routine hooks dprintf to change where the output goes. Ideally,
|
||||||
|
// the intent is to send the output somewhere easily accessible, like the
|
||||||
|
// visual studio debug output log, or the unreal editor output log, or
|
||||||
|
// something like that. Or, better yet, to all those places at once.
|
||||||
|
//
|
||||||
|
// The hook function will get passed one line of output at a time. The line
|
||||||
|
// will only contain printable characters. The line will not contain a
|
||||||
|
// newline - the newline is implied.
|
||||||
|
//
|
||||||
|
void (*hook_dprintf)(void (*func)(const char *oneline));
|
||||||
|
|
||||||
// Restore the wrapper to its initial blank state.
|
// Restore the wrapper to its initial blank state.
|
||||||
//
|
//
|
||||||
// Note that the wrapper must have already been initialized using
|
// Note that the wrapper must have already been initialized using
|
||||||
|
|||||||
@@ -1,72 +1,77 @@
|
|||||||
#include "luastack.hpp"
|
#include "luastack.hpp"
|
||||||
#include "globaldb.hpp"
|
#include "globaldb.hpp"
|
||||||
|
|
||||||
LuaDefine(global_once, "name", "for a given string, returns true exactly once") {
|
|
||||||
|
LuaDefine(global_set, "varname, value",
|
||||||
|
"|Store data in the global data table."
|
||||||
|
"|"
|
||||||
|
"|The variable name must be a string which is a valid"
|
||||||
|
"|lua identifier."
|
||||||
|
"|"
|
||||||
|
"|You can store global data using global.set, then you can"
|
||||||
|
"|retrieve it using global.get. You can also retrieve data using"
|
||||||
|
"|gv.varname, which is just shorthand for global.get. You may not"
|
||||||
|
"|store data using gv.varname=value, this yields the error 'Use "
|
||||||
|
"|global.set to store data in the global data table.'"
|
||||||
|
"|"
|
||||||
|
"|Values stored using global.set are transmitted to all"
|
||||||
|
"|connected clients immediately. When a new client connects,"
|
||||||
|
"|he will receive all the global data."
|
||||||
|
"|"
|
||||||
|
"|The global data table is not the same thing as the lua "
|
||||||
|
"|environment table. Trying to store data in the lua environment"
|
||||||
|
"|table will seem to work, at first, but the data will not get"
|
||||||
|
"|difference transmitted, and will eventually be cleared and lost."
|
||||||
|
"|Therefore, it is essential that global data be stored in the"
|
||||||
|
"|global data table (using global.set) instead of in the lua"
|
||||||
|
"|environment table."
|
||||||
|
"|"
|
||||||
|
"|There are certain restrictions on the values that you store."
|
||||||
|
"|The value can contain strings, numbers, simple tables, and"
|
||||||
|
"|tangibles. Nothing else is allowed. Simple tables are tables"
|
||||||
|
"|that don't have metatables, and that aren't special tables such"
|
||||||
|
"|as classes, the lua environment table, the registry, or the like."
|
||||||
|
"|"
|
||||||
|
"|When you store the value, a recursive copy is made and stored."
|
||||||
|
"|When you call global.get, you obtain the copy. Any attempt to"
|
||||||
|
"|mutate the copy will fail with this lua error message: 'Tables"
|
||||||
|
"|returned by global.get are immutable.' This rule prevents'"
|
||||||
|
"|aliasing between global data and other data structures."
|
||||||
|
"|") {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LuaDefine(global_get, "varname",
|
||||||
|
"|Get data stored using global.set"
|
||||||
|
"|"
|
||||||
|
"|See doc(global.set) for information on how to store global data."
|
||||||
|
"|") {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LuaDefine(global_once, "name",
|
||||||
|
"|For a given string, returns true exactly once"
|
||||||
|
"|"
|
||||||
|
"|The semantics and difference transmission behavior of global.once"
|
||||||
|
"|are identical to the semantics of global.set, since global.once"
|
||||||
|
"|uses global.set under the covers."
|
||||||
|
"|") {
|
||||||
LuaArg name;
|
LuaArg name;
|
||||||
LuaRet flag;
|
LuaRet flag;
|
||||||
LuaVar oncedb, val;
|
LuaVar oncedb, val;
|
||||||
LuaStack LS(L, name, flag, oncedb, val);
|
LuaStack LS(L, name, flag, oncedb, val);
|
||||||
|
return 0;
|
||||||
// Get a pointer to the oncedb.
|
|
||||||
LS.rawget(oncedb, LuaRegistry, "oncedb");
|
|
||||||
if (!LS.istable(oncedb)) {
|
|
||||||
LS.set(flag, false);
|
|
||||||
return LS.result();
|
|
||||||
}
|
|
||||||
|
|
||||||
LS.checkstring(name, "name");
|
|
||||||
LS.rawget(val, oncedb, name);
|
|
||||||
if (!LS.isnil(val)) {
|
|
||||||
LS.set(flag, false);
|
|
||||||
return LS.result();
|
|
||||||
}
|
|
||||||
LS.rawset(oncedb, name, true);
|
|
||||||
LS.set(flag, true);
|
|
||||||
return LS.result();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaDefine(global_clearonce, "name", "reset the specified once-flag") {
|
LuaDefine(global_clearonce, "name",
|
||||||
|
"|Reset the specified once-flag"
|
||||||
|
"|"
|
||||||
|
"|The semantics and difference transmission behavior of global.clearonce"
|
||||||
|
"|are identical to the semantics of global.set, since global.once"
|
||||||
|
"|uses global.set under the covers."
|
||||||
|
"|") {
|
||||||
LuaArg name;
|
LuaArg name;
|
||||||
LuaVar oncedb;
|
LuaVar oncedb;
|
||||||
LuaStack LS(L, name, oncedb);
|
LuaStack LS(L, name, oncedb);
|
||||||
|
return 0;
|
||||||
// Get a pointer to the oncedb.
|
|
||||||
LS.rawget(oncedb, LuaRegistry, "oncedb");
|
|
||||||
if (!LS.istable(oncedb)) {
|
|
||||||
return LS.result();
|
|
||||||
}
|
|
||||||
LS.checkstring(name, "name");
|
|
||||||
LS.rawset(oncedb, name, LuaNil);
|
|
||||||
return LS.result();
|
|
||||||
}
|
|
||||||
|
|
||||||
LuaDefine(global_table, "globalname", "get a table where global data can be stored") {
|
|
||||||
LuaArg globalname;
|
|
||||||
LuaRet globaltab;
|
|
||||||
LuaVar globaldb;
|
|
||||||
LuaStack LS(L, globalname, globaltab, globaldb);
|
|
||||||
LS.checkstring(globalname, "globalname");
|
|
||||||
|
|
||||||
// Get a pointer to the globaldb.
|
|
||||||
LS.rawget(globaldb, LuaRegistry, "globaldb");
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
if (LS.istable(globaltab)) {
|
|
||||||
return LS.result();
|
|
||||||
} else if (!LS.isnil(globaltab)) {
|
|
||||||
luaL_error(L, "%s is not a global", LS.ckstring(globalname).c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new globaltab and store it in the globaldb.
|
|
||||||
LS.newtable(globaltab);
|
|
||||||
LS.rawset(globaldb, globalname, globaltab);
|
|
||||||
LS.rawset(globaltab, "__global", globalname);
|
|
||||||
LS.settabletype(globaltab, LUA_TT_GLOBALDB);
|
|
||||||
return LS.result();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public:
|
|||||||
public:
|
public:
|
||||||
void set_initial_state() {
|
void set_initial_state() {
|
||||||
// Create the world model.
|
// Create the world model.
|
||||||
world_.reset(new World(util::WORLD_TYPE_C_SYNC));
|
world_.reset(new World(WORLD_TYPE_PREDICTIVE));
|
||||||
|
|
||||||
// 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
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public:
|
|||||||
public:
|
public:
|
||||||
virtual void event_init(int argc, char *argv[]) {
|
virtual void event_init(int argc, char *argv[]) {
|
||||||
// Create the master world model.
|
// Create the master world model.
|
||||||
master_.reset(new World(util::WORLD_TYPE_MASTER));
|
master_.reset(new World(WORLD_TYPE_MASTER));
|
||||||
|
|
||||||
// Update the source code of the master model.
|
// Update the source code of the master model.
|
||||||
master_->update_source(get_lua_source());
|
master_->update_source(get_lua_source());
|
||||||
@@ -189,7 +189,7 @@ public:
|
|||||||
Client *client = new Client;
|
Client *client = new Client;
|
||||||
client->actor_id_ = master_->create_login_actor();
|
client->actor_id_ = master_->create_login_actor();
|
||||||
client->channel_ = std::move(chan);
|
client->channel_ = std::move(chan);
|
||||||
client->sync_.reset(new World(util::WORLD_TYPE_S_SYNC));
|
client->sync_.reset(new World(WORLD_TYPE_PREDICTIVE));
|
||||||
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;
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ void LuaSnap::deserialize(StreamBuffer *sb) {
|
|||||||
void *ud = sb->lua_reader_ud(len);
|
void *ud = sb->lua_reader_ud(len);
|
||||||
|
|
||||||
// Call eris with the permanents table and passing the snapshot as a lua_Reader.
|
// Call eris with the permanents table and passing the snapshot as a lua_Reader.
|
||||||
lua_getfield(state_, LUA_REGISTRYINDEX, "unpersist");
|
lua_pushstring(state_, "unpersist");
|
||||||
|
lua_rawget(state_, LUA_REGISTRYINDEX);
|
||||||
eris_undump(state_, sb->lua_reader, ud);
|
eris_undump(state_, sb->lua_reader, ud);
|
||||||
assert(lua_gettop(state_) == 2);
|
assert(lua_gettop(state_) == 2);
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,20 @@ static void *l_alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
|
|||||||
lua_State *LuaStack::newstate (lua_Alloc allocf) {
|
lua_State *LuaStack::newstate (lua_Alloc allocf) {
|
||||||
if (allocf == nullptr) allocf = l_alloc;
|
if (allocf == nullptr) allocf = l_alloc;
|
||||||
lua_State *L = lua_newstate(allocf, NULL);
|
lua_State *L = lua_newstate(allocf, NULL);
|
||||||
if (L) lua_atpanic(L, &panicf);
|
assert(L != nullptr);
|
||||||
|
lua_atpanic(L, &panicf);
|
||||||
|
|
||||||
|
// We want all states to have a classes table and
|
||||||
|
// a tangibles table so that LS.makeclass and LS.maketan
|
||||||
|
// work out-of-the-box.
|
||||||
|
|
||||||
|
lua_pushstring(L, "classes");
|
||||||
|
lua_newtable(L);
|
||||||
|
lua_rawset(L, LUA_REGISTRYINDEX);
|
||||||
|
lua_pushstring(L, "tangibles");
|
||||||
|
lua_newtable(L);
|
||||||
|
lua_rawset(L, LUA_REGISTRYINDEX);
|
||||||
|
|
||||||
return L;
|
return L;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,27 +269,35 @@ bool LuaStack::validclassname(LuaSlot slot) const {
|
|||||||
|
|
||||||
eng::string LuaStack::classname(LuaSlot tab) const {
|
eng::string LuaStack::classname(LuaSlot tab) const {
|
||||||
eng::string result;
|
eng::string result;
|
||||||
if (istable(tab)) {
|
if ((istable(tab)) && (gettabletype(tab) == LUA_TT_CLASS)) {
|
||||||
lua_pushstring(L_, "__class");
|
LuaVar classes, name, dup;
|
||||||
lua_rawget(L_, tab);
|
LuaStack LS(L_, classes, name, dup);
|
||||||
if (lua_type(L_, -1) == LUA_TSTRING) {
|
// Get the classes table from the registry.
|
||||||
lua_pushglobaltable(L_); // cname table
|
LS.rawget(classes, LuaRegistry, "classes");
|
||||||
lua_pushvalue(L_, -2); // cname table cname
|
|
||||||
lua_rawget(L_, -2); // cname table ctab
|
// Try the efficient approach: get the class name from
|
||||||
if (lua_rawequal(L_, -1, tab)) {
|
// the class, and confirm it by checking the classes table.
|
||||||
size_t len;
|
LS.rawget(name, tab, "__class");
|
||||||
const char *s = lua_tolstring(L_, -3, &len);
|
if (LS.isstring(name)) {
|
||||||
result = eng::string(s, len);
|
LS.rawget(dup, classes, name);
|
||||||
if (!validclassname(result)) {
|
if (LS.rawequal(dup, tab)) {
|
||||||
result = "";
|
result = LS.ckstring(name);
|
||||||
}
|
LS.result();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do it the brute force way: scan the classes table.
|
||||||
|
LS.set(name, LuaNil);
|
||||||
|
while (LS.next(classes, name, dup)) {
|
||||||
|
if (LS.rawequal(dup, tab)) {
|
||||||
|
result = LS.ckstring(name);
|
||||||
|
LS.result();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
lua_pop(L_, 3);
|
|
||||||
} else {
|
|
||||||
lua_pop(L_, 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
eng::string LuaStack::getclass(LuaSlot classtab, LuaSlot classname) const {
|
eng::string LuaStack::getclass(LuaSlot classtab, LuaSlot classname) const {
|
||||||
@@ -342,24 +363,31 @@ eng::string LuaStack::getclass(LuaSlot tab, std::string_view name) const {
|
|||||||
|
|
||||||
void LuaStack::makeclass(LuaSlot classtab, LuaSlot classname) const {
|
void LuaStack::makeclass(LuaSlot classtab, LuaSlot classname) const {
|
||||||
lua_checkstack(L_, 20);
|
lua_checkstack(L_, 20);
|
||||||
LuaVar globtab, cname;
|
LuaVar classes, globtab, cname;
|
||||||
LuaStack LS(L_, globtab, cname);
|
LuaStack LS(L_, classes, globtab, cname);
|
||||||
|
|
||||||
// Validate the class name.
|
// Validate the class name.
|
||||||
assert(LS.validclassname(classname));
|
assert(LS.validclassname(classname));
|
||||||
|
|
||||||
|
// Fetch the classes table from the registry.
|
||||||
|
LS.rawget(classes, LuaRegistry, "classes");
|
||||||
|
assert(LS.istable(classes));
|
||||||
|
|
||||||
// Fetch the global environment from the registry.
|
// Fetch the global environment from the registry.
|
||||||
LS.getglobaltable(globtab);
|
LS.getglobaltable(globtab);
|
||||||
|
|
||||||
// Get the classtab from the global environment.
|
// Get the classtab from the classes table.
|
||||||
LS.rawget(classtab, globtab, classname);
|
LS.rawget(classtab, classes, classname);
|
||||||
|
|
||||||
// Make a new table if necessary.
|
// Make a new table if necessary.
|
||||||
if (!LS.istable(classtab)) {
|
if (!LS.istable(classtab)) {
|
||||||
LS.newtable(classtab);
|
LS.newtable(classtab);
|
||||||
LS.rawset(globtab, classname, classtab);
|
LS.rawset(classes, classname, classtab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Put the table into the global environment.
|
||||||
|
LS.rawset(globtab, classname, classtab);
|
||||||
|
|
||||||
// Repair the special fields.
|
// Repair the special fields.
|
||||||
LS.settabletype(classtab, LUA_TT_CLASS);
|
LS.settabletype(classtab, LUA_TT_CLASS);
|
||||||
LS.rawset(classtab, "__class", classname);
|
LS.rawset(classtab, "__class", classname);
|
||||||
@@ -376,6 +404,54 @@ void LuaStack::makeclass(LuaSlot tab, std::string_view name) const {
|
|||||||
lua_pop(L_, 1);
|
lua_pop(L_, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LuaStack::maketan(LuaSlot tab, int64_t id) const {
|
||||||
|
LuaVar tangibles, metatab;
|
||||||
|
LuaStack LS(L_, tangibles, metatab);
|
||||||
|
|
||||||
|
// Try to get the existing tangible.
|
||||||
|
LS.rawget(tangibles, LuaRegistry, "tangibles");
|
||||||
|
LS.rawget(tab, tangibles, id);
|
||||||
|
|
||||||
|
// If we succeeded, return it.
|
||||||
|
if (LS.istable(tab)) {
|
||||||
|
LS.result();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the tangible's database and metatable.
|
||||||
|
LS.set(tab, LuaNewTable);
|
||||||
|
LS.set(metatab, LuaNewTable);
|
||||||
|
LS.setmetatable(tab, metatab);
|
||||||
|
|
||||||
|
// Mark the tangible using the tabletype field.
|
||||||
|
LS.settabletype(tab, LUA_TT_TANGIBLE);
|
||||||
|
LS.settabletype(metatab, LUA_TT_TANGIBLEMETA);
|
||||||
|
|
||||||
|
// Store the tangible ID and lock the metatable.
|
||||||
|
LS.rawset(metatab, "id", id);
|
||||||
|
LS.rawset(metatab, "__metatable", false);
|
||||||
|
|
||||||
|
// Store the database into the tangibles table.
|
||||||
|
LS.rawset(tangibles, id, tab);
|
||||||
|
|
||||||
|
LS.result();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool LuaStack::tanblank(LuaSlot tab) const {
|
||||||
|
bool result = true;
|
||||||
|
if (istable(tab) && gettabletype(tab) == LUA_TT_TANGIBLE) {
|
||||||
|
if (lua_getmetatable(L_, tab.index())) {
|
||||||
|
lua_pushstring(L_, "threads");
|
||||||
|
lua_rawget(L_, -2);
|
||||||
|
if (lua_type(L_, -1) == LUA_TTABLE) {
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
lua_pop(L_, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
int64_t LuaStack::tanid(LuaSlot tab) const {
|
int64_t LuaStack::tanid(LuaSlot tab) const {
|
||||||
int64_t result = 0;
|
int64_t result = 0;
|
||||||
@@ -466,6 +542,24 @@ void LuaStack::setvisited(LuaSlot tab, bool visited) const {
|
|||||||
lua_modflagbits(L_, tab.index(), 0x0010, visited ? 0x0010 : 0);
|
lua_modflagbits(L_, tab.index(), 0x0010, visited ? 0x0010 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WorldType LuaStack::world_type() const {
|
||||||
|
lua_pushstring(L_, "worldtype");
|
||||||
|
lua_rawget(L_, LUA_REGISTRYINDEX);
|
||||||
|
lua_Integer n = lua_tointeger(L_, -1);
|
||||||
|
lua_pop(L_, 1);
|
||||||
|
assert(n != 0);
|
||||||
|
return (WorldType)n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LuaStack::guard_nopredict(const char *fn) {
|
||||||
|
if (lua_isyieldable(L_)) {
|
||||||
|
if (!is_authoritative()) {
|
||||||
|
lua_yield(L_, 0);
|
||||||
|
luaL_error(L_, "unexplained nopredict failure in %s", fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LuaKeywordParser::LuaKeywordParser(lua_State *L, int slot) {
|
LuaKeywordParser::LuaKeywordParser(lua_State *L, int slot) {
|
||||||
L_ = L;
|
L_ = L;
|
||||||
slot_ = slot;
|
slot_ = slot;
|
||||||
|
|||||||
@@ -222,9 +222,15 @@ int LuaTypeTagValue(lua_State *L) { return 0; }
|
|||||||
#define LUA_TT_GLOBALENV 18
|
#define LUA_TT_GLOBALENV 18
|
||||||
#define LUA_TT_TANGIBLE 19
|
#define LUA_TT_TANGIBLE 19
|
||||||
#define LUA_TT_TANGIBLEMETA 20
|
#define LUA_TT_TANGIBLEMETA 20
|
||||||
#define LUA_TT_DEADTANGIBLE 21
|
#define LUA_TT_GLOBALDB 21
|
||||||
#define LUA_TT_GLOBALDB 22
|
#define LUA_TT_CLASS 22
|
||||||
#define LUA_TT_CLASS 23
|
|
||||||
|
// World types enum.
|
||||||
|
|
||||||
|
enum WorldType {
|
||||||
|
WORLD_TYPE_MASTER = 1,
|
||||||
|
WORLD_TYPE_PREDICTIVE = 2,
|
||||||
|
};
|
||||||
|
|
||||||
// We use lightuserdata to store 'tokens': short
|
// We use lightuserdata to store 'tokens': short
|
||||||
// strings of 8 characters or less. These tokens
|
// strings of 8 characters or less. These tokens
|
||||||
@@ -451,8 +457,18 @@ public:
|
|||||||
void makeclass(LuaSlot tab, LuaSlot name) const;
|
void makeclass(LuaSlot tab, LuaSlot name) const;
|
||||||
void makeclass(LuaSlot tab, std::string_view name) const;
|
void makeclass(LuaSlot tab, std::string_view name) const;
|
||||||
|
|
||||||
// Get the ID of a tangible. It's a little weird to put this in
|
// Create a tangible, or look up an existing tangible.
|
||||||
// this module.
|
// If the tangible doesn't exist yet, this creates a tangible stub.
|
||||||
|
// It is possible to use World::tangible_make to transform a tangible
|
||||||
|
// stub into a full blown tangible, and World::tangible_delete to turn
|
||||||
|
// a full-blown tangible back into a stub. A stub doesn't have a
|
||||||
|
// class or a thread table.
|
||||||
|
void maketan(LuaSlot tab, int64_t id) const;
|
||||||
|
|
||||||
|
// Return true if a tangible is empty (deleted or not yet created).
|
||||||
|
bool tanblank(LuaSlot tab) const;
|
||||||
|
|
||||||
|
// Get the ID of a tangible.
|
||||||
int64_t tanid(LuaSlot tab) const;
|
int64_t tanid(LuaSlot tab) const;
|
||||||
|
|
||||||
// Return true if the value is a sortable key (string, number, or boolean).
|
// Return true if the value is a sortable key (string, number, or boolean).
|
||||||
@@ -467,8 +483,9 @@ public:
|
|||||||
return lua_rawequal(L_, v1, v2);
|
return lua_rawequal(L_, v1, v2);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool rawequal(LuaSlot v1, const char *name) const {
|
template<typename VT>
|
||||||
push_any_value(name);
|
bool rawequal(LuaSlot v1, VT value) const {
|
||||||
|
push_any_value(value);
|
||||||
bool result = lua_rawequal(L_, v1, -1);
|
bool result = lua_rawequal(L_, v1, -1);
|
||||||
lua_pop(L_, 1);
|
lua_pop(L_, 1);
|
||||||
return result;
|
return result;
|
||||||
@@ -517,6 +534,17 @@ public:
|
|||||||
bool getvisited(LuaSlot tab) const;
|
bool getvisited(LuaSlot tab) const;
|
||||||
void setvisited(LuaSlot tab, bool visited) const;
|
void setvisited(LuaSlot tab, bool visited) const;
|
||||||
|
|
||||||
|
// Return the world type (from the registry).
|
||||||
|
WorldType world_type() const;
|
||||||
|
|
||||||
|
// World types that are authoritative.
|
||||||
|
static bool is_authoritative(WorldType t) { return (t == WORLD_TYPE_MASTER); }
|
||||||
|
bool is_authoritative() { return is_authoritative(world_type()); }
|
||||||
|
|
||||||
|
// Stop execution of this thread if in a nonauth model,
|
||||||
|
// and if the thread is not a probe.
|
||||||
|
void guard_nopredict(const char *fn);
|
||||||
|
|
||||||
// Return true if the int64 value can be stored as a lua number.
|
// Return true if the int64 value can be stored as a lua number.
|
||||||
static bool int64_storable(int64_t v) { return (v <= MAXINT) && (v >= -MAXINT); }
|
static bool int64_storable(int64_t v) { return (v <= MAXINT) && (v >= -MAXINT); }
|
||||||
};
|
};
|
||||||
@@ -599,9 +627,11 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
#define LuaTokenConstant(name, tvalue, docs) \
|
#define LuaTokenConstant(name, tvalue, docs) \
|
||||||
|
LuaToken ltoken_##name(tvalue); \
|
||||||
LuaConstantReg reg_##name(#name, docs, LuaToken(tvalue), 0);
|
LuaConstantReg reg_##name(#name, docs, LuaToken(tvalue), 0);
|
||||||
|
|
||||||
#define LuaNumberConstant(name, nvalue, docs) \
|
#define LuaNumberConstant(name, nvalue, docs) \
|
||||||
|
lua_Number lnumber_##name(nvalue); \
|
||||||
LuaConstantReg reg_##name(#name, docs, LuaToken(), nvalue);
|
LuaConstantReg reg_##name(#name, docs, LuaToken(), nvalue);
|
||||||
|
|
||||||
#define LuaDefine(name, args, docs) \
|
#define LuaDefine(name, args, docs) \
|
||||||
|
|||||||
@@ -7,255 +7,354 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
class PrintMachine {
|
||||||
|
public:
|
||||||
|
LuaVar tabchpos_;
|
||||||
|
LuaStack LS_;
|
||||||
|
int next_id_;
|
||||||
|
bool indent_;
|
||||||
|
std::ostream *output_;
|
||||||
|
eng::map<int, int> chpos_to_tabnum_;
|
||||||
|
|
||||||
void atomic_print(LuaStack &LS, LuaSlot val, bool quote, std::ostream *os) {
|
void atomic_print(int xtype, LuaSlot val, bool quote) {
|
||||||
int tt = LS.type(val);
|
switch (xtype) {
|
||||||
switch (tt) {
|
case LUA_TNIL: {
|
||||||
case LUA_TNIL:
|
(*output_) << "nil";
|
||||||
(*os) << "nil";
|
return;
|
||||||
return;
|
}
|
||||||
case LUA_TSTRING:
|
case LUA_TSTRING: {
|
||||||
if (quote) {
|
if (quote) {
|
||||||
util::quote_string(LS.ckstring(val), os);
|
util::quote_string(LS_.ckstring(val), output_);
|
||||||
} else {
|
|
||||||
// TODO: this could be more efficient.
|
|
||||||
(*os) << LS.ckstring(val);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case LUA_TNUMBER: {
|
|
||||||
double value = LS.cknumber(val);
|
|
||||||
if (std::isnan(value)) {
|
|
||||||
(*os) << "nan";
|
|
||||||
} else {
|
|
||||||
int64_t ivalue = int64_t(value);
|
|
||||||
if (double(ivalue) == value) {
|
|
||||||
(*os) << ivalue;
|
|
||||||
} else {
|
} else {
|
||||||
(*os) << value;
|
// TODO: this could be more efficient.
|
||||||
}
|
(*output_) << LS_.ckstring(val);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return;
|
case LUA_TNUMBER: {
|
||||||
}
|
double value = LS_.cknumber(val);
|
||||||
case LUA_TBOOLEAN:
|
if (std::isnan(value)) {
|
||||||
(*os) << (LS.ckboolean(val) ? "true" : "false");
|
(*output_) << "nan";
|
||||||
return;
|
} else {
|
||||||
case LUA_TFUNCTION: {
|
int64_t ivalue = int64_t(value);
|
||||||
(*os) << "<function>";
|
if (double(ivalue) == value) {
|
||||||
return;
|
(*output_) << ivalue;
|
||||||
}
|
} else {
|
||||||
case LUA_TLIGHTUSERDATA: {
|
(*output_) << value;
|
||||||
LuaToken token = LS.cktoken(val);
|
|
||||||
(*os) << "[" << token.str() << "]";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
(*os) << "<" << lua_typename(LS.state(), tt) << ">";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Find tables recursively.
|
|
||||||
//
|
|
||||||
// Builds a table (tabcount) whose keys are tables. If a table
|
|
||||||
// is visited exactly once, then tabcount[t]=0. If a table is
|
|
||||||
// visited more than once, then tabcount[t]=-1.
|
|
||||||
//
|
|
||||||
static void findtables(LuaStack &LS0, LuaSlot root, LuaSlot tabcount) {
|
|
||||||
lua_State *L = LS0.state();
|
|
||||||
LuaVar key, val, tab, count;
|
|
||||||
LuaStack LS(L, key, val, tab, count);
|
|
||||||
|
|
||||||
LS.newtable(tabcount);
|
|
||||||
int top = lua_gettop(L);
|
|
||||||
if (LS.istable(root)) {
|
|
||||||
lua_pushvalue(L, root.index());
|
|
||||||
}
|
|
||||||
while (lua_gettop(L) > top) {
|
|
||||||
lua_checkstack(L, 20);
|
|
||||||
lua_replace(L, tab.index());
|
|
||||||
LS.rawget(count, tabcount, tab);
|
|
||||||
if (LS.isnil(count)) {
|
|
||||||
LS.rawset(tabcount, tab, 0);
|
|
||||||
LS.set(key, LuaNil);
|
|
||||||
while (LS.next(tab, key, val)) {
|
|
||||||
lua_checkstack(L, 20);
|
|
||||||
if (LS.istable(key)) {
|
|
||||||
lua_pushvalue(L, key.index());
|
|
||||||
}
|
|
||||||
if (LS.istable(val)) {
|
|
||||||
lua_pushvalue(L, val.index());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LS.getmetatable(val, tab);
|
return;
|
||||||
if (LS.istable(val)) {
|
}
|
||||||
lua_pushvalue(L, val.index());
|
case LUA_TBOOLEAN: {
|
||||||
|
(*output_) << (LS_.ckboolean(val) ? "true" : "false");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case LUA_TFUNCTION: {
|
||||||
|
(*output_) << "<function>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case LUA_TTHREAD: {
|
||||||
|
(*output_) << "<thread>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case LUA_TLIGHTUSERDATA: {
|
||||||
|
LuaToken token = LS_.cktoken(val);
|
||||||
|
(*output_) << "[" << token.str() << "]";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case LUA_TT_GENERAL: {
|
||||||
|
(*output_) << "<table>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case LUA_TT_TANGIBLE: {
|
||||||
|
(*output_) << "<tangible " << LS_.tanid(val) << ">";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case LUA_TT_CLASS: {
|
||||||
|
(*output_) << "<class " << LS_.classname(val) << ">";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case LUA_TT_GLOBALENV: {
|
||||||
|
(*output_) << "<global-env>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case LUA_TT_TANGIBLEMETA: {
|
||||||
|
(*output_) << "<tangible-metatable>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
(*output_) << "<unknown type #" << xtype << ">";
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tabify(int level) {
|
||||||
|
if (indent_) {
|
||||||
|
(*output_) << std::endl;
|
||||||
|
for (int i = 0; i < level; i++) {
|
||||||
|
(*output_) << " ";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LS.rawset(tabcount, tab, -1);
|
(*output_) << " ";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pprint_r(int level, bool expand, LuaSlot value) {
|
||||||
|
lua_State *L = LS_.state();
|
||||||
|
lua_checkstack(L, 20);
|
||||||
|
LuaVar loffset, pairs, key, val, lchpos, nextseq;
|
||||||
|
LuaStack LS(L, loffset, pairs, key, val, lchpos, nextseq);
|
||||||
|
|
||||||
|
// Determine the extended type of the object. If the
|
||||||
|
// expand flag is true, try to coerce it to a general table.
|
||||||
|
int xtype = LS.xtype(value);
|
||||||
|
|
||||||
|
// Print the atomic portion.
|
||||||
|
if (xtype != LUA_TT_GENERAL) {
|
||||||
|
atomic_print(xtype, value, true);
|
||||||
|
if ((xtype < LUA_TT_GENERAL) || (!expand)) {
|
||||||
|
LS.result();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find out whether it has a table number. If necessary,
|
||||||
|
// assign one.
|
||||||
|
int tabnum = 0;
|
||||||
|
LS.rawget(lchpos, tabchpos_, value);
|
||||||
|
if (!LS.isnumber(lchpos)) {
|
||||||
|
// First time. Record the character position where the
|
||||||
|
// table first appears in the output stream.
|
||||||
|
LS.rawset(tabchpos_, value, int((*output_).tellp()));
|
||||||
|
} else {
|
||||||
|
int chpos = LS.ckint(lchpos);
|
||||||
|
tabnum = chpos_to_tabnum_[chpos];
|
||||||
|
if (tabnum == 0) {
|
||||||
|
// Second time. The table is already in the output,
|
||||||
|
// but it hasn't been assigned a number. Assign one.
|
||||||
|
tabnum = next_id_++;
|
||||||
|
chpos_to_tabnum_[chpos] = tabnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the table has a number, that means we're visiting
|
||||||
|
// it for the second time. Just print an abbreviated version
|
||||||
|
// and return.
|
||||||
|
if (tabnum > 0) {
|
||||||
|
(*output_) << "<table " << tabnum << ">";
|
||||||
|
if (lua_nkeys(L, value.index())==0) {
|
||||||
|
(*output_) << "{}";
|
||||||
|
} else {
|
||||||
|
(*output_) << "{...}";
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// State variables.
|
||||||
|
bool needcomma = false;
|
||||||
|
bool multiline = false;
|
||||||
|
LS.set(nextseq, 1);
|
||||||
|
|
||||||
|
// Open the brackets.
|
||||||
|
(*output_) << "{";
|
||||||
|
|
||||||
|
// Output the table keys.
|
||||||
|
table_getpairs(LS, value, pairs, true);
|
||||||
|
for (int i = 2; ; i+=2) {
|
||||||
|
LS.rawget(key, pairs, i);
|
||||||
|
if (LS.isnil(key)) break;
|
||||||
|
LS.rawget(val, pairs, i+1);
|
||||||
|
if (needcomma) (*output_) << ",";
|
||||||
|
needcomma = true;
|
||||||
|
if (LS.rawequal(key, nextseq)) {
|
||||||
|
(*output_) << " ";
|
||||||
|
pprint_r(level + 1, false, val);
|
||||||
|
LS.set(nextseq, LS.ckinteger(nextseq) + 1);
|
||||||
|
} else {
|
||||||
|
multiline = true;
|
||||||
|
tabify(level + 1);
|
||||||
|
if (LS.isstring(key) && sv::is_lua_id(LS.ckstring(key))) {
|
||||||
|
(*output_) << LS.ckstring(key);
|
||||||
|
} else {
|
||||||
|
(*output_) << "[";
|
||||||
|
pprint_r(level + 1, false, key);
|
||||||
|
(*output_) << "]";
|
||||||
|
}
|
||||||
|
if (indent_) {
|
||||||
|
(*output_) << " = ";
|
||||||
|
} else {
|
||||||
|
(*output_) << "=";
|
||||||
|
}
|
||||||
|
pprint_r(level + 1, false, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output the metatable.
|
||||||
|
LS.getmetatable(val, value);
|
||||||
|
if (LS.istable(val) && (LS.gettabletype(val) != LUA_TT_TANGIBLEMETA)) {
|
||||||
|
multiline = true;
|
||||||
|
if (needcomma) (*output_) << ",";
|
||||||
|
needcomma = true;
|
||||||
|
tabify(level + 1);
|
||||||
|
(*output_) << "<meta> = ";
|
||||||
|
pprint_r(level + 1, false, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the brackets.
|
||||||
|
if (multiline) {
|
||||||
|
tabify(level);
|
||||||
|
} else if (LS.ckinteger(nextseq) > 1) {
|
||||||
|
(*output_) << " ";
|
||||||
|
}
|
||||||
|
(*output_) << "}";
|
||||||
|
|
||||||
|
LS.result();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atomic print interface.
|
||||||
|
PrintMachine(LuaStack &LS0, LuaSlot root, bool quote, std::ostream *os) :
|
||||||
|
LS_(LS0.state(), tabchpos_) {
|
||||||
|
output_ = os;
|
||||||
|
atomic_print(LS_.xtype(root), root, quote);
|
||||||
|
LS_.result();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pretty print interface.
|
||||||
|
PrintMachine(LuaStack &LS0, LuaSlot root, bool indent, int level, bool expand, std::ostream *os) :
|
||||||
|
LS_(LS0.state(), tabchpos_) {
|
||||||
|
next_id_ = 1;
|
||||||
|
indent_ = indent;
|
||||||
|
LS_.newtable(tabchpos_);
|
||||||
|
util::ostringstream preoutput;
|
||||||
|
output_ = &preoutput;
|
||||||
|
pprint_r(level, expand, root);
|
||||||
|
std::string_view pre = preoutput.view();
|
||||||
|
|
||||||
|
// Output the results. We would just copy the characters
|
||||||
|
// one by one to the target stream, except that we have to
|
||||||
|
// insert <table XX> in front of all tables that got referenced.
|
||||||
|
chpos_to_tabnum_.emplace(0x7FFFFFFF, 0);
|
||||||
|
auto iter = chpos_to_tabnum_.begin();
|
||||||
|
for (int i = 0; i < int(pre.size()); i++) {
|
||||||
|
if (i == iter->first) {
|
||||||
|
(*os) << "<table " << iter->second << ">";
|
||||||
|
iter++;
|
||||||
|
}
|
||||||
|
(*os).put(pre[i]);
|
||||||
|
}
|
||||||
|
LS_.result();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void PrettyPrintOptions::parse(LuaKeywordParser &kp) {
|
||||||
|
LuaVar option;
|
||||||
|
LuaStack LS(kp.state(), option);
|
||||||
|
if (kp.parse(option, "indent")) {
|
||||||
|
indent = LS.ckboolean(option);
|
||||||
|
}
|
||||||
|
if (kp.parse(option, "level")) {
|
||||||
|
level = LS.ckint(option);
|
||||||
|
}
|
||||||
|
if (kp.parse(option, "expand")) {
|
||||||
|
expand = LS.ckboolean(option);
|
||||||
|
}
|
||||||
LS.result();
|
LS.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaDefine(table_findtables, "root", "recursively find tables (debugging only)") {
|
void atomic_print(LuaStack &LS, LuaSlot val, bool quote, std::ostream *os) {
|
||||||
LuaArg root;
|
PrintMachine pm(LS, val, quote, os);
|
||||||
LuaRet tabcount;
|
}
|
||||||
LuaStack LS(L, root, tabcount);
|
|
||||||
findtables(LS, root, tabcount);
|
void pprint(LuaStack &LS, LuaSlot val, const PrettyPrintOptions &opts, std::ostream *os) {
|
||||||
|
PrintMachine pm(LS, val, opts.indent, opts.level, opts.expand, os);
|
||||||
|
}
|
||||||
|
|
||||||
|
LuaDefine(string_pprint, "obj1, obj2, ...",
|
||||||
|
"|Pretty-print the specified objects into a string."
|
||||||
|
"|"
|
||||||
|
"|See also: string.pprintx, which has a lot more options."
|
||||||
|
"|This function uses the default options: pretty print indented,"
|
||||||
|
"|start at indentation level zero, and always expand the"
|
||||||
|
"|top-level table."
|
||||||
|
"|") {
|
||||||
|
int n = lua_gettop(L);
|
||||||
|
LuaRet result;
|
||||||
|
LuaStack LS(L, result);
|
||||||
|
util::ostringstream oss;
|
||||||
|
for (int i = 1; i <= n; i++) {
|
||||||
|
LuaSpecial root(i);
|
||||||
|
pprint(LS, root, PrettyPrintOptions(), &oss);
|
||||||
|
if (i < n) oss << "\n";
|
||||||
|
}
|
||||||
|
oss << std::endl;
|
||||||
|
LS.set(result, oss.view());
|
||||||
return LS.result();
|
return LS.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Inspector {
|
LuaDefine(string_pprintx, "options",
|
||||||
lua_State *L;
|
"|Pretty-print the specified object into a string, with options"
|
||||||
LuaVar ids;
|
"|"
|
||||||
int nextid;
|
"|Options is a table with these fields:"
|
||||||
bool indent;
|
"|"
|
||||||
int maxlen;
|
"| value - the object to pretty-print"
|
||||||
bool anyindent;
|
"| indent - if false, suppress newlines and indentation (default: true)"
|
||||||
std::ostream *stream;
|
"| level - base level of indentation (default: zero)"
|
||||||
};
|
"| expand - if true, force expansion of top-level table (default: false)"
|
||||||
|
"|"
|
||||||
static void tabify(Inspector &insp, int level) {
|
"|About the expand flag: normally, when you print a class, it just "
|
||||||
if (insp.indent) {
|
"|prints '<class name>', and when you print a tangible, it just"
|
||||||
(*insp.stream) << std::endl;
|
"|prints '<tangible id>'. But sometimes, you want to see the details."
|
||||||
for (int i = 0; i < level; i++) {
|
"|The expand flag forces it to expand the top-level table, even if the"
|
||||||
(*insp.stream) << " ";
|
"|top-level table is a tangible or class."
|
||||||
}
|
"|") {
|
||||||
insp.anyindent = true;
|
LuaArg loptions;
|
||||||
} else {
|
LuaRet result;
|
||||||
(*insp.stream) << " ";
|
LuaVar value;
|
||||||
|
LuaStack LS(L, loptions, result, value);
|
||||||
|
PrettyPrintOptions options;
|
||||||
|
LuaKeywordParser kp(LS, loptions);
|
||||||
|
options.parse(kp);
|
||||||
|
if (!kp.parse(value, "value")) {
|
||||||
|
LS.set(value, LuaNil);
|
||||||
}
|
}
|
||||||
|
kp.final_check_throw();
|
||||||
|
util::ostringstream oss;
|
||||||
|
pprint(LS, value, options, &oss);
|
||||||
|
LS.set(result, oss.view());
|
||||||
|
return LS.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pprint_r(Inspector &insp, int level, LuaSlot root) {
|
LuaDefine(string_print, "obj",
|
||||||
lua_checkstack(insp.L, 20);
|
"|Concise print the specified object into a string"
|
||||||
LuaVar idv, pairs, key, val, nextseq;
|
"|"
|
||||||
LuaStack LS(insp.L, idv, pairs, key, val, nextseq);
|
"|This prints a concise representation of obj into a string. Tables"
|
||||||
|
"|are not expanded: for that, use string.pprintx. The functions"
|
||||||
// If it's anything but a table, use 'atomic_print'.
|
"|tostring and string.print are identical."
|
||||||
if (!LS.istable(root)) {
|
"|") {
|
||||||
atomic_print(LS, root, true, insp.stream);
|
LuaArg val;
|
||||||
LS.result();
|
LuaRet result;
|
||||||
return;
|
LuaStack LS(L, val, result);
|
||||||
}
|
eng::ostringstream oss;
|
||||||
|
atomic_print(LS, val, false, &oss);
|
||||||
// Determine the table's ID, allocating an ID if necessary.
|
LS.set(result, oss.str());
|
||||||
LS.rawget(idv, insp.ids, root);
|
return LS.result();
|
||||||
int id = LS.ckint(idv);
|
|
||||||
bool new_id = false;
|
|
||||||
if (id < 0) {
|
|
||||||
new_id = true;
|
|
||||||
id = insp.nextid++;
|
|
||||||
LS.rawset(insp.ids, root, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print the table's name, if any.
|
|
||||||
bool is_class = false;
|
|
||||||
bool is_tangible = false;
|
|
||||||
eng::string cname = LS.classname(root);
|
|
||||||
if (cname != "") {
|
|
||||||
is_class = true;
|
|
||||||
(*insp.stream) << "<class " << cname << ">";
|
|
||||||
} else {
|
|
||||||
int64_t tid = LS.tanid(root);
|
|
||||||
if (tid > 0) {
|
|
||||||
is_tangible = true;
|
|
||||||
(*insp.stream) << "<tangible " << tid << ">";
|
|
||||||
} else if (id > 0) {
|
|
||||||
(*insp.stream) << "<table " << id << ">";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is a class, and we're not at the top level, truncate.
|
|
||||||
if ((is_class || is_tangible) && (level > 0)) {
|
|
||||||
LS.result();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is a table we've already printed, truncate it.
|
|
||||||
if ((id > 0) && (!new_id)) {
|
|
||||||
if (lua_nkeys(insp.L, root.index())==0) {
|
|
||||||
(*insp.stream) << "{}";
|
|
||||||
} else {
|
|
||||||
(*insp.stream) << "{...}";
|
|
||||||
}
|
|
||||||
LS.result();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// State variables.
|
|
||||||
bool needcomma = false;
|
|
||||||
bool multiline = false;
|
|
||||||
LS.set(nextseq, 1);
|
|
||||||
|
|
||||||
// Open the brackets.
|
|
||||||
(*insp.stream) << "{";
|
|
||||||
|
|
||||||
// Output the table keys.
|
|
||||||
table_getpairs(LS, root, pairs, true);
|
|
||||||
for (int i = 2; ; i+=2) {
|
|
||||||
LS.rawget(key, pairs, i);
|
|
||||||
if (LS.isnil(key)) break;
|
|
||||||
LS.rawget(val, pairs, i+1);
|
|
||||||
if (needcomma) (*insp.stream) << ",";
|
|
||||||
needcomma = true;
|
|
||||||
if (LS.rawequal(key, nextseq)) {
|
|
||||||
(*insp.stream) << " ";
|
|
||||||
pprint_r(insp, level + 1, val);
|
|
||||||
LS.set(nextseq, LS.ckinteger(nextseq) + 1);
|
|
||||||
} else {
|
|
||||||
multiline = true;
|
|
||||||
tabify(insp, level + 1);
|
|
||||||
if (LS.isstring(key) && sv::is_lua_id(LS.ckstring(key))) {
|
|
||||||
(*insp.stream) << LS.ckstring(key);
|
|
||||||
} else {
|
|
||||||
(*insp.stream) << "[";
|
|
||||||
pprint_r(insp, level + 1, key);
|
|
||||||
(*insp.stream) << "]";
|
|
||||||
}
|
|
||||||
if (insp.indent) {
|
|
||||||
(*insp.stream) << " = ";
|
|
||||||
} else {
|
|
||||||
(*insp.stream) << "=";
|
|
||||||
}
|
|
||||||
pprint_r(insp, level + 1, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output the metatable.
|
|
||||||
LS.getmetatable(val, root);
|
|
||||||
if (LS.istable(val)) {
|
|
||||||
multiline = true;
|
|
||||||
if (needcomma) (*insp.stream) << ",";
|
|
||||||
needcomma = true;
|
|
||||||
tabify(insp, level + 1);
|
|
||||||
(*insp.stream) << "<meta> = ";
|
|
||||||
pprint_r(insp, level + 1, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the brackets.
|
|
||||||
if (multiline) {
|
|
||||||
tabify(insp, level);
|
|
||||||
} else if (LS.ckinteger(nextseq) > 1) {
|
|
||||||
(*insp.stream) << " ";
|
|
||||||
}
|
|
||||||
(*insp.stream) << "}";
|
|
||||||
|
|
||||||
LS.result();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void pprint(LuaStack &LS0, LuaSlot root, bool indent, std::ostream *os) {
|
|
||||||
Inspector insp;
|
LuaDefine(tostring, "obj",
|
||||||
LuaStack LS(LS0.state(), insp.ids);
|
"|Concise print the specified object into a string"
|
||||||
findtables(LS, root, insp.ids);
|
"|"
|
||||||
insp.L = LS0.state();
|
"|This prints a concise representation of obj into a string. Tables"
|
||||||
insp.nextid = 1;
|
"|are not expanded: for that, use string.pprint. The functions"
|
||||||
insp.indent = indent;
|
"|tostring and string.print are identical."
|
||||||
insp.anyindent = false;
|
"|") {
|
||||||
insp.stream = os;
|
LuaArg val;
|
||||||
pprint_r(insp, 0, root);
|
LuaRet result;
|
||||||
LS.result();
|
LuaStack LS(L, val, result);
|
||||||
|
eng::ostringstream oss;
|
||||||
|
atomic_print(LS, val, false, &oss);
|
||||||
|
LS.set(result, oss.str());
|
||||||
|
return LS.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaDefine(string_isidentifier, "str", "return true if the string is a valid lua identifier") {
|
LuaDefine(string_isidentifier, "str", "return true if the string is a valid lua identifier") {
|
||||||
@@ -271,33 +370,5 @@ LuaDefine(string_isidentifier, "str", "return true if the string is a valid lua
|
|||||||
return LS.result();
|
return LS.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaDefine(string_print, "obj", "print the specified object into a string") {
|
|
||||||
LuaArg val;
|
|
||||||
LuaRet result;
|
|
||||||
LuaStack LS(L, val, result);
|
|
||||||
eng::ostringstream oss;
|
|
||||||
atomic_print(LS, val, false, &oss);
|
|
||||||
LS.set(result, oss.str());
|
|
||||||
return LS.result();
|
|
||||||
}
|
|
||||||
|
|
||||||
LuaDefine(string_pprint, "obj,indent", "pretty-print the specified object into a string") {
|
|
||||||
LuaArg root, indent;
|
|
||||||
LuaRet result;
|
|
||||||
LuaStack LS(L, root, indent, result);
|
|
||||||
bool ind = LS.ckboolean(indent);
|
|
||||||
eng::ostringstream oss;
|
|
||||||
pprint(LS, root, ind, &oss);
|
|
||||||
LS.set(result, oss.str());
|
|
||||||
return LS.result();
|
|
||||||
}
|
|
||||||
|
|
||||||
LuaDefine(tostring, "obj", "print the specified object into a string") {
|
|
||||||
LuaArg val;
|
|
||||||
LuaRet result;
|
|
||||||
LuaStack LS(L, val, result);
|
|
||||||
eng::ostringstream oss;
|
|
||||||
atomic_print(LS, val, false, &oss);
|
|
||||||
LS.set(result, oss.str());
|
|
||||||
return LS.result();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,6 +22,14 @@
|
|||||||
#include "luastack.hpp"
|
#include "luastack.hpp"
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
|
||||||
|
struct PrettyPrintOptions {
|
||||||
|
bool indent;
|
||||||
|
int level;
|
||||||
|
bool expand;
|
||||||
|
PrettyPrintOptions() : indent(true), level(0), expand(true) {}
|
||||||
|
void parse(LuaKeywordParser &kp);
|
||||||
|
};
|
||||||
|
|
||||||
// Atomic print to a stream.
|
// Atomic print to a stream.
|
||||||
//
|
//
|
||||||
// This prints an atomic value to a stream. If you give it a table,
|
// This prints an atomic value to a stream. If you give it a table,
|
||||||
@@ -32,6 +40,6 @@ void atomic_print(LuaStack &LS, LuaSlot val, bool quote, std::ostream *os);
|
|||||||
|
|
||||||
// Pretty print to a stream.
|
// Pretty print to a stream.
|
||||||
//
|
//
|
||||||
void pprint(LuaStack &LS, LuaSlot val, bool indent, std::ostream *os);
|
void pprint(LuaStack &LS, LuaSlot val, const PrettyPrintOptions &opts, std::ostream *os);
|
||||||
|
|
||||||
#endif // PPRINT_HPP
|
#endif // PPRINT_HPP
|
||||||
@@ -245,26 +245,24 @@ void SourceDB::update(const util::LuaSourceVec &source) {
|
|||||||
LS.result();
|
LS.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete everything from the global environment except
|
// Delete everything from the global environment.
|
||||||
// the class tables.
|
// Clear all the classes in the registry classes table.
|
||||||
//
|
//
|
||||||
static void source_clear_globals(lua_State *L) {
|
static void source_clear_globals(lua_State *L) {
|
||||||
LuaVar classname, classtab, key, globtab;
|
LuaVar classname, classtab, key, globtab, classes;
|
||||||
LuaStack LS(L, classname, classtab, key, globtab);
|
LuaStack LS(L, classname, classtab, key, globtab, classes);
|
||||||
|
|
||||||
LS.getglobaltable(globtab);
|
LS.getglobaltable(globtab);
|
||||||
LS.rawset(globtab, "_G", LuaNil);
|
LS.cleartable(globtab, true);
|
||||||
LS.set(classname, LuaNil);
|
|
||||||
while (LS.next(globtab, classname, classtab) != 0) {
|
|
||||||
if (LS.rawequal(globtab, classtab)) {
|
|
||||||
LS.rawset(globtab, classname, LuaNil);
|
|
||||||
} else if (LS.istable(classtab)) {
|
|
||||||
LS.cleartable(classtab, true);
|
|
||||||
} else {
|
|
||||||
LS.rawset(globtab, classname, LuaNil);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LS.rawset(globtab, "_G", globtab);
|
LS.rawset(globtab, "_G", globtab);
|
||||||
|
|
||||||
|
LS.rawget(classes, LuaRegistry, "classes");
|
||||||
|
assert(LS.istable(classes));
|
||||||
|
LS.set(classname, LuaNil);
|
||||||
|
while (LS.next(classes, classname, classtab) != 0) {
|
||||||
|
assert(LS.istable(classtab));
|
||||||
|
LS.cleartable(classtab, true);
|
||||||
|
}
|
||||||
LS.result();
|
LS.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -589,7 +589,17 @@ LuaDefine(table_nextsortedpair, "sortedpairs,dummy", "next function used by sort
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaDefine(table_sortedpairs, "table", "iterate over table, sorting all keys") {
|
LuaDefine(table_sortedpairs, "table",
|
||||||
|
"|Iterate over table, sorting all keys"
|
||||||
|
"|"
|
||||||
|
"|Some keys can't be sorted. For example, you can use a closure"
|
||||||
|
"|as a table key. If you try to iterate over a table containing"
|
||||||
|
"|a non-sortable key, the error 'Cannot sort the table keys' will"
|
||||||
|
"|be generated."
|
||||||
|
"|"
|
||||||
|
"|See doc(genlt) for information about the sort order."
|
||||||
|
"|"
|
||||||
|
"|") {
|
||||||
LuaArg tab;
|
LuaArg tab;
|
||||||
LuaRet closure, rtab, key;
|
LuaRet closure, rtab, key;
|
||||||
LuaStack LS(L, tab, closure, rtab, key);
|
LuaStack LS(L, tab, closure, rtab, key);
|
||||||
@@ -602,7 +612,17 @@ LuaDefine(table_sortedpairs, "table", "iterate over table, sorting all keys") {
|
|||||||
return LS.result();
|
return LS.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaDefine(table_semisortedpairs, "table", "iterate over table, sorting those keys that can be sorted") {
|
LuaDefine(table_semisortedpairs, "table",
|
||||||
|
"|Iterate over table, sorting all the keys that can be sorted."
|
||||||
|
"|"
|
||||||
|
"|Some keys can't be sorted. For example, you can use a closure"
|
||||||
|
"|as a table key. If you try to iterate over a table containing"
|
||||||
|
"|a non-sortable key, the non-sortable elements will appear at"
|
||||||
|
"|the end of the iteration, after all the sortable elements. The"
|
||||||
|
"|non-sortable elements will be in an arbitrary order."
|
||||||
|
"|"
|
||||||
|
"|See doc(genlt) for information about the sort order."
|
||||||
|
"|") {
|
||||||
LuaArg tab;
|
LuaArg tab;
|
||||||
LuaRet closure, rtab, key;
|
LuaRet closure, rtab, key;
|
||||||
LuaStack LS(L, tab, closure, rtab, key);
|
LuaStack LS(L, tab, closure, rtab, key);
|
||||||
@@ -612,7 +632,37 @@ LuaDefine(table_semisortedpairs, "table", "iterate over table, sorting those key
|
|||||||
return LS.result();
|
return LS.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaDefine(genlt, "obj1,obj2", "return true if obj1 is less than obj2 in general ordering") {
|
#define LUA_TNIL 0
|
||||||
|
#define LUA_TBOOLEAN 1
|
||||||
|
#define LUA_TLIGHTUSERDATA 2
|
||||||
|
#define LUA_TNUMBER 3
|
||||||
|
#define LUA_TSTRING 4
|
||||||
|
#define LUA_TTABLE 5
|
||||||
|
#define LUA_TFUNCTION 6
|
||||||
|
#define LUA_TUSERDATA 7
|
||||||
|
#define LUA_TTHREAD 8
|
||||||
|
|
||||||
|
#define LUA_NUMTAGS 9
|
||||||
|
|
||||||
|
LuaDefine(genlt, "obj1,obj2",
|
||||||
|
"|Generalized less-than function"
|
||||||
|
"|"
|
||||||
|
"|This comparison function can compare any two objects. The"
|
||||||
|
"|return value is as follows:"
|
||||||
|
"|"
|
||||||
|
"|* Numbers are compared in the obvious numeric manner."
|
||||||
|
"|* Strings are compared alphabetically."
|
||||||
|
"|* Booleans are compared with false being less than true."
|
||||||
|
"|* Tables are all considered equal to other tables."
|
||||||
|
"|* Functions are all considered equal to other functions."
|
||||||
|
"|* Coroutines are all considered equal to other coroutines."
|
||||||
|
"|"
|
||||||
|
"|* Numbers are less than strings."
|
||||||
|
"|* Strings are less than booleans."
|
||||||
|
"|* Booleans are less than functions."
|
||||||
|
"|* Functions are less than coroutines."
|
||||||
|
"|* Coroutines are less than tables."
|
||||||
|
"|") {
|
||||||
LuaArg o1,o2;
|
LuaArg o1,o2;
|
||||||
LuaRet lt;
|
LuaRet lt;
|
||||||
LuaStack LS(L, o1, o2, lt);
|
LuaStack LS(L, o1, o2, lt);
|
||||||
|
|||||||
@@ -25,4 +25,5 @@ bool table_equal(LuaStack &LS0, LuaSlot tab1, LuaSlot tab2);
|
|||||||
//
|
//
|
||||||
bool table_getpairs(LuaStack &LS0, LuaSlot tab, LuaSlot pairs, bool sort);
|
bool table_getpairs(LuaStack &LS0, LuaSlot tab, LuaSlot pairs, bool sort);
|
||||||
|
|
||||||
|
|
||||||
#endif // TABLE_HPP
|
#endif // TABLE_HPP
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ private:
|
|||||||
|
|
||||||
void event_init(int argc, char *argv[])
|
void event_init(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
world_.reset(new World(util::WORLD_TYPE_STANDALONE));
|
world_.reset(new World(WORLD_TYPE_MASTER));
|
||||||
world_->update_source(get_lua_source());
|
world_->update_source(get_lua_source());
|
||||||
world_->run_unittests();
|
world_->run_unittests();
|
||||||
actor_id_ = world_->create_login_actor();
|
actor_id_ = world_->create_login_actor();
|
||||||
|
|||||||
@@ -720,6 +720,63 @@ eng::string XYZ::debug_string() const {
|
|||||||
return oss.str();
|
return oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void (*dprintf_hook)(const char *oneline);
|
||||||
|
|
||||||
|
void hook_dprintf(void (*func)(const char *oneline)) {
|
||||||
|
dprintf_hook = func;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void chop_up_dprintf(char *buffer, int size) {
|
||||||
|
// Drop the final newline, if any. We're going
|
||||||
|
// to automatically end with a newline and we don't
|
||||||
|
// want to double up.
|
||||||
|
if ((size > 0) && (buffer[size-1] == '\n')) {
|
||||||
|
size -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chop it up into lines and call the hook one line
|
||||||
|
// at a time. Replace control characters as we go.
|
||||||
|
const char *base = buffer;
|
||||||
|
for (int i = 0; i <= size; i++) {
|
||||||
|
if (buffer[i] < ' ') {
|
||||||
|
if ((buffer[i] == '\n') || (buffer[i] == 0)) {
|
||||||
|
buffer[i] = 0;
|
||||||
|
if (dprintf_hook == nullptr) {
|
||||||
|
fprintf(stderr, "%s\n", base);
|
||||||
|
} else {
|
||||||
|
dprintf_hook(base);
|
||||||
|
}
|
||||||
|
base = buffer + i + 1;
|
||||||
|
} else {
|
||||||
|
buffer[i] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're sending to stderr, flush. If not, then
|
||||||
|
// the hook routine is responsible for flushing its own
|
||||||
|
// output.
|
||||||
|
if (dprintf_hook == nullptr) {
|
||||||
|
fflush(stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dprintf(const char *format, ...) {
|
||||||
|
char buffer[256];
|
||||||
|
va_list args;
|
||||||
|
va_start (args, format);
|
||||||
|
int n = vsnprintf(buffer, 256, format, args);
|
||||||
|
if (n <= 255) {
|
||||||
|
chop_up_dprintf(buffer, n);
|
||||||
|
} else {
|
||||||
|
char *lbuffer = (char *)malloc(n + 1);
|
||||||
|
vsnprintf(lbuffer, n+1, format, args);
|
||||||
|
chop_up_dprintf(lbuffer, n);
|
||||||
|
free(lbuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace util
|
} // namespace util
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -198,13 +198,6 @@ bool valid_number(string_view v, bool plus, bool minus, bool dec, bool exp);
|
|||||||
|
|
||||||
namespace util {
|
namespace util {
|
||||||
|
|
||||||
enum WorldType {
|
|
||||||
WORLD_TYPE_STANDALONE,
|
|
||||||
WORLD_TYPE_C_SYNC,
|
|
||||||
WORLD_TYPE_S_SYNC,
|
|
||||||
WORLD_TYPE_MASTER,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum MessageType {
|
enum MessageType {
|
||||||
MSG_NULL,
|
MSG_NULL,
|
||||||
MSG_DIFF,
|
MSG_DIFF,
|
||||||
@@ -357,6 +350,47 @@ inline eng::string ss(const ARGS & ... args) {
|
|||||||
return oss.str();
|
return oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// util::ostringstream
|
||||||
|
//
|
||||||
|
// This is a variant of ostringstream in which it is possible
|
||||||
|
// to get the contents without copying. To get the contents
|
||||||
|
// without copying, use oss.size() and oss.c_str()
|
||||||
|
//
|
||||||
|
class ostringstream : public eng::ostringstream {
|
||||||
|
class rstringbuf : public std::basic_stringbuf<char_type, traits_type, allocator_type> {
|
||||||
|
public:
|
||||||
|
char *eback() const { return std::streambuf::eback(); }
|
||||||
|
char *pptr() const { return std::streambuf::pptr(); }
|
||||||
|
};
|
||||||
|
rstringbuf rstringbuf_;
|
||||||
|
public:
|
||||||
|
ostringstream() {
|
||||||
|
std::basic_ostream<char>::rdbuf(&rstringbuf_);
|
||||||
|
}
|
||||||
|
std::string_view view() const {
|
||||||
|
char *p = rstringbuf_.eback();
|
||||||
|
size_t size = rstringbuf_.pptr() - p;
|
||||||
|
return std::string_view(p, size);
|
||||||
|
}
|
||||||
|
eng::string str() const {
|
||||||
|
return rstringbuf_.str();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// dprintf
|
||||||
|
//
|
||||||
|
// Send a debugging message to somewhere that it can be seen. This routine
|
||||||
|
// initially just sends output to stderr. But it can be hooked to send output
|
||||||
|
// somewhere else, like to a debug output window.
|
||||||
|
//
|
||||||
|
// The hook function must be a function that accepts a single line of text. The
|
||||||
|
// hook function will always be passed one line, consisting of printable
|
||||||
|
// characters only. There will be no control characters. The newline is
|
||||||
|
// implied.
|
||||||
|
//
|
||||||
|
void dprintf(const char *format, ...);
|
||||||
|
void hook_dprintf(void (*func)(const char *oneline));
|
||||||
|
|
||||||
// A better API than std::setfill, std::hex, std::setw, std::setprecision
|
// A better API than std::setfill, std::hex, std::setw, std::setprecision
|
||||||
//
|
//
|
||||||
// Usage examples:
|
// Usage examples:
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ LuaDefine(tangible_animstate, "tan",
|
|||||||
LuaRet graphic, plane, x, y, z, facing;
|
LuaRet graphic, plane, x, y, z, facing;
|
||||||
LuaStack LS(L, tanobj, graphic, plane, x, y, z, facing);
|
LuaStack LS(L, tanobj, graphic, plane, x, y, z, facing);
|
||||||
World *w = World::fetch_global_pointer(L);
|
World *w = World::fetch_global_pointer(L);
|
||||||
Tangible *tan = w->tangible_get(LS, tanobj);
|
Tangible *tan = w->tangible_get(LS, tanobj, false);
|
||||||
const AnimStep &aqback = tan->anim_queue_.back();
|
const AnimStep &aqback = tan->anim_queue_.back();
|
||||||
LS.set(graphic, aqback.graphic());
|
LS.set(graphic, aqback.graphic());
|
||||||
LS.set(plane, aqback.plane());
|
LS.set(plane, aqback.plane());
|
||||||
@@ -44,7 +44,7 @@ LuaDefine(tangible_xyz, "tan",
|
|||||||
LuaRet x, y, z;
|
LuaRet x, y, z;
|
||||||
LuaStack LS(L, tanobj, x, y, z);
|
LuaStack LS(L, tanobj, x, y, z);
|
||||||
World *w = World::fetch_global_pointer(L);
|
World *w = World::fetch_global_pointer(L);
|
||||||
Tangible *tan = w->tangible_get(LS, tanobj);
|
Tangible *tan = w->tangible_get(LS, tanobj, false);
|
||||||
const AnimStep &aqback = tan->anim_queue_.back();
|
const AnimStep &aqback = tan->anim_queue_.back();
|
||||||
LS.set(x, aqback.xyz().x);
|
LS.set(x, aqback.xyz().x);
|
||||||
LS.set(y, aqback.xyz().y);
|
LS.set(y, aqback.xyz().y);
|
||||||
@@ -60,7 +60,7 @@ LuaDefine(tangible_animate, "tan,configtable",
|
|||||||
LuaStack LS(L, tanobj, config);
|
LuaStack LS(L, tanobj, config);
|
||||||
LuaKeywordParser kp(LS, config);
|
LuaKeywordParser kp(LS, config);
|
||||||
World *w = World::fetch_global_pointer(L);
|
World *w = World::fetch_global_pointer(L);
|
||||||
Tangible *tan = w->tangible_get(LS, tanobj);
|
Tangible *tan = w->tangible_get(LS, tanobj, false);
|
||||||
int64_t id = w->alloc_id_predictable();
|
int64_t id = w->alloc_id_predictable();
|
||||||
AnimStep step;
|
AnimStep step;
|
||||||
step.configure(kp, tan->anim_queue_.back());
|
step.configure(kp, tan->anim_queue_.back());
|
||||||
@@ -82,7 +82,7 @@ LuaDefine(tangible_setclass, "tan,class",
|
|||||||
LuaVar classtab, mt;
|
LuaVar classtab, mt;
|
||||||
LuaStack LS(L, tanobj, classname, classtab, mt);
|
LuaStack LS(L, tanobj, classname, classtab, mt);
|
||||||
World *w = World::fetch_global_pointer(L);
|
World *w = World::fetch_global_pointer(L);
|
||||||
w->tangible_get(LS, tanobj);
|
w->tangible_get(LS, tanobj, false);
|
||||||
eng::string err = LS.getclass(classtab, classname);
|
eng::string err = LS.getclass(classtab, classname);
|
||||||
if (err != "") {
|
if (err != "") {
|
||||||
luaL_error(L, "%s", err.c_str());
|
luaL_error(L, "%s", err.c_str());
|
||||||
@@ -101,7 +101,7 @@ LuaDefine(tangible_getclass, "tan",
|
|||||||
LuaRet classname;
|
LuaRet classname;
|
||||||
LuaStack LS(L, tanobj, mt, classtab, classname);
|
LuaStack LS(L, tanobj, mt, classtab, classname);
|
||||||
World *w = World::fetch_global_pointer(L);
|
World *w = World::fetch_global_pointer(L);
|
||||||
w->tangible_get(LS, tanobj);
|
w->tangible_get(LS, tanobj, false);
|
||||||
LS.getmetatable(mt, tanobj);
|
LS.getmetatable(mt, tanobj);
|
||||||
LS.rawget(classtab, mt, "__index");
|
LS.rawget(classtab, mt, "__index");
|
||||||
eng::string name = LS.classname(classtab);
|
eng::string name = LS.classname(classtab);
|
||||||
@@ -120,8 +120,10 @@ LuaDefine(tangible_delete, "tan",
|
|||||||
LuaArg tanobj;
|
LuaArg tanobj;
|
||||||
LuaStack LS(L, tanobj);
|
LuaStack LS(L, tanobj);
|
||||||
World *w = World::fetch_global_pointer(L);
|
World *w = World::fetch_global_pointer(L);
|
||||||
Tangible *tan = w->tangible_get(LS, tanobj);
|
Tangible *tan = w->tangible_get(LS, tanobj, true);
|
||||||
assert(tan != nullptr); // this should be checked above.
|
if (tan == nullptr) {
|
||||||
|
return LS.result();
|
||||||
|
}
|
||||||
if (tan->is_an_actor()) {
|
if (tan->is_an_actor()) {
|
||||||
luaL_error(L, "Cannot delete a player using tangible.delete, use tangible.redirect instead.");
|
luaL_error(L, "Cannot delete a player using tangible.delete, use tangible.redirect instead.");
|
||||||
return 0;
|
return 0;
|
||||||
@@ -205,14 +207,14 @@ LuaDefine(tangible_redirect, "tan1,tan2,bulldozetan1",
|
|||||||
LuaStack LS(L, actor1, actor2, bldz);
|
LuaStack LS(L, actor1, actor2, bldz);
|
||||||
World *w = World::fetch_global_pointer(L);
|
World *w = World::fetch_global_pointer(L);
|
||||||
bool bulldoze = LS.ckboolean(bldz);
|
bool bulldoze = LS.ckboolean(bldz);
|
||||||
Tangible *tan1 = w->tangible_get(LS, actor1);
|
Tangible *tan1 = w->tangible_get(LS, actor1, false);
|
||||||
if (!tan1->is_an_actor()) {
|
if (!tan1->is_an_actor()) {
|
||||||
luaL_error(L, "redirect source is not an actor");
|
luaL_error(L, "redirect source is not an actor");
|
||||||
}
|
}
|
||||||
if (LS.isnil(actor2)) {
|
if (LS.isnil(actor2)) {
|
||||||
w->redirects_[tan1->id()] = 0;
|
w->redirects_[tan1->id()] = 0;
|
||||||
} else {
|
} else {
|
||||||
Tangible *tan2 = w->tangible_get(LS, actor2);
|
Tangible *tan2 = w->tangible_get(LS, actor2, false);
|
||||||
tan2->configure_id_pool_for_actor();
|
tan2->configure_id_pool_for_actor();
|
||||||
w->redirects_[tan1->id()] = tan2->id();
|
w->redirects_[tan1->id()] = tan2->id();
|
||||||
}
|
}
|
||||||
@@ -268,7 +270,7 @@ LuaDefine(tangible_near, "tan,radius,omit_nowhere,omit_self",
|
|||||||
LuaRet list;
|
LuaRet list;
|
||||||
LuaStack LS(L, ltan, lradius, lomit_nowhere, lomit_self, list);
|
LuaStack LS(L, ltan, lradius, lomit_nowhere, lomit_self, list);
|
||||||
World *w = World::fetch_global_pointer(L);
|
World *w = World::fetch_global_pointer(L);
|
||||||
Tangible *tan = w->tangible_get(LS, ltan);
|
Tangible *tan = w->tangible_get(LS, ltan, false);
|
||||||
const AnimStep &aqback = tan->anim_queue_.back();
|
const AnimStep &aqback = tan->anim_queue_.back();
|
||||||
|
|
||||||
PlaneScan scan;
|
PlaneScan scan;
|
||||||
@@ -460,13 +462,20 @@ LuaDefine(tangible_start, "tangible,function,arg1,arg2...",
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check the entire tangible list for validity.
|
||||||
|
for (int i = 1; ; i++) {
|
||||||
|
LS.rawget(place, tanlist, i);
|
||||||
|
if (LS.isnil(place)) break;
|
||||||
|
w->tangible_get(LS, place, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start threads on all the tangibles.
|
||||||
for (int i = 1; ; i++) {
|
for (int i = 1; ; i++) {
|
||||||
LS.rawget(place, tanlist, i);
|
LS.rawget(place, tanlist, i);
|
||||||
if (LS.isnil(place)) break;
|
if (LS.isnil(place)) break;
|
||||||
|
|
||||||
// Confirm that the place is a valid tangible,
|
// Confirm that the place is a valid tangible,
|
||||||
// and get the tangible ID.
|
// and get the tangible ID.
|
||||||
w->tangible_get(LS, place);
|
|
||||||
place_id = LS.tanid(place);
|
place_id = LS.tanid(place);
|
||||||
|
|
||||||
// Get place's metatable and threads table.
|
// Get place's metatable and threads table.
|
||||||
@@ -711,21 +720,61 @@ LuaDefine(math_randomstate, "seed",
|
|||||||
|
|
||||||
LuaSandboxBuiltin(math_randomseed, "", "");
|
LuaSandboxBuiltin(math_randomseed, "", "");
|
||||||
|
|
||||||
|
LuaDefine(pprint, "obj1, obj2, ...",
|
||||||
LuaDefine(pprint, "obj1,obj2,...",
|
"|Pretty-print the specified objects."
|
||||||
"|Pretty-print object or objects.") {
|
"|"
|
||||||
|
"|See also: pprintx, which has a lot more options."
|
||||||
|
"|This function uses the default options: pretty print indented,"
|
||||||
|
"|start at indentation level zero, and always expand the"
|
||||||
|
"|top-level table."
|
||||||
|
"|") {
|
||||||
World *w = World::fetch_global_pointer(L);
|
World *w = World::fetch_global_pointer(L);
|
||||||
std::ostream *ostream = w->lthread_print_stream();
|
std::ostream *ostream = w->lthread_print_stream();
|
||||||
|
int n = lua_gettop(L);
|
||||||
LuaStack LS(L);
|
LuaStack LS(L);
|
||||||
for (int i = 1; i <= lua_gettop(L); i++) {
|
for (int i = 1; i <= n; i++) {
|
||||||
LuaSpecial root(i);
|
LuaSpecial root(i);
|
||||||
pprint(LS, root, true, ostream);
|
pprint(LS, root, PrettyPrintOptions(), ostream);
|
||||||
(*ostream) << std::endl;
|
if (i < n) (*ostream) << "\n";
|
||||||
}
|
}
|
||||||
|
(*ostream) << std::endl;
|
||||||
|
return LS.result();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LuaDefine(pprintx, "options",
|
||||||
|
"|Pretty-print the specified object, with options"
|
||||||
|
"|"
|
||||||
|
"|Options is a table with these fields:"
|
||||||
|
"|"
|
||||||
|
"| value - the object to pretty-print"
|
||||||
|
"| indent - if false, suppress newlines and indentation (default: true)"
|
||||||
|
"| level - base level of indentation (default: zero)"
|
||||||
|
"| expand - if true, force expansion of top-level table (default: false)"
|
||||||
|
"|"
|
||||||
|
"|About the expand flag: normally, when you print a class, it just "
|
||||||
|
"|prints '<class name>', and when you print a tangible, it just"
|
||||||
|
"|prints '<tangible id>'. But sometimes, you want to see the details."
|
||||||
|
"|The expand flag forces it to expand the top-level table, even if the"
|
||||||
|
"|top-level table is a tangible or class."
|
||||||
|
"|") {
|
||||||
|
World *w = World::fetch_global_pointer(L);
|
||||||
|
std::ostream *ostream = w->lthread_print_stream();
|
||||||
|
LuaArg loptions;
|
||||||
|
LuaVar value;
|
||||||
|
LuaStack LS(L, loptions, value);
|
||||||
|
PrettyPrintOptions options;
|
||||||
|
LuaKeywordParser kp(LS, loptions);
|
||||||
|
options.parse(kp);
|
||||||
|
if (!kp.parse(value, "value")) {
|
||||||
|
LS.set(value, LuaNil);
|
||||||
|
}
|
||||||
|
kp.final_check_throw();
|
||||||
|
pprint(LS, value, options, ostream);
|
||||||
return LS.result();
|
return LS.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
LuaDefine(print, "obj1,obj2,...",
|
LuaDefine(print, "obj1, obj2, ...",
|
||||||
"|Print object or objects.") {
|
"|Print object or objects.") {
|
||||||
World *w = World::fetch_global_pointer(L);
|
World *w = World::fetch_global_pointer(L);
|
||||||
std::ostream *ostream = w->lthread_print_stream();
|
std::ostream *ostream = w->lthread_print_stream();
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ World *World::fetch_global_pointer(lua_State *L) {
|
|||||||
World::~World() {
|
World::~World() {
|
||||||
}
|
}
|
||||||
|
|
||||||
World::World(util::WorldType wt) {
|
World::World(WorldType wt) {
|
||||||
// Master world model by default.
|
// Master world model by default.
|
||||||
world_type_ = wt;
|
world_type_ = wt;
|
||||||
|
|
||||||
// Initialize the ID allocator in master mode.
|
// Initialize the ID allocator in master mode.
|
||||||
if (wt == util::WORLD_TYPE_MASTER || wt == util::WORLD_TYPE_STANDALONE) {
|
if (is_authoritative()) {
|
||||||
id_global_pool_.init_master();
|
id_global_pool_.init_master();
|
||||||
} else {
|
} else {
|
||||||
id_global_pool_.init_synch();
|
id_global_pool_.init_synch();
|
||||||
@@ -60,14 +60,11 @@ World::World(util::WorldType wt) {
|
|||||||
LS.getglobaltable(globtab);
|
LS.getglobaltable(globtab);
|
||||||
LS.settabletype(globtab, LUA_TT_GLOBALENV);
|
LS.settabletype(globtab, LUA_TT_GLOBALENV);
|
||||||
|
|
||||||
// Create the tangibles table in the registry.
|
// Store the world type in the registry.
|
||||||
LS.rawset(LuaRegistry, "tangibles", LuaNewTable);
|
LS.rawset(LuaRegistry, "worldtype", wt);
|
||||||
|
|
||||||
// Create the globaldb and oncedb in the registry.
|
// Create the globaldb in the registry.
|
||||||
if ((wt == util::WORLD_TYPE_MASTER) || (wt == util::WORLD_TYPE_STANDALONE)) {
|
LS.rawset(LuaRegistry, "globaldb", LuaNewTable);
|
||||||
LS.rawset(LuaRegistry, "globaldb", LuaNewTable);
|
|
||||||
LS.rawset(LuaRegistry, "oncedb", LuaNewTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the SourceDB. At this stage, the sourcedb is
|
// Initialize the SourceDB. At this stage, the sourcedb is
|
||||||
// empty, so it's just populating the lua builtins.
|
// empty, so it's just populating the lua builtins.
|
||||||
@@ -134,14 +131,16 @@ World::TanVector World::tangible_get_all(const IdVector &ids) const {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tangible *World::tangible_get(const LuaStack &LS, LuaSlot tab) {
|
Tangible *World::tangible_get(const LuaStack &LS, LuaSlot tab, bool allowdel) {
|
||||||
int64_t id = LS.tanid(tab);
|
int64_t id = LS.tanid(tab);
|
||||||
if (id == 0) {
|
if (id == 0) {
|
||||||
luaL_error(LS.state(), "parameter is not a tangible");
|
luaL_error(LS.state(), "parameter is not a tangible");
|
||||||
}
|
}
|
||||||
Tangible *result = tangible_get(id);
|
Tangible *result = tangible_get(id);
|
||||||
if (result == nullptr) {
|
if (!allowdel) {
|
||||||
luaL_error(LS.state(), "parameter is not a tangible");
|
if (result == nullptr) {
|
||||||
|
luaL_error(LS.state(), "argument is a deleted tangible, which is not allowed here");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -154,9 +153,9 @@ Tangible *World::tangible_make(lua_State *L, int64_t id, const eng::string &plan
|
|||||||
}
|
}
|
||||||
assert(id != 0);
|
assert(id != 0);
|
||||||
|
|
||||||
LuaVar tangibles, metatab;
|
LuaVar metatab;
|
||||||
LuaRet database;
|
LuaRet database;
|
||||||
LuaStack LS(L, tangibles, database, metatab);
|
LuaStack LS(L, database, metatab);
|
||||||
|
|
||||||
// Create the C++ part of the structure.
|
// Create the C++ part of the structure.
|
||||||
UniqueTangible &t = tangibles_[id];
|
UniqueTangible &t = tangibles_[id];
|
||||||
@@ -167,25 +166,14 @@ Tangible *World::tangible_make(lua_State *L, int64_t id, const eng::string &plan
|
|||||||
t->anim_queue_.clear(plane);
|
t->anim_queue_.clear(plane);
|
||||||
t->update_plane_item();
|
t->update_plane_item();
|
||||||
|
|
||||||
// Create the tangible's database and metatable.
|
// Fetch the tangible's Lua database and metatable.
|
||||||
LS.set(database, LuaNewTable);
|
LS.maketan(database, id);
|
||||||
LS.set(metatab, LuaNewTable);
|
LS.getmetatable(metatab, database);
|
||||||
LS.setmetatable(database, metatab);
|
|
||||||
|
|
||||||
// Mark the tangible using the tabletype field.
|
// Set up the inventory and thread table.
|
||||||
LS.settabletype(database, LUA_TT_TANGIBLE);
|
|
||||||
LS.settabletype(metatab, LUA_TT_TANGIBLEMETA);
|
|
||||||
|
|
||||||
// Store the database into the tangibles table.
|
|
||||||
LS.rawget(tangibles, LuaRegistry, "tangibles");
|
|
||||||
LS.rawset(tangibles, id, database);
|
|
||||||
|
|
||||||
// Populate the database and metatable with initial stuff.
|
|
||||||
LS.rawset(database, "inventory", LuaNewTable);
|
LS.rawset(database, "inventory", LuaNewTable);
|
||||||
LS.rawset(metatab, "id", id);
|
|
||||||
LS.rawset(metatab, "threads", LuaNewTable);
|
LS.rawset(metatab, "threads", LuaNewTable);
|
||||||
// LS.rawset(metatab, "__metatable", LuaNil);
|
|
||||||
|
|
||||||
LS.result();
|
LS.result();
|
||||||
if (!pushdb) lua_pop(L, 1);
|
if (!pushdb) lua_pop(L, 1);
|
||||||
return t.get();
|
return t.get();
|
||||||
@@ -193,8 +181,8 @@ Tangible *World::tangible_make(lua_State *L, int64_t id, const eng::string &plan
|
|||||||
|
|
||||||
void World::tangible_delete(int64_t id) {
|
void World::tangible_delete(int64_t id) {
|
||||||
lua_State *L = state();
|
lua_State *L = state();
|
||||||
LuaVar tangibles, database;
|
LuaVar tangibles, database, metatab;
|
||||||
LuaStack LS(L, tangibles, database);
|
LuaStack LS(L, tangibles, database, metatab);
|
||||||
|
|
||||||
// Fetch the C++ side of the tangible.
|
// Fetch the C++ side of the tangible.
|
||||||
auto iter = tangibles_.find(id);
|
auto iter = tangibles_.find(id);
|
||||||
@@ -204,16 +192,17 @@ void World::tangible_delete(int64_t id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the lua side of the tangible.
|
// Fetch the lua side of the tangible.
|
||||||
LS.rawget(tangibles, LuaRegistry, "tangibles");
|
LS.maketan(database, id);
|
||||||
LS.rawget(database, tangibles, id);
|
|
||||||
assert(LS.istable(database));
|
assert(LS.istable(database));
|
||||||
|
LS.getmetatable(metatab, database);
|
||||||
// Clear out the database.
|
|
||||||
LS.cleartable(database, true);
|
|
||||||
LS.settabletype(database, LUA_TT_DEADTANGIBLE);
|
|
||||||
|
|
||||||
// Remove the lua portion from the tangibles table.
|
// Clear out the database and the metatable.
|
||||||
LS.rawset(tangibles, id, LuaNil);
|
LS.cleartable(database, false);
|
||||||
|
LS.cleartable(metatab, true);
|
||||||
|
|
||||||
|
// Now put the bare minimum info back into the metatable.
|
||||||
|
LS.rawset(metatab, "id", id);
|
||||||
|
LS.rawset(metatab, "__metatable", false);
|
||||||
|
|
||||||
// Remove the C++ portion from the tangibles table.
|
// Remove the C++ portion from the tangibles table.
|
||||||
tangibles_.erase(iter);
|
tangibles_.erase(iter);
|
||||||
@@ -297,7 +286,7 @@ eng::string World::probe_lua(int64_t actor_id, const eng::string &lua) {
|
|||||||
if (msg.empty()) {
|
if (msg.empty()) {
|
||||||
for (int i = top + 1; i <= lua_gettop(L); i++) {
|
for (int i = top + 1; i <= lua_gettop(L); i++) {
|
||||||
LuaSpecial root(i);
|
LuaSpecial root(i);
|
||||||
pprint(LS, root, true, ostream);
|
pprint(LS, root, PrettyPrintOptions(), ostream);
|
||||||
// TODO: this endl is unnecessary if we just printed a newline.
|
// TODO: this endl is unnecessary if we just printed a newline.
|
||||||
(*ostream) << std::endl;
|
(*ostream) << std::endl;
|
||||||
}
|
}
|
||||||
@@ -600,7 +589,7 @@ void World::invoke_flush_prints(int64_t actor_id, int64_t place_id, const eng::s
|
|||||||
void World::invoke_lua(int64_t actor_id, int64_t place_id, const eng::string &action, const InvocationData &data) {
|
void World::invoke_lua(int64_t actor_id, int64_t place_id, const eng::string &action, const InvocationData &data) {
|
||||||
assert(stack_is_clear());
|
assert(stack_is_clear());
|
||||||
|
|
||||||
// Make sure that actor and place exist.
|
// Make sure that actor and place exist and are not stubs.
|
||||||
Tangible *tactor = tangible_get(actor_id);
|
Tangible *tactor = tangible_get(actor_id);
|
||||||
Tangible *tplace = tangible_get(place_id);
|
Tangible *tplace = tangible_get(place_id);
|
||||||
if ((tactor == nullptr) || (tplace == nullptr)) {
|
if ((tactor == nullptr) || (tplace == nullptr)) {
|
||||||
@@ -788,12 +777,12 @@ void World::invoke_lua_source(int64_t actor_id, int64_t place_id, const eng::str
|
|||||||
|
|
||||||
void World::guard_blockable(lua_State *L, const char *fn) {
|
void World::guard_blockable(lua_State *L, const char *fn) {
|
||||||
if (lthread_thread_id_ == 0) {
|
if (lthread_thread_id_ == 0) {
|
||||||
// in a probe, http.get throws an error.
|
// in a probe, blocking functions like http.get throw an error.
|
||||||
luaL_error(L, "cannot %s in a probe", fn);
|
luaL_error(L, "cannot %s in a probe", fn);
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
if (!is_authoritative()) {
|
if (!is_authoritative()) {
|
||||||
// in a nonauth model, http.get is converted to nopredict.
|
// in a nonauth model, blocking functions like http.get are converted to nopredict.
|
||||||
lua_yield(L, 0);
|
lua_yield(L, 0);
|
||||||
luaL_error(L, "unexplained nopredict failure in %s", fn);
|
luaL_error(L, "unexplained nopredict failure in %s", fn);
|
||||||
assert(false);
|
assert(false);
|
||||||
@@ -801,6 +790,8 @@ void World::guard_blockable(lua_State *L, const char *fn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void World::guard_nopredict(lua_State *L, const char *fn) {
|
void World::guard_nopredict(lua_State *L, const char *fn) {
|
||||||
|
// Caution: this code must be equivalent to the
|
||||||
|
// code in LuaStack::guard_nopredict.
|
||||||
if (lthread_thread_id_ == 0) {
|
if (lthread_thread_id_ == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -874,7 +865,7 @@ void World::run_scheduled_threads() {
|
|||||||
LuaStack LSCO(CO);
|
LuaStack LSCO(CO);
|
||||||
if (LS.ckboolean(print)) {
|
if (LS.ckboolean(print)) {
|
||||||
for (int i = 1; i <= lua_gettop(CO); i++) {
|
for (int i = 1; i <= lua_gettop(CO); i++) {
|
||||||
pprint(LSCO, LuaSpecial(i), true, ostream);
|
pprint(LSCO, LuaSpecial(i), PrettyPrintOptions(), ostream);
|
||||||
(*ostream) << std::endl;
|
(*ostream) << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -883,7 +874,10 @@ void World::run_scheduled_threads() {
|
|||||||
LS.rawset(thinfo, "isnew", false);
|
LS.rawset(thinfo, "isnew", false);
|
||||||
LS.rawset(thinfo, "useppool", false);
|
LS.rawset(thinfo, "useppool", false);
|
||||||
} else {
|
} else {
|
||||||
// In a nonauth model, a yield is converted to a 'nopredict'.
|
// When a nonauthoritative model yields, for any reason,
|
||||||
|
// the thread is discarded. This is also used as a way to implement
|
||||||
|
// nopredict: the thread that wants to 'nopredict' just yields,
|
||||||
|
// knowing that this will cause it to be killed.
|
||||||
LS.rawset(threads, sched.thread_id(), LuaNil);
|
LS.rawset(threads, sched.thread_id(), LuaNil);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -304,12 +304,7 @@ static void set_transmitted_value(LuaStack &LS, LuaSlot tangibles, LuaSlot ntmap
|
|||||||
case LUA_TT_TANGIBLE: {
|
case LUA_TT_TANGIBLE: {
|
||||||
int64_t id = sb->read_int64();
|
int64_t id = sb->read_int64();
|
||||||
DebugLine(dbc) << dbinfo << "tan " << id;
|
DebugLine(dbc) << dbinfo << "tan " << id;
|
||||||
LS.rawget(target, tangibles, id);
|
LS.maketan(target, id);
|
||||||
if (LS.isnil(target)) {
|
|
||||||
World *w = World::fetch_global_pointer(LS.state());
|
|
||||||
w->tangible_make(LS.state(), id, "nowhere", true);
|
|
||||||
lua_replace(LS.state(), target.index());
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case LUA_TT_GLOBALENV: {
|
case LUA_TT_GLOBALENV: {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
#include "world.hpp"
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "world.hpp"
|
||||||
|
|
||||||
util::IdVector World::get_visible_union(int64_t actor_id, World *master) {
|
util::IdVector World::get_visible_union(int64_t actor_id, World *master) {
|
||||||
return util::sort_union_id_vectors(
|
return util::sort_union_id_vectors(
|
||||||
master->get_near(actor_id, RadiusVisibility, true, false, false),
|
master->get_near(actor_id, RadiusVisibility, true, false, false),
|
||||||
get_near(actor_id, RadiusVisibility, true, false, false));
|
get_near(actor_id, RadiusVisibility, true, false, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t World::patch_actor(StreamBuffer *sb, DebugCollector *dbc) {
|
int64_t World::patch_actor(StreamBuffer *sb, DebugCollector *dbc) {
|
||||||
@@ -27,14 +28,14 @@ int64_t World::patch_actor(StreamBuffer *sb, DebugCollector *dbc) {
|
|||||||
return actor_id;
|
return actor_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::diff_actor(int64_t actor_id, World *master, StreamBuffer *xsb) {
|
void World::diff_actor(int64_t actor_id, World *master, StreamBuffer *xsb) {
|
||||||
StreamBuffer tsb;
|
StreamBuffer tsb;
|
||||||
|
|
||||||
// Get the actor in both models. s_actor may be nil.
|
// Get the actor in both models. s_actor may be nil.
|
||||||
const Tangible *s_actor = tangible_get(actor_id);
|
const Tangible *s_actor = tangible_get(actor_id);
|
||||||
const Tangible *m_actor = master->tangible_get(actor_id);
|
const Tangible *m_actor = master->tangible_get(actor_id);
|
||||||
assert(m_actor != nullptr);
|
assert(m_actor != nullptr);
|
||||||
|
|
||||||
// Calculate diffs.
|
// Calculate diffs.
|
||||||
tsb.write_int64(actor_id);
|
tsb.write_int64(actor_id);
|
||||||
if (s_actor == nullptr) {
|
if (s_actor == nullptr) {
|
||||||
@@ -85,15 +86,17 @@ void World::patch_visible(StreamBuffer *sb, DebugCollector *dbc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::diff_visible(const util::IdVector &visible, World *master, StreamBuffer *xsb) {
|
void World::diff_visible(const util::IdVector &visible, World *master,
|
||||||
|
StreamBuffer *xsb) {
|
||||||
StreamBuffer tsb;
|
StreamBuffer tsb;
|
||||||
|
|
||||||
// Get the specified tangibles in both models.
|
// Get the specified tangibles in both models.
|
||||||
// Some tangibles may be missing in the master, some may be missing in the sync.
|
// Some tangibles may be missing in the master, some may be missing in the
|
||||||
|
// sync.
|
||||||
TanVector mvis = master->tangible_get_all(visible);
|
TanVector mvis = master->tangible_get_all(visible);
|
||||||
TanVector svis = tangible_get_all(visible);
|
TanVector svis = tangible_get_all(visible);
|
||||||
assert(mvis.size() == svis.size());
|
assert(mvis.size() == svis.size());
|
||||||
|
|
||||||
// For each tangible that exists in the master, but not
|
// For each tangible that exists in the master, but not
|
||||||
// in the synchronous model, send a create message.
|
// in the synchronous model, send a create message.
|
||||||
tsb.write_int32(0);
|
tsb.write_int32(0);
|
||||||
@@ -169,11 +172,13 @@ void World::patch_luatabs(StreamBuffer *sb, DebugCollector *dbc) {
|
|||||||
int64_t actor_id = sb->read_int64();
|
int64_t actor_id = sb->read_int64();
|
||||||
util::HashValue closehash = sb->read_hashvalue();
|
util::HashValue closehash = sb->read_hashvalue();
|
||||||
int ncreate = sb->read_int32();
|
int ncreate = sb->read_int32();
|
||||||
util::IdVector closetans = get_near(actor_id, RadiusClose, true, false, true);
|
util::IdVector closetans =
|
||||||
|
get_near(actor_id, RadiusClose, true, false, true);
|
||||||
assert(closehash == util::hash_id_vector(closetans));
|
assert(closehash == util::hash_id_vector(closetans));
|
||||||
number_lua_tables(closetans);
|
number_lua_tables(closetans);
|
||||||
create_new_tables(ncreate);
|
create_new_tables(ncreate);
|
||||||
// DebugLine(dbc) << "lua tables: " << nt << " existing " << ncreate << " new";
|
// DebugLine(dbc) << "lua tables: " << nt << " existing " << ncreate << "
|
||||||
|
// new";
|
||||||
patch_tangible_databases(sb, dbc);
|
patch_tangible_databases(sb, dbc);
|
||||||
patch_numbered_tables(sb, dbc);
|
patch_numbered_tables(sb, dbc);
|
||||||
unnumber_lua_tables();
|
unnumber_lua_tables();
|
||||||
@@ -183,7 +188,8 @@ void World::diff_luatabs(int64_t actor_id, World *master, StreamBuffer *xsb) {
|
|||||||
StreamBuffer tsb;
|
StreamBuffer tsb;
|
||||||
|
|
||||||
// Calculate the set of close tangibles.
|
// Calculate the set of close tangibles.
|
||||||
util::IdVector closetans = master->get_near(actor_id, RadiusClose, true, false, true);
|
util::IdVector closetans =
|
||||||
|
master->get_near(actor_id, RadiusClose, true, false, true);
|
||||||
assert(get_near(actor_id, RadiusClose, true, false, true) == closetans);
|
assert(get_near(actor_id, RadiusClose, true, false, true) == closetans);
|
||||||
util::HashValue closehash = util::hash_id_vector(closetans);
|
util::HashValue closehash = util::hash_id_vector(closetans);
|
||||||
|
|
||||||
@@ -199,7 +205,7 @@ void World::diff_luatabs(int64_t actor_id, World *master, StreamBuffer *xsb) {
|
|||||||
tsb.write_int32(ncreate);
|
tsb.write_int32(ncreate);
|
||||||
diff_tangible_databases(closetans, master->state(), &tsb);
|
diff_tangible_databases(closetans, master->state(), &tsb);
|
||||||
diff_numbered_tables(master->state(), &tsb);
|
diff_numbered_tables(master->state(), &tsb);
|
||||||
|
|
||||||
// Forward to client, and apply to server-synchronous.
|
// Forward to client, and apply to server-synchronous.
|
||||||
tsb.copy_into(xsb);
|
tsb.copy_into(xsb);
|
||||||
assert(tsb.read_int64() == actor_id);
|
assert(tsb.read_int64() == actor_id);
|
||||||
@@ -207,7 +213,7 @@ void World::diff_luatabs(int64_t actor_id, World *master, StreamBuffer *xsb) {
|
|||||||
assert(tsb.read_int32() == ncreate);
|
assert(tsb.read_int32() == ncreate);
|
||||||
patch_tangible_databases(&tsb, nullptr);
|
patch_tangible_databases(&tsb, nullptr);
|
||||||
patch_numbered_tables(&tsb, nullptr);
|
patch_numbered_tables(&tsb, nullptr);
|
||||||
assert(tsb.empty());
|
assert(tsb.empty());
|
||||||
|
|
||||||
// Unnumber tables in both models.
|
// Unnumber tables in both models.
|
||||||
unnumber_lua_tables();
|
unnumber_lua_tables();
|
||||||
@@ -249,8 +255,10 @@ void World::diff_tanclass(int64_t actor_id, World *master, StreamBuffer *xsb) {
|
|||||||
MLS.rawget(mtangibles, LuaRegistry, "tangibles");
|
MLS.rawget(mtangibles, LuaRegistry, "tangibles");
|
||||||
|
|
||||||
// Calculate the set of close tangibles.
|
// Calculate the set of close tangibles.
|
||||||
// TODO: we've already calculated this in an earlier function. This is wasteful.
|
// TODO: we've already calculated this in an earlier function. This is
|
||||||
util::IdVector closetans = master->get_near(actor_id, RadiusClose, true, false, true);
|
// wasteful.
|
||||||
|
util::IdVector closetans =
|
||||||
|
master->get_near(actor_id, RadiusClose, true, false, true);
|
||||||
|
|
||||||
tsb.write_int32(0);
|
tsb.write_int32(0);
|
||||||
int write_count_after = tsb.total_writes();
|
int write_count_after = tsb.total_writes();
|
||||||
@@ -310,7 +318,7 @@ int64_t World::patch_everything(StreamBuffer *sb, DebugCollector *dbc) {
|
|||||||
return actor_id;
|
return actor_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::diff_everything(int64_t actor_id, World *master, StreamBuffer *sb) {
|
void World::diff_everything(int64_t actor_id, World *master, StreamBuffer *sb) {
|
||||||
diff_actor(actor_id, master, sb);
|
diff_actor(actor_id, master, sb);
|
||||||
util::IdVector visible = get_visible_union(actor_id, master);
|
util::IdVector visible = get_visible_union(actor_id, master);
|
||||||
diff_visible(visible, master, sb);
|
diff_visible(visible, master, sb);
|
||||||
|
|||||||
@@ -136,8 +136,6 @@ void World::pair_lua_tables(const IdVector &basis, lua_State *master) {
|
|||||||
// If the master table is already paired, skip.
|
// If the master table is already paired, skip.
|
||||||
MLS.rawget(midx, mtnmap, mtab);
|
MLS.rawget(midx, mtnmap, mtab);
|
||||||
if (MLS.isnumber(midx)) continue;
|
if (MLS.isnumber(midx)) continue;
|
||||||
// If the synch table is not a table, skip.
|
|
||||||
if (!SLS.istable(stab)) continue;
|
|
||||||
// If the synch table doesn't have a number, skip.
|
// If the synch table doesn't have a number, skip.
|
||||||
SLS.rawget(sidx, stnmap, stab);
|
SLS.rawget(sidx, stnmap, stab);
|
||||||
if (!SLS.isnumber(sidx)) continue;
|
if (!SLS.isnumber(sidx)) continue;
|
||||||
|
|||||||
@@ -55,12 +55,11 @@ eng::string World::tangible_pprint(int64_t id) const {
|
|||||||
LS.rawget(tan, tangibles, id);
|
LS.rawget(tan, tangibles, id);
|
||||||
eng::ostringstream oss;
|
eng::ostringstream oss;
|
||||||
if (LS.istable(tan)) {
|
if (LS.istable(tan)) {
|
||||||
LS.getmetatable(meta, tan);
|
PrettyPrintOptions opts;
|
||||||
LS.clearmetatable(tan);
|
opts.indent = false;
|
||||||
pprint(LS, tan, false, &oss);
|
pprint(LS, tan, opts, &oss);
|
||||||
LS.setmetatable(tan, meta);
|
|
||||||
} else {
|
} else {
|
||||||
oss << "<no such tangible: " << id << ">";
|
oss << "<no such tangible " << id << ">{}";
|
||||||
}
|
}
|
||||||
LS.result();
|
LS.result();
|
||||||
return oss.str();
|
return oss.str();
|
||||||
@@ -241,9 +240,9 @@ static bool worlds_identical(const UniqueWorld &w1, const UniqueWorld &w2) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LuaDefine(unittests_world1animdiff, "", "some unit tests") {
|
LuaDefine(unittests_world1animdiff, "", "some unit tests") {
|
||||||
UniqueWorld m(new World(util::WORLD_TYPE_MASTER));
|
UniqueWorld m(new World(WORLD_TYPE_MASTER));
|
||||||
UniqueWorld ss(new World(util::WORLD_TYPE_S_SYNC));
|
UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE));
|
||||||
UniqueWorld cs(new World(util::WORLD_TYPE_C_SYNC));
|
UniqueWorld cs(new World(WORLD_TYPE_PREDICTIVE));
|
||||||
StreamBuffer sb;
|
StreamBuffer sb;
|
||||||
util::IdVector ids = util::id_vector_create(123, 345);
|
util::IdVector ids = util::id_vector_create(123, 345);
|
||||||
|
|
||||||
@@ -311,8 +310,8 @@ LuaDefine(unittests_world1animdiff, "", "some unit tests") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LuaDefine(unittests_world2pairtab, "", "some unit tests") {
|
LuaDefine(unittests_world2pairtab, "", "some unit tests") {
|
||||||
UniqueWorld m(new World(util::WORLD_TYPE_MASTER));
|
UniqueWorld m(new World(WORLD_TYPE_MASTER));
|
||||||
UniqueWorld ss(new World(util::WORLD_TYPE_S_SYNC));
|
UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE));
|
||||||
StreamBuffer sb;
|
StreamBuffer sb;
|
||||||
int ncreate;
|
int ncreate;
|
||||||
|
|
||||||
@@ -359,9 +358,9 @@ LuaDefine(unittests_world2pairtab, "", "some unit tests") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LuaDefine(unittests_world3diffluatab, "", "some unit tests") {
|
LuaDefine(unittests_world3diffluatab, "", "some unit tests") {
|
||||||
UniqueWorld m(new World(util::WORLD_TYPE_MASTER));
|
UniqueWorld m(new World(WORLD_TYPE_MASTER));
|
||||||
UniqueWorld ss(new World(util::WORLD_TYPE_S_SYNC));
|
UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE));
|
||||||
UniqueWorld cs(new World(util::WORLD_TYPE_C_SYNC));
|
UniqueWorld cs(new World(WORLD_TYPE_PREDICTIVE));
|
||||||
StreamBuffer sb;
|
StreamBuffer sb;
|
||||||
|
|
||||||
// Initialize all three models so that a tangible exists.
|
// Initialize all three models so that a tangible exists.
|
||||||
@@ -382,7 +381,7 @@ LuaDefine(unittests_world3diffluatab, "", "some unit tests") {
|
|||||||
|
|
||||||
// The data in the master model should now look like this:
|
// The data in the master model should now look like this:
|
||||||
const char *expect_123 =
|
const char *expect_123 =
|
||||||
"{ "
|
"<tangible 123>{ "
|
||||||
"bacon='crispy', "
|
"bacon='crispy', "
|
||||||
"inventory={ gold='wealthy' }, "
|
"inventory={ gold='wealthy' }, "
|
||||||
"skills={ "
|
"skills={ "
|
||||||
@@ -391,7 +390,7 @@ LuaDefine(unittests_world3diffluatab, "", "some unit tests") {
|
|||||||
"} "
|
"} "
|
||||||
"}";
|
"}";
|
||||||
const char *expect_345 =
|
const char *expect_345 =
|
||||||
"{ "
|
"<tangible 345>{ "
|
||||||
"inventory={ gold='poor' }, "
|
"inventory={ gold='poor' }, "
|
||||||
"phone='867-5309' "
|
"phone='867-5309' "
|
||||||
"}";
|
"}";
|
||||||
@@ -413,9 +412,9 @@ LuaDefine(unittests_world3diffluatab, "", "some unit tests") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LuaDefine(unittests_world4difftanclass, "", "some unit tests") {
|
LuaDefine(unittests_world4difftanclass, "", "some unit tests") {
|
||||||
UniqueWorld m(new World(util::WORLD_TYPE_MASTER));
|
UniqueWorld m(new World(WORLD_TYPE_MASTER));
|
||||||
UniqueWorld ss(new World(util::WORLD_TYPE_S_SYNC));
|
UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE));
|
||||||
UniqueWorld cs(new World(util::WORLD_TYPE_C_SYNC));
|
UniqueWorld cs(new World(WORLD_TYPE_PREDICTIVE));
|
||||||
StreamBuffer sb;
|
StreamBuffer sb;
|
||||||
|
|
||||||
// Initialize all three models so that a tangible exists.
|
// Initialize all three models so that a tangible exists.
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ public:
|
|||||||
// The constructor also calls 'lua_open' to create a new
|
// The constructor also calls 'lua_open' to create a new
|
||||||
// lua interpreter for this world model.
|
// lua interpreter for this world model.
|
||||||
//
|
//
|
||||||
World(util::WorldType wt);
|
World(WorldType wt);
|
||||||
|
|
||||||
// Destructor.
|
// Destructor.
|
||||||
//
|
//
|
||||||
@@ -137,17 +137,21 @@ public:
|
|||||||
|
|
||||||
// Get a pointer to the specified tangible.
|
// Get a pointer to the specified tangible.
|
||||||
//
|
//
|
||||||
// If there's no such tangible, returns nullptr.
|
// If there's no such tangible, or if the tangible is deleted,
|
||||||
|
// returns nullptr.
|
||||||
//
|
//
|
||||||
Tangible *tangible_get(int64_t id);
|
Tangible *tangible_get(int64_t id);
|
||||||
const Tangible *tangible_get(int64_t id) const;
|
const Tangible *tangible_get(int64_t id) const;
|
||||||
|
|
||||||
// Get a pointer to the specified tangible.
|
// Get a pointer to the specified tangible.
|
||||||
//
|
//
|
||||||
// The value on the lua stack should be a valid lua tangible. If not,
|
// The value on the lua stack should be a lua tangible.
|
||||||
// a lua error is generated.
|
|
||||||
//
|
//
|
||||||
Tangible *tangible_get(const LuaStack &LS, LuaSlot slot);
|
// If the 'allowdel' flag is true, then it is valid to pass in
|
||||||
|
// a deleted tangible. In that case, this function returns nullptr,
|
||||||
|
// but this is not a Lua error.
|
||||||
|
//
|
||||||
|
Tangible *tangible_get(const LuaStack &LS, LuaSlot slot, bool allowdel);
|
||||||
|
|
||||||
// Get pointers to many tangibles.
|
// Get pointers to many tangibles.
|
||||||
//
|
//
|
||||||
@@ -236,8 +240,8 @@ public:
|
|||||||
|
|
||||||
// Check if the world is authoritative.
|
// Check if the world is authoritative.
|
||||||
//
|
//
|
||||||
bool is_authoritative() const { return (world_type_ == util::WORLD_TYPE_MASTER) || (world_type_ == util::WORLD_TYPE_STANDALONE); }
|
bool is_authoritative() const { return LuaStack::is_authoritative(world_type_); }
|
||||||
|
|
||||||
// Get a table showing all outstanding HTTP requests.
|
// Get a table showing all outstanding HTTP requests.
|
||||||
//
|
//
|
||||||
const HttpClientRequestMap &http_requests() const { return http_requests_; }
|
const HttpClientRequestMap &http_requests() const { return http_requests_; }
|
||||||
@@ -249,6 +253,9 @@ public:
|
|||||||
|
|
||||||
// Snapshot and rollback.
|
// Snapshot and rollback.
|
||||||
//
|
//
|
||||||
|
// These are used by the client to convert the synchronous model
|
||||||
|
// to an asynchronous model and back.
|
||||||
|
//
|
||||||
void snapshot();
|
void snapshot();
|
||||||
void rollback();
|
void rollback();
|
||||||
bool snapshot_empty() { return snapshot_.empty(); }
|
bool snapshot_empty() { return snapshot_.empty(); }
|
||||||
@@ -494,7 +501,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// Type of model
|
// Type of model
|
||||||
util::WorldType world_type_;
|
WorldType world_type_;
|
||||||
|
|
||||||
// A lua intepreter with snapshot function.
|
// A lua intepreter with snapshot function.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#define POLLVEC_SIZE (DRV_MAX_CHAN + 1)
|
#define POLLVEC_SIZE (DRV_MAX_CHAN + 1)
|
||||||
|
#define MAX_BIO_BUFFER (128 * 1024)
|
||||||
|
|
||||||
|
|
||||||
static void if_error_print_and_exit(const std::string_view str) {
|
static void if_error_print_and_exit(const std::string_view str) {
|
||||||
@@ -8,6 +9,11 @@ static void if_error_print_and_exit(const std::string_view str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void dprintf_callback(const char *oneline) {
|
||||||
|
fprintf(stderr, "DPRINTF: %s\n", oneline);
|
||||||
|
fflush(stderr);
|
||||||
|
}
|
||||||
|
|
||||||
class Driver {
|
class Driver {
|
||||||
public:
|
public:
|
||||||
enum ChanState {
|
enum ChanState {
|
||||||
@@ -21,15 +27,26 @@ class Driver {
|
|||||||
int chid;
|
int chid;
|
||||||
SOCKET socket;
|
SOCKET socket;
|
||||||
SSL *ssl;
|
SSL *ssl;
|
||||||
|
BIO *recv_bio;
|
||||||
|
BIO *send_bio;
|
||||||
|
|
||||||
|
// If recent_error is set, that means that a recent IO operation generated
|
||||||
|
// an error. As a special case, EOF on read is considered an error, we use
|
||||||
|
// the string "EOF" for this case.
|
||||||
|
std::string recent_error;
|
||||||
|
|
||||||
|
// OpenSSL has a rule: if you try to SSL_write and it returns
|
||||||
|
// SSL_ERROR_WANT_READ, then you have to retry the write with the same
|
||||||
|
// number of bytes. In this event, we record how many bytes we
|
||||||
|
// attempted to write, which will enable us to retry.
|
||||||
|
int retry_write_nbytes;
|
||||||
|
|
||||||
|
// True if the channel needs to be advanced.
|
||||||
|
bool need_advance;
|
||||||
|
|
||||||
ChanState state;
|
ChanState state;
|
||||||
uint32_t nbytes;
|
uint32_t nbytes;
|
||||||
const char *bytes;
|
const char *bytes;
|
||||||
bool ready_now;
|
|
||||||
bool ready_on_pollin;
|
|
||||||
bool ready_on_pollout;
|
|
||||||
bool ready_on_outgoing;
|
|
||||||
uint32_t last_write_nbytes;
|
|
||||||
|
|
||||||
bool marked_for_deletion() const { return state == CHAN_INACTIVE; }
|
bool marked_for_deletion() const { return state == CHAN_INACTIVE; }
|
||||||
};
|
};
|
||||||
@@ -45,6 +62,82 @@ class Driver {
|
|||||||
sslutil::UniqueCTX ssl_client_secure_ctx_;
|
sslutil::UniqueCTX ssl_client_secure_ctx_;
|
||||||
sslutil::UniqueCTX ssl_client_insecure_ctx_;
|
sslutil::UniqueCTX ssl_client_insecure_ctx_;
|
||||||
|
|
||||||
|
// Return the amount of 'space left' in a BIO. This is a fiction,
|
||||||
|
// because MEM BIOs technically have unlimited capacity. We're
|
||||||
|
// artificially limiting them to a certain size because there's no
|
||||||
|
// reason to buffer huge amounts of data.
|
||||||
|
//
|
||||||
|
int bio_space(BIO *bio) {
|
||||||
|
int space = (MAX_BIO_BUFFER) - BIO_pending(bio);
|
||||||
|
if (space < 0) space = 0;
|
||||||
|
return space;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a terribly inefficient way to discard data that has
|
||||||
|
// already been processed. There has to be something better.
|
||||||
|
//
|
||||||
|
void bio_discard(BIO *b, int nbytes) {
|
||||||
|
while (nbytes > 0) {
|
||||||
|
int nread = nbytes;
|
||||||
|
if (nread > DRV_SHORTSTRING_SIZE) nread = DRV_SHORTSTRING_SIZE;
|
||||||
|
int ndropped = BIO_read(b, chbuf_.get(), nread);
|
||||||
|
assert(ndropped == nread);
|
||||||
|
nbytes -= ndropped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void make_channel(SOCKET sock, int chid, SSL_CTX *ctx, ChanState state) {
|
||||||
|
ChanInfo newchan;
|
||||||
|
newchan.chid = chid;
|
||||||
|
newchan.socket = sock;
|
||||||
|
newchan.recv_bio = BIO_new(BIO_s_mem());
|
||||||
|
newchan.send_bio = BIO_new(BIO_s_mem());
|
||||||
|
newchan.recent_error.clear();
|
||||||
|
newchan.retry_write_nbytes = 0;
|
||||||
|
newchan.need_advance = true;
|
||||||
|
|
||||||
|
if (state == CHAN_PLAINTEXT) {
|
||||||
|
newchan.ssl = nullptr;
|
||||||
|
} else {
|
||||||
|
newchan.ssl = SSL_new(ctx);
|
||||||
|
SSL_set_bio(newchan.ssl, newchan.recv_bio, newchan.send_bio);
|
||||||
|
}
|
||||||
|
|
||||||
|
newchan.state = state;
|
||||||
|
newchan.nbytes = 0;
|
||||||
|
newchan.bytes = 0;
|
||||||
|
chans_.push_back(newchan);
|
||||||
|
}
|
||||||
|
|
||||||
|
void close_channel(ChanInfo &chan, std::string_view err) {
|
||||||
|
// std::cerr << "Closing channel " << chan.chid << " with " << err << std::endl;
|
||||||
|
assert(chan.state != CHAN_INACTIVE);
|
||||||
|
|
||||||
|
// Close and release the SSL channel.
|
||||||
|
// This frees the BIO objects as well.
|
||||||
|
if (chan.ssl != nullptr) {
|
||||||
|
SSL_free(chan.ssl);
|
||||||
|
chan.ssl = nullptr;
|
||||||
|
}
|
||||||
|
chan.recv_bio = nullptr;
|
||||||
|
chan.send_bio = nullptr;
|
||||||
|
chan.recent_error.clear();
|
||||||
|
chan.retry_write_nbytes = 0;
|
||||||
|
chan.need_advance = false;
|
||||||
|
|
||||||
|
// Close and release the socket.
|
||||||
|
assert(chan.socket != INVALID_SOCKET);
|
||||||
|
assert(socket_close(chan.socket) == 0);
|
||||||
|
chan.socket = INVALID_SOCKET;
|
||||||
|
|
||||||
|
// Close everything else.
|
||||||
|
engw.play_notify_close(&engw, chan.chid, err.size(), err.data());
|
||||||
|
chan.state = CHAN_INACTIVE;
|
||||||
|
chan.chid = -1;
|
||||||
|
chan.nbytes = 0;
|
||||||
|
chan.bytes = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void handle_listen_ports() {
|
void handle_listen_ports() {
|
||||||
uint32_t nports; const uint32_t *ports;
|
uint32_t nports; const uint32_t *ports;
|
||||||
engw.get_listen_ports(&engw, &nports, &ports);
|
engw.get_listen_ports(&engw, &nports, &ports);
|
||||||
@@ -65,34 +158,11 @@ class Driver {
|
|||||||
drvutil::ostringstream oss;
|
drvutil::ostringstream oss;
|
||||||
std::string err = drvutil::package_lua_source(".", &oss);
|
std::string err = drvutil::package_lua_source(".", &oss);
|
||||||
if_error_print_and_exit(err);
|
if_error_print_and_exit(err);
|
||||||
engw.play_set_lua_source(&engw, oss.size(), oss.c_str());
|
std::string_view ossv = oss.view();
|
||||||
|
engw.play_set_lua_source(&engw, ossv.size(), ossv.data());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_channel(ChanInfo &chan, std::string_view err) {
|
|
||||||
// std::cerr << "Closing channel " << chan.chid << std::endl;
|
|
||||||
assert(chan.state != CHAN_INACTIVE);
|
|
||||||
// Close and release the SSL channel.
|
|
||||||
if (chan.ssl != nullptr) {
|
|
||||||
SSL_free(chan.ssl);
|
|
||||||
chan.ssl = nullptr;
|
|
||||||
}
|
|
||||||
// Close and release the socket.
|
|
||||||
assert(chan.socket != INVALID_SOCKET);
|
|
||||||
assert(socket_close(chan.socket) == 0);
|
|
||||||
chan.socket = INVALID_SOCKET;
|
|
||||||
// Close everything else.
|
|
||||||
engw.play_notify_close(&engw, chan.chid, err.size(), err.data());
|
|
||||||
chan.state = CHAN_INACTIVE;
|
|
||||||
chan.chid = -1;
|
|
||||||
chan.nbytes = 0;
|
|
||||||
chan.bytes = 0;
|
|
||||||
chan.ready_now = false;
|
|
||||||
chan.ready_on_pollin = false;
|
|
||||||
chan.ready_on_pollout = false;
|
|
||||||
chan.ready_on_outgoing = false;
|
|
||||||
chan.last_write_nbytes = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void handle_console_output() {
|
void handle_console_output() {
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -117,25 +187,6 @@ class Driver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void make_channel(SOCKET sock, int chid, SSL_CTX *ctx, ChanState state) {
|
|
||||||
ChanInfo newchan;
|
|
||||||
newchan.chid = chid;
|
|
||||||
newchan.socket = sock;
|
|
||||||
newchan.ssl = SSL_new(ctx);
|
|
||||||
newchan.state = state;
|
|
||||||
newchan.nbytes = 0;
|
|
||||||
newchan.bytes = 0;
|
|
||||||
newchan.ready_now = false;
|
|
||||||
newchan.ready_on_pollin = false;
|
|
||||||
newchan.ready_on_pollout = true;
|
|
||||||
newchan.ready_on_outgoing = false;
|
|
||||||
newchan.last_write_nbytes = 0;
|
|
||||||
SSL_set_fd(newchan.ssl, newchan.socket);
|
|
||||||
// SSL_set_msg_callback(newchan.ssl, SSL_trace);
|
|
||||||
// SSL_set_msg_callback_arg(newchan.ssl, BIO_new_fp(stderr,0));
|
|
||||||
chans_.push_back(newchan);
|
|
||||||
}
|
|
||||||
|
|
||||||
void handle_new_outgoing_sockets() {
|
void handle_new_outgoing_sockets() {
|
||||||
uint32_t nchids; const uint32_t *chids;
|
uint32_t nchids; const uint32_t *chids;
|
||||||
engw.get_new_outgoing(&engw, &nchids, &chids);
|
engw.get_new_outgoing(&engw, &nchids, &chids);
|
||||||
@@ -166,7 +217,6 @@ class Driver {
|
|||||||
engw.play_notify_close(&engw, chid, err.size(), err.c_str());
|
engw.play_notify_close(&engw, chid, err.size(), err.c_str());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// std::cerr << "Opening channel " << chid << std::endl;
|
|
||||||
make_channel(sock, chid, ctx, CHAN_SSL_CONNECTING);
|
make_channel(sock, chid, ctx, CHAN_SSL_CONNECTING);
|
||||||
}
|
}
|
||||||
engw.play_clear_new_outgoing(&engw);
|
engw.play_clear_new_outgoing(&engw);
|
||||||
@@ -178,123 +228,188 @@ class Driver {
|
|||||||
if_error_print_and_exit(err);
|
if_error_print_and_exit(err);
|
||||||
if (socket != INVALID_SOCKET) {
|
if (socket != INVALID_SOCKET) {
|
||||||
uint32_t chid = engw.play_notify_accept(&engw, port);
|
uint32_t chid = engw.play_notify_accept(&engw, port);
|
||||||
// std::cerr << "Accepted channel " << chid << std::endl;
|
|
||||||
make_channel(socket, chid, ssl_server_ctx_.get(), CHAN_SSL_ACCEPTING);
|
make_channel(socket, chid, ssl_server_ctx_.get(), CHAN_SSL_ACCEPTING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void advance_plaintext(ChanInfo &chan) {
|
// Copy data from the socket into the recv bio.
|
||||||
std::string err;
|
//
|
||||||
|
// If it detects an error or EOF, sets the recent_errno flag.
|
||||||
|
//
|
||||||
|
void transfer_socket_to_recv_bio(ChanInfo &chan) {
|
||||||
|
if ((chan.state == CHAN_INACTIVE) || (!chan.recent_error.empty())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Try to write plaintext to the channel.
|
std::string err;
|
||||||
uint32_t ndata; const char *data;
|
int nread = socket_recv(chan.socket, chbuf_.get(), DRV_SHORTSTRING_SIZE, err);
|
||||||
engw.get_outgoing(&engw, chan.chid, &ndata, &data);
|
// std::cerr << "chan " << chan.chid << " recv " << nread << " err=" << err << std::endl;
|
||||||
if (ndata > 0) {
|
if (nread < 0) {
|
||||||
int sbytes = ndata;
|
chan.recent_error = err;
|
||||||
if (sbytes > DRV_SHORTSTRING_SIZE) sbytes = DRV_SHORTSTRING_SIZE;
|
} else {
|
||||||
int wbytes = socket_send(chan.socket, data, sbytes, err);
|
if (nread == 0) {
|
||||||
if (wbytes < 0) {
|
chan.recent_error = "EOF";
|
||||||
close_channel(chan, err.c_str());
|
|
||||||
} else {
|
} else {
|
||||||
engw.play_sent_outgoing(&engw, chan.chid, wbytes);
|
int nstored = BIO_write(chan.recv_bio, chbuf_.get(), nread);
|
||||||
|
assert(nstored == nread);
|
||||||
|
chan.need_advance = true;
|
||||||
|
// std::cerr << "chan " << chan.chid << " stored " << nread << " bytes" << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to read plaintext from the channel.
|
|
||||||
// Someday, find a way to avoid this copy.
|
|
||||||
int nrecv = socket_recv(chan.socket, chbuf_.get(), DRV_SHORTSTRING_SIZE, err);
|
|
||||||
if (nrecv < 0) {
|
|
||||||
close_channel(chan, err.c_str());
|
|
||||||
} else {
|
|
||||||
engw.play_recv_incoming(&engw, chan.chid, nrecv, chbuf_.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the ready-flags for next time.
|
|
||||||
chan.ready_on_outgoing = true;
|
|
||||||
chan.ready_on_pollin = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void process_ssl_error(ChanInfo &chan, int retval) {
|
// Copy data from the send BIO into the socket.
|
||||||
int error = SSL_get_error(chan.ssl, retval);
|
//
|
||||||
// std::cerr << "SSL error code = " << error << " ";
|
// If it detects an error, sets the recent_errno flag.
|
||||||
if (error == SSL_ERROR_WANT_READ) {
|
//
|
||||||
chan.ready_on_pollin = true;
|
void transfer_send_bio_to_socket(ChanInfo &chan) {
|
||||||
} else if (error == SSL_ERROR_WANT_WRITE) {
|
if ((chan.state == CHAN_INACTIVE) || (!chan.recent_error.empty())) {
|
||||||
chan.ready_on_pollout = true;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *data;
|
||||||
|
int ndata = BIO_get_mem_data(chan.send_bio, &data);
|
||||||
|
if (ndata > DRV_SHORTSTRING_SIZE) ndata = DRV_SHORTSTRING_SIZE;
|
||||||
|
std::string err;
|
||||||
|
int nwrote = socket_send(chan.socket, data, ndata, err);
|
||||||
|
// std::cerr << "chan " << chan.chid << " send " << nwrote << " err=" << err << std::endl;
|
||||||
|
if (nwrote < 0) {
|
||||||
|
chan.recent_error = err;
|
||||||
} else {
|
} else {
|
||||||
std::string error = sslutil::error_string();
|
assert(nwrote != 0);
|
||||||
if (error == "") error = "unknown error";
|
bio_discard(chan.send_bio, nwrote);
|
||||||
close_channel(chan, error);
|
chan.need_advance = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the channel if there's a serious OpenSSL error.
|
||||||
|
//
|
||||||
|
// The 'retval' is the return value of the SSL function that returned an
|
||||||
|
// error.
|
||||||
|
//
|
||||||
|
// All errors are considered serious except for SSL_ERROR_WANT_READ, which
|
||||||
|
// is not serious because it is transient. However, if you get an
|
||||||
|
// SSL_ERROR_WANT_READ when there's tons of data available in the read
|
||||||
|
// buffer, that's inexplicable and therefore serious.
|
||||||
|
//
|
||||||
|
void if_error_is_serious_close_channel(ChanInfo &chan, int retval) {
|
||||||
|
int error = SSL_get_error(chan.ssl, retval);
|
||||||
|
//std::cerr << "chan " << chan.chid << " ssl error = " << error << std::endl;
|
||||||
|
|
||||||
|
// Should never have write errors, because we're
|
||||||
|
// using a memory BIO with unlimited capacity.
|
||||||
|
assert(error != SSL_ERROR_WANT_WRITE);
|
||||||
|
|
||||||
|
// If we get a read error, make sure it's plausible:
|
||||||
|
// if the recv bio is full, that makes no sense.
|
||||||
|
if (error == SSL_ERROR_WANT_READ) {
|
||||||
|
if (bio_space(chan.recv_bio) == 0) {
|
||||||
|
close_channel(chan, "ssl waiting for data, but there's tons of data");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any other error is an actual error. Close
|
||||||
|
// the channel.
|
||||||
|
std::string errstr = sslutil::error_string();
|
||||||
|
if (errstr == "") errstr = "unknown error";
|
||||||
|
close_channel(chan, errstr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void advance_plaintext(ChanInfo &chan) {
|
||||||
|
uint32_t ndata; const char *data;
|
||||||
|
|
||||||
|
// Transfer all data from the recv BIO into the channel.
|
||||||
|
ndata = BIO_get_mem_data(chan.recv_bio, &data);
|
||||||
|
if (ndata > 0) {
|
||||||
|
engw.play_recv_incoming(&engw, chan.chid, ndata, data);
|
||||||
|
bio_discard(chan.recv_bio, ndata);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer all data from the channel to the send BIO.
|
||||||
|
engw.get_outgoing(&engw, chan.chid, &ndata, &data);
|
||||||
|
if (ndata > 0) {
|
||||||
|
int nwrote = BIO_write(chan.send_bio, data, ndata);
|
||||||
|
assert(nwrote == int(ndata));
|
||||||
|
engw.play_sent_outgoing(&engw, chan.chid, ndata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void advance_ssl_connecting(ChanInfo &chan) {
|
void advance_ssl_connecting(ChanInfo &chan) {
|
||||||
// std::cerr << "In advance_ssl_connecting" << std::endl;
|
|
||||||
int retval = SSL_connect(chan.ssl);
|
int retval = SSL_connect(chan.ssl);
|
||||||
|
//std::cerr << "chan " << chan.chid << " ssl_connect returns " << retval << std::endl;
|
||||||
if (retval == 1) {
|
if (retval == 1) {
|
||||||
// Connection successful.
|
|
||||||
chan.state = CHAN_SSL_READWRITE;
|
chan.state = CHAN_SSL_READWRITE;
|
||||||
chan.ready_now = true;
|
chan.need_advance = true;
|
||||||
} else {
|
} else {
|
||||||
// std::cerr << "ssl_connect_error";
|
if_error_is_serious_close_channel(chan, retval);
|
||||||
process_ssl_error(chan, retval);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void advance_ssl_accepting(ChanInfo &chan) {
|
void advance_ssl_accepting(ChanInfo &chan) {
|
||||||
// std::cerr << "In advance_ssl_accepting" << std::endl;
|
|
||||||
int retval = SSL_accept(chan.ssl);
|
int retval = SSL_accept(chan.ssl);
|
||||||
|
//std::cerr << "chan " << chan.chid << " ssl_accept returns " << retval << std::endl;
|
||||||
if (retval == 1) {
|
if (retval == 1) {
|
||||||
// Connection successful.
|
|
||||||
chan.state = CHAN_SSL_READWRITE;
|
chan.state = CHAN_SSL_READWRITE;
|
||||||
chan.ready_now = true;
|
chan.need_advance = true;
|
||||||
} else {
|
} else {
|
||||||
process_ssl_error(chan, retval);
|
if_error_is_serious_close_channel(chan, retval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void advance_ssl_readwrite(ChanInfo &chan) {
|
void advance_ssl_readwrite(ChanInfo &chan) {
|
||||||
// std::cerr << "In advance_ssl_readwrite" << std::endl;
|
// Read as much as we can, which of course will be limited
|
||||||
// Try to read data.
|
// by the fact that the recv_bio contains finite data.
|
||||||
int read_result = SSL_read(chan.ssl, chbuf_.get(), DRV_SHORTSTRING_SIZE);
|
while (true) {
|
||||||
if (read_result > 0) {
|
int read_result = SSL_read(chan.ssl, chbuf_.get(), DRV_SHORTSTRING_SIZE);
|
||||||
engw.play_recv_incoming(&engw, chan.chid, read_result, chbuf_.get());
|
if (read_result > 0) {
|
||||||
chan.ready_now = true;
|
engw.play_recv_incoming(&engw, chan.chid, read_result, chbuf_.get());
|
||||||
} else {
|
} else {
|
||||||
process_ssl_error(chan, read_result);
|
if_error_is_serious_close_channel(chan, read_result);
|
||||||
if (chan.state == CHAN_INACTIVE) return;
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The read process could have generated an error which could
|
||||||
|
// have closed the channel. If so, don't try writing.
|
||||||
|
if (chan.state == CHAN_INACTIVE) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to write data.
|
// Try to write data.
|
||||||
uint32_t wbytes;
|
while (chan.nbytes) {
|
||||||
if (chan.last_write_nbytes > 0) {
|
uint32_t wbytes;
|
||||||
wbytes = chan.last_write_nbytes;
|
if (chan.retry_write_nbytes > 0) {
|
||||||
assert(wbytes < chan.nbytes);
|
wbytes = chan.retry_write_nbytes;
|
||||||
} else {
|
assert(wbytes < chan.nbytes);
|
||||||
wbytes = chan.nbytes;
|
} else {
|
||||||
if (wbytes > 65536) wbytes = 65536;
|
wbytes = chan.nbytes;
|
||||||
}
|
if (wbytes > DRV_SHORTSTRING_SIZE) wbytes = DRV_SHORTSTRING_SIZE;
|
||||||
if (wbytes > 0) {
|
}
|
||||||
|
if (wbytes == 0) break;
|
||||||
int write_result = SSL_write(chan.ssl, chan.bytes, wbytes);
|
int write_result = SSL_write(chan.ssl, chan.bytes, wbytes);
|
||||||
if (write_result > 0) {
|
if (write_result > 0) {
|
||||||
engw.play_sent_outgoing(&engw, chan.chid, write_result);
|
engw.play_sent_outgoing(&engw, chan.chid, write_result);
|
||||||
chan.last_write_nbytes = 0;
|
chan.retry_write_nbytes = 0;
|
||||||
chan.ready_on_outgoing = true;
|
chan.nbytes -= write_result;
|
||||||
|
chan.bytes += write_result;
|
||||||
} else {
|
} else {
|
||||||
chan.last_write_nbytes = wbytes;
|
if_error_is_serious_close_channel(chan, write_result);
|
||||||
process_ssl_error(chan, write_result);
|
chan.retry_write_nbytes = wbytes;
|
||||||
if (chan.state == CHAN_INACTIVE) return;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
chan.ready_on_outgoing = true;
|
|
||||||
}
|
}
|
||||||
// std::cerr << "rpi=" << chan.ready_on_pollin << ".rpo=" <<
|
|
||||||
// chan.ready_on_pollout << ".rn=" << chan.ready_now << ".rog=" <<
|
|
||||||
// chan.ready_on_outgoing << " ";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void advance_channel(ChanInfo &chan) {
|
void advance_channel(ChanInfo &chan) {
|
||||||
sslutil::clear_all_errors();
|
sslutil::clear_all_errors();
|
||||||
|
|
||||||
|
// We set the need_advance flag to false here, but
|
||||||
|
// the rest of the advance routine is allowed to set
|
||||||
|
// it back to true in the event that the advance routine
|
||||||
|
// only processes some of the available data.
|
||||||
|
chan.need_advance = false;
|
||||||
|
|
||||||
switch (chan.state) {
|
switch (chan.state) {
|
||||||
case CHAN_PLAINTEXT:
|
case CHAN_PLAINTEXT:
|
||||||
advance_plaintext(chan);
|
advance_plaintext(chan);
|
||||||
@@ -349,13 +464,17 @@ class Driver {
|
|||||||
pfd.fd = chan.socket;
|
pfd.fd = chan.socket;
|
||||||
pfd.events = 0;
|
pfd.events = 0;
|
||||||
pfd.revents = 0;
|
pfd.revents = 0;
|
||||||
if (chan.ready_now) mstimeout = 0;
|
// If there's room in the receive buffer, set POLLIN
|
||||||
if (chan.ready_on_pollin) pfd.events |= POLLIN;
|
if (bio_space(chan.recv_bio) > 0) {
|
||||||
if (chan.ready_on_pollout) pfd.events |= POLLOUT;
|
pfd.events |= POLLIN;
|
||||||
if (chan.ready_on_outgoing && (chan.nbytes > 0))
|
}
|
||||||
|
// If there's data in the outgoing buffer, set POLLOUT
|
||||||
|
if (BIO_pending(chan.send_bio) > 0) {
|
||||||
pfd.events |= POLLOUT;
|
pfd.events |= POLLOUT;
|
||||||
// std::cerr << "evt=" << pfd.events << ".nb=" << chan.nbytes <<
|
}
|
||||||
// std::endl;
|
if (chan.need_advance) {
|
||||||
|
mstimeout = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the poll.
|
// Do the poll.
|
||||||
@@ -370,23 +489,26 @@ class Driver {
|
|||||||
accept_connection(p.first, p.second);
|
accept_connection(p.first, p.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Advance channels where possible.
|
// Advance channels where possible.
|
||||||
for (ChanInfo &chan : chans_) {
|
for (ChanInfo &chan : chans_) {
|
||||||
struct pollfd &pfd = pollvec_[index++];
|
struct pollfd &pfd = pollvec_[index++];
|
||||||
bool pollin = ((pfd.revents & POLLIN) != 0);
|
if ((pfd.revents & POLLIN) != 0) {
|
||||||
bool pollout = ((pfd.revents & POLLOUT) != 0);
|
transfer_socket_to_recv_bio(chan);
|
||||||
bool pollerr = ((pfd.revents & (POLLERR | POLLHUP)) != 0);
|
}
|
||||||
if (chan.ready_now || pollerr ||
|
if ((pfd.revents & POLLOUT) != 0) {
|
||||||
(chan.ready_on_pollin && pollin) ||
|
transfer_send_bio_to_socket(chan);
|
||||||
(chan.ready_on_pollout && pollout) ||
|
}
|
||||||
(chan.ready_on_outgoing && (chan.nbytes > 0) && pollout)) {
|
if (chan.need_advance || (!chan.recent_error.empty())) {
|
||||||
chan.ready_now = false;
|
|
||||||
chan.ready_on_pollin = false;
|
|
||||||
chan.ready_on_pollout = false;
|
|
||||||
chan.ready_on_outgoing = false;
|
|
||||||
advance_channel(chan);
|
advance_channel(chan);
|
||||||
}
|
}
|
||||||
|
if (!chan.recent_error.empty()) {
|
||||||
|
if (chan.recent_error == "EOF") {
|
||||||
|
close_channel(chan, "");
|
||||||
|
} else {
|
||||||
|
close_channel(chan, chan.recent_error);
|
||||||
|
}
|
||||||
|
chan.recent_error.clear();
|
||||||
|
}
|
||||||
chan.nbytes = 0;
|
chan.nbytes = 0;
|
||||||
chan.bytes = 0;
|
chan.bytes = 0;
|
||||||
}
|
}
|
||||||
@@ -420,6 +542,7 @@ class Driver {
|
|||||||
// Load the DLL and gain access to its functions.
|
// Load the DLL and gain access to its functions.
|
||||||
call_init_engine_wrapper(&engw);
|
call_init_engine_wrapper(&engw);
|
||||||
engw.replay_cb_sent_outgoing = replay_cb_sent_outgoing;
|
engw.replay_cb_sent_outgoing = replay_cb_sent_outgoing;
|
||||||
|
engw.hook_dprintf(dprintf_callback);
|
||||||
|
|
||||||
// If argv contains "replay <filename>", do a replay,
|
// If argv contains "replay <filename>", do a replay,
|
||||||
// and then skip everything else.
|
// and then skip everything else.
|
||||||
@@ -468,7 +591,8 @@ class Driver {
|
|||||||
if_error_print_and_exit(srcpakerr);
|
if_error_print_and_exit(srcpakerr);
|
||||||
|
|
||||||
// Initialize the engine.
|
// Initialize the engine.
|
||||||
engw.play_initialize(&engw, argc, argv, srcpak.size(), srcpak.c_str(), replaylogfn.c_str());
|
std::string_view srcpakv = srcpak.view();
|
||||||
|
engw.play_initialize(&engw, argc, argv, srcpakv.size(), srcpakv.data(), replaylogfn.c_str());
|
||||||
if_error_print_and_exit(engw.error);
|
if_error_print_and_exit(engw.error);
|
||||||
|
|
||||||
// Set up listening ports.
|
// Set up listening ports.
|
||||||
@@ -486,10 +610,10 @@ class Driver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
engw.release(&engw);
|
|
||||||
for (ChanInfo &chan : chans_) {
|
for (ChanInfo &chan : chans_) {
|
||||||
close_channel(chan, "");
|
close_channel(chan, "");
|
||||||
}
|
}
|
||||||
|
engw.release(&engw);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -149,40 +149,42 @@ static SOCKET accept_on_socket(SOCKET listen_socket, std::string &err) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the return values for socket_send and socket_recv are:
|
// the return values for socket_send:
|
||||||
//
|
//
|
||||||
// positive: sent or received bytes successfully
|
// positive: sent bytes successfully
|
||||||
// zero: would block
|
// negative: error.
|
||||||
// negative: channel closed, possibly cleanly or possibly with error
|
// If the error message is empty, then it's "would block"
|
||||||
|
// Any other error generates an error message.
|
||||||
//
|
//
|
||||||
static int socket_send(SOCKET socket, const char *bytes, int nbytes, std::string &err) {
|
static int socket_send(SOCKET socket, const char *bytes, int nbytes, std::string &err) {
|
||||||
err.clear();
|
|
||||||
int wbytes = send(socket, bytes, nbytes, 0);
|
int wbytes = send(socket, bytes, nbytes, 0);
|
||||||
if (wbytes < 0) {
|
if (wbytes < 0) {
|
||||||
if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
|
if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
|
||||||
return 0;
|
err.clear();
|
||||||
} else {
|
} else {
|
||||||
err = drvutil::strerror_str(errno);
|
err = drvutil::strerror_str(errno);
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
|
err.clear();
|
||||||
return wbytes;
|
return wbytes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int socket_recv(SOCKET socket, char *bytes, int nbytes, std::string &err) {
|
static int socket_recv(SOCKET socket, char *bytes, int nbytes, std::string &err) {
|
||||||
err.clear();
|
|
||||||
int nrecv = recv(socket, bytes, nbytes, 0);
|
int nrecv = recv(socket, bytes, nbytes, 0);
|
||||||
if (nrecv < 0) {
|
if (nrecv < 0) {
|
||||||
if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) {
|
if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
|
||||||
err = drvutil::strerror_str(errno);
|
err.clear();
|
||||||
return -1;
|
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
err = drvutil::strerror_str(errno);
|
||||||
}
|
}
|
||||||
} else if (nrecv == 0) {
|
|
||||||
return -1;
|
return -1;
|
||||||
|
} else if (nrecv == 0) {
|
||||||
|
err.clear();
|
||||||
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
|
err.clear();
|
||||||
return nrecv;
|
return nrecv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,37 +152,46 @@ static SOCKET accept_on_socket(SOCKET listen_socket, std::string &err) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the return values for socket_send:
|
||||||
|
//
|
||||||
|
// positive: sent bytes successfully
|
||||||
|
// negative: error.
|
||||||
|
// If the error message is empty, then it's "would block"
|
||||||
|
// Any other error generates an error message.
|
||||||
|
//
|
||||||
|
|
||||||
static int socket_send(SOCKET socket, const char *bytes, int nbytes, std::string &err) {
|
static int socket_send(SOCKET socket, const char *bytes, int nbytes, std::string &err) {
|
||||||
err.clear();
|
|
||||||
int wbytes = send(socket, bytes, nbytes, 0);
|
int wbytes = send(socket, bytes, nbytes, 0);
|
||||||
if (wbytes == SOCKET_ERROR) {
|
if (wbytes == SOCKET_ERROR) {
|
||||||
int errcode = WSAGetLastError();
|
int errcode = WSAGetLastError();
|
||||||
if (errcode == WSAEWOULDBLOCK) {
|
if (errcode == WSAEWOULDBLOCK) {
|
||||||
return 0;
|
err.clear();
|
||||||
} else {
|
} else {
|
||||||
err = "send failure";
|
err = "send failure";
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
assert(wbytes > 0);
|
assert(wbytes > 0);
|
||||||
|
err.clear();
|
||||||
return wbytes;
|
return wbytes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int socket_recv(SOCKET socket, char *bytes, int nbytes, std::string &err) {
|
static int socket_recv(SOCKET socket, char *bytes, int nbytes, std::string &err) {
|
||||||
err.clear();
|
|
||||||
int nrecv = recv(socket, bytes, nbytes, 0);
|
int nrecv = recv(socket, bytes, nbytes, 0);
|
||||||
if (nrecv < 0) {
|
if (nrecv < 0) {
|
||||||
int errcode = WSAGetLastError();
|
int errcode = WSAGetLastError();
|
||||||
if (errcode == WSAEWOULDBLOCK) {
|
if (errcode == WSAEWOULDBLOCK) {
|
||||||
return 0;
|
err = "";
|
||||||
} else {
|
} else {
|
||||||
err = "recv failure";
|
err = "recv failure";
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
} else if (nrecv == 0) {
|
|
||||||
return -1;
|
return -1;
|
||||||
|
} else if (nrecv == 0) {
|
||||||
|
err.clear();
|
||||||
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
|
err.clear();
|
||||||
return nrecv;
|
return nrecv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,23 +68,27 @@ double get_monotonic_clock();
|
|||||||
// without copying, use oss.size() and oss.c_str()
|
// without copying, use oss.size() and oss.c_str()
|
||||||
//
|
//
|
||||||
class ostringstream : public std::ostringstream {
|
class ostringstream : public std::ostringstream {
|
||||||
class rstringbuf : public std::stringbuf {
|
class rstringbuf : public std::basic_stringbuf<char_type, traits_type, allocator_type> {
|
||||||
public:
|
public:
|
||||||
char *eback() { return std::streambuf::eback(); }
|
char *eback() const { return std::streambuf::eback(); }
|
||||||
|
char *pptr() const { return std::streambuf::pptr(); }
|
||||||
};
|
};
|
||||||
rstringbuf rsbuf_;
|
rstringbuf rstringbuf_;
|
||||||
public:
|
public:
|
||||||
ostringstream() {
|
ostringstream() {
|
||||||
std::basic_ostream<char>::rdbuf(&rsbuf_);
|
std::basic_ostream<char>::rdbuf(&rstringbuf_);
|
||||||
}
|
}
|
||||||
size_t size() {
|
std::string_view view() const {
|
||||||
return tellp();
|
char *p = rstringbuf_.eback();
|
||||||
|
size_t size = rstringbuf_.pptr() - p;
|
||||||
|
return std::string_view(p, size);
|
||||||
}
|
}
|
||||||
const char *c_str() {
|
std::string str() const {
|
||||||
return rsbuf_.eback();
|
return rstringbuf_.str();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Remove items from a vector that are marked for deletion.
|
// Remove items from a vector that are marked for deletion.
|
||||||
//
|
//
|
||||||
template<class T>
|
template<class T>
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ class basic_ostringstream : public std::basic_ostringstream<C, T, eng::allocator
|
|||||||
using underlying = std::basic_ostringstream<C, T, eng::allocator<C>>;
|
using underlying = std::basic_ostringstream<C, T, eng::allocator<C>>;
|
||||||
using underlying::basic_ostringstream;
|
using underlying::basic_ostringstream;
|
||||||
};
|
};
|
||||||
|
//template<class C, class T=std::char_traits<C>>
|
||||||
|
//using basic_stringbuf = std::basic_stringbuf<C, T, eng::allocator<C>>;
|
||||||
using ostringstream = basic_ostringstream<char>;
|
using ostringstream = basic_ostringstream<char>;
|
||||||
|
//using stringbuf = basic_stringbuf<char>;
|
||||||
} // namespace eng
|
} // namespace eng
|
||||||
|
|
||||||
#endif // WRAP_SSTREAM_HPP
|
#endif // WRAP_SSTREAM_HPP
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
ut-table.lua
|
ut-table.lua
|
||||||
ut-globaldb.lua
|
|
||||||
ut-tablecmp.lua
|
ut-tablecmp.lua
|
||||||
basics.lua
|
basics.lua
|
||||||
uglyglobals.lua
|
uglyglobals.lua
|
||||||
|
|||||||
@@ -51,11 +51,6 @@ function unittests.diffcompare()
|
|||||||
-- Try a table containing a pointer to the global environment.
|
-- Try a table containing a pointer to the global environment.
|
||||||
assert(tdc({}, {a=_G}, {}, {}) == "a=globals;")
|
assert(tdc({}, {a=_G}, {}, {}) == "a=globals;")
|
||||||
|
|
||||||
-- GlobalDB tables should be forced to NIL.
|
|
||||||
assert(tdc({}, {a=global.table("foo")}, {}, {a=global.table("foo")}) == "a=nil;");
|
|
||||||
assert(tdc({}, {}, {}, {a=global.table("foo")}) == "a=nil;");
|
|
||||||
assert(tdc({}, {a=global.table("foo")}, {}, {}) == "");
|
|
||||||
|
|
||||||
-- Set up some numbered tables for tests involving such.
|
-- Set up some numbered tables for tests involving such.
|
||||||
local mtab10 = {}
|
local mtab10 = {}
|
||||||
local stab10 = {}
|
local stab10 = {}
|
||||||
@@ -116,11 +111,6 @@ function unittests.diffapply()
|
|||||||
-- verify a table containing the global environment.
|
-- verify a table containing the global environment.
|
||||||
assert(tda(tnmap, {a=_G}, {}))
|
assert(tda(tnmap, {a=_G}, {}))
|
||||||
|
|
||||||
-- GlobalDB tables should be forced to NIL.
|
|
||||||
rtab={a=3}
|
|
||||||
assert(not tda({}, {a=global.table("foo")}, rtab))
|
|
||||||
assert(rtab.a == nil)
|
|
||||||
|
|
||||||
-- Unnumbered tables should be forced to NIL.
|
-- Unnumbered tables should be forced to NIL.
|
||||||
rtab={a=3}
|
rtab={a=3}
|
||||||
assert(not tda({}, {a={}}, rtab))
|
assert(not tda({}, {a={}}, rtab))
|
||||||
|
|||||||
Reference in New Issue
Block a user