global.set has been implemented, but not diff xmit for globals
This commit is contained in:
@@ -1016,7 +1016,7 @@ static void init_engine_wrapper_helper(EngineWrapper *w) {
|
||||
w->replay_initialize = replaycore_initialize;
|
||||
w->replay_step = replaycore_step;
|
||||
|
||||
w->hook_dprintf = util::hook_dprintf;
|
||||
w->hook_dprint = util::hook_dprint;
|
||||
w->release = release;
|
||||
};
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ struct EngineWrapper {
|
||||
// will only contain printable characters. The line will not contain a
|
||||
// newline - the newline is implied.
|
||||
//
|
||||
void (*hook_dprintf)(void (*func)(const char *oneline));
|
||||
void (*hook_dprint)(void (*func)(const char *oneline, size_t size));
|
||||
|
||||
// Restore the wrapper to its initial blank state.
|
||||
//
|
||||
|
||||
@@ -1,77 +1,3 @@
|
||||
#include "luastack.hpp"
|
||||
#include "globaldb.hpp"
|
||||
|
||||
|
||||
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;
|
||||
LuaRet flag;
|
||||
LuaVar oncedb, val;
|
||||
LuaStack LS(L, name, flag, oncedb, val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
LuaVar oncedb;
|
||||
LuaStack LS(L, name, oncedb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2,31 +2,13 @@
|
||||
//
|
||||
// GLOBALDB
|
||||
//
|
||||
// The master world model is allowed to maintain global data structures.
|
||||
//
|
||||
// Unfortunately, any attempt to put a global data structure into the global
|
||||
// environment will fail, because the global environment periodically gets wiped
|
||||
// clean by the "source rebuild" procedure (see module 'source').
|
||||
//
|
||||
// This module creates a safe space where you can put global data structures
|
||||
// that won't get wiped out.
|
||||
//
|
||||
// THE GLOBAL OPERATOR
|
||||
//
|
||||
// This module adds a new function to lua: "global". Global takes a global data
|
||||
// structure name (a string) and returns a table for that global data structure.
|
||||
// You can put anything you want into the table.
|
||||
//
|
||||
// Since you're only allowed to use global data structures in the master world
|
||||
// model, any attempt to call "global" in a synchronous world model will
|
||||
// result in a 'donotpredict' error.
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef GLOBALDB_HPP
|
||||
#define GLOBALDB_HPP
|
||||
|
||||
|
||||
|
||||
#endif // GLOBALDB_HPP
|
||||
|
||||
|
||||
|
||||
@@ -721,43 +721,52 @@ eng::string XYZ::debug_string() const {
|
||||
}
|
||||
|
||||
|
||||
void (*dprintf_hook)(const char *oneline);
|
||||
void (*dprint_hook)(const char *oneline, size_t size);
|
||||
|
||||
void hook_dprintf(void (*func)(const char *oneline)) {
|
||||
dprintf_hook = func;
|
||||
void hook_dprint(void (*func)(const char *oneline, size_t size)) {
|
||||
dprint_hook = func;
|
||||
}
|
||||
|
||||
static void chop_up_dprintf(char *buffer, int size) {
|
||||
void dprintview(std::string_view view) {
|
||||
// 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;
|
||||
if ((view.size() > 0) && (view.back() == '\n')) {
|
||||
view.remove_suffix(1);
|
||||
}
|
||||
|
||||
// Chop it up into lines and call the hook one line
|
||||
// at a time. Replace control characters as we go.
|
||||
// at a time.
|
||||
const char *buffer = view.data();
|
||||
const char *base = buffer;
|
||||
for (int i = 0; i <= size; i++) {
|
||||
if (buffer[i] < ' ') {
|
||||
for (int i = 0; i < int(view.size()); i++) {
|
||||
if ((buffer[i] == '\n') || (buffer[i] == 0)) {
|
||||
buffer[i] = 0;
|
||||
if (dprintf_hook == nullptr) {
|
||||
fprintf(stderr, "%s\n", base);
|
||||
size_t sz = buffer + i - base;
|
||||
if (dprint_hook == nullptr) {
|
||||
fwrite(base, 1, sz, stderr);
|
||||
fwrite("\n", 1, 1, stderr);
|
||||
} else {
|
||||
dprintf_hook(base);
|
||||
dprint_hook(base, sz);
|
||||
}
|
||||
base = buffer + i + 1;
|
||||
} else {
|
||||
buffer[i] = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
// Output the final line.
|
||||
size_t sz = buffer + view.size() - base;
|
||||
if (sz > 0) {
|
||||
if (dprint_hook == nullptr) {
|
||||
fwrite(base, 1, sz, stderr);
|
||||
fwrite("\n", 1, 1, stderr);
|
||||
} else {
|
||||
dprint_hook(base, sz);
|
||||
}
|
||||
}
|
||||
|
||||
// If we're sending to stderr, flush. If not, then
|
||||
// the hook routine is responsible for flushing its own
|
||||
// output.
|
||||
if (dprintf_hook == nullptr) {
|
||||
if (dprint_hook == nullptr) {
|
||||
fflush(stderr);
|
||||
}
|
||||
}
|
||||
@@ -768,11 +777,11 @@ void dprintf(const char *format, ...) {
|
||||
va_start (args, format);
|
||||
int n = vsnprintf(buffer, 256, format, args);
|
||||
if (n <= 255) {
|
||||
chop_up_dprintf(buffer, n);
|
||||
dprintview(std::string_view(buffer, n));
|
||||
} else {
|
||||
char *lbuffer = (char *)malloc(n + 1);
|
||||
vsnprintf(lbuffer, n+1, format, args);
|
||||
chop_up_dprintf(lbuffer, n);
|
||||
dprintview(std::string_view(lbuffer, n));
|
||||
free(lbuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,10 +328,35 @@ struct XYZ {
|
||||
eng::string debug_string() const;
|
||||
};
|
||||
|
||||
class NullStreamBuffer : public std::streambuf
|
||||
{
|
||||
// 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.view().
|
||||
//
|
||||
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:
|
||||
int overflow(int c) { return c; }
|
||||
ostringstream() {
|
||||
std::basic_ostream<char>::rdbuf(&rstringbuf_);
|
||||
}
|
||||
char *data() const {
|
||||
return rstringbuf_.eback();
|
||||
}
|
||||
size_t size() const {
|
||||
return rstringbuf_.pptr() - rstringbuf_.eback();
|
||||
}
|
||||
std::string_view view() const {
|
||||
return std::string_view(data(), size());
|
||||
}
|
||||
eng::string str() const {
|
||||
return rstringbuf_.str();
|
||||
}
|
||||
};
|
||||
|
||||
// send_to_stream: send all arguments to the specified stream.
|
||||
@@ -350,34 +375,7 @@ inline eng::string ss(const ARGS & ... args) {
|
||||
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.view().
|
||||
//
|
||||
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
|
||||
// dprintf / dprint
|
||||
//
|
||||
// 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
|
||||
@@ -388,8 +386,16 @@ public:
|
||||
// characters only. There will be no control characters. The newline is
|
||||
// implied.
|
||||
//
|
||||
void dprintview(std::string_view view);
|
||||
void dprintf(const char *format, ...);
|
||||
void hook_dprintf(void (*func)(const char *oneline));
|
||||
void hook_dprint(void (*func)(const char *oneline, size_t size));
|
||||
|
||||
template <class... ARGS>
|
||||
inline void dprint(const ARGS & ... args) {
|
||||
util::ostringstream oss;
|
||||
send_to_stream(oss, args...);
|
||||
dprintview(oss.view());
|
||||
}
|
||||
|
||||
// A better API than std::setfill, std::hex, std::setw, std::setprecision
|
||||
//
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
#include "world.hpp"
|
||||
#include "pprint.hpp"
|
||||
#include "serializelua.hpp"
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
@@ -854,3 +855,171 @@ LuaDefine(http_post, "request",
|
||||
"|See doc(http.clientrequest) and doc(http.clientresponse).") {
|
||||
return lfn_http_request(L, "POST");
|
||||
}
|
||||
|
||||
void global_set(LuaStack &LS0, const eng::string &gvar, LuaSlot value) {
|
||||
lua_State *L = LS0.state();
|
||||
World *w = World::fetch_global_pointer(L);
|
||||
LuaVar globaldb, copy;
|
||||
LuaStack LS(L, globaldb, copy);
|
||||
|
||||
// Serialize then deserialize the data, to produce a copy.
|
||||
StreamBuffer sb;
|
||||
eng::string error = serialize_lua(LS, value, &sb);
|
||||
if (!error.empty()) {
|
||||
luaL_error(L, "%s", error.c_str());
|
||||
return;
|
||||
}
|
||||
eng::string serialized(sb.view());
|
||||
error = deserialize_lua(LS, copy, &sb);
|
||||
if (!error.empty()) {
|
||||
luaL_error(L, "%s", error.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the copy in the globalDB.
|
||||
LS.rawget(globaldb, LuaRegistry, "globaldb");
|
||||
LS.rawset(globaldb, gvar, copy);
|
||||
|
||||
// Store the serialized blob in the master model.
|
||||
w->gvar_to_serial_.emplace(gvar, serialized);
|
||||
|
||||
// In an authoritative model only, update the assignment counters.
|
||||
if (w->is_authoritative()) {
|
||||
int64_t newassign = w->next_gvar_assign_++;
|
||||
auto oldassigniter = w->gvar_to_assign_.find(gvar);
|
||||
if (oldassigniter != w->gvar_to_assign_.end()) {
|
||||
int64_t oldassign = oldassigniter->second;
|
||||
w->assign_to_gvar_.erase(oldassign);
|
||||
}
|
||||
w->assign_to_gvar_.emplace(newassign, gvar);
|
||||
w->gvar_to_assign_[gvar] = newassign;
|
||||
}
|
||||
LS.result();
|
||||
}
|
||||
|
||||
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."
|
||||
"|Only data that can be serialized according to doc(table.serialize)"
|
||||
"|can be stored."
|
||||
"|"
|
||||
"|When you store the value, it is immediately serialized and then"
|
||||
"|deserialized again, and the deserialized copy is stored in the"
|
||||
"|variable."
|
||||
"|"
|
||||
// "|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."
|
||||
"|") {
|
||||
LuaArg varname;
|
||||
LuaArg value;
|
||||
LuaStack LS(L, varname, value);
|
||||
|
||||
// Check the varname argument.
|
||||
eng::string gvar = LS.ckstring(varname);
|
||||
if (!sv::is_lua_id(gvar)) {
|
||||
luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str());
|
||||
return LS.result();
|
||||
}
|
||||
|
||||
global_set(LS, gvar, value);
|
||||
return LS.result();
|
||||
}
|
||||
|
||||
LuaDefine(global_get, "varname",
|
||||
"|Get data stored using global.set"
|
||||
"|"
|
||||
"|See doc(global.set) for information on how to store global data."
|
||||
"|"
|
||||
"|Do not mutate data returned by global.get: doing so will produce"
|
||||
"|unpredictable results. Instead, using global.set to mutate global"
|
||||
"|variables."
|
||||
"|") {
|
||||
LuaArg varname;
|
||||
LuaRet value;
|
||||
LuaVar globaldb;
|
||||
LuaStack LS(L, varname, value, globaldb);
|
||||
LS.rawget(globaldb, LuaRegistry, "globaldb");
|
||||
LS.rawget(value, globaldb, varname);
|
||||
return LS.result();
|
||||
}
|
||||
|
||||
LuaDefine(global_once, "varname",
|
||||
"|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 varname;
|
||||
LuaRet result;
|
||||
LuaVar globaldb, flag;
|
||||
LuaStack LS(L, varname, flag, result, globaldb);
|
||||
|
||||
// Check the varname argument.
|
||||
eng::string gvar = LS.ckstring(varname);
|
||||
if (!sv::is_lua_id(gvar)) {
|
||||
luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str());
|
||||
return LS.result();
|
||||
}
|
||||
gvar += ":once";
|
||||
|
||||
LS.rawget(globaldb, LuaRegistry, "globaldb");
|
||||
LS.rawget(flag, globaldb, gvar);
|
||||
|
||||
if (!LS.isnil(flag)) {
|
||||
LS.set(result, false);
|
||||
return LS.result();
|
||||
}
|
||||
|
||||
LS.set(result, true);
|
||||
global_set(LS, gvar, result);
|
||||
return LS.result();
|
||||
}
|
||||
|
||||
LuaDefine(global_clearonce, "varname",
|
||||
"|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 varname;
|
||||
LuaVar null;
|
||||
LuaStack LS(L, varname, null);
|
||||
|
||||
// Check the varname argument.
|
||||
eng::string gvar = LS.ckstring(varname);
|
||||
if (!sv::is_lua_id(gvar)) {
|
||||
luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str());
|
||||
return LS.result();
|
||||
}
|
||||
gvar += ":once";
|
||||
|
||||
LS.set(null, LuaNil);
|
||||
global_set(LS, gvar, null);
|
||||
return LS.result();
|
||||
}
|
||||
|
||||
@@ -74,6 +74,9 @@ World::World(WorldType wt) {
|
||||
// Clear the clock.
|
||||
clock_ = 0;
|
||||
|
||||
// Initialize global variable state.
|
||||
next_gvar_assign_ = 1;
|
||||
|
||||
// There shouldn't be any lua errors in the sourceDB at this
|
||||
// point, since there's no lua code in the sourceDB.
|
||||
assert(srcerrs == "");
|
||||
|
||||
@@ -379,7 +379,9 @@ public:
|
||||
//
|
||||
void tangible_set_string(int64_t id, const eng::string &path, const eng::string &value);
|
||||
|
||||
// Copy a lua global variable into the tangible's database.
|
||||
// Copy a from the lua global environment into the tangible's database.
|
||||
//
|
||||
// This is for unit testing.
|
||||
//
|
||||
void tangible_copy_global(int64_t id, const eng::string &path, const eng::string &global);
|
||||
|
||||
@@ -516,6 +518,13 @@ private:
|
||||
SourceDB source_db_;
|
||||
PlaneMap plane_map_;
|
||||
|
||||
// Lua Globals
|
||||
//
|
||||
int64_t next_gvar_assign_;
|
||||
eng::map<eng::string, int64_t> gvar_to_assign_;
|
||||
eng::map<eng::string, eng::string> gvar_to_serial_;
|
||||
eng::map<int64_t, eng::string> assign_to_gvar_;
|
||||
|
||||
// Tangibles table.
|
||||
//
|
||||
eng::unordered_map<int64_t, UniqueTangible> tangibles_;
|
||||
@@ -551,6 +560,7 @@ private:
|
||||
std::unique_ptr<eng::ostringstream> lthread_prints_;
|
||||
|
||||
friend class Tangible;
|
||||
friend void global_set(LuaStack &LS0, const eng::string &gvar, LuaSlot value);
|
||||
friend int lfn_tangible_animate(lua_State *L);
|
||||
friend int lfn_tangible_build(lua_State *L);
|
||||
friend int lfn_tangible_redirect(lua_State *L);
|
||||
@@ -566,6 +576,7 @@ private:
|
||||
friend int lfn_wait(lua_State *L);
|
||||
friend int lfn_nopredict(lua_State *L);
|
||||
friend int lfn_http_request(lua_State *L, const char *method);
|
||||
friend int lfn_global_set(lua_State *L);
|
||||
};
|
||||
|
||||
using UniqueWorld = std::unique_ptr<World>;
|
||||
|
||||
@@ -9,8 +9,10 @@ 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);
|
||||
static void dprint_callback(const char *oneline, size_t size) {
|
||||
fwrite("DPRINT:", 1, 7, stderr);
|
||||
fwrite(oneline, 1, size, stderr);
|
||||
fwrite("\n", 1, 1, stderr);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
@@ -546,7 +548,7 @@ class Driver {
|
||||
// Load the DLL and gain access to its functions.
|
||||
call_init_engine_wrapper(&engw);
|
||||
engw.replay_cb_sent_outgoing = replay_cb_sent_outgoing;
|
||||
engw.hook_dprintf(dprintf_callback);
|
||||
engw.hook_dprint(dprint_callback);
|
||||
|
||||
// If argv contains "replay <filename>", do a replay,
|
||||
// and then skip everything else.
|
||||
|
||||
Reference in New Issue
Block a user