Skip to content

Commit

Permalink
Add bytecode precompilation support
Browse files Browse the repository at this point in the history
On load/reload, the scripts will be compiled to bytecode and stored in memory. New states will always load this precompiled bytecode, leading to much faster load times.
  • Loading branch information
Foereaper committed Jan 7, 2024
1 parent 0c40c46 commit 215cea7
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 13 deletions.
74 changes: 63 additions & 11 deletions ElunaLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,15 @@
#include <filesystem>
#include <fstream>
#include <boost/filesystem.hpp>

#include "MapManager.h"

extern "C" {
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}

ElunaLoader::ElunaLoader()
{
}
Expand Down Expand Up @@ -41,21 +48,16 @@ void ElunaLoader::LoadScripts()
if (const char* home = getenv("HOME"))
lua_folderpath.replace(0, 1, home);
#endif
ELUNA_LOG_INFO("[Eluna]: Searching scripts from `%s`", lua_folderpath.c_str());
ELUNA_LOG_INFO("[Eluna]: Searching for scripts in `%s`", lua_folderpath.c_str());
lua_requirepath.clear();
ReadFiles(lua_folderpath);

// Combine extensions and lua scripts into one list for proper loading order.
CombineLists();

// Erase last ;
if (!lua_requirepath.empty())
lua_requirepath.erase(lua_requirepath.end() - 1);

ELUNA_LOG_INFO("[Eluna]: Loaded %u scripts in %u ms", uint32(combined_scripts.size()), ElunaUtil::GetTimeDiff(oldMSTime));

ELUNA_LOG_INFO("[Eluna]: Loaded and precompiled %u scripts in %u ms", uint32(combined_scripts.size()), ElunaUtil::GetTimeDiff(oldMSTime));
requiredMaps.clear();

std::string maps = eConfigMgr->GetStringDefault("Eluna.OnlyOnMaps", "");
for (std::string_view mapIdStr : Trinity::Tokenize(maps, ',', false))
{
Expand All @@ -69,11 +71,23 @@ void ElunaLoader::LoadScripts()
preloadMaps = eConfigMgr->GetBoolDefault("Eluna.PreloadOnlyOnMaps", false);
}

int ElunaLoader::LoadBytecodeChunk(lua_State* L, uint8* bytes, size_t len, BytecodeBuffer* buffer)
{
for (size_t i = 0; i < len; i++)
buffer->push_back(bytes[i]);

return 0;
}

// Finds lua script files from given path (including subdirectories) and pushes them to scripts
void ElunaLoader::ReadFiles(std::string path)
{
ELUNA_LOG_DEBUG("[Eluna]: GetScripts from path `%s`", path.c_str());

// Open a new Lua state to compile bytecode in
lua_State* L = luaL_newstate();
luaL_openlibs(L);

boost::filesystem::path someDir(path);
boost::filesystem::directory_iterator end_iter;

Expand Down Expand Up @@ -131,15 +145,49 @@ void ElunaLoader::ReadFiles(std::string path)

// was file, try add
std::string filename = dir_iter->path().filename().generic_string();
AddScriptPath(filename, fullpath, mapId);
ProcessScript(L, filename, fullpath, mapId);
}
}
}

// close Lua state
lua_close(L);
}

void ElunaLoader::AddScriptPath(std::string filename, const std::string& fullpath, int32 mapId)
bool ElunaLoader::CompileScript(lua_State* L, LuaScript& script)
{
ELUNA_LOG_DEBUG("[Eluna]: AddScriptPath Checking file `%s`", fullpath.c_str());
// Attempt to load the file
int err = luaL_loadbuffer(L, script.filedata.c_str(), script.filedata.size(), script.filename.c_str());

// If something bad happened, try to find an error.
if (err != LUA_OK)
{
ELUNA_LOG_ERROR("[Eluna]: CompileScript failed to load the Lua script `%s`.", script.filename.c_str());
return false;
}
ELUNA_LOG_DEBUG("[Eluna]: CompileScript loaded Lua script `%s`", script.filename.c_str());
BytecodeBuffer buffer;

// Everything's OK so far, the script has been loaded, now we need to start dumping it to bytecode.
err = lua_dump(L, (lua_Writer)LoadBytecodeChunk, &buffer);
if (err || buffer.empty())
{
ELUNA_LOG_ERROR("[Eluna]: CompileScript failed to dump the Lua script `%s` to bytecode.", script.filename.c_str());
return false;
}
ELUNA_LOG_DEBUG("[Eluna]: CompileScript dumped Lua script `%s` to bytecode.", script.filename.c_str());

// Write buffer to bytecode
script.bytecode = buffer;

// pop the loaded function from the stack
lua_pop(L, 1);
return true;
}

void ElunaLoader::ProcessScript(lua_State* L, std::string filename, const std::string& fullpath, int32 mapId)
{
ELUNA_LOG_DEBUG("[Eluna]: ProcessScript checking file `%s`", fullpath.c_str());

// split file name
std::size_t extDot = filename.find_last_of('.');
Expand Down Expand Up @@ -172,11 +220,15 @@ void ElunaLoader::AddScriptPath(std::string filename, const std::string& fullpat
script.filedata = content;
script.mapId = mapId;

// if compilation fails, we don't add the script
if (!CompileScript(L, script))
return;

if (extension)
lua_extensions.push_back(script);
else
lua_scripts.push_back(script);
ELUNA_LOG_DEBUG("[Eluna]: AddScriptPath add path `%s`", fullpath.c_str());
ELUNA_LOG_DEBUG("[Eluna]: ProcessScript processed `%s` successfully", fullpath.c_str());
}

static bool ScriptPathComparator(const LuaScript& first, const LuaScript& second)
Expand Down
12 changes: 11 additions & 1 deletion ElunaLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@

#ifndef _ELUNALOADER_H
#define _ELUNALOADER_H

#include "ElunaUtility.h"

extern "C"
{
#include "lua.h"
};

struct LuaScript;

class ElunaLoader
Expand All @@ -24,9 +32,11 @@ class ElunaLoader
static ElunaLoader* instance();
void LoadScripts();
void ReadFiles(std::string path);
void AddScriptPath(std::string filename, const std::string& fullpath, int32 mapId);
void CombineLists();
void ProcessScript(lua_State* L, std::string filename, const std::string& fullpath, int32 mapId);
bool ShouldMapLoadEluna(uint32 mapId);
bool CompileScript(lua_State* L, LuaScript& script);
static int LoadBytecodeChunk(lua_State* L, uint8* bytes, size_t len, BytecodeBuffer* buffer);
void PreloadElunaMaps();

// Lua script folder path
Expand Down
2 changes: 2 additions & 0 deletions ElunaUtility.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ typedef QueryNamedResult ElunaQuery;
#endif
#endif

typedef std::vector<uint8> BytecodeBuffer;

class Unit;
class WorldObject;
struct FactionTemplateEntry;
Expand Down
2 changes: 1 addition & 1 deletion LuaEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ void Eluna::RunScripts()
lua_pop(L, 1);
// Stack: package, modules

if (luaL_loadbuffer(L, it->filedata.c_str(), it->filedata.size(), it->filename.c_str()))
if (luaL_loadbuffer(L, reinterpret_cast<const char*>(&it->bytecode[0]), it->bytecode.size(), it->filename.c_str()))
{
// Stack: package, modules, errmsg
ELUNA_LOG_ERROR("[Eluna]: Error loading `%s`", it->filepath.c_str());
Expand Down
1 change: 1 addition & 0 deletions LuaEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ struct LuaScript
std::string filepath;
std::string modulepath;
std::string filedata;
BytecodeBuffer bytecode;
int32 mapId;
};

Expand Down

0 comments on commit 215cea7

Please sign in to comment.