aboutsummaryrefslogtreecommitdiff
path: root/src/script/cpp_api
diff options
context:
space:
mode:
Diffstat (limited to 'src/script/cpp_api')
-rw-r--r--src/script/cpp_api/s_async.cpp183
-rw-r--r--src/script/cpp_api/s_async.h54
-rw-r--r--src/script/cpp_api/s_base.cpp8
-rw-r--r--src/script/cpp_api/s_client.cpp10
-rw-r--r--src/script/cpp_api/s_entity.cpp13
-rw-r--r--src/script/cpp_api/s_entity.h2
-rw-r--r--src/script/cpp_api/s_env.cpp4
-rw-r--r--src/script/cpp_api/s_security.cpp14
8 files changed, 235 insertions, 53 deletions
diff --git a/src/script/cpp_api/s_async.cpp b/src/script/cpp_api/s_async.cpp
index dacdcd75a..42a794ceb 100644
--- a/src/script/cpp_api/s_async.cpp
+++ b/src/script/cpp_api/s_async.cpp
@@ -21,9 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <cstdlib>
extern "C" {
-#include "lua.h"
-#include "lauxlib.h"
-#include "lualib.h"
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
}
#include "server.h"
@@ -32,6 +32,7 @@ extern "C" {
#include "filesys.h"
#include "porting.h"
#include "common/c_internal.h"
+#include "common/c_packer.h"
#include "lua_api/l_base.h"
/******************************************************************************/
@@ -76,19 +77,34 @@ void AsyncEngine::initialize(unsigned int numEngines)
{
initDone = true;
- for (unsigned int i = 0; i < numEngines; i++) {
- AsyncWorkerThread *toAdd = new AsyncWorkerThread(this,
- std::string("AsyncWorker-") + itos(i));
- workerThreads.push_back(toAdd);
- toAdd->start();
+ if (numEngines == 0) {
+ // Leave one core for the main thread and one for whatever else
+ autoscaleMaxWorkers = Thread::getNumberOfProcessors();
+ if (autoscaleMaxWorkers >= 2)
+ autoscaleMaxWorkers -= 2;
+ infostream << "AsyncEngine: using at most " << autoscaleMaxWorkers
+ << " threads with automatic scaling" << std::endl;
+
+ addWorkerThread();
+ } else {
+ for (unsigned int i = 0; i < numEngines; i++)
+ addWorkerThread();
}
}
+void AsyncEngine::addWorkerThread()
+{
+ AsyncWorkerThread *toAdd = new AsyncWorkerThread(this,
+ std::string("AsyncWorker-") + itos(workerThreads.size()));
+ workerThreads.push_back(toAdd);
+ toAdd->start();
+}
+
/******************************************************************************/
u32 AsyncEngine::queueAsyncJob(std::string &&func, std::string &&params,
const std::string &mod_origin)
{
- jobQueueMutex.lock();
+ MutexAutoLock autolock(jobQueueMutex);
u32 jobId = jobIdCounter++;
jobQueue.emplace_back();
@@ -99,7 +115,23 @@ u32 AsyncEngine::queueAsyncJob(std::string &&func, std::string &&params,
to_add.mod_origin = mod_origin;
jobQueueCounter.post();
- jobQueueMutex.unlock();
+ return jobId;
+}
+
+u32 AsyncEngine::queueAsyncJob(std::string &&func, PackedValue *params,
+ const std::string &mod_origin)
+{
+ MutexAutoLock autolock(jobQueueMutex);
+ u32 jobId = jobIdCounter++;
+
+ jobQueue.emplace_back();
+ auto &to_add = jobQueue.back();
+ to_add.id = jobId;
+ to_add.function = std::move(func);
+ to_add.params_ext.reset(params);
+ to_add.mod_origin = mod_origin;
+
+ jobQueueCounter.post();
return jobId;
}
@@ -132,6 +164,12 @@ void AsyncEngine::putJobResult(LuaJobInfo &&result)
/******************************************************************************/
void AsyncEngine::step(lua_State *L)
{
+ stepJobResults(L);
+ stepAutoscale();
+}
+
+void AsyncEngine::stepJobResults(lua_State *L)
+{
int error_handler = PUSH_ERROR_HANDLER(L);
lua_getglobal(L, "core");
@@ -148,7 +186,10 @@ void AsyncEngine::step(lua_State *L)
luaL_checktype(L, -1, LUA_TFUNCTION);
lua_pushinteger(L, j.id);
- lua_pushlstring(L, j.result.data(), j.result.size());
+ if (j.result_ext)
+ script_unpack(L, j.result_ext.get());
+ else
+ lua_pushlstring(L, j.result.data(), j.result.size());
// Call handler
const char *origin = j.mod_origin.empty() ? nullptr : j.mod_origin.c_str();
@@ -161,12 +202,71 @@ void AsyncEngine::step(lua_State *L)
lua_pop(L, 2); // Pop core and error handler
}
+void AsyncEngine::stepAutoscale()
+{
+ if (workerThreads.size() >= autoscaleMaxWorkers)
+ return;
+
+ MutexAutoLock autolock(jobQueueMutex);
+
+ // 2) If the timer elapsed, check again
+ if (autoscaleTimer && porting::getTimeMs() >= autoscaleTimer) {
+ autoscaleTimer = 0;
+ // Determine overlap with previous snapshot
+ unsigned int n = 0;
+ for (const auto &it : jobQueue)
+ n += autoscaleSeenJobs.count(it.id);
+ autoscaleSeenJobs.clear();
+ infostream << "AsyncEngine: " << n << " jobs were still waiting after 1s" << std::endl;
+ // Start this many new threads
+ while (workerThreads.size() < autoscaleMaxWorkers && n > 0) {
+ addWorkerThread();
+ n--;
+ }
+ return;
+ }
+
+ // 1) Check if there's anything in the queue
+ if (!autoscaleTimer && !jobQueue.empty()) {
+ // Take a snapshot of all jobs we have seen
+ for (const auto &it : jobQueue)
+ autoscaleSeenJobs.emplace(it.id);
+ // and set a timer for 1 second
+ autoscaleTimer = porting::getTimeMs() + 1000;
+ }
+}
+
/******************************************************************************/
-void AsyncEngine::prepareEnvironment(lua_State* L, int top)
+bool AsyncEngine::prepareEnvironment(lua_State* L, int top)
{
for (StateInitializer &stateInitializer : stateInitializers) {
stateInitializer(L, top);
}
+
+ auto *script = ModApiBase::getScriptApiBase(L);
+ try {
+ script->loadMod(Server::getBuiltinLuaPath() + DIR_DELIM + "init.lua",
+ BUILTIN_MOD_NAME);
+ } catch (const ModError &e) {
+ errorstream << "Execution of async base environment failed: "
+ << e.what() << std::endl;
+ FATAL_ERROR("Execution of async base environment failed");
+ }
+
+ // Load per mod stuff
+ if (server) {
+ const auto &list = server->m_async_init_files;
+ try {
+ for (auto &it : list)
+ script->loadMod(it.second, it.first);
+ } catch (const ModError &e) {
+ errorstream << "Failed to load mod script inside async environment." << std::endl;
+ server->setAsyncFatalError(e.what());
+ return false;
+ }
+ }
+
+ return true;
}
/******************************************************************************/
@@ -178,15 +278,25 @@ AsyncWorkerThread::AsyncWorkerThread(AsyncEngine* jobDispatcher,
{
lua_State *L = getStack();
+ if (jobDispatcher->server) {
+ setGameDef(jobDispatcher->server);
+
+ if (g_settings->getBool("secure.enable_security"))
+ initializeSecurity();
+ }
+
// Prepare job lua environment
lua_getglobal(L, "core");
int top = lua_gettop(L);
// Push builtin initialization type
- lua_pushstring(L, "async");
+ lua_pushstring(L, jobDispatcher->server ? "async_game" : "async");
lua_setglobal(L, "INIT");
- jobDispatcher->prepareEnvironment(L, top);
+ if (!jobDispatcher->prepareEnvironment(L, top)) {
+ // can't throw from here so we're stuck with this
+ isErrored = true;
+ }
}
/******************************************************************************/
@@ -198,19 +308,20 @@ AsyncWorkerThread::~AsyncWorkerThread()
/******************************************************************************/
void* AsyncWorkerThread::run()
{
- lua_State *L = getStack();
+ if (isErrored)
+ return nullptr;
- try {
- loadMod(getServer()->getBuiltinLuaPath() + DIR_DELIM + "init.lua",
- BUILTIN_MOD_NAME);
- } catch (const ModError &e) {
- errorstream << "Execution of async base environment failed: "
- << e.what() << std::endl;
- FATAL_ERROR("Execution of async base environment failed");
- }
+ lua_State *L = getStack();
int error_handler = PUSH_ERROR_HANDLER(L);
+ auto report_error = [this] (const ModError &e) {
+ if (jobDispatcher->server)
+ jobDispatcher->server->setAsyncFatalError(e.what());
+ else
+ errorstream << e.what() << std::endl;
+ };
+
lua_getglobal(L, "core");
if (lua_isnil(L, -1)) {
FATAL_ERROR("Unable to find core within async environment!");
@@ -223,6 +334,8 @@ void* AsyncWorkerThread::run()
if (!jobDispatcher->getJob(&j) || stopRequested())
continue;
+ const bool use_ext = !!j.params_ext;
+
lua_getfield(L, -1, "job_processor");
if (lua_isnil(L, -1))
FATAL_ERROR("Unable to get async job processor!");
@@ -232,7 +345,10 @@ void* AsyncWorkerThread::run()
errorstream << "ASYNC WORKER: Unable to deserialize function" << std::endl;
lua_pushnil(L);
}
- lua_pushlstring(L, j.params.data(), j.params.size());
+ if (use_ext)
+ script_unpack(L, j.params_ext.get());
+ else
+ lua_pushlstring(L, j.params.data(), j.params.size());
// Call it
setOriginDirect(j.mod_origin.empty() ? nullptr : j.mod_origin.c_str());
@@ -241,19 +357,28 @@ void* AsyncWorkerThread::run()
try {
scriptError(result, "<async>");
} catch (const ModError &e) {
- errorstream << e.what() << std::endl;
+ report_error(e);
}
} else {
// Fetch result
- size_t length;
- const char *retval = lua_tolstring(L, -1, &length);
- j.result.assign(retval, length);
+ if (use_ext) {
+ try {
+ j.result_ext.reset(script_pack(L, -1));
+ } catch (const ModError &e) {
+ report_error(e);
+ result = LUA_ERRERR;
+ }
+ } else {
+ size_t length;
+ const char *retval = lua_tolstring(L, -1, &length);
+ j.result.assign(retval, length);
+ }
}
lua_pop(L, 1); // Pop retval
// Put job result
- if (!j.result.empty())
+ if (result == 0)
jobDispatcher->putJobResult(std::move(j));
}
diff --git a/src/script/cpp_api/s_async.h b/src/script/cpp_api/s_async.h
index 697cb0221..1e34e40ea 100644
--- a/src/script/cpp_api/s_async.h
+++ b/src/script/cpp_api/s_async.h
@@ -21,11 +21,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <vector>
#include <deque>
+#include <unordered_set>
+#include <memory>
+#include <lua.h>
#include "threading/semaphore.h"
#include "threading/thread.h"
-#include "lua.h"
+#include "common/c_packer.h"
#include "cpp_api/s_base.h"
+#include "cpp_api/s_security.h"
// Forward declarations
class AsyncEngine;
@@ -42,8 +46,12 @@ struct LuaJobInfo
std::string function;
// Parameter to be passed to function (serialized)
std::string params;
+ // Alternative parameters
+ std::unique_ptr<PackedValue> params_ext;
// Result of function call (serialized)
std::string result;
+ // Alternative result
+ std::unique_ptr<PackedValue> result_ext;
// Name of the mod who invoked this call
std::string mod_origin;
// JobID used to identify a job and match it to callback
@@ -51,7 +59,8 @@ struct LuaJobInfo
};
// Asynchronous working environment
-class AsyncWorkerThread : public Thread, virtual public ScriptApiBase {
+class AsyncWorkerThread : public Thread,
+ virtual public ScriptApiBase, public ScriptApiSecurity {
friend class AsyncEngine;
public:
virtual ~AsyncWorkerThread();
@@ -63,6 +72,7 @@ protected:
private:
AsyncEngine *jobDispatcher = nullptr;
+ bool isErrored = false;
};
// Asynchornous thread and job management
@@ -71,6 +81,7 @@ class AsyncEngine {
typedef void (*StateInitializer)(lua_State *L, int top);
public:
AsyncEngine() = default;
+ AsyncEngine(Server *server) : server(server) {};
~AsyncEngine();
/**
@@ -81,7 +92,7 @@ public:
/**
* Create async engine tasks and lock function registration
- * @param numEngines Number of async threads to be started
+ * @param numEngines Number of worker threads, 0 for automatic scaling
*/
void initialize(unsigned int numEngines);
@@ -95,8 +106,16 @@ public:
const std::string &mod_origin = "");
/**
+ * Queue an async job
+ * @param func Serialized lua function
+ * @param params Serialized parameters (takes ownership!)
+ * @return ID of queued job
+ */
+ u32 queueAsyncJob(std::string &&func, PackedValue *params,
+ const std::string &mod_origin = "");
+
+ /**
* Engine step to process finished jobs
- * the engine step is one way to pass events back, PushFinishedJobs another
* @param L The Lua stack
*/
void step(lua_State *L);
@@ -117,18 +136,43 @@ protected:
void putJobResult(LuaJobInfo &&result);
/**
+ * Start an additional worker thread
+ */
+ void addWorkerThread();
+
+ /**
+ * Process finished jobs callbacks
+ */
+ void stepJobResults(lua_State *L);
+
+ /**
+ * Handle automatic scaling of worker threads
+ */
+ void stepAutoscale();
+
+ /**
* Initialize environment with current registred functions
* this function adds all functions registred by registerFunction to the
* passed lua stack
* @param L Lua stack to initialize
* @param top Stack position
+ * @return false if a mod error ocurred
*/
- void prepareEnvironment(lua_State* L, int top);
+ bool prepareEnvironment(lua_State* L, int top);
private:
// Variable locking the engine against further modification
bool initDone = false;
+ // Maximum number of worker threads for automatic scaling
+ // 0 if disabled
+ unsigned int autoscaleMaxWorkers = 0;
+ u64 autoscaleTimer = 0;
+ std::unordered_set<u32> autoscaleSeenJobs;
+
+ // Only set for the server async environment (duh)
+ Server *server = nullptr;
+
// Internal store for registred state initializers
std::vector<StateInitializer> stateInitializers;
diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp
index f7b8a5102..595c9e540 100644
--- a/src/script/cpp_api/s_base.cpp
+++ b/src/script/cpp_api/s_base.cpp
@@ -121,6 +121,14 @@ ScriptApiBase::ScriptApiBase(ScriptingType type):
lua_newtable(m_luastack);
lua_setglobal(m_luastack, "core");
+ // vector.metatable is stored in the registry for quick access from C++.
+ lua_newtable(m_luastack);
+ lua_rawseti(m_luastack, LUA_REGISTRYINDEX, CUSTOM_RIDX_VECTOR_METATABLE);
+ lua_newtable(m_luastack);
+ lua_rawgeti(m_luastack, LUA_REGISTRYINDEX, CUSTOM_RIDX_VECTOR_METATABLE);
+ lua_setfield(m_luastack, -2, "metatable");
+ lua_setglobal(m_luastack, "vector");
+
if (m_type == ScriptingType::Client)
lua_pushstring(m_luastack, "/");
else
diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp
index c889fffa0..b02a0c7be 100644
--- a/src/script/cpp_api/s_client.cpp
+++ b/src/script/cpp_api/s_client.cpp
@@ -281,15 +281,7 @@ bool ScriptApiClient::on_inventory_open(Inventory *inventory)
lua_getglobal(L, "core");
lua_getfield(L, -1, "registered_on_inventory_open");
- std::vector<const InventoryList*> lists = inventory->getLists();
- std::vector<const InventoryList*>::iterator iter = lists.begin();
- lua_createtable(L, 0, lists.size());
- for (; iter != lists.end(); iter++) {
- const char* name = (*iter)->getName().c_str();
- lua_pushstring(L, name);
- push_inventory_list(L, inventory, name);
- lua_rawset(L, -3);
- }
+ push_inventory_lists(L, *inventory);
try {
runCallbacks(1, RUN_CALLBACKS_MODE_OR);
diff --git a/src/script/cpp_api/s_entity.cpp b/src/script/cpp_api/s_entity.cpp
index 06337b9e8..852a27ddc 100644
--- a/src/script/cpp_api/s_entity.cpp
+++ b/src/script/cpp_api/s_entity.cpp
@@ -103,7 +103,7 @@ void ScriptApiEntity::luaentity_Activate(u16 id,
lua_pop(L, 2); // Pop object and error handler
}
-void ScriptApiEntity::luaentity_Deactivate(u16 id)
+void ScriptApiEntity::luaentity_Deactivate(u16 id, bool removal)
{
SCRIPTAPI_PRECHECKHEADER
@@ -120,9 +120,9 @@ void ScriptApiEntity::luaentity_Deactivate(u16 id)
if (!lua_isnil(L, -1)) {
luaL_checktype(L, -1, LUA_TFUNCTION);
lua_pushvalue(L, object);
-
+ lua_pushboolean(L, removal);
setOriginFromTable(object);
- PCALL_RES(lua_pcall(L, 1, 0, error_handler));
+ PCALL_RES(lua_pcall(L, 2, 0, error_handler));
} else {
lua_pop(L, 1);
}
@@ -244,7 +244,7 @@ bool ScriptApiEntity::luaentity_Punch(u16 id,
{
SCRIPTAPI_PRECHECKHEADER
- //infostream<<"scriptapi_luaentity_step: id="<<id<<std::endl;
+ assert(puncher);
int error_handler = PUSH_ERROR_HANDLER(L);
@@ -294,7 +294,10 @@ bool ScriptApiEntity::luaentity_run_simple_callback(u16 id,
}
luaL_checktype(L, -1, LUA_TFUNCTION);
lua_pushvalue(L, object); // self
- objectrefGetOrCreate(L, sao); // killer reference
+ if (sao)
+ objectrefGetOrCreate(L, sao); // sao reference
+ else
+ lua_pushnil(L);
setOriginFromTable(object);
PCALL_RES(lua_pcall(L, 2, 1, error_handler));
diff --git a/src/script/cpp_api/s_entity.h b/src/script/cpp_api/s_entity.h
index 7658ae922..13f3e9aa3 100644
--- a/src/script/cpp_api/s_entity.h
+++ b/src/script/cpp_api/s_entity.h
@@ -33,7 +33,7 @@ public:
bool luaentity_Add(u16 id, const char *name);
void luaentity_Activate(u16 id,
const std::string &staticdata, u32 dtime_s);
- void luaentity_Deactivate(u16 id);
+ void luaentity_Deactivate(u16 id, bool removal);
void luaentity_Remove(u16 id);
std::string luaentity_GetStaticdata(u16 id);
void luaentity_GetProperties(u16 id,
diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp
index 874c37b6e..af68f689f 100644
--- a/src/script/cpp_api/s_env.cpp
+++ b/src/script/cpp_api/s_env.cpp
@@ -140,10 +140,10 @@ void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env)
bool simple_catch_up = true;
getboolfield(L, current_abm, "catch_up", simple_catch_up);
-
+
s16 min_y = INT16_MIN;
getintfield(L, current_abm, "min_y", min_y);
-
+
s16 max_y = INT16_MAX;
getintfield(L, current_abm, "max_y", max_y);
diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp
index a6c5114b2..316b19926 100644
--- a/src/script/cpp_api/s_security.cpp
+++ b/src/script/cpp_api/s_security.cpp
@@ -98,6 +98,7 @@ void ScriptApiSecurity::initializeSecurity()
"type",
"unpack",
"_VERSION",
+ "vector",
"xpcall",
};
static const char *whitelist_tables[] = {
@@ -127,8 +128,6 @@ void ScriptApiSecurity::initializeSecurity()
"gethook",
"traceback",
"getinfo",
- "getmetatable",
- "setmetatable",
"upvalueid",
"sethook",
"debug",
@@ -254,6 +253,10 @@ void ScriptApiSecurity::initializeSecurity()
lua_pushnil(L);
lua_setfield(L, old_globals, "core");
+ // 'vector' as well.
+ lua_pushnil(L);
+ lua_setfield(L, old_globals, "vector");
+
lua_pop(L, 1); // Pop globals_backup
@@ -296,6 +299,7 @@ void ScriptApiSecurity::initializeSecurityClient()
"type",
"unpack",
"_VERSION",
+ "vector",
"xpcall",
// Completely safe libraries
"coroutine",
@@ -413,6 +417,12 @@ void ScriptApiSecurity::setLuaEnv(lua_State *L, int thread)
bool ScriptApiSecurity::isSecure(lua_State *L)
{
+#ifndef SERVER
+ auto script = ModApiBase::getScriptApiBase(L);
+ // CSM keeps no globals backup but is always secure
+ if (script->getType() == ScriptingType::Client)
+ return true;
+#endif
lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP);
bool secure = !lua_isnil(L, -1);
lua_pop(L, 1);