From bb8383737798be0dda7c7c4d18fba9346503348c Mon Sep 17 00:00:00 2001 From: jyelon Date: Wed, 20 Mar 2024 14:29:20 -0400 Subject: [PATCH] Lots of work on documenting class LuaStack --- luprex/cpp/core/json.cpp | 6 +- luprex/cpp/core/luastack.cpp | 25 +- luprex/cpp/core/luastack.hpp | 1411 ++++++++++++++++++++---------- luprex/cpp/core/serializelua.cpp | 2 +- luprex/cpp/core/util.hpp | 10 + luprex/cpp/core/world-core.cpp | 4 +- luprex/cpp/core/world.hpp | 2 +- 7 files changed, 981 insertions(+), 479 deletions(-) diff --git a/luprex/cpp/core/json.cpp b/luprex/cpp/core/json.cpp index c5aef3fc..cb7d1625 100644 --- a/luprex/cpp/core/json.cpp +++ b/luprex/cpp/core/json.cpp @@ -85,7 +85,7 @@ static bool encode_nil(lua_State *L, eng::ostringstream &oss) { static bool encode_token(lua_State *L, eng::ostringstream &oss) { LuaToken token(lua_touserdata(L, -1)); - if (token == LuaToken("jsonnull")) { + if (token == ltoken_json_null) { oss << "null"; return true; } else { @@ -312,7 +312,7 @@ static bool decode_number(lua_State *L, std::string_view &v) { // is OK. if (sv::valid_number(n, true, true, false, false)) { int64_t i = sv::to_int64(n); - if (!LuaCoreStack::int64_storable(i)) return false; + if (!LuaCoreStack::validinteger(i)) return false; lua_pushnumber(L, double(i)); return true; } else { @@ -362,7 +362,7 @@ static bool decode_int_string(lua_State *L, std::string_view &v) { // Make sure the number fits in a lua double, // and push it on the stack. int64_t i = sv::to_int64(n); - if (!LuaCoreStack::int64_storable(i)) { + if (!LuaCoreStack::validinteger(i)) { return false; } lua_pushnumber(L, double(i)); diff --git a/luprex/cpp/core/luastack.cpp b/luprex/cpp/core/luastack.cpp index f50a3338..325d9b3a 100644 --- a/luprex/cpp/core/luastack.cpp +++ b/luprex/cpp/core/luastack.cpp @@ -88,6 +88,9 @@ lua_State *LuaCoreStack::newstate (lua_Alloc allocf) { lua_pushstring(L, "tangibles"); lua_newtable(L); lua_rawset(L, LUA_REGISTRYINDEX); + lua_pushstring(L, "worldtype"); + lua_pushnumber(L, WORLD_TYPE_MASTER); + lua_rawset(L, LUA_REGISTRYINDEX); return L; } @@ -299,14 +302,14 @@ bool LuaCoreStack::getmetatable(LuaSlot mt, LuaSlot tab) const { } } -int LuaCoreStack::next(LuaSlot tab, LuaSlot key, LuaSlot value) const { +bool LuaCoreStack::next(LuaSlot tab, LuaSlot key, LuaSlot value) const { lua_pushvalue(L_, key); int ret = lua_next(L_, tab); if (ret != 0) { lua_replace(L_, value); lua_replace(L_, key); } - return ret; + return (ret != 0); } void LuaCoreStack::getglobaltable(LuaSlot target) const { @@ -334,14 +337,6 @@ bool LuaCoreStack::valididentifier(std::string_view str) { return sv::is_lua_id(str); } -bool LuaCoreStack::validint64(int64_t value) { - return (value <= MAXINT) && (value >= -MAXINT); -} - -bool LuaCoreStack::validpositiveint64(int64_t value) { - return (value <= MAXINT) && (value >= 1); -} - bool LuaCoreStack::validclassname(std::string_view cname) { if (cname == "_G") return false; return valididentifier(cname); @@ -473,7 +468,7 @@ void LuaCoreStack::makeclass(LuaSlot tab, std::string_view name) const { } void LuaCoreStack::maketan(LuaSlot tab, int64_t id) const { - assert(validpositiveint64(id)); + assert(validpositiveinteger(id)); LuaVar tangibles, metatab; LuaExtStack LS(L_, tangibles, metatab); @@ -627,7 +622,7 @@ void LuaCoreStack::setvisited(LuaSlot tab, bool visited) const { lua_modflagbits(L_, tab.index(), 0x0010, visited ? 0x0010 : 0); } -WorldType LuaCoreStack::world_type() const { +WorldType LuaCoreStack::get_world_type() const { lua_pushstring(L_, "worldtype"); lua_rawget(L_, LUA_REGISTRYINDEX); lua_Integer n = lua_tointeger(L_, -1); @@ -636,6 +631,12 @@ WorldType LuaCoreStack::world_type() const { return (WorldType)n; } +void LuaCoreStack::set_world_type(WorldType t) const { + lua_pushstring(L_, "worldtype"); + lua_pushnumber(L_, (int)t); + lua_rawset(L_, LUA_REGISTRYINDEX); +} + void LuaCoreStack::guard_nopredict(const char *fn) { if (lua_isyieldable(L_)) { if (!is_authoritative()) { diff --git a/luprex/cpp/core/luastack.hpp b/luprex/cpp/core/luastack.hpp index b2d0bd06..0a76b032 100644 --- a/luprex/cpp/core/luastack.hpp +++ b/luprex/cpp/core/luastack.hpp @@ -1,141 +1,371 @@ ///////////////////////////////////////////////////////// // // -// LuaStack +// GENERAL CONCEPT // -// The standard lua C API asks you to work with a stack machine. You're supposed -// to manually push and pop values on the lua stack. I find this difficult, I -// find it hard to remember what stack position contains what value. +// The original lua C API asks you to work with a stack machine. When you +// use the original API, you're manually pushing and popping in every +// line of code. I find it hard to remember what stack position contains +// what value. This wrapper is designed to alleviate that problem. // -// To make it easier, I've created this module, "LuaStack." This module creates -// the illusion that you're working with local variables that contain lua -// values. +// However, the lua garbage collector demands that we keep all lua values +// on the lua stack. I can't change that. I can create a wrapper, but I +// still have to keep all lua values on the lua stack. So, here's how +// this wrapper works: // -// Of course, this is all using the lua stack under the covers. Lua local -// variables are actually just lua stack addresses. But that's all kept fairly -// well hidden. When you use Lua local variables, and the accessors inside -// class LuaStack, it appears that you're manipulating data using local -// variables instead of using a stack. For people like me, that's easier to -// think about. +// At the beginning of any C++ lua function, we allocate enough space on +// the lua stack to hold all the arguments, all the return values, and all +// the temporary values that we need. We give each of the stack slots +// that we allocate a human-readable name. From that point forward, +// we don't push or pop anything on the lua stack. Instead, we do all our +// lua value manipulation using the stack slots that we allocated and named +// at the beginning of the function. // -// Here's an example. // -// let's say you have a function that takes two arguments ARG1 and ARG2, has a -// single return value RET1, and needs two local variables LOC1 and LOC2. We -// would declare it like this: +///////////////////////////////////////////////////////// // -// int myfunc(lua_State *L) { +// LUAARG, LUARET, LUAVAR, and LUADEFSTACK // -// LuaArg arg1, arg2; // Declare local variables to hold the arguments. -// LuaRet ret1; // Declare local variables to hold the return values. -// LuaVar loc1, loc2, loc3; // Declare local variables for other purposes. +// The best way to explain this wrapper is with an example. This +// function, 'table_removevalue', takes a table and a 'removethis' +// value. It searches the table for any (key,val) pairs +// where val==removethis, and it removes those pairs. // -// // Assign every local var a stack index. -// LuaDefStack LS(L, arg1, arg2, ret1, loc1, loc2, loc3); -// -// // manipulate the data in the lua local variables... -// LS.rawget(loc1, arg1, arg2); -// ... etc ... +// int table_removevalue(lua_State *L) { +// LuaArg table, removethis; +// LuaRet returnvalue; +// LuaVar key, val; +// LuaDefStack LS(L, table, removethis, returnvalue, key, val); +// LS.set(key, LuaNil); +// while (LS.next(table, key, val)) { +// if (LS.rawequal(val, removethis)) { +// LS.rawset(table, key, LuaNil); +// } +// } +// LS.set(returnvalue, table); +// return LS.result(); // } // -// Class LuaArg, LuaRet, and LuaVar are all lua local variables. The LuaDefStack -// constructor assigns each one of them a position on the lua stack. It also -// makes sure that the arguments are in the LuaArg variables, and it makes sure -// that the LuaRet values are the only thing left on the stack at return time. +// Now I'll explain this function. First, you have some declarations for +// LuaArg (lua function arguments), LuaRet (lua function return values), and +// LuaVar (lua local variables). These are small structs each containing +// an 'int' indicating an absolute position on the lua stack. These are all +// collectively called lua stack slots. Class LuaArg, LuaRet, and +// LuaVar all derive from a base class LuaSlot. // -// Class LuaDefStack provides a complete catalog of accessors like 'rawget' - -// roughly speaking, it provides equivalents to every major accessor in the lua -// API. However, the accessors provided by LuaDefStack take input and output -// from lua locals, not from the stack. For example, consider this: +// At construction time, all the LuaSlots are initialized to zero, which +// means they aren't ready to use yet. They don't point to anywhere on the +// lua stack. Remember, lua numbers everything starting at 1, so zero is +// not a valid lua stack position. // -// LS.rawget(value, tab, key); +// After the LuaSlot declarations, you have the LuaDefStack constructor. +// This takes the lua stack as a parameter, and then all the LuaSlots. +// This allocates space on the lua stack for all the LuaSlots. When it's +// done, the lua stack will contain exactly five values, one for each of the +// five LuaSlots. The LuaSlot structs will contain the stack positions of +// these values. The LuaRet and LuaVar stack slots will be initialized to nil. +// The LuaArg stack slots will be initialized with the function arguments. +// After the LuaDefStack constructor, you are ready to do lua calculations. // -// In the above, value, tab, and key should be lua local variables. This does a -// rawget on 'table', with the specified 'key', and stores the result in -// 'value'. Nothing is added to or removed from the lua stack. In general, -// none of the accessors in class LuaDefStack add anything to the stack, or pop -// anything from the stack. +// Next, you have the loop that iterates over the table. To iterate over +// a table in lua, you initialize 'key' to nil, then you call the 'next' +// operator to get the next (key,val) pair. The 'next' operator will return +// false if there is no next pair. For each (key,val) pair in the table, +// we check if the val is equal to the thing we want to remove, and if so, +// we change the table entry to nil. After the loop, we set the returnvalue +// slot to the table that was passed in. // -// Class LuaDefStack can also do automatic type conversions. For example, -// suppose you do this: +// The last line, return LS.result(), is a piece of boilerplate, every lua +// C function must end with this. This function removes everything but the +// return values from the stack, and returns the number of return values. // -// LS.rawget(value, tab, key); // -// Nominally, you would expect value, tab, and key to be lua local variables. -// But if you pass a eng::string for key, then LuaDefStack will automatically -// convert it. +///////////////////////////////////////////////////////// // -// On output, LuaDefStack can convert lua_Integers, lua_Numbers, and -// eng::strings. In this case, strict type checking is done. If there is a -// type mismatch, a lua error is thrown. +// CLASS LUAEXTSTACK. // -// You can use the operator 'set' to assign a value to a lua local variable: +// Class LuaDefStack, which I showed above, is meant to be used at the +// top level of a C++ lua function. +// +// Class LuaExtStack is meant to be used when you want to allocate +// some MORE stack slots halfway through a C++ lua function. +// Class LuaExtStack is particularly useful when you want to write +// a recursive implementation. Typically you would put LuaDefStack +// in the top-level function, and LuaExtStack in the recursive +// implementation. +// +// Class LuaExtStack differs in two ways: first of all, it doesn't allow +// LuaArg and LuaRet slots, only LuaVar. Second, it has a destructor that +// automatically puts the stack back the way it was when it was constructed. +// +// You might wonder why class LuaDefStack doesn't have a destructor that +// cleans up the lua stack. It's because of return values: it can't remove +// everything from the stack, because it has to leave the return values +// on the stack. +// +// I call these two classes the 'LuaStack' classes. When I say that +// the LuaStack classes do something, I mean that both LuaDefStack +// and LuaExtStack do that thing. +// +// +///////////////////////////////////////////////////////// +// +// THE LIBRARY OF BUILTIN OPERATORS +// +// The LuaStack classes provide a whole library of methods to operate +// on lua values. Roughly speaking, there is one function in LuaStack +// for every function in the raw lua API, and they are similarly named. +// +// However, the functions in the raw lua API push and pop values on the +// lua stack. The equivalent functions in LuaStack take inputs from +// LuaSlots, and store their outputs into other LuaSlots. Nothing is +// pushed or popped. +// +// To get the complete list, you will have to scroll down to class +// LuaCoreStack, below. All the prototypes are there, and they are all +// documented. +// +// +///////////////////////////////////////////////////////// +// +// AUTOMATIC TYPE CONVERSION +// +// In some cases, LuaStack can do automatic conversions of C++ values into +// lua values. This is supported in any function where it makes sense. +// One function that supports automatic conversion is 'LuaStack::set': // // LS.set(val1, val2); // // This is actually a copy operation that copies from one lua local variable to -// another. But using type conversions, it can also be used to assign arbitrary -// values to lua local variables, or to get values from lua local variables. +// another. But using auto conversions, it can also be used to assign C++ +// values to lua slots. These are all valid: // -// Passing LuaNewTable as an input will cause a new table to be created before -// calling the specified operation. +// LS.set(value, 1); // int and int64_t can be converted. +// LS.set(value, "banana"); // char*, string, and string_view can be converted. +// LS.set(value, true); // bool can be converted. +// LS.set(value, 2.39); // float and double can be converted. +// LS.set(value, LuaNil); // This special token stores nil. +// +// Automatic conversion is generally allowed in any context where it would +// make sense. For example, these are all valid: +// +// LS.rawget(value, mytable, 1); +// LS.rawget(value, mytable, "banana"); +// LS.rawget(value, mytable, true); +// LS.rawget(value, mytable, 2.39); +// LS.rawget(value, mytable, LuaNil); +// +// There's also the 'LuaNewTable' constant. This is handled by the +// auto-conversion system, but it's not really a conversion: +// +// LS.set(value, LuaNewTable); +// +// This will cause a new table to be created, and stored in value. // // ///////////////////////////////////////////////////////// // +// API INTEROPERABILITY // -// LuaDefStack type checking +// This wrapper can intentionally be mixed with the standard, raw lua API. +// You still have an explicit handle to the lua stack, and you can get the +// stack addresses out of the LuaSlot variables using LuaSlot::index. So +// if there's some tricky thing you can't do with this wrapper, you can always +// fall back to the raw API for just the section of code that you need to. // -// LuaDefStack contains accessors for type checking. These include: +// I also sometimes use the raw lua API for code that is doing truly +// unusual things, that aren't easy to do with this wrapper. // -// bool LuaDefStack::isnumber(LuaSlot s) -// bool LuaDefStack::isinteger(LuaSlot s) -// bool LuaDefStack::isstring(LuaSlot s) -// etc... +///////////////////////////////////////////////////////// // -// And it also contains operations that throw errors: // -// void LuaDefStack::checknumber(LuaSlot s) -// void LuaDefStack::checkinteger(LuaSlot s) -// void LuaDefStack::checkstring(LuaSlot s) -// etc... +// ON RAWGET AND METAMETHODS // -// These are different from the lua builtins in that they are strict. For -// example, 'isnumber' only returns true if the value in the lua local variable -// is already a number. No conversions are done. +// The raw lua API provides functions like lua_gettab and lua_equal +// which automatically call metamethods. I do not think it is wise to +// habitually use functions like that, because some of the code we write +// is not executed in a protected context. That means that a metamethod +// that generates an error would bring down the whole system rather than +// just stopping a script thread. It also means that a metamethod could +// return an invalid piece of data, corrupting a system data structure +// rather than just a script data structure. // -// These functions do checking and also conversion at the same time: +// Because there are so many contexts where it is just not safe to call +// metamethods, I've made it deliberately difficult to call metamethods. +// Our API includes 'rawget' which doesn't call metamethods, +// but it omits 'gettab' which does. // -// lua_Integer LuaDefStack::ckinteger(LuaSlot s) -// lua_Number LuaDefStack::cknumber(LuaSlot s) -// eng::string LuaDefStack::ckstring(LuaSlot s) -// lua_State *LuaDefStack::ckthread(LuaSlot s) +// If someday we actually need metamethod support, we can do that, +// but I'll have to write a safe wrapper for them. I know how to do that, +// but it's a lot of work and I'm not going to bother unless it's needed. // -// Like the other operations, they are strict. // +///////////////////////////////////////////////////////// +// +// LUA TABLE TYPES +// +// We have modified the lua interpreter to allow us to assign +// table subtypes to different tables. Most lua tables are +// marked as 'general' tables. If you create a table using the +// lua newtable operator, you'll get a general table. +// +// Aside from general tables, we have special table types for the +// lua registry, for lua global environment tables, for tangibles, +// for tangible metatables, and for classes. These tables get +// handled specially in various parts of the code. +// +// +///////////////////////////////////////////////////////// +// +// LIGHTUSERDATA AND CLASS LUATOKEN +// +// Before I tell you what tokens are, I'm going to tell you what problem +// I was trying to solve that led me to create tokens. +// +// I was trying to write a JSON to LUA converter. That's mostly +// straightforward. Json tables are very similar to lua tables. +// For example, consider this JSON: +// +// { +// "name": "John Smith", +// "alive": true, +// "address": { +// "city": "New York", +// "state": "NY", +// }, +// "spouse": null +// } +// +// That converts very straightforwardly to a lua table: +// +// { +// name = "John Smith", +// alive = true, +// address = { +// city = "New York", +// state = "NY", +// }, +// spouse = nil +// } +// +// There's only one problem here: that "spouse" line doesn't really work. +// Setting a key to nil in lua is the same as not setting that key at all. +// That's not correct. Instead of storing json null as lua nil, +// we could store json null as the string "null". But that would be make it +// impossible to parse and store the literal string "null". That's not correct +// either. +// +// Lua has a datatype called 'lightuserdata'. A lightuserdata holds an +// int64. That gives me an option: I can store json null as +// lightuserdata(0x6E756C6C00000000). When we see this lightuserdata +// value, we would know we have a json null. Why 0x6E756C6C00000000? +// Because if you interpret those 8 bytes as 8 ascii characters, it's the +// string "null". +// +// So that finally brings me to what a "token" is. A token is a lightuserdata +// containing up to 8 ascii characters. So in effect, it's a short string, +// but it's a string that's distinguishable from a normal lua string. It +// doesn't have the same type as a lua string (it shows up as a lightuserdata). +// The purpose of tokens is to represent special unique values, like json null. +// +// To make working with tokens easy, I've created a C++ class 'LuaToken'. +// It stores an int64. You can construct a LuaToken in two different ways: +// +// LuaToken(0x6E756C6C00000000) +// LuaToken("null") +// +// Those are equivalent. The second form is just as fast as the first, +// because of C++ constexpr magic. You can use our automatic type conversion +// system to automatically convert C++ LuaToken structs into lua lightuserdata +// values, like this: +// +// LS.set(value, LuaToken("null")) +// +// When our pretty-printer is printing out lua data structures, and it +// encounters a lightuserdata, it prints it out as a token, ie, as a short +// string, but using brackets instead of quotation marks. +// +///////////////////////////////////////////////////////// +// +// LUA CLASSES +// +// This module adds the concept of a 'class' to lua. The function +// LuaStack::makeclass creates a class. This function is exposed to lua. +// For example, you could create a lua class to manipulate deques: +// +// makeclass("deque") +// +// This creates a new table, which you will find in the global +// environment under the name 'deque'. The class is a table for +// storing functions related to deques. You can now add lua functions +// to the class: +// +// function deque.insertr(...) +// end +// +// If you call makeclass a second time with the same classname, this +// is a no-op. This is useful because if you have two sourcefiles that +// both add functions to class 'deque', they can both makeclass 'deque', +// and no conflict will occur. +// +// Class tables contain their own classname. This is primarily for +// ease of debugging. C.__class = "deque" +// +// Class tables have a distinct LuaTableType: LUA_TT_CLASS. That +// way, it is easy to tell that the table is intended as a class. +// Class tables are treated uniquely by our engine in several ways. +// +// A class also has a field "__index" which points to itself. +// C.__index = C. That makes it possible to use the class as a +// metatable, and any attempt to look up a name in the table +// that fails will then attempt to look up that name in the class. +// +// +///////////////////////////////////////////////////////// // // LUADEFINE // -// LuaDefine is a macro that defines a C function which is exposed to lua. It -// creates a global registry of functions created with LuaDefine. You use it -// like so: +// LuaDefine is a macro that defines a C function which is exposed to lua. +// In addition to actually defining the C function, it also creates a global +// registry of all such functions. The registry is used to actually +// know which functions to inject into lua. // -// LuaDefine(function_name, "arguments", "documentation") { -// ... +// Here is an example of a typical LuaDefine: +// +// LuaDefine(table_removevalue, "table, removethis", +// "|This function removes a specified value from a table." +// "|", +// "|Iterates over all the (key, val) pairs in a table and" +// "|removes the ones where val == removethis." +// ) { +// LuaArg table, removethis; +// LuaRet returnvalue; +// LuaVar key, val; +// LuaDefStack LS(L, table, removethis, returnvalue, key, val); +// LS.set(key, LuaNil); +// while (LS.next(table, key, val)) { +// if (LS.rawequal(val, removethis)) { +// LS.rawset(table, key, LuaNil); +// } // } +// LS.set(returnvalue, table); +// return LS.result(); +// } // -// This macroexpands into a function definition and a function registration. -// The function definition looks like this: +// So you might notice that this is the same example function from +// earlier, but this time with LuaDefine. If you omit the LuaDefine +// and write the function as it was shown earlier, you will get a +// function that can be called from C++, and which works fine when +// called from C++, but it won't be visible from lua. // -// int function_name(lua_State *L) { -// ... -// } -// -// The macro expansion generates this function definition, but it also generates -// a "registration object" whose constructor puts this function into a global -// registry of lua-callable C functions. This global registry is later used to -// inject these C functions into the lua intepreter. +// The example function above will show up in lua as 'table.removevalue'. +// This lua function name is derived straightforwardly from the C++ +// function name. // +// Note that both of the string parameters to LuaDefine are part of the +// function documentation, neither has any effect on how the lua function +// behaves. The function documentation goes into the registry and from +// there is accessible through the lua documentation system. // ///////////////////////////////////////////////////////// @@ -156,24 +386,113 @@ #include "lualib.h" #include "eris.h" +//////////////////////////////////////////////////////////////////// +// +// LUA TABLE TYPES +// +//////////////////////////////////////////////////////////////////// + +enum LuaTableType { + LUA_TT_GENERAL = LUA_NUMTAGS, + LUA_TT_REGISTRY, + LUA_TT_GLOBALENV, + LUA_TT_TANGIBLE, + LUA_TT_TANGIBLEMETA, + LUA_TT_CLASS, + + LUA_TT_SENTINEL +}; + +//////////////////////////////////////////////////////////////////// +// +// LuaToken +// +//////////////////////////////////////////////////////////////////// + +struct LuaToken { +public: + uint64_t value; + + // Get rid of the default constructors. + // + template + LuaToken(T arg) = delete; + + // Construct a token from a short string. + // + constexpr LuaToken(const char *str) : value(literal_to_token(str)) {} + + // Construct a token from an int64. + // + LuaToken(uint64_t v) : value(v) {} + + // Construct a token from a void pointer. + // + LuaToken(void *v) : value((uint64_t)v) {} + + // Default constructor: construct the empty token. + // + LuaToken() : value(0) {} + + // Empty: return true if the token is all zero bytes. + // + bool empty() const { return value == 0; } + + // Compare two tokens for equality. + // + bool operator ==(const LuaToken &other) const { return value == other.value; } + + // Convert the token to a void pointer. + // + void *voidvalue() const { return (void*)value; } + + // Convert the token to a string. + // + eng::string str() const; + +private: + static constexpr uint64_t literal_to_token(const char *str) { + uint64_t result = 0; + for (int i = 0; i < 8; i++) { + unsigned char c = *str; + result = (result << 8) + c; + if (*str) str++; + } + return result; + } +}; + +//////////////////////////////////////////////////////////////////// +// +// LuaSlots. +// +//////////////////////////////////////////////////////////////////// + class LuaSlot : public eng::nevernew { protected: int index_; constexpr LuaSlot(int n) : index_(n) {} -private: - inline operator int() const { - return index_; - } - public: + // LuaSlots are normally constructed without arguments. + // They are uninitialized until the LuaStack constructor runs. + // constexpr LuaSlot() : index_(0) {} + // You can fetch the stack index from the LuaSlot. + // inline int index() const { return index_; } +private: + // Our code can fetch the stack index using an implicit conversion. + // + inline operator int() const { + return index_; + } + friend class LuaCoreStack; friend class LuaDefStack; friend class LuaExtStack; @@ -190,6 +509,12 @@ public: extern LuaSpecial LuaRegistry; +//////////////////////////////////////////////////////////////////// +// +// LuaExtraArgs +// +//////////////////////////////////////////////////////////////////// + class LuaExtraArgs { private: int index_; @@ -208,6 +533,535 @@ public: friend class LuaDefStack; }; +//////////////////////////////////////////////////////////////////// +// +// LuaNil and LuaNewTable +// +//////////////////////////////////////////////////////////////////// + +class LuaNilMarker {}; +extern LuaNilMarker LuaNil; + +class LuaNewTableMarker {}; +extern LuaNewTableMarker LuaNewTable; + +//////////////////////////////////////////////////////////////////// +// +// LuaCoreStack +// +// This is the common base class for LuaDefStack and LuaExtStack. +// You should use one of those classes in your code, not this class. +// However, this class is where all the interesting operators on lua +// local variables resides. +// +//////////////////////////////////////////////////////////////////// + +class LuaCoreStack : public eng::nevernew { +protected: + lua_State *L_; + +public: + // Constructor. You should almost never use this, instead, + // you should construct a LuaDefStack or a LuaExtStack. + // + LuaCoreStack(lua_State *L) : L_(L) {} + + // Get the raw pointer to the lua_State. + // + lua_State *state() const { return L_; } + + // Turn a Lua value into a C++ value, if possible. + // + // If the lua value doesn't match the desired type, then these return + // an empty optional. The ones that return bool only verify + // the value's type, they don't actually fetch the value. + // + std::optional tryboolean(LuaSlot s) const; + std::optional tryinteger(LuaSlot s) const; + std::optional tryint(LuaSlot s) const; + std::optional trynumber(LuaSlot s) const; + std::optional trystring(LuaSlot s) const; + std::optional trystringview(LuaSlot s) const; + std::optional trythread(LuaSlot s) const; + std::optional trytoken(LuaSlot s) const; + std::optional tryxyz(LuaSlot s) const; + bool trytable(LuaSlot s) const; + bool trynil(LuaSlot s) const; + bool tryfunction(LuaSlot s) const; + bool trycfunction(LuaSlot s) const; + bool trytangible(LuaSlot s) const; + + // Turn a lua value into a C++ value, or throw an error. + // + // If the lua value doesn't match the desired type, + // then these throw a lua error. It is invalid to use these + // outside of a protected context. The argname is used + // for making a nice error message. + // + bool ckboolean(LuaSlot s, const char *argname = "value") const; + lua_Integer ckinteger(LuaSlot s, const char *argname = "value") const; + int ckint(LuaSlot s, const char *argname = "value") const; + lua_Number cknumber(LuaSlot s, const char *argname = "value") const; + eng::string ckstring(LuaSlot s, const char *argname = "value") const; + std::string_view ckstringview(LuaSlot s, const char *argname = "value") const; + lua_State * ckthread(LuaSlot s, const char *argname = "value") const; + LuaToken cktoken(LuaSlot s, const char *argname = "value") const; + util::DXYZ ckxyz(LuaSlot s, const char *argname = "value") const; + void cktable(LuaSlot s, const char *argname = "value") const; + void cknil(LuaSlot s, const char *argname = "value") const; + void ckfunction(LuaSlot s, const char *argname = "value") const; + void ckcfunction(LuaSlot s, const char *argname = "value") const; + void cktangible(LuaSlot s, const char *argname = "value") const; + + // Check if a lua value can be converted to C++. + // + // These functions check if a value can be converted + // to a C++ value. They don't actually return the C++ value. + // It is more efficient to use the 'try' or 'ck' functions above if + // you also want the value. + // + bool isboolean(LuaSlot s) const { return lua_type(L_, s) == LUA_TBOOLEAN; } + bool isinteger(LuaSlot s) const { return bool(tryinteger(s)); } + bool isint(LuaSlot s) const { return bool(tryint(s)); } + bool isnumber(LuaSlot s) const { return lua_type(L_, s) == LUA_TNUMBER; } + bool isstring(LuaSlot s) const { return lua_type(L_, s) == LUA_TSTRING; } + bool isstringview(LuaSlot s) const { return lua_type(L_, s) == LUA_TSTRING; } + bool isthread(LuaSlot s) const { return lua_type(L_, s) == LUA_TTHREAD; } + bool istoken(LuaSlot s) const { return lua_type(L_, s) == LUA_TLIGHTUSERDATA; } + bool isxyz(LuaSlot s) const { return bool(tryxyz(s)); } + bool istable(LuaSlot s) const { return lua_istable(L_, s); } + bool isnil(LuaSlot s) const { return lua_isnil(L_, s); } + bool isfunction(LuaSlot s) const { return lua_isfunction(L_, s); } + bool iscfunction(LuaSlot s) const { return lua_iscfunction(L_, s); } + bool istangible(LuaSlot s) const { return bool(trytangible(s)); } + + // Create a new interpreter using the specified allocator. + // + // Typically, the allocator used would be eng::l_alloc. + // You can also pass nullptr to use a default allocator based + // on malloc. + // + static lua_State *newstate (lua_Alloc allocf); + + // Create a new thread. + // + // The target parameter is an output parameter, this will contain + // the new thread. Also returns a C++ pointer to the thread. Remember + // that the C++ pointer by itself doesn't prevent garbage collection, + // you must keep the thread in the LuaSlot or in some other lua data + // structure to prevent it from getting garbage collected. + // + lua_State *newthread(LuaSlot target) const; + + // Get the type of a LuaSlot. + // + // Returns one of the standard lua type tags. These include: + // + // LUA_TBOOLEAN, LUA_TNUMBER, LUA_TSTRING, LUA_TTABLE, LUA_TFUNCTION, + // LUA_TTHREAD, LUA_TLIGHTUSERDATA, LUA_TUSERDATA. + // + int type(LuaSlot s) const { return lua_type(L_, s); } + + // Get the extended type of a LuaSlot. + // + // If the variable contains a table, returns one of the LuaTableType + // constants. Search for this enum above. If it is not a table, + // returns one of the standard lua type tags. See the 'type' + // method above. + // + int xtype(LuaSlot slot) const; + + // Get the table type of a lua table. + // + // Tab must contain a lua table. Returns a value from enum LuaTableType. + // + int gettabletype(LuaSlot tab) const; + + // Set the table type of a lua table. + // + // Tab must contain a lua table. T must be a value from enum LuaTableType. + // + void settabletype(LuaSlot tab, int t) const; + + // Get the length of a lua string. + // + // Techically, you can also use this on tables, but it's not recommended. + // Instead, use 'nkeys' below. The semantics of rawlen on tables + // is just plain weird: see lua documentation if you are curious. + // + int rawlen(LuaSlot val) const; + + // Get the number of key/value pairs in a lua table. + // + // This works on any table, even tables where the keys aren't integers. + // + int nkeys(LuaSlot tab) const; + + // Get the metatable of a table. + // + // Tab must be a table. The metatable of tab is stored in mt. + // + bool getmetatable(LuaSlot mt, LuaSlot tab) const; + + // Set the metatable of a table. + // + // Tab must be a table. Mt must be a table or nil. + // + void setmetatable(LuaSlot tab, LuaSlot mt) const; + + // Remove the metatable from a table. + // + // Tab must be a table. The metatable, if any, is removed from tab. + // + void clearmetatable(LuaSlot tab) const; + + // Create a new table. + // + // The new table is stored in target. + // + void newtable(LuaSlot target) const; + + // Create a new table with a storage hint. + // + // The new table is stored in target. The new table has space + // pre-allocated for narr array elements and nrec non-array elements. + // + void createtable(LuaSlot target, int narr, int nrec) const; + + // Get the global environment table. + // + // The global environment table is stored in gltab. + // + void getglobaltable(LuaSlot gltab) const; + + // Delete everything in a table. + // + // Tab must be a table. Removes all (key,val) pairs from tab. + // If clearmeta is true, then the metatable is also removed from tab. + // + void cleartable(LuaSlot tab, bool clearmeta) const; + + // Iterate over the key,val pairs in a table. + // + // Before the iteration begins, you should initialize 'key' to nil. + // Then, you should call 'next' to fetch the next key,val pair in + // the table. You can keep calling 'next' to obtain successive key,val + // pairs until 'next' returns false. + // + // Do not alter 'key' during the iteration, if you do, then the 'next' + // function will return the wrong next-value. + // + bool next(LuaSlot tab, LuaSlot key, LuaSlot val) const; + + // Return true if the int64 can be stored losslessly in a lua_Number. + // + // Lua numbers are actually double-precision floating point. double + // can hold integers losslessly as long as they're small enough to + // fit within the double's mantissa. The mantissa of an IEEE double + // is big enough to hold a 53-bit integer. + // + static bool validinteger(int64_t value) { return (value <= MAXINT) && (value >= -MAXINT); } + + // Return true if the int64 can be stored losslessly and is positive. + // + // This returns true if the number is a validinteger (see above), and is + // a positive number. + // + static bool validpositiveinteger(int64_t value) { return (value <= MAXINT) && (value >= 1); } + + // Return true if the string is a valid lua identifier. + // + // Lua identifiers can contain ascii uppercase, ascii lowercase, + // digits, and underscores. They may not start with a digit. + // + static bool valididentifier(std::string_view id); + + // Return true if the classname is legal. + // + // Returns true if value is a string, and is a valid lua identifier, + // and is not "_G". + // + bool validclassname(LuaSlot value) const; + static bool validclassname(std::string_view cname); + + // Get the class name given a class table. + // + // Return the class name if x is a valid class table. Otherwise, returns + // empty string. If nonempty, the result is guaranteed to be a + // validclassname. This can also function as an "isclass" operator. + // + eng::string classname(LuaSlot x) const; + + // Look for a class by class-name. + // + // If there is a problem, returns an error message. There are lots + // of error conditions, including such things as no such class, corrupted + // class, classname invalid, etc. + // + eng::string getclass(LuaSlot tab, LuaSlot name) const; + eng::string getclass(LuaSlot tab, std::string_view name) const; + + // Create a class, or look up an existing class. + // + // Creates a new class, unless the class already exists. Stores the + // class in the global environment table. This routine assert-fails if the + // parameter is not a valid classname. + // + void makeclass(LuaSlot tab, LuaSlot name) const; + void makeclass(LuaSlot tab, std::string_view name) const; + + // Create a tangible, or look up an existing tangible. + // + // If the tangible doesn't exist yet, this creates a tangible stub. + // A stub tangible is an empty table with a metatable containing the + // tangible's ID. Nothing else is present in the stub. It is up to + // the World module to transform stubs into full-blown tangibles. + // Assert-fails if the tangible ID is not a validpositiveinteger. + // + void maketan(LuaSlot tab, int64_t id) const; + + // Return true if a tangible is empty, ie, a stub. + // + bool tanblank(LuaSlot tab) const; + + // Get the ID of a tangible. + // + // This works on both full-blown tangibles and stubs. If tab + // is not a valid tangible, returns zero. + // + int64_t tanid(LuaSlot tab) const; + + // Get the class of a tangible. + // + // If the tangible has been assigned a class, then puts the class + // table into classobj and returns true. Otherwise, sets classobj + // to nil and returns false. + // + bool tangetclass(LuaSlot classobj, LuaSlot tan); + + // Assign a lua variable. + // + // Copies value into target. The 'value' parameter can be a LuaSlot or + // any lua-convertible C++ type. + // + template + void set(LuaSlot target, VT value) const { + push_any_value(value); + lua_replace(L_, target); + } + + // Return true if two values are equal. + // + // Checks if the two values are equal. Note that in lua, if two strings + // contain the same text, then they're equal. The 'value' parameter + // can be a LuaSlot or any lua-convertible C++ type. + // + // This could possibly be faster if we were to write some specializations + // for strings, numbers, and bools. + // + template + bool rawequal(LuaSlot v1, VT value) const { + push_any_value(value); + bool result = lua_rawequal(L_, v1, -1); + lua_pop(L_, 1); + return result; + } + + // Return true if val1 is less than val2. + // + // This is NOT the same as the lua_lessthan function. This is a more + // general function that can compare any two lua objects. + // + // 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. + // + // Does not call metamethods. + // + bool genlt(LuaSlot val1, LuaSlot val2) const { return lua_genlt(L_, val1, val2); } + + // Return true if the value is a sortable key. + // + // Sortable keys are: strings, booleans, and numbers. + // These three types can be meaningfully compared using genlt, + // above. They also can be meaningfully transferred from lua to C++ + // and back without losing anything. + // + bool issortablekey(LuaSlot s) const; + + // Move a sortable key to another lua environment. + // + // This is used when you've created two lua interpreters and you + // want to move data from one to the other. The only kinds of data + // you can move are strings, booleans, and numbers. + // + void movesortablekey(LuaSlot val, LuaCoreStack &other, LuaSlot otherslot); + + // Fetch a value from a table. + // + // Fetches the specified key from the table tab, and stores the + // result in target. The key parameter can be a LuaSlot or any lua- + // convertible C++ value. + // + template + void rawget(LuaSlot target, LuaSlot tab, KT key) const { + push_any_value(key); + lua_rawget(L_, tab); + lua_replace(L_, target); + } + + // Store a value in a table. + // + // Sets the table entry for key to value. The key and val + // parameters can be LuaSlots, or they can be any lua-convertible + // C++ value. + // + template + void rawset(LuaSlot tab, KT key, VT value) const { + push_any_value(key); + push_any_value(value); + lua_rawset(L_, tab); + } + + // Get the 'visited' bit from a lua table. + // + bool getvisited(LuaSlot tab) const; + + // Set the 'visited' bit in a lua table. + // + void setvisited(LuaSlot tab, bool visited) const; + + // Store the world type in the registry. + // + // This just stores the enum value in the registry key "worldtype". + // + void set_world_type(WorldType t) const; + + // Return the world type from the registry. + // + // This just fetches the enum value from the registry key "worldtype". + // + WorldType get_world_type() const; + + // Return true if this world is authoritative. + // + // This fetches the enum value from the registry key "worldtype", + // then it checks if the world type is authoritative. + // + bool is_authoritative() { return util::is_authoritative(get_world_type()); } + + // Yield this thread with zero if in a nonauth model, and not a probe. + // + // The function name is just used for generating better error messages. + // + void guard_nopredict(const char *fn); + + // This is the largest integer that can be stored in a lua_Number. + // In other words, any 53-bit number can be stored. + // + static const int64_t MAXINT = 0x001FFFFFFFFFFFFF; + + // Template Specializations. + // + // These are all specializations of functions that are defined above. + // These are typically here purely to make the functions above faster. + // + void set(LuaSlot target, LuaSlot value) const { + lua_copy(L_, value, target); + } + + bool rawequal(LuaSlot v1, LuaSlot v2) const { + return lua_rawequal(L_, v1, v2); + } + + void rawget(LuaSlot target, LuaSlot tab, int key) const { + lua_rawgeti(L_, tab, key); + lua_replace(L_, target); + } + + template + void rawset(LuaSlot tab, int key, VT value) const { + push_any_value(value); + lua_rawseti(L_, tab, key); + } + +protected: + + // Assign slots: this is used by the LuaDefStack and LuaExtStack + // constructors to assign stack indices to LuaSlots. + // + template + inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaRet &v, SS & ... stackslots) { + v.index_ = retp; + vassign_slots(retp+1, argp, varp, extrap, extrac, stackslots...); + } + template + inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaArg &v, SS & ... stackslots) { + v.index_ = argp; + vassign_slots(retp, argp+1, varp, extrap, extrac, stackslots...); + } + template + inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaVar &v, SS & ... stackslots) { + v.index_ = varp; + vassign_slots(retp, argp, varp+1, extrap, extrac, stackslots...); + } + template + inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaExtraArgs &v, SS & ... stackslots) { + v.index_ = extrap; + v.size_ = extrac; + vassign_slots(retp, argp, varp, extrap, extrac, stackslots...); + } + inline void vassign_slots(int retp, int argp, int varp, int extrap, int extrac) {} + + + // Push any value on the stack, by type. + // + inline void push_any_value(LuaNewTableMarker s) const { lua_newtable(L_); } + inline void push_any_value(LuaNilMarker s) const { lua_pushnil(L_); } + inline void push_any_value(LuaSlot s) const { lua_pushvalue(L_, s); } + inline void push_any_value(const eng::string &s) const { lua_pushlstring(L_, s.c_str(), s.size()); } + inline void push_any_value(std::string_view s) const { lua_pushlstring(L_, s.data(), s.size()); } + inline void push_any_value(const char *s) const { lua_pushstring(L_, s); } + inline void push_any_value(float s) const { lua_pushnumber(L_, s); } + inline void push_any_value(double s) const { lua_pushnumber(L_, s); } + inline void push_any_value(int s) const { lua_pushinteger(L_, s); } + inline void push_any_value(lua_Integer s) const { lua_pushinteger(L_, s); } + inline void push_any_value(lua_CFunction s) const { lua_pushcfunction(L_, s); } + inline void push_any_value(bool b) const { lua_pushboolean(L_, b ? 1:0); } + inline void push_any_value(LuaToken token) const { lua_pushlightuserdata(L_, (void*)(token.value)); } + + // Push multiple values on the stack, in order, by type. + // + template + void push_any_values(T0 arg0, T... args) { + push_any_value(arg0); + push_any_values(args...); + } + void push_any_values() { + } + + // Throw a lua error message + void argerr(const char *arg, const char *tp) const; +}; + +//////////////////////////////////////////////////////////////////// +// +// Argument Counting Templates. +// +// These are internal functions used by LuaDefStack and LuaExtStack +// to help with the processing of constructor arguments. +// +//////////////////////////////////////////////////////////////////// + struct LuaArgCounts { int nret; int narg; @@ -243,340 +1097,6 @@ struct LuaCountArgs { static constexpr LuaArgCounts value = LuaArgCounts(0, 0, 0, 1) + LuaCountArgs::value; }; - -class LuaNilMarker {}; -extern LuaNilMarker LuaNil; - -class LuaNewTableMarker {}; -extern LuaNewTableMarker LuaNewTable; - -using LuaDeleterFn = void (*)(void *); - -using LuaTypeTag = lua_CFunction; -template -int LuaTypeTagValue(lua_State *L) { return 0; } - -// Lua table types. These deliberately do not overlap -// with lua type values. -// -enum LuaTableType { - LUA_TT_GENERAL = LUA_NUMTAGS, - LUA_TT_REGISTRY, - LUA_TT_GLOBALENV, - LUA_TT_TANGIBLE, - LUA_TT_TANGIBLEMETA, - LUA_TT_CLASS, - - LUA_TT_SENTINEL -}; - -// World types enum. - -enum WorldType { - WORLD_TYPE_MASTER = 1, - WORLD_TYPE_PREDICTIVE = 2, -}; - -// We use lightuserdata to store 'tokens': short -// strings of 8 characters or less. These tokens -// are useful as unique markers. The 8 characters -// are packed into a uint64. - -struct LuaToken { -private: - static constexpr uint64_t literal_to_token(const char *str) { - uint64_t result = 0; - for (int i = 0; i < 8; i++) { - unsigned char c = *str; - result = (result << 8) + c; - if (*str) str++; - } - return result; - } -public: - uint64_t value; - - template - LuaToken(T arg) = delete; - - constexpr LuaToken(const char *str) : value(literal_to_token(str)) {} - LuaToken(uint64_t v) : value(v) {} - LuaToken(void *v) : value((uint64_t)v) {} - LuaToken() : value(0) {} - - bool empty() const { return value == 0; } - bool operator ==(const LuaToken &other) const { return value == other.value; } - void *voidvalue() const { return (void*)value; } - - eng::string str() const; -}; - -//////////////////////////////////////////////////////////////////// -// -// LuaCoreStack -// -// This class is not meant to be used directly: it doesn't contain any code to -// allocate a stack frame on the lua stack and allocate slots within the frame -// to lua local variables. That code is in derived classes, below. The only -// time this class is used directly is in the extremely rare case that you want -// to manually allocate stack slots yourself. -// -//////////////////////////////////////////////////////////////////// - -class LuaCoreStack : public eng::nevernew { -protected: - lua_State *L_; - -protected: - -private: - // Push any value on the stack, by type. - void push_any_value(LuaNewTableMarker s) const { lua_newtable(L_); } - void push_any_value(LuaNilMarker s) const { lua_pushnil(L_); } - void push_any_value(LuaSlot s) const { lua_pushvalue(L_, s); } - void push_any_value(const eng::string &s) const { lua_pushlstring(L_, s.c_str(), s.size()); } - void push_any_value(std::string_view s) const { lua_pushlstring(L_, s.data(), s.size()); } - void push_any_value(const char *s) const { lua_pushstring(L_, s); } - void push_any_value(float s) const { lua_pushnumber(L_, s); } - void push_any_value(double s) const { lua_pushnumber(L_, s); } - void push_any_value(int s) const { lua_pushinteger(L_, s); } - void push_any_value(lua_Integer s) const { lua_pushinteger(L_, s); } - void push_any_value(lua_CFunction s) const { lua_pushcfunction(L_, s); } - void push_any_value(bool b) const { lua_pushboolean(L_, b ? 1:0); } - void push_any_value(LuaToken token) const { lua_pushlightuserdata(L_, (void*)(token.value)); } - - // Push multiple values on the stack, in order, by type. - template - void push_any_values(T0 arg0, T... args) { - push_any_value(arg0); - push_any_values(args...); - } - void push_any_values() { - } - - void argerr(const char *arg, const char *tp) const; -public: - LuaCoreStack(lua_State *L) : L_(L) {} - - lua_State *state() const { return L_; } - - // This is the largest integer that can be stored in a lua_Number. - // In other words, any 53-bit number can be stored. - static const int64_t MAXINT = 0x001FFFFFFFFFFFFF; - - static lua_State *newstate (lua_Alloc allocf); - - int type(LuaSlot s) const { return lua_type(L_, s); } - void checktype(LuaSlot s, int type) const { luaL_checktype(L_, s, type); } - - // These functions try to turn a Lua value into a C++ value. - // If the lua value doesn't match the desired type, then these return - // false or an empty optional. The ones that return bool only verify - // the value's type, they don't actually fetch the value. - - std::optional tryboolean(LuaSlot s) const; - std::optional tryinteger(LuaSlot s) const; - std::optional tryint(LuaSlot s) const; - std::optional trynumber(LuaSlot s) const; - std::optional trystring(LuaSlot s) const; - std::optional trystringview(LuaSlot s) const; - std::optional trythread(LuaSlot s) const; - std::optional trytoken(LuaSlot s) const; - std::optional tryxyz(LuaSlot s) const; - bool trytable(LuaSlot s) const; - bool trynil(LuaSlot s) const; - bool tryfunction(LuaSlot s) const; - bool trycfunction(LuaSlot s) const; - bool trytangible(LuaSlot s) const; - - // These functions turn a Lua value into a C++ value. - // If the lua value doesn't match the desired type, - // then these throw a lua error. The argname is used - // for making a nice error message. - - bool ckboolean(LuaSlot s, const char *argname = "value") const; - lua_Integer ckinteger(LuaSlot s, const char *argname = "value") const; - int ckint(LuaSlot s, const char *argname = "value") const; - lua_Number cknumber(LuaSlot s, const char *argname = "value") const; - eng::string ckstring(LuaSlot s, const char *argname = "value") const; - std::string_view ckstringview(LuaSlot s, const char *argname = "value") const; - lua_State * ckthread(LuaSlot s, const char *argname = "value") const; - LuaToken cktoken(LuaSlot s, const char *argname = "value") const; - util::DXYZ ckxyz(LuaSlot s, const char *argname = "value") const; - void cktable(LuaSlot s, const char *argname = "value") const; - void cknil(LuaSlot s, const char *argname = "value") const; - void ckfunction(LuaSlot s, const char *argname = "value") const; - void ckcfunction(LuaSlot s, const char *argname = "value") const; - void cktangible(LuaSlot s, const char *argname = "value") const; - - // These functions check if a value can be converted - // to a C++ value. They don't actually return the C++ value. - // It is more efficient to use the 'try' or 'ck' functions above if - // you also want the value itself. - - bool isboolean(LuaSlot s) const { return lua_type(L_, s) == LUA_TBOOLEAN; } - bool isinteger(LuaSlot s) const { return bool(tryinteger(s)); } - bool isint(LuaSlot s) const { return bool(tryint(s)); } - bool isnumber(LuaSlot s) const { return lua_type(L_, s) == LUA_TNUMBER; } - bool isstring(LuaSlot s) const { return lua_type(L_, s) == LUA_TSTRING; } - bool isstringview(LuaSlot s) const { return lua_type(L_, s) == LUA_TSTRING; } - bool isthread(LuaSlot s) const { return lua_type(L_, s) == LUA_TTHREAD; } - bool istoken(LuaSlot s) const { return lua_islightuserdata(L_, s) != 0; } - bool isxyz(LuaSlot s) const { return bool(tryxyz(s)); } - bool istable(LuaSlot s) const { return lua_type(L_, s) == LUA_TTABLE; } - bool isnil(LuaSlot s) const { return lua_type(L_, s) == LUA_TNIL; } - bool isfunction(LuaSlot s) const { return lua_isfunction(L_, s); } - bool iscfunction(LuaSlot s) const { return lua_iscfunction(L_, s) != 0; } - bool istangible(LuaSlot s) const { return trytangible(s); } - - - - void clearmetatable(LuaSlot tab) const; - void setmetatable(LuaSlot tab, LuaSlot mt) const; - bool getmetatable(LuaSlot mt, LuaSlot tab) const; - - void newtable(LuaSlot target) const; - void createtable(LuaSlot target, int narr, int nrec) const; - lua_State *newthread(LuaSlot target) const; - void getglobaltable(LuaSlot gltab) const; - void cleartable(LuaSlot tab, bool clearmeta) const; - - int rawlen(LuaSlot val) const; - - int nkeys(LuaSlot tab) const; - - int next(LuaSlot tab, LuaSlot key, LuaSlot value) const; - - // Return true if the string is a valid lua identifier. - static bool valididentifier(std::string_view id); - - // Return true if the int64 can be stored losslessly in a lua_Number. - static bool validint64(int64_t value); - - // Return true if the int64 is storable in lua and is positive. - static bool validpositiveint64(int64_t value); - - // Return true if the classname is legal. - bool validclassname(LuaSlot value) const; - static bool validclassname(std::string_view cname); - - // Return the class name if x is a valid classtab. - // Otherwise, returns empty string. If nonempty, the - // result is guaranteed to be a validclassname. - // This can also function as an "isclass" operator. - eng::string classname(LuaSlot x) const; - - // Look up a class. - // If there is a problem, returns an error message. - // There are lots of error conditions, including such things - // as no such class, corrupted class, classname invalid, etc. - eng::string getclass(LuaSlot tab, LuaSlot name) const; - eng::string getclass(LuaSlot tab, std::string_view name) const; - - // Create a class, or look up an existing class. - // WARNING: this routine assert-fails if the parameter is not - // a valid classname. Check the classname before calling this! - void makeclass(LuaSlot tab, LuaSlot name) const; - void makeclass(LuaSlot tab, std::string_view name) const; - - // Create a tangible, or look up an existing tangible. - // 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. - // - // Assert-fails if the tangible ID is not a validpositiveint64. - // - 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; - - // Get the class of a tangible. - bool tangetclass(LuaSlot classobj, LuaSlot tan); - - // Return true if the value is a sortable key (string, number, or boolean). - bool issortablekey(LuaSlot s) const; - - // Move a sortable key (string, number, or boolean) from one lua - // environment to another lua environment. WARNING: this assert-fails - // if the value is not a sortable key. - void movesortablekey(LuaSlot val, LuaCoreStack &other, LuaSlot otherslot); - - bool rawequal(LuaSlot v1, LuaSlot v2) const { - return lua_rawequal(L_, v1, v2); - } - - template - bool rawequal(LuaSlot v1, VT value) const { - push_any_value(value); - bool result = lua_rawequal(L_, v1, -1); - lua_pop(L_, 1); - return result; - } - - template - void set(LuaSlot target, VT value) const { - push_any_value(value); - lua_replace(L_, target); - } - - template - void rawget(LuaSlot target, LuaSlot tab, KT key) const { - push_any_value(key); - lua_rawget(L_, tab); - lua_replace(L_, target); - } - - void rawget(LuaSlot target, LuaSlot tab, int key) const { - lua_rawgeti(L_, tab, key); - lua_replace(L_, target); - } - - template - void rawset(LuaSlot tab, KT key, VT value) const { - push_any_value(key); - push_any_value(value); - lua_rawset(L_, tab); - } - - template - void rawset(LuaSlot tab, int key, VT value) const { - push_any_value(value); - lua_rawseti(L_, tab, key); - } - - // Lua flagbits manipulation: Table types. - int gettabletype(LuaSlot tab) const; - void settabletype(LuaSlot tab, int t) const; - - // If slot is a table, returns the LUA_TT_XXX table type. - // If slot is not a table, returns the LUA_TXXX general type. - int xtype(LuaSlot slot) const; - - // Lua flagbits manipulation: visited bit. - bool getvisited(LuaSlot tab) 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. - static bool int64_storable(int64_t v) { return (v <= MAXINT) && (v >= -MAXINT); } -}; - //////////////////////////////////////////////////////////////////// // // LuaDefStack @@ -605,33 +1125,11 @@ public: // //////////////////////////////////////////////////////////////////// + class LuaDefStack : public LuaCoreStack { private: int nret_; - template - void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaRet &v, SS & ... stackslots) { - v.index_ = retp; - vassign_slots(retp+1, argp, varp, extrap, extrac, stackslots...); - } - template - void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaArg &v, SS & ... stackslots) { - v.index_ = argp; - vassign_slots(retp, argp+1, varp, extrap, extrac, stackslots...); - } - template - void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaVar &v, SS & ... stackslots) { - v.index_ = varp; - vassign_slots(retp, argp, varp+1, extrap, extrac, stackslots...); - } - template - void vassign_slots(int retp, int argp, int varp, int extrap, int extrac, LuaExtraArgs &v, SS & ... stackslots) { - v.index_ = extrap; - v.size_ = extrac; - vassign_slots(retp, argp, varp, extrap, extrac, stackslots...); - } - void vassign_slots(int retp, int argp, int varp, int extrap, int extrac) {} - public: template @@ -684,13 +1182,6 @@ class LuaExtStack : public LuaCoreStack { private: int oldtop_; - template - void assign_slots(int varp, LuaVar &v, SS & ... stackslots) { - v.index_ = varp; - assign_slots(varp+1, stackslots...); - } - void assign_slots(int varp) {} - public: template LuaExtStack(lua_State *L, SS & ... stackslots) : LuaCoreStack(L) { @@ -703,7 +1194,7 @@ public: for (int i = 0; i < counts.nvar; i++) { lua_pushnil(L_); } - assign_slots(oldtop_ + 1, stackslots...); + vassign_slots(0, 0, oldtop_ + 1, 0, 0, stackslots...); } template diff --git a/luprex/cpp/core/serializelua.cpp b/luprex/cpp/core/serializelua.cpp index 116af233..cd103563 100644 --- a/luprex/cpp/core/serializelua.cpp +++ b/luprex/cpp/core/serializelua.cpp @@ -81,7 +81,7 @@ class Deserializer { } case LUA_TT_TANGIBLE: { int64_t tanid = sb_->read_int64(); - if (!LS_.validpositiveint64(tanid)) { + if (!LS_.validpositiveinteger(tanid)) { throw DeserializeError(); } LS_.maketan(target, tanid); diff --git a/luprex/cpp/core/util.hpp b/luprex/cpp/core/util.hpp index ed8b3641..9ac368eb 100644 --- a/luprex/cpp/core/util.hpp +++ b/luprex/cpp/core/util.hpp @@ -32,6 +32,13 @@ #include #include "spookyv2.hpp" + +enum WorldType { + WORLD_TYPE_MASTER = 1, + WORLD_TYPE_PREDICTIVE = 2, +}; + + namespace sv { // Bring this into our namespace. @@ -320,6 +327,9 @@ double distance_squared(double x1, double y1, double x2, double y2); // Make a LuaSourceVec with one element, for unit testing. LuaSourcePtr make_lua_source(const eng::string &code); +// Return true if the worldtype is authoritative. +inline bool is_authoritative(WorldType t) { return (t == WORLD_TYPE_MASTER); } + // Remove items from a vector that are nullptr. template void remove_nullptrs(T &vec) { diff --git a/luprex/cpp/core/world-core.cpp b/luprex/cpp/core/world-core.cpp index 62554e91..0662ffb0 100644 --- a/luprex/cpp/core/world-core.cpp +++ b/luprex/cpp/core/world-core.cpp @@ -62,7 +62,7 @@ World::World(WorldType wt) { LS.settabletype(globtab, LUA_TT_GLOBALENV); // Store the world type in the registry. - LS.rawset(LuaRegistry, "worldtype", wt); + LS.set_world_type(wt); // Create the globaldb in the registry. LS.rawset(LuaRegistry, "globaldb", LuaNewTable); @@ -141,7 +141,7 @@ Tangible *World::tangible_get(const LuaCoreStack &LS, LuaSlot tab, bool allowdel } Tangible *World::tangible_make(const LuaCoreStack &LS0, LuaSlot database, int64_t id) { - assert(LS0.validpositiveint64(id)); + assert(LS0.validpositiveinteger(id)); LuaVar metatab; LuaExtStack LS(LS0.state(), metatab); diff --git a/luprex/cpp/core/world.hpp b/luprex/cpp/core/world.hpp index 5e8167ce..758242ba 100644 --- a/luprex/cpp/core/world.hpp +++ b/luprex/cpp/core/world.hpp @@ -308,7 +308,7 @@ public: // Check if the world is authoritative. // - bool is_authoritative() const { return LuaCoreStack::is_authoritative(world_type_); } + bool is_authoritative() const { return util::is_authoritative(world_type_); } // Get a table showing all outstanding HTTP requests. //