optimizations + bugfixes
* Bombs explode on damage/explosion * Orbits spell fixed * Performance: implemented shared component indexes, compiled Lua caching, and updates only for changed projectile fields. Also removed accumulating hidden entity slots.
This commit is contained in:
1 parent
87bff164f1
commit
a34f42d53e
17 files changed
+727
-131
No files matched your search
+109
-4
@@ -86,10 +86,19 @@ double num(lua_State* L, int i, double def = 0) { return lua_isnumber(L, i) ? lu
|
||||
std::string str(lua_State* L, int i) { return lua_isstring(L, i) ? lua_tostring(L, i) : ""; }
|
||||
|
||||
// ---- file loading ----
|
||||
static size_t script_cache_revision = 0;
|
||||
|
||||
bool load_from_wak(LuaHost& h, lua_State* L, const std::string& path, std::string* err) {
|
||||
static std::unordered_map<std::string, std::vector<uint8_t>> cache; // the scripts of the entities run again and again: read the archive once
|
||||
if (LuaHost::scripts_changed) { cache.clear(); LuaHost::scripts_changed = false; } // (a mod replaced a file)
|
||||
static std::unordered_map<std::string, std::vector<uint8_t>> compiled;
|
||||
if (LuaHost::scripts_changed) { cache.clear(); compiled.clear(); ++script_cache_revision; LuaHost::scripts_changed = false; } // (a mod replaced a file)
|
||||
if (const auto chunk = compiled.find(path); chunk != compiled.end()) {
|
||||
// Load a fresh closure so each entity still receives its own environment.
|
||||
// Parsing identical source for every per-frame projectile script is costly.
|
||||
if (luaL_loadbuffer(L, (const char*)chunk->second.data(), chunk->second.size(), ("@" + path).c_str()) == 0) return true;
|
||||
lua_pop(L, 1);
|
||||
compiled.erase(chunk);
|
||||
}
|
||||
auto it = cache.find(path);
|
||||
if (it == cache.end()) {
|
||||
std::vector<uint8_t> raw;
|
||||
@@ -117,6 +126,14 @@ bool load_from_wak(LuaHost& h, lua_State* L, const std::string& path, std::strin
|
||||
lua_pop(L, 1);
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> bytecode;
|
||||
const auto writer = [](lua_State*, const void* bytes, size_t size, void* output) -> int {
|
||||
auto& out = *static_cast<std::vector<uint8_t>*>(output);
|
||||
const auto* begin = static_cast<const uint8_t*>(bytes);
|
||||
out.insert(out.end(), begin, begin + size);
|
||||
return 0;
|
||||
};
|
||||
if (lua_dump(L, writer, &bytecode) == 0 && !bytecode.empty()) compiled.emplace(path, std::move(bytecode));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -461,6 +478,21 @@ LuaHost::LuaHost(const Wak& wak, GameRandom& rng) : wak_(wak), rng_(rng) {
|
||||
L_ = luaL_newstate();
|
||||
luaL_openlibs(L_);
|
||||
register_api();
|
||||
// Retain function identities for validated native script paths. Lua overrides
|
||||
// (including overrides local to an entity's VM) must keep running through Lua.
|
||||
auto capture = [&](const char* prefix) {
|
||||
lua_pushnil(L_);
|
||||
while (lua_next(L_, -2)) {
|
||||
if (lua_type(L_, -2) == LUA_TSTRING && lua_isfunction(L_, -1)) {
|
||||
const std::string key = std::string(prefix) + lua_tostring(L_, -2);
|
||||
lua_pushvalue(L_, -1);
|
||||
original_api_[key] = luaL_ref(L_, LUA_REGISTRYINDEX);
|
||||
}
|
||||
lua_pop(L_, 1);
|
||||
}
|
||||
};
|
||||
lua_pushvalue(L_, LUA_GLOBALSINDEX); capture(""); lua_pop(L_, 1);
|
||||
lua_getglobal(L_, "math"); capture("math."); lua_pop(L_, 1);
|
||||
// Spawn functions the engine registers for every biome before its script runs (the script may override colours).
|
||||
// Colours as RGBA hex, the table common to all biomes of the reference data (noita-tools maps.json).
|
||||
static const struct { uint32_t rgba; const char* fn; } kDefaults[] = {
|
||||
@@ -554,7 +586,7 @@ void LuaHost::register_api() {
|
||||
register_host_natives(*this);
|
||||
}
|
||||
|
||||
void LuaHost::register_native(const char* name, NativeFn fn) { reg(L_, *this, name, fn); }
|
||||
void LuaHost::register_native(const char* name, NativeFn fn) { ++execution_revision_; reg(L_, *this, name, fn); }
|
||||
|
||||
// Pushes the current environment table (the globals when env is empty), made on first use: a table whose metatable sends missing names to the globals
|
||||
static void push_env_table(lua_State* L, const std::string& key) {
|
||||
@@ -580,7 +612,12 @@ static void get_fn(lua_State* L, const std::string& env, const std::string& name
|
||||
}
|
||||
|
||||
void LuaHost::drop_env(const std::string& key) {
|
||||
++execution_revision_;
|
||||
if (key.empty()) return;
|
||||
if (const auto files = compiled_files_.find(key); files != compiled_files_.end()) {
|
||||
for (const auto& [file, ref] : files->second) luaL_unref(L_, LUA_REGISTRYINDEX, ref);
|
||||
compiled_files_.erase(files);
|
||||
}
|
||||
lua_getfield(L_, LUA_REGISTRYINDEX, "ng_envs");
|
||||
if (lua_istable(L_, -1)) { lua_pushnil(L_); lua_setfield(L_, -2, key.c_str()); }
|
||||
lua_pop(L_, 1);
|
||||
@@ -593,7 +630,50 @@ bool LuaHost::has_function(const std::string& name, bool own) {
|
||||
return f;
|
||||
}
|
||||
|
||||
bool LuaHost::has_cached_file(const std::string& path) const {
|
||||
if (scripts_changed || compiled_revision_ != script_cache_revision) return false;
|
||||
const auto files = compiled_files_.find(env);
|
||||
return files != compiled_files_.end() && files->second.count(path) != 0;
|
||||
}
|
||||
|
||||
bool LuaHost::uses_original_api(std::initializer_list<const char*> names) {
|
||||
const int top = lua_gettop(L_);
|
||||
push_env_table(L_, env);
|
||||
const int environment = lua_gettop(L_);
|
||||
auto finish = [&](bool result) { lua_settop(L_, top); return result; };
|
||||
if (!env.empty()) {
|
||||
if (!lua_getmetatable(L_, environment)) return finish(false);
|
||||
lua_pushliteral(L_, "__index"); lua_rawget(L_, -2);
|
||||
lua_pushvalue(L_, LUA_GLOBALSINDEX);
|
||||
const bool standardLookup = lua_rawequal(L_, -1, -2) != 0;
|
||||
lua_pop(L_, 3);
|
||||
if (!standardLookup) return finish(false);
|
||||
}
|
||||
auto global = [&](const std::string& name) {
|
||||
lua_pushlstring(L_, name.data(), name.size()); lua_rawget(L_, environment);
|
||||
if (lua_isnil(L_, -1) && !env.empty()) { lua_pop(L_, 1); lua_pushlstring(L_, name.data(), name.size()); lua_rawget(L_, LUA_GLOBALSINDEX); }
|
||||
};
|
||||
for (const char* name : names) {
|
||||
const auto original = original_api_.find(name);
|
||||
if (original == original_api_.end()) return finish(false);
|
||||
const char* dot = std::strchr(name, '.');
|
||||
if (dot) {
|
||||
global(std::string(name, dot));
|
||||
if (!lua_istable(L_, -1)) return finish(false);
|
||||
lua_pushstring(L_, dot + 1); lua_rawget(L_, -2); lua_remove(L_, -2);
|
||||
} else global(name);
|
||||
lua_rawgeti(L_, LUA_REGISTRYINDEX, original->second);
|
||||
const bool same = lua_rawequal(L_, -1, -2) != 0;
|
||||
lua_pop(L_, 2);
|
||||
if (!same) return finish(false);
|
||||
}
|
||||
global("NULL_ENTITY");
|
||||
const bool zero = lua_isnumber(L_, -1) && lua_tonumber(L_, -1) == 0;
|
||||
return finish(zero);
|
||||
}
|
||||
|
||||
bool LuaHost::call_function(const std::string& name, const std::vector<LVal>& args, std::string* err) {
|
||||
++execution_revision_;
|
||||
get_fn(L_, env, name);
|
||||
if (!lua_isfunction(L_, -1)) {
|
||||
lua_pop(L_, 1);
|
||||
@@ -612,6 +692,7 @@ bool LuaHost::call_function(const std::string& name, const std::vector<LVal>& ar
|
||||
}
|
||||
|
||||
bool LuaHost::call_function_numbers(const std::string& name, const std::vector<LVal>& args, int nresults, std::vector<double>& results, std::string* err) {
|
||||
++execution_revision_;
|
||||
results.assign(nresults, 0.0);
|
||||
get_fn(L_, env, name); // (not a function: the pcall fails, as the engine's does)
|
||||
for (const LVal& a : args) {
|
||||
@@ -713,12 +794,35 @@ bool LuaHost::gen_set(int cid, const std::string& key, const std::string& value)
|
||||
bool LuaHost::load_once(const std::string& path) { return loaded_once_.insert(path).second; }
|
||||
|
||||
bool LuaHost::run_file(const std::string& path, std::string* err) {
|
||||
if (!load_from_wak(*this, L_, path, err)) return false;
|
||||
if (!env.empty()) { push_env_table(L_, env); lua_setfenv(L_, -2); }
|
||||
++execution_revision_;
|
||||
if (scripts_changed) { // refresh source/bytecode before consulting this state's closures
|
||||
if (!load_from_wak(*this, L_, path, err)) return false;
|
||||
lua_pop(L_, 1);
|
||||
}
|
||||
if (compiled_revision_ != script_cache_revision) {
|
||||
for (const auto& [key, files] : compiled_files_) for (const auto& [file, ref] : files) luaL_unref(L_, LUA_REGISTRYINDEX, ref);
|
||||
compiled_files_.clear();
|
||||
compiled_revision_ = script_cache_revision;
|
||||
}
|
||||
auto& files = compiled_files_[env];
|
||||
const auto cached = files.find(path);
|
||||
if (cached != files.end()) lua_rawgeti(L_, LUA_REGISTRYINDEX, cached->second);
|
||||
else {
|
||||
if (!load_from_wak(*this, L_, path, err)) return false;
|
||||
lua_pushvalue(L_, -1);
|
||||
files.emplace(path, luaL_ref(L_, LUA_REGISTRYINDEX));
|
||||
}
|
||||
// Separate closures for separate environments; dropping a VM releases its chunks too.
|
||||
// Reusing a chunk also lets LuaJIT retain its hot counters and compiled traces.
|
||||
push_env_table(L_, env);
|
||||
lua_getfenv(L_, -2);
|
||||
if (lua_rawequal(L_, -1, -2)) lua_pop(L_, 2);
|
||||
else { lua_pop(L_, 1); lua_setfenv(L_, -2); }
|
||||
return pcall_traced(L_, 0, 0, err);
|
||||
}
|
||||
|
||||
bool LuaHost::run_string(const std::string& code, const std::string& chunk_name, std::string* err) {
|
||||
++execution_revision_;
|
||||
if (luaL_loadbuffer(L_, code.data(), code.size(), chunk_name.c_str()) != 0) {
|
||||
if (err) *err = lua_tostring(L_, -1);
|
||||
lua_pop(L_, 1);
|
||||
@@ -728,6 +832,7 @@ bool LuaHost::run_string(const std::string& code, const std::string& chunk_name,
|
||||
}
|
||||
|
||||
bool LuaHost::call_global(const std::string& name, const std::vector<double>& args, std::string* err) {
|
||||
++execution_revision_;
|
||||
lua_getglobal(L_, name.c_str());
|
||||
if (!lua_isfunction(L_, -1)) {
|
||||
lua_pop(L_, 1);
|
||||
|
||||
Reference in new issue
Block a user