Page MenuHomePhabricator (Chris)

No OneTemporary

Size
91 KB
Referenced Files
None
Subscribers
None
diff --git a/source/game/main/initialization.cpp b/source/game/main/initialization.cpp
index 6013eca..ea782b7 100644
--- a/source/game/main/initialization.cpp
+++ b/source/game/main/initialization.cpp
@@ -1,1732 +1,1732 @@
#include "util/random.h"
#include "util/threaded_loader.h"
#include "util/name_generator.h"
#include "render/gl_driver.h"
#include "render/lighting.h"
#include "main/profiler.h"
#include "main/initialization.h"
#include "main/logging.h"
#include "main/input_handling.h"
#include "main/version.h"
#include "main/references.h"
#include "main/console.h"
#include "main/game_platform.h"
#include "os/glfw_driver.h"
#include "scripts/manager.h"
#include "scripts/binds.h"
#include "scripts/context_cache.h"
#include "scripts/script_components.h"
#include "constants.h"
#include "files.h"
#include "threads.h"
#include "processing.h"
#include "general_states.h"
#include "empire.h"
#include "empire_stats.h"
#include "scene/node.h"
#include "scene/mesh_node.h"
#include "scene/plane_node.h"
#include "scene/billboard_node.h"
#include "scene/animation/anim_node_sync.h"
#include "scene/scripted_node.h"
#include "obj/universe.h"
#include "obj/lock.h"
#include "as/as_jit.h"
#include "design/hull.h"
#include "design/design.h"
#include "design/subsystem.h"
#include "design/effect.h"
#include "design/projectiles.h"
#include "ISoundDevice.h"
#include "network/network_manager.h"
#include "str_util.h"
#include "physics/physics_world.h"
#include "util/save_file.h"
#include "save_load.h"
#include "render/lighting.h"
#include <stdio.h>
#include <fstream>
namespace scripts {
void processEvents();
};
#ifdef _WIN32
#define USE_JIT
#endif
#ifdef __i386__
#define USE_JIT
#endif
#ifdef __amd64__
#define USE_JIT
#endif
GameConfig gameConfig;
bool create_window = true;
bool load_resources = true;
bool watch_resources = false;
bool monitor_files = false;
bool use_sound = true;
bool use_steam = true;
bool isLoadedGame = false;
int SAVE_VERSION = -1, START_VERSION = -1;
bool gameEnding = false;
bool fullscreen = false;
bool cancelAssets = false;
#ifdef USE_JIT
bool useJIT = true;
#else
bool useJIT = false;
#endif
std::string loadSaveName;
std::unordered_set<std::string> dlcList;
//Poor mans steam-independent DLC checks
bool hasDLC(const std::string& name) {
return dlcList.find(name) != dlcList.end();
}
void checkDLC(const std::string& name, const std::string& hashname) {
//std::string fname("data/dlc/");
//fname += hashname;
//if(fileExists(fname) || (devices.cloud && devices.cloud->hasDLC(name)))
// Open source version always has DLC
dlcList.insert(name);
}
void checkDLC() {
dlcList.clear();
checkDLC("Heralds", "");
}
void checkCloudDLC(const std::string& name, const std::string& hashname) {
/*std::string fname("data/dlc/");
fname += hashname;
if(devices.cloud->hasDLC(name)) {
if(!fileExists(fname)) {
makeDirectory("data/dlc/");
std::fstream file(fname, std::ios_base::out | std::ios_base::binary);
file << "\n";
file.close();
}
dlcList.insert("Heralds");
}
else {
if(fileExists(fname))
remove(fname.c_str());
dlcList.erase(name);
}*/
// Open source version always has DLC
dlcList.insert(name);
}
void checkCloudDLC() {
if(!devices.cloud)
return;
checkCloudDLC("Heralds", "");
}
extern const render::Shader* fsShader;
extern std::set<std::string> validPaths;
extern void prepareShaderStateVars();
extern void clearShaderStateVars();
int threadCount;
extern bool queuedModSwitch;
extern std::vector<std::string> modSetup;
template<resource::ResourceType type>
void loadLibraryFile(const std::string& path) {
devices.library.load(type, path);
}
#define libraryDir(dir, type) devices.mods.listFiles(dir, "*.txt", loadLibraryFile<type>);
bool processQueuedMeshes();
asCJITCompiler* makeJITCompiler() {
#ifdef USE_JIT
if(!useJIT)
return 0;
unsigned flags = 0;
#ifndef PROFILE_EXECUTION
return new asCJITCompiler(flags | JIT_NO_SUSPEND | JIT_SYSCALL_FPU_NORESET | JIT_ALLOC_SIMPLE | JIT_FAST_REFCOUNT);
#else
return new asCJITCompiler(flags | JIT_SYSCALL_FPU_NORESET | JIT_ALLOC_SIMPLE | JIT_FAST_REFCOUNT);
#endif
#else
return 0;
#endif
}
void loadEngineSettings();
Threaded(std::vector<std::function<void()>>*) cleanupFunctions = 0;
threads::Mutex scriptListLock;
std::map<std::string, std::map<std::string, std::string>*> scriptfiles;
void clearScriptList() {
threads::Lock lock(scriptListLock);
for(auto i = scriptfiles.begin(), end = scriptfiles.end(); i != end; ++i)
delete i->second;
scriptfiles.clear();
}
const std::map<std::string, std::string>& findScripts(const std::string& path) {
threads::Lock lock(scriptListLock);
auto*& m = scriptfiles[path];
if(!m) {
m = new std::map<std::string, std::string>;
devices.mods.listFiles(path, *m, "*.as", true);
}
return *m;
}
void onScripts(const std::string& path, std::function<void(const std::pair<std::string,std::string>&)> f) {
auto& scripts = findScripts(path);
for(auto i = scripts.cbegin(), end = scripts.cend(); i != end; ++i)
f(*i);
}
threads::Signal scriptPreloads;
threads::threadreturn threadcall PreloadBatch(void* arg) {
std::string* pPath = (std::string*)arg;
std::string& path = *pPath;
auto preload = [](const std::pair<std::string,std::string>& file) {
if(file.second.find("/include/") == std::string::npos)
scripts::preloadScript(file.second);
};
onScripts(path, preload);
scriptPreloads.signalDown();
delete pPath;
return 0;
}
void initNewThread() {
//Start up random number generator
initRandomizer();
//Initialize thread-local script context cache
scripts::initContextCache();
}
void cleanupThread() {
scripts::freeContextCache();
asThreadCleanup();
freeRandomizer();
if(cleanupFunctions) {
foreach(it, (*cleanupFunctions))
(*it)();
delete cleanupFunctions;
cleanupFunctions = 0;
}
}
void addThreadCleanup(std::function<void()> func) {
if(!cleanupFunctions)
cleanupFunctions = new std::vector<std::function<void()>>();
cleanupFunctions->push_back(func);
}
bool initGlobal(bool loadGraphics, bool createWindow) {
//Create the sound device
try {
if(use_sound) {
print("Initializing sound");
auto* audioDevice = devices.settings.engine.getSetting("sAudioDevice");
if(audioDevice)
devices.sound = audio::createAudioDevice(audioDevice->toString().c_str());
else
devices.sound = audio::createAudioDevice();
}
if(!devices.sound)
devices.sound = audio::createDummyAudioDevice();
}
catch(const char* err) {
error("Sound Error: %s", err);
devices.sound = audio::createDummyAudioDevice();
}
devices.sound->setVolume(0.5f);
devices.sound->setRolloffFactor(1.f / 10000.f);
//Safe AS multithreading
asPrepareMultithread();
print("Initializing window system");
devices.driver = os::getGLFWDriver();
devices.driver->resetTimer();
//Load engine settings
print("Loading engine settings");
loadEngineSettings();
console.addCommand("fov", [](argList& args) {
if(devices.render && !args.empty()) {
double newFOV = toNumber<double>(args[0]);
if(newFOV > 0.1 && newFOV < 179.9)
devices.render->setFOV(newFOV);
else
console.printLn("FOV must be within 0.1 and 179.9 degrees");
}
} );
console.addCommand("vsync", [](argList& args) {
if(devices.driver) {
if(args.empty())
devices.driver->setVerticalSync(true);
else if(streq_nocase(args[0],"adaptive"))
devices.driver->setVerticalSync(-1);
else
devices.driver->setVerticalSync(toBool(args[0]));
}
} );
console.addCommand("load", [](argList& args) {
std::string savePath = devices.mods.getProfile("saves");
std::string filename = path_join(savePath, args.empty() ? "quicksave.sr2" : args[0]);
if(!match(filename.c_str(),"*.sr2"))
filename += ".sr2";
scripts::scr_loadGame(filename);
});
console.addCommand("maxfps", [](argList& args) {
extern double* maxfps;
if(!args.empty())
*maxfps = toNumber<double>(args[0]);
console.printLn(toString(maxfps,0));
} );
console.addCommand("r", [](argList& args) {
if(devices.scripts.client && !args.empty())
devices.scripts.client->reload(args[0]);
} );
if(load_resources) {
print("Initializing OpenGL Engine");
devices.render = render::createGLDriver();
}
else {
devices.render = nullptr;
}
create_window = createWindow;
load_resources = loadGraphics;
//Create network driver
devices.network = new NetworkManager();
auto* netLimit = devices.settings.engine.getSetting("iNetworkRateLimit");
net::Transport::RATE_LIMIT = (netLimit ? *netLimit : 250) * 1024;
devices.scripts.client = nullptr;
devices.scripts.server = nullptr;
devices.scripts.cache_server = nullptr;
devices.scripts.cache_shadow = nullptr;
devices.scripts.menu = nullptr;
devices.engines.client = nullptr;
devices.engines.server = nullptr;
devices.engines.menu = nullptr;
//Check marked DLCs
checkDLC();
//Add mod sources
print("Registering mods");
devices.mods.registerDirectory("mods");
devices.mods.registerDirectory(getProfileRoot() + "mods");
devices.mods.registerDirectory("../../workshop/content/282590");
//Shortcut for the scene tree
if(devices.render)
devices.scene = &devices.render->rootNode;
else
devices.scene = nullptr;
devices.universe = nullptr;
//Initialize input handling
registerInput();
//Create processing queue
unsigned cpuCount = devices.driver->getProcessorCount();
threadCount = std::max(cpuCount+1,1u);
print("Starting %d threads on %d processors.", threadCount, cpuCount);
processing::start(threadCount);
return true;
}
void destroyGlobal() {
devices.network->disconnect();
delete devices.network;
asUnprepareMultithread();
devices.driver->closeWindow();
delete devices.driver;
}
void loadLocales(std::string folder, mods::Mod* mod) {
if(mod->parent != nullptr)
loadLocales(folder, mod->parent);
std::map<std::string, std::string> files;
mod->listFiles(folder, files, "*.txt", true, false);
foreach(file, files)
devices.locale.load(file->second);
}
void switchLocale(const std::string& locale) {
devices.locale.clear();
foreach(m, devices.mods.activeMods)
loadLocales("locales/english", *m);
if(locale != "english") {
std::string dirname = path_join("locales", locale);
foreach(m, devices.mods.activeMods)
loadLocales(dirname, *m);
}
}
std::string makeModuleName(const std::string& fname) {
std::string mname;
std::vector<std::string> components;
path_split(fname, components);
for(size_t i = 0, cnt = components.size() - 1; i < cnt; ++i)
mname += components[i]+".";
mname += getBasename(fname, false);
return mname;
}
static threads::Signal jitInit;
void loadServerScripts() {
std::map<std::string, std::string> scriptfiles;
double start = devices.driver->getAccurateTime();
devices.scripts.cache_server = new scripts::Manager(makeJITCompiler());
#ifdef PROFILE_LOCKS
devices.scripts.cache_server->threadedCallMutex.name = "Server Threaded Call Lock";
devices.scripts.cache_server->threadedCallMutex.observed = true;
#endif
devices.scripts.cache_server->addIncludePath("scripts/");
devices.scripts.cache_server->addIncludePath("scripts/server");
devices.scripts.cache_server->addIncludePath("scripts/shared");
devices.scripts.cache_server->addIncludePath("scripts/definitions");
devices.scripts.cache_server->addIncludePath("maps/");
scripts::RegisterServerBinds(devices.scripts.cache_server->engine);
auto load = [](const std::pair<std::string,std::string>& file) {
if(cancelAssets)
return;
if(file.second.find("/include/") != std::string::npos)
return;
devices.scripts.cache_server->load(
makeModuleName(file.first),
file.second);
};
onScripts("scripts/server", load);
onScripts("scripts/shared", load);
onScripts("scripts/definitions", load);
onScripts("maps", load);
double load_end = devices.driver->getAccurateTime();
devices.scripts.cache_server->compile(
devices.mods.getProfile("as_cache/server"),
SERVER_SCRIPT_BUILD);
double compile_end = devices.driver->getAccurateTime();
print("Server scripts: %dms load, %dms compile",
int((load_end - start) * 1000.0),
int((compile_end - load_end) * 1000.0));
}
void loadShadowScripts() {
std::map<std::string, std::string> scriptfiles;
double start = devices.driver->getAccurateTime();
devices.scripts.cache_shadow = new scripts::Manager(makeJITCompiler());
#ifdef PROFILE_LOCKS
devices.scripts.cache_shadow->threadedCallMutex.name = "Shadow Threaded Call Lock";
devices.scripts.cache_shadow->threadedCallMutex.observed = true;
#endif
devices.scripts.cache_shadow->addIncludePath("scripts/");
devices.scripts.cache_shadow->addIncludePath("scripts/shadow");
devices.scripts.cache_shadow->addIncludePath("scripts/shared");
devices.scripts.cache_shadow->addIncludePath("scripts/definitions");
devices.scripts.cache_shadow->addIncludePath("maps/");
scripts::RegisterShadowBinds(devices.scripts.cache_shadow->engine);
auto load = [](const std::pair<std::string,std::string>& file) {
if(cancelAssets)
return;
if(file.second.find("/include/") != std::string::npos)
return;
devices.scripts.cache_shadow->load(
makeModuleName(file.first),
file.second);
};
onScripts("scripts/shadow", load);
onScripts("scripts/shared", load);
onScripts("scripts/definitions", load);
onScripts("maps", load);
double load_end = devices.driver->getAccurateTime();
devices.scripts.cache_shadow->compile(
devices.mods.getProfile("as_cache/shadow"),
SERVER_SCRIPT_BUILD);
double compile_end = devices.driver->getAccurateTime();
print("Shadow scripts: %dms load, %dms compile",
int((load_end - start) * 1000.0),
int((compile_end - load_end) * 1000.0));
}
void loadClientScripts() {
std::map<std::string, std::string> scriptfiles;
double start = devices.driver->getAccurateTime();
devices.scripts.client = new scripts::Manager(makeJITCompiler());
devices.engines.client = devices.scripts.client->engine;
#ifdef PROFILE_LOCKS
devices.scripts.client->threadedCallMutex.name = "Client Threaded Call Lock";
devices.scripts.client->threadedCallMutex.observed = true;
#endif
scripts::RegisterClientBinds(devices.scripts.client->engine);
devices.scripts.client->addIncludePath("scripts/");
devices.scripts.client->addIncludePath("scripts/client");
devices.scripts.client->addIncludePath("scripts/gui");
devices.scripts.client->addIncludePath("scripts/shared");
devices.scripts.client->addIncludePath("scripts/toolkit");
devices.scripts.client->addIncludePath("scripts/definitions");
devices.scripts.client->addIncludePath("maps/");
auto load = [](const std::pair<std::string,std::string>& file) {
if(cancelAssets)
return;
if(file.second.find("/include/") != std::string::npos)
return;
devices.scripts.client->load(
makeModuleName(file.first),
file.second);
};
onScripts("scripts/client", load);
onScripts("scripts/gui", load);
onScripts("scripts/shared", load);
onScripts("scripts/toolkit", load);
onScripts("scripts/definitions", load);
onScripts("maps", load);
double load_end = devices.driver->getAccurateTime();
devices.scripts.client->compile(
devices.mods.getProfile("as_cache/client"),
CLIENT_SCRIPT_BUILD);
double compile_end = devices.driver->getAccurateTime();
print("Client scripts: %dms load, %dms compile",
int((load_end - start) * 1000.0),
int((compile_end - load_end) * 1000.0));
}
void bindScripts() {
//Bind all script binds
scripts::BindEventBinds();
bindScriptObjectTypes();
scripts::bindComponentClasses();
scene::bindScriptNodeTypes();
//We cannot actually execute things on
//the shadow, so don't try to bind things
bindEffectorHooks(devices.network->isClient);
bindSubsystemHooks();
if(!devices.network->isClient) {
bindEffectHooks();
}
}
volatile bool idleImageActive = false;
threads::threadreturn threadcall idleProcessImages(void*) {
idleImageActive = true;
while(devices.library.hasQueuedImages()) {
devices.library.processImages(INT_MIN,1);
threads::sleep(1);
}
idleImageActive = false;
return 0;
}
volatile bool idleSoundsActive = false;
threads::threadreturn threadcall idleProcessSounds(void*) {
idleSoundsActive = true;
while(devices.library.hasQueuedSounds()) {
devices.library.processSounds(INT_MIN,1);
threads::sleep(1);
}
idleSoundsActive = false;
return 0;
}
volatile bool idleHullsActive = false;
threads::threadreturn threadcall idleComputeHulls(void*) {
double tstart = devices.driver->getAccurateTime();
idleHullsActive = true;
initRandomizer();
while(!isFinishedComputingHulls() && !cancelAssets) {
computeHulls(1);
threads::sleep(1);
}
idleHullsActive = false;
double tend = devices.driver->getAccurateTime();
print("Finished computing hulls in %gms", (tend-tstart)*1000.0);
return 0;
}
bool preloading = false;
void startPreload() {
if(preloading)
return;
//Preload things for the game
Loading::prepare(5, initNewThread, cleanupThread);
Loading::addTask("ServerScripts", 0, [] {
loadServerScripts();
});
Loading::addTask("ShadowScripts", 0, [] {
loadShadowScripts();
});
Loading::addTask("ClientScripts", 0, [] {
loadClientScripts();
});
Loading::addTask("CleanupScripts", "ServerScripts,ShadowScripts,ClientScripts", [] {
clearScriptList();
scripts::clearCachedScripts();
});
Loading::addTask("ProcessImages", 0, [] {
devices.library.processImages(-10);
threads::createThread(idleProcessImages, 0);
});
Loading::addTask("ProcessSounds", 0, [] {
devices.library.processSounds(-10);
threads::createThread(idleProcessSounds, 0);
});
Loading::addTask("ComputeHulls", 0, [] {
if(load_resources)
threads::createThread(idleComputeHulls, 0);
});
#ifdef DOCUMENT_API
Loading::addTask("Documentation", "ClientScripts,ServerScripts", [] {
scripts::documentBinds();
});
#endif
preloading = true;
}
bool isPreloading() {
return preloading;
}
void finishPreload() {
if(!preloading) {
startPreload();
Loading::finalize();
}
while(!Loading::finished()) {
Loading::process();
devices.driver->handleEvents(1);
}
const int priority = -10;
while(devices.library.processImages(priority)) {}
while(devices.library.processTextures(priority)) {}
while(devices.library.processSounds(priority)) {}
while(devices.library.processMeshes(priority)) {}
while(!Loading::finished()) {
Loading::process();
devices.driver->handleEvents(1);
}
Loading::finish();
preloading = false;
}
namespace resource {
extern threads::Signal unqueuedMeshes;
};
void cancelLoad() {
cancelAssets = true;
while(!Loading::finished()) {
Loading::process();
devices.driver->handleEvents(1);
}
const int priority = INT_MIN;
while(devices.library.processImages(priority)) {}
while(idleImageActive) { devices.driver->handleEvents(1); }
while(devices.library.processTextures(priority)) {}
while(devices.library.processSounds(priority)) {}
while(devices.library.processMeshes(priority)) {}
while(idleSoundsActive) { devices.driver->handleEvents(1); }
while(idleHullsActive) { devices.driver->handleEvents(1); }
resource::unqueuedMeshes.wait(0);
Loading::finish();
cancelAssets = false;
preloading = false;
}
void readGameConfig(const std::string& filename) {
gameConfig.count = 0;
//delete gameConfig.names;
//delete gameConfig.values;
gameConfig.indices.clear();
std::vector<std::string> names;
std::vector<double> values;
DataHandler file;
file.defaultHandler([&](std::string& key, std::string& value) {
names.push_back(key);
values.push_back(toNumber<double>(value));
});
file.read(filename);
gameConfig.count = names.size();
gameConfig.names = new std::string[gameConfig.count];
gameConfig.values = new double[gameConfig.count];
gameConfig.defaultValues = new double[gameConfig.count];
for(size_t i = 0; i < gameConfig.count; ++i) {
gameConfig.names[i] = names[i];
gameConfig.values[i] = values[i];
gameConfig.defaultValues[i] = values[i];
gameConfig.indices[names[i]] = i;
}
}
void resetGameConfig() {
for(size_t i = 0; i < gameConfig.count; ++i)
gameConfig.values[i] = gameConfig.defaultValues[i];
}
void readGameConfig(net::Message& msg) {
for(size_t i = 0; i < gameConfig.count; ++i)
msg >> gameConfig.values[i];
}
void writeGameConfig(net::Message& msg) {
for(size_t i = 0; i < gameConfig.count; ++i)
msg << gameConfig.values[i];
}
void saveGameConfig(SaveFile& file) {
file << (unsigned)gameConfig.count;
for(size_t i = 0; i < gameConfig.count; ++i) {
file << gameConfig.names[i];
file << gameConfig.values[i];
}
}
void loadGameConfig(SaveFile& file) {
unsigned count = 0;
if(file >= SFV_0003) {
file >> count;
}
else {
size_t woops = 0;
file >> woops;
count = (unsigned)woops;
}
for(size_t i = 0; i < count; ++i) {
std::string name;
file >> name;
double value;
file >> value;
auto it = gameConfig.indices.find(name);
if(it != gameConfig.indices.end())
gameConfig.values[it->second] = value;
}
}
bool loadPrep(const std::string& fname) {
if(!scripts::isAccessible(fname))
return false;
SaveFileInfo info;
getSaveFileInfo(fname, info);
//Check if we need to switch mods
std::vector<bool> modCheck;
unsigned modCnt = info.mods.size();
unsigned activeCnt = devices.mods.activeMods.size();
modCheck.resize(activeCnt);
for(unsigned i = 0; i < activeCnt; ++i)
modCheck[i] = false;
std::vector<std::string> ids;
bool shouldSwitch = false;
for(unsigned i = 0; i < modCnt; ++i) {
bool found = false;
auto* mod = devices.mods.getMod(info.mods[i].id);
if(mod)
mod = mod->getFallback(info.mods[i].version);
if(mod == nullptr)
return false;
ids.push_back(mod->ident);
for(unsigned j = 0; j < activeCnt; ++j) {
if(modCheck[j])
continue;
if(devices.mods.activeMods[j] == mod) {
found = true;
modCheck[j] = true;
break;
}
}
if(!found) {
shouldSwitch = true;
break;
}
}
for(unsigned i = 0; i < activeCnt; ++i) {
if(!modCheck[i]) {
shouldSwitch = true;
break;
}
}
if(game_running)
destroyGame();
//Switch mods if needed
if(shouldSwitch) {
::info("Switching mods for savegame...");
destroyMod();
initMods(ids);
}
//Actually load
clearGameSettings();
loadSaveName = fname;
game_state = GS_Game;
return true;
}
struct ToggleConsoleBind : profile::Keybind {
void call(bool pressed) {
if(!pressed) {
console.toggle();
}
}
};
void initMods(const std::vector<std::string>& mods) {
auto prevSection = enterSection(NS_Loading);
//Load global resources
Loading::prepare(5, initNewThread, cleanupThread);
Loading::addTask("CloudInit", 0, [] {
#ifndef NSTEAM
if(use_steam && !devices.cloud)
devices.cloud = GamePlatform::acquireSteam();
#endif
} );
static bool didCloudFiles = false, didCloudMods = false;
Loading::addTask("CloudMods", "CloudInit", [] {
if(didCloudMods)
return;
didCloudMods = true;
if(devices.cloud) {
CloudDownload dl;
for(unsigned i = 0, cnt = devices.cloud->getDownloadedItemCount(); i < cnt; ++i) {
if(devices.cloud->getDownloadedItem(i, dl)) {
validPaths.insert(dl.path);
auto folderNameEnds = dl.path.find_last_not_of("\\/");
auto folderNameStarts = dl.path.find_last_of("\\/", folderNameEnds);
if(folderNameStarts != std::string::npos)
devices.mods.registerMod(dl.path, dl.path.substr(folderNameStarts+1, folderNameEnds - folderNameStarts));
}
}
checkCloudDLC();
}
devices.mods.finalize();
} );
Loading::addTask("CloudFiles", "CloudInit", [] {
if(devices.cloud && !didCloudFiles) {
didCloudFiles = false;
devices.cloud->addCloudFolder(getProfileRoot(), "saves");
devices.cloud->syncCloudFiles(getProfileRoot());
}
} );
Loading::addTask("ModInit", "CloudMods", [&] {
//Set the mod
devices.mods.clearMods();
if(mods.empty()) {
foreach(mod, devices.mods.mods) {
if((*mod)->enabled) {
if(devices.mods.enableMod((*mod)->name))
info("Loading enabled mod %s", (*mod)->name.c_str());
}
}
}
else foreach(mod, mods) {
if(devices.mods.enableMod(*mod))
info("Loading mod %s", mod->c_str());
}
//Initialize the library
devices.library.prepErrorResources();
//Load keybind descriptors
std::vector<std::string> bindfiles;
devices.mods.resolve("data/keybinds.txt", bindfiles);
foreach(it, bindfiles)
devices.keybinds.loadDescriptors(*it);
//Load keybind values from profile, or set defaults
std::string bindFile = path_join(devices.mods.getProfile("settings"), "keybinds.txt");
devices.keybinds.setDefaultBinds();
if(fileExists(bindFile)) {
devices.keybinds.loadBinds(bindFile);
}
else {
//Load parent keybinds
foreach(it, devices.mods.activeMods) {
mods::Mod* prt = *it;
while(prt) {
std::string kfile(prt->getProfile("settings"));
kfile = path_join(kfile, "keybinds.txt");
if(fileExists(kfile))
devices.keybinds.loadBinds(kfile);
prt = prt->parent;
}
}
devices.keybinds.saveBinds(bindFile);
}
//Do console bind
auto* group = devices.keybinds.getGroup("Global");
if(group) {
auto* desc = group->getDescriptor("TOGGLE_CONSOLE");
if(desc) {
console.keybind = new ToggleConsoleBind();
group->addBind(desc->id, console.keybind);
}
}
//Run menu autoexec
console.executeFile(path_join(getProfileRoot(), "autoexec_menu.txt"));
//Load the locales
switchLocale(game_locale);
//Load setting descriptors
std::vector<std::string> settingfiles;
devices.mods.resolve("data/settings.txt", settingfiles);
foreach(it, settingfiles)
devices.settings.mod.loadDescriptors(*it);
//Load setting values from profile, or set defaults
std::string stFile = path_join(devices.mods.getProfile("settings"), "settings.txt");
if(fileExists(stFile)) {
devices.settings.mod.loadSettings(stFile);
}
else {
//Load parent keybinds
foreach(it, devices.mods.activeMods) {
mods::Mod* prt = *it;
while(prt) {
std::string kfile(prt->getProfile("settings"));
kfile = path_join(kfile, "settings.txt");
if(fileExists(kfile))
devices.settings.mod.loadSettings(kfile);
prt = prt->parent;
}
}
devices.settings.mod.saveSettings(stFile);
}
});
Loading::addTask("Window", "ModInit", [] {
if(create_window) {
os::WindowData windat;
windat.verticalSync = 1;
bool cursorCapture = false;
auto* vsync = devices.settings.engine.getSetting("iVsync");
if(vsync)
windat.verticalSync = *vsync;
extern double ui_scale;
auto* scale = devices.settings.engine.getSetting("dGUIScale");
if(scale)
ui_scale = *scale;
if(!fullscreen)
fullscreen = *devices.settings.engine.getSetting("bFullscreen");
if(fullscreen) {
windat.mode = os::WM_Fullscreen;
auto* ovr = devices.settings.engine.getSetting("bOverrideResolution");
if(ovr)
windat.overrideMonitor = *ovr;
unsigned w, h;
devices.driver->getDesktopSize(w, h);
auto* screenWidth = devices.settings.engine.getSetting("iFsResolutionX");
int settingWidth = screenWidth ? *screenWidth : 0;
windat.width = settingWidth > 0 ? settingWidth : (int)w;
auto* screenHeight = devices.settings.engine.getSetting("iFsResolutionY");
int settingHeight = screenHeight ? *screenHeight : 0;
windat.height = settingHeight > 0 ? settingHeight : (int)h;
auto* fsCapture = devices.settings.engine.getSetting("bFsCursorCapture");
cursorCapture = fsCapture ? *fsCapture : true;
auto* mon = devices.settings.engine.getSetting("sMonitor");
if(mon)
windat.targetMonitor = mon->toString();
}
else {
auto* screenWidth = devices.settings.engine.getSetting("iResolutionX");
windat.width = screenWidth ? *screenWidth : 1280;
auto* screenHeight = devices.settings.engine.getSetting("iResolutionY");
windat.height = screenHeight ? *screenHeight : 720;
auto* fsCapture = devices.settings.engine.getSetting("bCursorCapture");
cursorCapture = fsCapture ? *fsCapture : true;
}
auto* aa = devices.settings.engine.getSetting("iSamples");
if(aa)
windat.aa_samples = *aa;
auto* ss = devices.settings.engine.getSetting("bSupersample");
if(ss) {
extern double scale_3d;
scale_3d = (bool)*ss ? 2.0 : 1.0;
}
auto* refresh = devices.settings.engine.getSetting("iRefreshRate");
if(refresh)
windat.refreshRate = *refresh;
devices.driver->createWindow(windat);
devices.driver->setCursorLocked(cursorCapture);
devices.driver->setWindowTitle("Star Ruler 2");
devices.render->init();
//We should only make a window once
create_window = 0;
}
}, threads::getThreadID() );
if(load_resources) {
Loading::addTask("Errors", "Window", [] {
devices.library.generateErrorResources();
}, threads::getThreadID() );
}
Loading::addTask("EmpireStats", "ModInit", [] {
loadEmpireStats( devices.mods.resolve("data/stats.txt") );
} );
Loading::addTask("ObjectTypes", "ModInit", [] {
loadStateDefinitions(devices.mods.resolve("data/objects.txt"), "Object");
prepScriptObjectTypes();
} );
Loading::addTask("Generics", "ObjectTypes", [] {
scripts::initGenericTypes();
for(unsigned i = 0, cnt = getScriptObjectTypeCount(); i < cnt; ++i) {
auto* type = getScriptObjectType(i);
scripts::bindGenericObjectType(type, type->name);
}
} );
Loading::addTask("NodeTypes", "Generics", [] {
scene::loadScriptNodeTypes(devices.mods.resolve("data/nodes.txt"));
} );
Loading::addTask("Components", "Generics", [] {
devices.mods.listFiles("data/components", "*.txt", [](const std::string& file) {
scripts::loadComponents(file);
});
} );
Loading::addTask("States", "Components,ObjectTypes", [] {
resetStateValueTypes();
addObjectStateValueTypes();
scripts::addComponentStateValueTypes();
scripts::addNamespaceState();
loadStateDefinitions(devices.mods.resolve("data/empire_states.txt"));
finalizeStateDefinitions();
Empire::setEmpireStates(&getStateDefinition("Empire"));
scripts::BindEmpireComponentOffsets();
scripts::buildEmpAttribIndices();
setScriptObjectStates();
} );
Loading::addTask("GameConfig", "ModInit", [] {
readGameConfig(devices.mods.resolve("data/game_config.txt"));
} );
Loading::addTask("ObjComponentOffsets", "ObjectTypes,Components,States", [] {
scripts::SetObjectTypeOffsets();
});
Loading::addTask("Effects", "ModInit", [] {
devices.mods.listFiles("data/effects", "*.txt", loadEffectDefinitions, true);
} );
Loading::addTask("Effectors", "Effects", [] {
devices.mods.listFiles("data/effectors", "*.txt", loadEffectorDefinitions, true);
} );
Loading::addTask("SubSystems", "Effects,Effectors,GameConfig", [] {
devices.mods.listFiles("data/subsystems", "*.txt", loadSubsystemDefinitions, true);
executeSubsystemTemplates();
finalizeSubsystems();
} );
Loading::addTask("Shaders", "ModInit", [] {
devices.library.clearShaderGlobals();
libraryDir("data/shaders", resource::RT_Shader);
});
Loading::addTask("Materials", "Shaders", [] {
libraryDir("data/materials", resource::RT_Material);
devices.mods.listFiles("data/shipsets", "materials.txt", loadLibraryFile<resource::RT_Material>, true);
});
if(load_resources) {
Loading::addTask("CompileShaders", "Shaders,Window", [] {
devices.library.compileShaders();
}, threads::getThreadID() );
}
Loading::addTask("Fonts", "ModInit", [] {
libraryDir("data/fonts", resource::RT_Font);
} );
Loading::addTask("Sounds", "ModInit", [] {
libraryDir("data/sounds", resource::RT_Sound);
} );
Loading::addTask("Particles", "Materials,Sounds", [] {
devices.mods.listFiles("data/particles", "*.ps", loadLibraryFile<resource::RT_ParticleSystem>, true);
});
Loading::addTask("SkinStyles", "Fonts", [] {
libraryDir("data/skin styles", resource::RT_Skin);
} );
Loading::addTask("Events", "Generics", [] {
devices.mods.listFiles("data/events", "*.txt", [](const std::string& file) {
scripts::ReadEvents(file);
});
scripts::LoadScriptHooks(devices.mods.resolve("data/hooks.txt"));
} );
Loading::addTask("MaterialBinds", "Materials,SkinStyles,Particles,SubSystems", [] {
devices.library.bindSkinMaterials();
bindSubsystemMaterials();
} );
Loading::addTask("ResourceBinds", "Materials,Sounds,Particles", [] {
bindEffectorResources();
} );
Loading::addTask("Models", "ModInit", [] {
libraryDir("data/models", resource::RT_Mesh);
devices.mods.listFiles("data/shipsets", "models.txt", loadLibraryFile<resource::RT_Mesh>, true);
} );
Loading::addTask("Shipsets", "Models,Materials,SubSystems", [] {
devices.mods.listFiles("data/shipsets", "hulls.txt", loadHullDefinitions, true);
devices.mods.listFiles("data/shipsets", "shipset.txt", loadShipset, true);
initAllShipset();
} );
if(load_resources) {
Loading::addTask("ShaderStateVars", "Shaders,ObjectTypes,States", [] {
prepareShaderStateVars();
});
Loading::addTask("ProcessSounds", "Sounds", [] {
devices.library.processSounds(10);
});
Loading::addTask("ProcessImages", "Materials,Fonts,Window", [] {
devices.library.processImages(10);
});
Loading::addTask("ProcessTextures", "ProcessImages,Window", [] {
devices.library.processTextures(10);
}, threads::getThreadID() );
Loading::addTask("ProcessModels", "Models,Window,Shipsets", [] {
devices.library.processMeshes();
}, threads::getThreadID() );
}
Loading::addTask("LocateScripts", "ModInit", [] {
findScripts("scripts/shared");
findScripts("scripts/definitions");
findScripts("maps");
findScripts("scripts/menu");
findScripts("scripts/toolkit");
findScripts("scripts/server");
findScripts("scripts/client");
});
Loading::addTask("PreloadScripts", "LocateScripts", [] {
scriptPreloads.signal(7);
threads::createThread(PreloadBatch, new std::string("scripts/shared"));
threads::createThread(PreloadBatch, new std::string("scripts/definitions"));
threads::createThread(PreloadBatch, new std::string("scripts/menu"));
threads::createThread(PreloadBatch, new std::string("maps"));
threads::createThread(PreloadBatch, new std::string("scripts/toolkit"));
threads::createThread(PreloadBatch, new std::string("scripts/server"));
threads::createThread(PreloadBatch, new std::string("scripts/client"));
});
Loading::addTask("MenuScripts", "LocateScripts,SkinStyles,Materials,Models,Sounds,SubSystems", [] {
std::map<std::string, std::string> scriptfiles;
devices.scripts.menu = new scripts::Manager(makeJITCompiler());
devices.engines.menu = devices.scripts.menu->engine;
devices.scripts.menu->addIncludePath("scripts/menu");
devices.scripts.menu->addIncludePath("scripts/shared");
devices.scripts.menu->addIncludePath("scripts/toolkin");
devices.scripts.menu->addIncludePath("scripts/gui");
devices.scripts.menu->addIncludePath("scripts/definitions");
devices.scripts.menu->addIncludePath("maps/");
auto loadScript = [](const std::pair<std::string,std::string>& file) {
if(file.second.find("/include/") != std::string::npos)
return;
devices.scripts.menu->load(
makeModuleName(file.first),
file.second);
};
onScripts("scripts/menu", loadScript);
onScripts("scripts/toolkit", loadScript);
onScripts("maps", loadScript);
scripts::RegisterMenuBinds(devices.scripts.menu->engine);
devices.scripts.menu->compile(
devices.mods.getProfile("as_cache/menu"),
MENU_SCRIPT_BUILD);
devices.scripts.menu->init();
bindInputScripts(GS_Menu, devices.scripts.menu);
scripts::BindEventBinds(true);
} );
Loading::finalize();
while(!Loading::finished()) {
Loading::process();
devices.driver->handleEvents(1);
}
Loading::finish();
enterSection(prevSection);
devices.library.bindHotloading();
startPreload();
Loading::finalize();
if(devices.scripts.menu)
devices.scripts.menu->garbageCollect(true);
if(devices.scripts.client)
devices.scripts.client->garbageCollect(true);
if(devices.scripts.server)
devices.scripts.server->garbageCollect(true);
}
void destroyMod() {
//Make sure we finish loading, or we could de-initialize while adding resources
cancelLoad();
stopProjectiles();
//End processing
processing::end();
processing::clear();
//Remove input script binds
clearInputScripts(GS_Menu);
//Clear any active game
if(game_running)
destroyGame();
clearEmpireStats();
//Remove script nodes
scene::clearScriptNodeTypes();
//Clear keybinds
devices.keybinds.clear();
//Destroy skin indices
gui::skin::clearDynamicIndices();
//Clear shader state vars
clearShaderStateVars();
//Clear resources
devices.library.clear();
fsShader = nullptr;
//Clear menu scripts
delete devices.scripts.menu;
devices.scripts.menu = 0;
devices.engines.menu = 0;
scripts::resetContextCache(true);
//Stop playing sounds
if(devices.sound)
devices.sound->stopAllSounds();
//Clear console commands
console.clearCommands();
//Clear state definitions (For objects, empires)
clearStateDefinitions();
//Clear other definitions
clearHullDefinitions();
clearShipsets();
scripts::ClearEvents();
scripts::clearComponents();
clearEffectDefinitions();
clearEffectorDefinitions();
clearEffectors();
clearSubsystemDefinitions();
Empire::setEmpireStates(0);
}
net::Message game_settings;
void setGameSettings(net::Message& msg) {
game_settings = msg;
}
void clearGameSettings() {
game_settings.clear();
}
void passSettings(scripts::Manager* man) {
auto& modules = man->modules;
for(auto it = modules.begin(); it != modules.end(); ++it) {
scripts::Module& mod = *it->second;
- if(mod.callbacks[scripts::SC_game_settings] > 0) {
+ if(mod.callbacks[scripts::SC_game_settings] != nullptr) {
game_settings.rewind();
scripts::Call cl = mod.call(scripts::SC_game_settings);
cl.push(&game_settings);
cl.call();
}
}
}
void initGame() {
if(queuedModSwitch) {
queuedModSwitch = false;
destroyMod();
initMods(modSetup);
}
//Tell all the clients to start the game
if(devices.network->isServer)
devices.network->startSignal();
//Wait for all the preloading to finish
finishPreload();
//Set the correct server engine to use
if(devices.network->isClient)
devices.scripts.server = devices.scripts.cache_shadow;
else
devices.scripts.server = devices.scripts.cache_server;
devices.engines.server = devices.scripts.server->engine;
bindScripts();
//Initialize default empire
processing::resume();
if(!processing::isRunning()) {
unsigned cpuCount = devices.driver->getProcessorCount();
threadCount = std::max(cpuCount+1,1u);
processing::start(cpuCount);
}
Empire::initEmpires();
//Enable projectile processing
initProjectiles();
//Create lock groups
processing::pause();
initLocks(threadCount);
Object::GALAXY_CREATION = true;
processing::resume();
//Create the universe
devices.driver->resetGameTime(0.0);
resetGameTime();
resetGameConfig();
devices.universe = new Universe();
isLoadedGame = false;
bool loaded = false;
if(devices.network->isClient) {
//Tell the server we're ready to receive
devices.network->signalClientReady();
//Initialize server scripts
devices.scripts.server->init();
//Wait for the galaxy to be received before
//starting all the scripts
while(!devices.network->currentPlayer.hasGalaxy) {
if(!devices.network->client || !devices.network->client->active) {
devices.scripts.client->init();
destroyGame();
clearGameSettings();
game_state = GS_Menu;
return;
}
game_state = GS_Menu;
tickMenu();
game_state = GS_Game;
}
//Pass in game settings after objects
passSettings(devices.scripts.server);
passSettings(devices.scripts.client);
//Initialize client scripts
devices.scripts.client->init();
}
else {
isLoadedGame = !loadSaveName.empty();
//Create the universe by passing game settings
passSettings(devices.scripts.server);
passSettings(devices.scripts.client);
volatile bool done = false;
if(isLoadedGame) {
//LoadGame does script init() in the right place
threads::async([&done,&loaded]() -> int {
initNewThread();
pauseProjectiles();
loaded = loadGame(loadSaveName);
resumeProjectiles();
done = true;
cleanupThread();
return 0;
});
}
else {
threads::async([&done]() -> int {
initNewThread();
devices.scripts.server->init();
devices.scripts.client->init();
done = true;
cleanupThread();
return 0;
});
}
while(!done) {
tickMenu();
threads::sleep(1);
}
}
loadSaveName.clear();
if(isLoadedGame && !loaded) {
game_state = GS_Menu;
return;
}
bindInputScripts(GS_Game, devices.scripts.client);
//Run console autoexec
console.executeFile(path_join(getProfileRoot(), "autoexec.txt"));
//Tell the clients we're ready to transmit galaxies
Object::GALAXY_CREATION = false;
devices.network->currentPlayer.emp = Empire::getPlayerEmpire();
Empire::getPlayerEmpire()->player = &devices.network->currentPlayer;
if(devices.network->isServer)
devices.network->signalServerReady();
game_running = true;
//Process events that were deferred until client scripts were initialized
scripts::processEvents();
}
void destroyGame() {
gameEnding = true;
stopProjectiles();
//Wait for all processing jobs to finish
processing::end();
processing::clear();
//Inform the network
if(devices.network->serverReady)
devices.network->endGame();
//Clear game scripts
if(devices.scripts.server)
devices.scripts.server->deinit();
if(devices.scripts.client)
devices.scripts.client->deinit();
//Disconnect network interface if multiplayer
if(devices.network->isClient || devices.network->serverReady)
devices.network->disconnect();
//Clear all lights
render::light::destroyLights();
//Remove lock groups
destroyLocks();
//Remove input script binds
clearInputScripts(GS_Game);
//Clear all object IDs
clearObjects();
//Destroy universe
if(devices.universe) {
devices.universe->destroyAll();
devices.universe->drop();
devices.universe = 0;
}
if(devices.physics) {
devices.physics->drop();
devices.physics = 0;
}
if(devices.nodePhysics) {
devices.nodePhysics->drop();
devices.nodePhysics = 0;
}
//Destroy scene
extern void endAnimation();
endAnimation();
extern void clearProjectileBatches();
clearProjectileBatches();
devices.scene->destroyTree();
scene::clearNodeEvents();
//Delete existing empires
Empire::clearEmpires();
scripts::resetContextCache();
delete devices.scripts.cache_server;
delete devices.scripts.cache_shadow;
delete devices.scripts.client;
devices.scripts.cache_server = 0;
devices.scripts.cache_shadow = 0;
devices.scripts.client = 0;
devices.scripts.server = 0;
devices.engines.client = 0;
devices.engines.server = 0;
gameEnding = false;
console.clearCommands();
game_running = false;
if(devices.network->isClient || devices.network->serverReady)
devices.network->resetNetState();
}
void loadEngineSettings() {
//Hardcoded engine settings go here
{
profile::SettingCategory* cat = new profile::SettingCategory("Graphics");
cat->settings.push_back(new NamedGeneric("iSamples", 4));
cat->settings.push_back(new NamedGeneric("bSupersample", false));
cat->settings.push_back(new NamedGeneric("iRefreshRate", (int)0));
cat->settings.push_back(new NamedGeneric("iResolutionX", 1280));
cat->settings.push_back(new NamedGeneric("iResolutionY", 720));
cat->settings.push_back(new NamedGeneric("iFsResolutionX", (int)0));
cat->settings.push_back(new NamedGeneric("iFsResolutionY", (int)0));
cat->settings.push_back(new NamedGeneric("bFullscreen", true));
cat->settings.push_back(new NamedGeneric("bOverrideResolution", false));
cat->settings.push_back(new NamedGeneric("iVsync", 1));
cat->settings.push_back(new NamedGeneric("sMonitor", ""));
cat->settings.push_back(new NamedGeneric("bShaderFallback", false));
auto* guiScale = new NamedGeneric("dGUIScale", 1.0);
guiScale->flt_min = 0.25;
guiScale->flt_max = 4.0;
cat->settings.push_back(guiScale);
auto* fps = new NamedGeneric("dMaxFPS", 65.0);
fps->flt_min = 24.0;
fps->flt_max = 200.0;
extern double* maxfps;
maxfps = &fps->flt;
cat->settings.push_back(fps);
auto* tq = new NamedGeneric("iTextureQuality", 3);
tq->num_min = 2;
tq->num_max = 5;
cat->settings.push_back(tq);
auto* sl = new NamedGeneric("iShaderLevel", 3);
sl->num_min = 1;
sl->num_max = 4;
cat->settings.push_back(sl);
devices.settings.engine.addCategory(cat);
}
{
profile::SettingCategory* cat = new profile::SettingCategory("Sound");
cat->settings.push_back(new NamedGeneric("sAudioDevice", ""));
devices.settings.engine.addCategory(cat);
}
{
profile::SettingCategory* cat = new profile::SettingCategory("Input");
cat->settings.push_back(new NamedGeneric("bCursorCapture", true));
cat->settings.push_back(new NamedGeneric("bFsCursorCapture", true));
cat->settings.push_back(new NamedGeneric("iDoubleClickMS", (int)devices.driver->getDoubleClickTime()));
devices.settings.engine.addCategory(cat);
}
{
profile::SettingCategory* cat = new profile::SettingCategory("General");
cat->settings.push_back(new NamedGeneric("sLocale", "english"));
cat->settings.push_back(new NamedGeneric("sAPIToken", ""));
auto* autosave = new NamedGeneric("dAutosaveMinutes", 3.0);
autosave->flt_min = 0.0;
autosave->flt_max = 60.0;
cat->settings.push_back(autosave);
auto* count = new NamedGeneric("iAutosaveCount", (int)0);
count->num_min = 1;
count->num_max = 10;
cat->settings.push_back(count);
cat->settings.push_back(new NamedGeneric("iNetworkRateLimit", (int)250));
devices.settings.engine.addCategory(cat);
}
//Save to profile
std::string file = path_join(getProfileRoot(), "settings.txt");
if(fileExists(file))
devices.settings.engine.loadSettings(file);
else
devices.settings.engine.saveSettings(file);
//Generate new tokens
{
auto* token = devices.settings.engine.getSetting("sAPIToken");
if(token && (token->toString().empty() || token->toString() == "JGeaaKcaaa")) {
std::string pass;
do {
pass.clear();
uint64_t pw = (uint64_t)sysRandomi() << 32 | (uint64_t)sysRandomi();
const char* base64 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-+=!~$%#&^*";
for(unsigned i = 0; i < 10; ++i) {
auto v = (pw % 64);
pw >>= 6;
pass.append(1, base64[v]);
}
} while(pass == "JGeaaKcaaa");
token->setString(pass);
devices.settings.engine.saveSettings(file);
}
}
}
diff --git a/source/game/scripts/manager.cpp b/source/game/scripts/manager.cpp
index 8b18995..1b33317 100644
--- a/source/game/scripts/manager.cpp
+++ b/source/game/scripts/manager.cpp
@@ -1,1847 +1,1847 @@
#include "main/references.h"
#include "main/input_handling.h"
#include "main/logging.h"
#include "main/tick.h"
#include "main/console.h"
#include "scripts/manager.h"
#include "scripts/binds.h"
#include "scripts/context_cache.h"
#include "str_util.h"
#include "files.h"
#include "threads.h"
#include "angelscript.h"
#include "scriptarray.h"
#include "scriptstdstring.h"
#include "scriptmath.h"
#include "scriptdictionary.h"
#include "scriptmap.h"
#include "scriptany.h"
#include "scripthandle.h"
#include "compat/misc.h"
#include "compat/regex.h"
#include "util/save_file.h"
#include <stack>
#include <string>
#include <iostream>
#include <fstream>
#include <queue>
#include <algorithm>
#include <set>
#include "../angelscript/source/as_module.h"
#include "../angelscript/source/as_objecttype.h"
//Fix angelscript including windows.h in an ugly way
#ifdef _MSC_VER
#undef max
#endif
#ifdef _DEBUG
#ifndef SCRIPT_MAX_TICK_FAILS
#define SCRIPT_MAX_TICK_FAILS 1
#endif
#ifndef SCRIPT_MAX_DRAW_FAILS
#define SCRIPT_MAX_DRAW_FAILS 1
#endif
#ifndef SCRIPT_MAX_RENDER_FAILS
#define SCRIPT_MAX_RENDER_FAILS 1
#endif
#else
#ifndef SCRIPT_MAX_TICK_FAILS
#define SCRIPT_MAX_TICK_FAILS 16
#endif
#ifndef SCRIPT_MAX_DRAW_FAILS
#define SCRIPT_MAX_DRAW_FAILS 16
#endif
#ifndef SCRIPT_MAX_RENDER_FAILS
#define SCRIPT_MAX_RENDER_FAILS 16
#endif
#endif
#define DISABLE_SCRIPT_CACHE
namespace scripts {
extern void RegisterEarlyNetworkBinds(asIScriptEngine* engine);
Threaded(asIScriptContext*) clientContext = 0;
threads::Mutex sectionLock;
std::string lastSection;
#ifndef DISABLE_SCRIPT_CACHE
static bool loadCached(Module& mod, const File& fl, const std::string& cache_file, unsigned cache_version);
static void saveCached(Module& mod, const std::string& cache_file, unsigned cache_version);
#endif
void printErrors(asSMessageInfo* msg, void* ptr) {
if(msg->section) {
threads::Lock lock(sectionLock);
if(lastSection != msg->section) {
lastSection = msg->section;
error("%s:", msg->section);
}
}
if(msg->type == asMSGTYPE_INFORMATION) {
error(" %s", msg->message);
}
else {
const char* type;
if(msg->type == asMSGTYPE_ERROR)
type = "Error";
else// if(msg->type == asMSGTYPE_WARNING)
type = "Warning";
if(msg->col || msg->row)
error(" %s (%d:%d): %s", type, msg->row, msg->col, msg->message);
else
error(" %s: %s", type, msg->message);
}
}
enum ManagerType {
MT_Server,
MT_Shadow,
MT_Menu,
MT_GUI,
MT_Server_Side,
MT_Client_Side,
MT_Game,
};
const char* manager_names[MT_Game] = {
"server",
"shadow",
"menu",
"gui",
};
Manager* getActiveManager() {
auto* ctx = asGetActiveContext();
if(!ctx)
return 0;
return &Manager::fromEngine(ctx->GetEngine());
}
ManagerType getManagerType(Manager* man) {
if(man == devices.scripts.cache_server)
return MT_Server;
if(man == devices.scripts.cache_shadow)
return MT_Shadow;
if(man == devices.scripts.menu)
return MT_Menu;
return MT_GUI;
}
void throwException(const char* msg) {
asGetActiveContext()->SetException(msg);
}
const char* callback_decl[SC_COUNT] = {
"void preInit()",
"void init()",
"void postInit()",
"void tick(double)",
"void deinit()",
"void preRender(double)",
"void render(double)",
"void draw()",
"void onSettingsChanged()",
"void onGameSettings(Message&)",
"void syncInitial(Message&)",
"bool sendPeriodic(Message&)",
"void recvPeriodic(Message&)",
"void save(SaveFile&)",
"void load(SaveFile&)",
"void saveIdentifiers(SaveFile&)",
"void onGameStateChange()",
"void preReload(Message&)",
"void postReload(Message&)",
};
asIScriptFunction* Module::getFunction(const char* decl) {
return module->GetFunctionByDecl(decl);
}
Module* Manager::getModule(const char* module) {
auto it = modules.find(module);
if(it != modules.end())
return it->second;
return 0;
}
asIScriptFunction* Manager::getFunction(int fid) {
return engine->GetFunctionById(fid);
}
asIScriptFunction* Manager::getFunction(const char* module, const char* decl) {
auto it = modules.find(module);
if(it != modules.end())
return it->second->getFunction(decl);
return 0;
}
asIScriptFunction* Manager::getFunction(const std::string& def, const char* fin, const char* ret) {
std::vector<std::string> args;
split(def, args, "::");
//Find the module the function is in
scripts::Module* module = nullptr;
if(args.size() == 1) {
auto* ctx = asGetActiveContext();
if(ctx) {
auto* func = ctx->GetFunction();
module = getModule(func->GetModuleName());
}
if(!module) {
error("Error: Invalid script module.");
return 0;
}
}
else if(args.size() == 2) {
module = getModule(args[0].c_str());
if(!module) {
error("Error: Invalid script module '%s'.", args[0].c_str());
return 0;
}
}
else {
error("Error: Invalid script function reference '%s'.", def.c_str());
return 0;
}
//Build declaration for function
std::string decl = std::string(ret)+" "+args[args.size() - 1]+fin;
//Find the function
asIScriptFunction* func = module->getFunction(decl.c_str());
if(!func) {
error("Error: Unknown script function '%s'.", decl.c_str());
return 0;
}
return func;
}
asITypeInfo* Module::getClass(const char* decl) {
int id = module->GetTypeIdByDecl(decl);
return manager->engine->GetTypeInfoById(id);
}
asITypeInfo* Manager::getClass(const char* module, const char* decl) {
auto it = modules.find(module);
if(it != modules.end())
return it->second->getClass(decl);
return 0;
}
Manager::Manager(asIJITCompiler* Jit) : prevGCSize(5000), pauseScripts(false), clearScripts(false) {
//Create the engine
engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);
if(Jit) {
engine->SetEngineProperty(asEP_INCLUDE_JIT_INSTRUCTIONS, 1);
engine->SetJITCompiler(Jit);
}
engine->SetEngineProperty(asEP_ALLOW_UNSAFE_REFERENCES, 1);
engine->SetEngineProperty(asEP_USE_CHARACTER_LITERALS, 1);
engine->SetEngineProperty(asEP_ALLOW_MULTILINE_STRINGS, 1);
engine->SetEngineProperty(asEP_SCRIPT_SCANNER, 1);
#ifndef DO_LINE_CALLBACK
engine->SetEngineProperty(asEP_BUILD_WITHOUT_LINE_CUES, 1);
#endif
engine->SetEngineProperty(asEP_OPTIMIZE_BYTECODE, 1);
engine->SetEngineProperty(asEP_AUTO_GARBAGE_COLLECT, 0);
engine->SetEngineProperty(asEP_ALTER_SYNTAX_NAMED_ARGS, 1);
engine->SetMessageCallback(asFUNCTION(printErrors), 0, asCALL_CDECL);
engine->SetUserData(this, EDID_Manager);
//Bind all addon types
RegisterScriptAny(engine);
RegisterScriptHandle(engine);
RegisterScriptArray(engine, true);
//HACK: Has to be here so stdstring doesn't instantiate array<string> yet
scripts::RegisterEarlyNetworkBinds(engine);
RegisterStdString(engine);
RegisterStdStringUtils(engine);
RegisterScriptDictionary(engine);
RegisterScriptMap(engine);
RegisterScriptMath(engine);
}
Manager& Manager::fromEngine(asIScriptEngine* engine) {
return *(Manager*)engine->GetUserData(EDID_Manager);
}
void Manager::addIncludePath(std::string path) {
includePaths.push_back(path);
}
void Manager::loadDirectory(const std::string& dirname) {
std::vector<std::string> list;
listDirectory(dirname, list, "*.as");
foreach(it, list) {
std::string& file = *it;
load(file.substr(0, file.size() - 3), path_join(dirname, file));
}
}
class CachedScript {
bool loaded;
threads::Mutex lock;
std::string path;
std::vector<std::string> lines;
public:
size_t size;
CachedScript(const std::string& filename) : loaded(false), path(filename), size(0) {
}
const std::vector<std::string>& get() {
threads::Lock l(lock);
if(!loaded) {
std::ifstream file(path.c_str(), std::ios_base::in);
skipBOM(file);
std::string line;
while(file.good()) {
std::getline(file, line);
size += line.size() + 1;
lines.push_back(line);
}
loaded = true;
}
return lines;
}
};
threads::Mutex loadedScriptsLock;
std::unordered_map<std::string, CachedScript*> cachedScripts;
const std::vector<std::string>& loadScript(const std::string& filename, size_t& size) {
auto path = getAbsolutePath(filename);
CachedScript* script = nullptr;
{
threads::Lock l(loadedScriptsLock);
auto i = cachedScripts.find(path);
if(i != cachedScripts.end()) {
script = i->second;
}
else {
script = new CachedScript(path);
cachedScripts[path] = script;
}
}
assert(script);
size = script->size;
return script->get();
}
void clearCachedScripts() {
threads::Lock l(loadedScriptsLock);
for(auto i = cachedScripts.begin(), end = cachedScripts.end(); i != end; ++i)
delete i->second;
cachedScripts.clear();
}
void preloadScript(const std::string& filename) {
size_t dummy;
loadScript(filename, dummy);
}
threads::Mutex reg_compile_lock;
void parseFile(Manager* man, File& fl, const std::string& filename, bool cacheFiles = true) {
std::string path = getAbsolutePath(filename);
size_t size;
auto& lines = loadScript(path, size);
fl.path = path;
std::string& output = fl.contents;
output.reserve(size);
ManagerType curType = getManagerType(man);
ManagerType selType = curType;
reg_compile_lock.lock();
reg_compile(pre_include, "^[ \t]*#include[ \t]+\"(.+)\"[ \t]*");
reg_compile(pre_priority, "^[ \t]*#priority[ \t]+(.+)[ \t]+(.+)[ \t]*");
reg_compile(pre_section, "^[ \t]*#section[ \\t]+(.+)[ \t]*");
reg_compile(pre_import_from, "^[ \t]*from[ \t]+([A-Za-z0-9._-]+)[ \t]+import[ \t]+([^;]+);");
reg_compile(pre_import_all, "^[ \t]*import[ \t]+(([A-Za-z0-9._-]|, )+);");
reg_compile(pre_export, "^[ \t]*export[ \t]+(([A-Za-z0-9._-]|,* )+);");
reg_compile_lock.release();
reg_result match;
for(auto iLine = lines.begin(), end = lines.end(); iLine != end; ++iLine) {
const std::string& line = *iLine;
auto lineStart = line.find_first_not_of(" \t");
if(lineStart == std::string::npos) {
output.append(1, '\n');
continue;
}
bool mayBeDirective = line[lineStart] == '#';
if(mayBeDirective && reg_match(line, match, pre_section)) {
std::string type = trim(reg_str(line, match, 1));
if(type == "server")
selType = MT_Server;
else if(type == "server-side")
selType = MT_Server_Side;
else if(type == "client-side")
selType = MT_Client_Side;
else if(type == "client")
selType = MT_Client_Side;
else if(type == "shadow")
selType = MT_Shadow;
else if(type == "gui")
selType = MT_GUI;
else if(type == "game")
selType = MT_Game;
else if(type == "menu")
selType = MT_Menu;
else if(type == "all")
selType = curType;
else if(type == "disable menu") {
if(curType == MT_Menu)
break;
}
else
selType = MT_Client_Side;
output += "\n";
continue;
}
else {
switch(selType) {
case MT_Server:
case MT_Shadow:
case MT_GUI:
case MT_Menu:
if(selType != curType) {
output += "\n";
continue;
}
break;
case MT_Game:
if(curType != MT_GUI && curType != MT_Server && curType != MT_Shadow) {
output += "\n";
continue;
}
break;
case MT_Client_Side:
if(curType != MT_Menu && curType != MT_GUI) {
output += "\n";
continue;
}
break;
case MT_Server_Side:
if(curType != MT_Shadow && curType != MT_Server) {
output += "\n";
continue;
}
break;
}
}
if(line[lineStart] == 'f' && reg_match(line, match, pre_import_from)) {
std::string mod = reg_str(line, match, 1);
std::string def = reg_str(line, match, 2);
fl.imports.insert(ImportSpec(mod, def));
output += "\n";
continue;
}
if(line[lineStart] == 'i' && reg_match(line, match, pre_import_all)) {
std::string mod = reg_str(line, match, 1);
if(mod.find(",") != std::string::npos) {
std::vector<std::string> modules;
split(mod, modules, ",", true);
foreach(m, modules)
fl.imports.insert(ImportSpec(*m, "*"));
}
else {
fl.imports.insert(ImportSpec(mod, "*"));
}
output += "\n";
continue;
}
if(line[lineStart] == 'e' && reg_match(line, match, pre_export)) {
std::string sym = reg_str(line, match, 1);
auto* lst = &fl.exports;
if(sym.compare(0, 5, "from ") == 0) {
sym = sym.substr(5);
lst = &fl.exportsFrom;
}
if(sym.find(",") != std::string::npos) {
std::vector<std::string> symbols;
split(sym, symbols, ",", true);
foreach(s, symbols)
lst->insert(*s);
}
else {
lst->insert(sym);
}
output += "\n";
continue;
}
if(mayBeDirective && reg_match(line, match, pre_priority)) {
std::string type = reg_str(line, match, 1);
std::string prior = reg_str(line, match, 2);
int priority = toNumber<int>(prior);
if(type == "init") {
fl.priority_init = priority;
}
else if(type == "render") {
fl.priority_render = priority;
}
else if(type == "draw") {
fl.priority_draw = priority;
}
else if(type == "sync") {
fl.priority_sync = priority;
}
else {
error("ERROR: In File %s:\n Invalid priority directive '%s'.",
filename.c_str(), type.c_str());
}
output += "\n";
}
else if(mayBeDirective && reg_match(line, match, pre_include)) {
std::string filePart = reg_str(line, match, 1);
//Check include relative to current file first
std::string includeFile = getAbsolutePath(devices.mods.resolve(
filePart, getDirname(filename)));
//Load the included file contents
if(!fileExists(includeFile)) {
//Then check include paths
bool exists = false;
foreach(it, man->includePaths) {
includeFile = getAbsolutePath(devices.mods.resolve(
filePart, *it));
if(fileExists(includeFile)) {
exists = true;
break;
}
}
if(!exists) {
error("ERROR: In File %s:\n Could not find file '%s' to include.",
filename.c_str(), filePart.c_str());
foreach(it, man->includePaths) {
includeFile = getAbsolutePath(devices.mods.resolve(
filePart, *it));
error(" Tried %s", includeFile.c_str());
}
output += "\n";
continue;
}
}
//Don't include the same file multiple times
if(fl.includes.find(includeFile) != fl.includes.end()) {
output += "\n";
continue;
}
if(!cacheFiles) {
std::string path = getAbsolutePath(includeFile);
File& incl = *new File();
parseFile(man, incl, includeFile, false);
fl.includes[path] = &incl;
}
else {
man->load("", includeFile);
//Update and flatten include file tree
auto it = man->files.find(includeFile);
if(it != man->files.end()) {
fl.includes[it->first] = it->second;
foreach(inc, it->second->includes)
fl.includes[inc->first] = inc->second;
}
}
output.append(1,'\n');
}
else {
output += line;
output.append(1,'\n');
}
}
}
void Manager::load(const std::string& modulename, const std::string& filename) {
std::string path = getAbsolutePath(filename);
auto ex = files.find(path);
if(ex != files.end()) {
//If a file was included before, give it its
//proper module name afterwards
if(ex->second->module.empty())
ex->second->module = modulename;
return;
}
File& fl = *new File();
fl.module = modulename;
parseFile(this, fl, filename, true);
files[path] = &fl;
}
void importSymbol(Module& mod, Module& otherMod, const std::string& symbol) {
auto* self = (asCModule*)mod.module;
auto* other = (asCModule*)otherMod.module;
//Check if it's a type
auto* type = other->GetType(symbol.c_str(), other->defaultNamespace);
if(type) {
self->ImportType(type);
return;
}
//Check if it's a global
int glob = other->GetGlobalVarIndexByName(symbol.c_str());
if(glob != asNO_GLOBAL_VAR) {
self->ImportGlobalVariable(other, (asUINT)glob);
return;
}
glob = other->importedGlobals.GetFirstIndex(self->defaultNamespace, symbol.c_str());
if(glob != -1) {
mod.globalExports.push_back(other->scriptGlobals.GetSize() + (unsigned)glob);
return;
}
//Check if it's a function
bool foundFunction = false;
{
const asCArray<unsigned int>& idxs = other->globalFunctions.GetIndexes(other->defaultNamespace, symbol.c_str());
for(unsigned i = 0; i < idxs.GetLength(); ++i) {
asCScriptFunction* func = other->globalFunctions.Get(idxs[i]);
if(func) {
self->ImportGlobalFunction(func);
foundFunction = true;
}
}
}
{
const asCArray<unsigned int>& idxs = other->importedGlobalFunctions.GetIndexes(other->defaultNamespace, symbol.c_str());
for(unsigned i = 0; i < idxs.GetLength(); ++i) {
asCScriptFunction* func = other->importedGlobalFunctions.Get(idxs[i]);
if(func) {
self->ImportGlobalFunction(func);
foundFunction = true;
}
}
}
if(!foundFunction) {
//Check if it's a global accessor
auto* getAcc = (asCScriptFunction*)other->GetFunctionByName((std::string("get_")+symbol).c_str());
auto* setAcc = (asCScriptFunction*)other->GetFunctionByName((std::string("set_")+symbol).c_str());
if(getAcc || setAcc) {
if(getAcc)
self->ImportGlobalFunction(getAcc);
if(setAcc)
self->ImportGlobalFunction(setAcc);
}
else {
error("ERROR: %s: Cannot find '%s' to import from '%s'.",
mod.name.c_str(), symbol.c_str(), otherMod.name.c_str());
}
}
}
void buildModule(Manager* man, File& fl, Module& mod) {
//Create the module in the script engine
mod.manager = man;
mod.file = &fl;
mod.module = man->engine->GetModule(mod.name.c_str(), asGM_ALWAYS_CREATE);
//Add the script sections and imports
mod.module->AddScriptSection(fl.path.c_str(),
fl.contents.c_str(), fl.contents.size());
mod.imports = fl.imports;
mod.exports = fl.exports;
mod.exportsFrom = fl.exportsFrom;
foreach(inc, fl.includes) {
mod.module->AddScriptSection(inc->second->path.c_str(),
inc->second->contents.c_str(), inc->second->contents.size());
foreach(s, inc->second->imports)
mod.imports.insert(*s);
foreach(s, inc->second->exports)
mod.exports.insert(*s);
foreach(s, inc->second->exportsFrom)
mod.exportsFrom.insert(*s);
fl.priority_init = std::max(fl.priority_init, inc->second->priority_init);
fl.priority_render = std::max(fl.priority_render, inc->second->priority_render);
fl.priority_draw = std::max(fl.priority_draw, inc->second->priority_draw);
fl.priority_tick = std::max(fl.priority_init, inc->second->priority_tick);
fl.priority_sync = std::max(fl.priority_init, inc->second->priority_sync);
}
}
bool compileModule(Manager* man, Module& mod) {
if(mod.compiled)
return true;
mod.compiled = true;
mod.compiling = true;
//Prepare all dependencies
bool foundImports = true;
foreach(it, mod.imports) {
auto f = man->modules.find(it->first);
if(f == man->modules.end()) {
error("ERROR: %s: Cannot find module '%s' to import from.", mod.name.c_str(), it->first.c_str());
continue;
}
Module& impMod = *f->second;
mod.dependencies.insert(&impMod);
//Build dependent module
if(!impMod.compiled) {
impMod.compiledBy = mod.name;
compileModule(man, impMod);
}
if(impMod.module == nullptr) {
//Silently fail here, we already reported errors before
foundImports = false;
continue;
}
//Inject imports
auto* self = (asCModule*)mod.module;
auto* other = (asCModule*)impMod.module;
//Check cyclic imports
if(impMod.compiling) {
error("ERROR: %s: Cannot import from module '%s', circular import.", mod.name.c_str(), it->first.c_str());
Module* cur = &mod;
std::string prefix;
while(cur) {
error("%s^-- %s", prefix.c_str(), cur->name.c_str());
prefix += " ";
if(!cur->compiledBy.empty()) {
auto it = man->modules.find(cur->compiledBy);
if(it != man->modules.end()) {
cur = it->second;
}
else {
cur = nullptr;
error("??? %s\n", cur->compiledBy.c_str());
}
}
else
cur = nullptr;
}
continue;
}
if(it->second == "*") {
//Check if the module defined any exports
if(!impMod.exports.empty()) {
foreach(tp, impMod.typeExports)
self->ImportType(*tp);
foreach(fun, impMod.funcExports)
self->ImportGlobalFunction(*fun);
foreach(var, impMod.globalExports)
self->ImportGlobalVariable(impMod.module, *var);
}
else {
//Import types
for(unsigned i = 0, cnt = other->classTypes.GetLength(); i < cnt; ++i)
self->ImportType(other->classTypes[i]);
for(unsigned i = 0, cnt = other->enumTypes.GetLength(); i < cnt; ++i)
self->ImportType(other->enumTypes[i]);
for(unsigned i = 0, cnt = other->typeDefs.GetLength(); i < cnt; ++i)
self->ImportType(other->typeDefs[i]);
for(unsigned i = 0, cnt = other->funcDefs.GetLength(); i < cnt; ++i)
self->ImportType(other->funcDefs[i]);
//This is recursive: also import things that were imported
for(unsigned i = 0, cnt = other->importedClassTypes.GetLength(); i < cnt; ++i)
self->ImportType(other->importedClassTypes[i]);
for(unsigned i = 0, cnt = other->importedEnumTypes.GetLength(); i < cnt; ++i)
self->ImportType(other->importedEnumTypes[i]);
for(unsigned i = 0, cnt = other->importedTypeDefs.GetLength(); i < cnt; ++i)
self->ImportType(other->importedTypeDefs[i]);
for(unsigned i = 0, cnt = other->importedFuncDefs.GetLength(); i < cnt; ++i)
self->ImportType(other->importedFuncDefs[i]);
//Import global functions
for(unsigned i = 0, cnt = other->GetFunctionCount(); i < cnt; ++i) {
auto* func = (asCScriptFunction*)other->GetFunctionByIndex(i);
//Don't star-import any callback global functions
bool isCallback = false;
for(int i = 0; i < SC_COUNT; ++i) {
if(func == impMod.callbacks[i]) {
isCallback = true;
break;
}
}
if(isCallback)
continue;
//Inject into the new module
self->ImportGlobalFunction(func);
}
for(unsigned i = 0, cnt = other->importedGlobalFunctions.GetSize(); i < cnt; ++i) {
auto* func = (asCScriptFunction*)other->importedGlobalFunctions.Get(i);
self->ImportGlobalFunction(func);
}
//Import global values
for(unsigned i = 0, cnt = other->scriptGlobals.GetSize(); i < cnt; ++i)
self->ImportGlobalVariable(other, i);
for(unsigned i = 0, cnt = other->importedGlobals.GetSize(); i < cnt; ++i)
self->ImportGlobalVariable(other, i+other->scriptGlobals.GetSize());
}
}
else {
std::vector<std::string> declarations;
split(it->second, declarations, ',', true);
foreach(imp, declarations)
importSymbol(mod, impMod, *imp);
}
}
//Build the module
mod.compiling = false;
int code;
if(foundImports) {
code = mod.module->Build();
if(code != asSUCCESS) {
error("ERROR: Failed to build module '%s' in manager '%s' (code %d).",
mod.name.c_str(), manager_names[getManagerType(man)], code);
mod.module = nullptr;
}
}
else {
mod.module = nullptr;
}
//Cache exports
if(mod.module) {
for(int i = 0; i < SC_COUNT; ++i)
mod.callbacks[i] = mod.getFunction(callback_decl[i]);
foreach(exp, mod.exports) {
auto* self = (asCModule*)mod.module;
const std::string& symbol = *exp;
//Check if it's a type
auto* type = self->GetType(symbol.c_str(), self->defaultNamespace);
if(type) {
mod.typeExports.push_back(type);
continue;
}
//Check if it's a global
int glob = self->GetGlobalVarIndexByName(symbol.c_str());
if(glob != asNO_GLOBAL_VAR) {
mod.globalExports.push_back((unsigned)glob);
continue;
}
glob = self->importedGlobals.GetFirstIndex(self->defaultNamespace, symbol.c_str());
if(glob != -1) {
mod.globalExports.push_back(self->scriptGlobals.GetSize() + (unsigned)glob);
continue;
}
//Check if it's a function
bool foundExports = false;
{
const asCArray<unsigned int>& idxs = self->globalFunctions.GetIndexes(self->defaultNamespace, symbol.c_str());
for(unsigned i = 0; i < idxs.GetLength(); ++i) {
asCScriptFunction* func = self->globalFunctions.Get(idxs[i]);
if(func) {
mod.funcExports.push_back(func);
foundExports = true;
}
}
}
{
const asCArray<unsigned int>& idxs = self->globalFunctions.GetIndexes(self->defaultNamespace, symbol.c_str());
for(unsigned i = 0; i < idxs.GetLength(); ++i) {
asCScriptFunction* func = self->globalFunctions.Get(idxs[i]);
if(func) {
mod.funcExports.push_back(func);
foundExports = true;
}
}
}
if(!foundExports) {
//Check if it's a global accessor
auto* getAcc = (asCScriptFunction*)self->GetFunctionByName((std::string("get_")+symbol).c_str());
auto* setAcc = (asCScriptFunction*)self->GetFunctionByName((std::string("set_")+symbol).c_str());
if(getAcc || setAcc) {
if(getAcc)
mod.funcExports.push_back(getAcc);
if(setAcc)
mod.funcExports.push_back(setAcc);
}
else {
error("ERROR: %s: Cannot find '%s' to export.",
mod.name.c_str(), symbol.c_str());
}
}
}
foreach(exp, mod.exportsFrom) {
auto f = man->modules.find(*exp);
if(f == man->modules.end()) {
error("ERROR: %s: Cannot find module '%s' to export from.",
mod.name.c_str(), exp->c_str());
}
foreach(it, f->second->typeExports)
mod.typeExports.push_back(*it);
foreach(it, f->second->funcExports)
mod.funcExports.push_back(*it);
foreach(it, f->second->globalExports)
mod.globalExports.push_back(*it);
}
}
else {
for(int i = 0; i < SC_COUNT; ++i)
mod.callbacks[i] = nullptr;
}
return mod.module != nullptr;
}
void bindImports(Manager* man, Module& mod) {
auto* tMod = mod.module;
if(tMod == nullptr)
return;
unsigned fcnt = tMod->GetImportedFunctionCount();
for(unsigned i = 0; i < fcnt; ++i) {
const char* decl = tMod->GetImportedFunctionDeclaration(i);
const char* sourcemod = tMod->GetImportedFunctionSourceModule(i);
auto it = man->modules.find(sourcemod);
if(it == man->modules.end()) {
error("ERROR: '%s': Cannot import function '%s': module '%s' does not exist.",
tMod->GetName(), decl, sourcemod);
continue;
}
auto* sMod = it->second->module;
if(sMod == nullptr)
continue;
asIScriptFunction* func = sMod->GetFunctionByDecl(decl);
if(func == 0) {
error("ERROR: '%s': Cannot import function '%s' from module '%s': function does not exist.",
tMod->GetName(), decl, sourcemod);
continue;
}
tMod->BindImportedFunction(i, func);
}
}
void sortPriorities(Manager* man) {
man->priority_init.clear();
man->priority_render.clear();
man->priority_draw.clear();
man->priority_tick.clear();
man->priority_sync.clear();
for(auto it = man->modules.begin(); it != man->modules.end(); ++it) {
Module& mod = *it->second;
File& fl = *mod.file;
if(mod.callbacks[SC_init] || mod.callbacks[SC_preInit] || mod.callbacks[SC_postInit])
man->priority_init.insert(std::pair<int,Module*>(-fl.priority_init, &mod));
if(mod.callbacks[SC_render])
man->priority_render.insert(std::pair<int,Module*>(-fl.priority_render, &mod));
if(mod.callbacks[SC_draw])
man->priority_draw.insert(std::pair<int,Module*>(-fl.priority_draw, &mod));
if(mod.callbacks[SC_tick])
man->priority_tick.insert(std::pair<int,Module*>(-fl.priority_tick, &mod));
if(mod.callbacks[SC_sync_initial])
man->priority_sync.insert(std::pair<int,Module*>(-fl.priority_sync, &mod));
}
}
void Manager::compile(const std::string& cache_root, unsigned cache_version) {
//Generate all the modules
for(auto it = files.begin(); it != files.end(); ++it) {
File& fl = *it->second;
//Skip over empty module names, they're included files
if(fl.path.empty() || fl.module.empty())
continue;
//Find unused module name
std::string modname = fl.module;
int i = 2;
while(modules.find(modname) != modules.end()) {
modname = fl.module + toString(i);
++i;
}
//Display a warning when we have duplicate module names
if(i != 2) {
warn("WARNING: File '%s' has duplicate module name '%s'."
" Using temporary module name '%s' instead.", fl.path.c_str(),
fl.module.c_str(), modname.c_str());
}
//Create the module
Module& mod = *new Module();
mod.name = modname;
buildModule(this, fl, mod);
modules[modname] = &mod;
}
//Compile the modules
for(auto it = modules.begin(); it != modules.end(); ++it) {
Module& mod = *it->second;
//Build the module dependency tree
compileModule(this, mod);
}
//Bind all imported functions
for(auto mod = modules.begin(); mod != modules.end(); ++mod) {
bindImports(this, *mod->second);
}
//Erase modules that didn't compile succesfully
for(auto it = modules.begin(); it != modules.end();) {
if(it->second->module == nullptr)
it = modules.erase(it);
else
++it;
}
//Sort by priority
sortPriorities(this);
}
void reloadModule(Manager* man, Module& curMod) {
//Load the file
std::string filename = curMod.file->path;
std::string module = curMod.name;
File& fl = *new File();
fl.module = module+"__reload";
parseFile(man, fl, filename, false);
//Build the module
Module& mod = *new Module();
mod.name = module+"__reload";
buildModule(man, fl, mod);
print("Reloading module %s", module.c_str());
if(!compileModule(man, mod)) {
man->engine->DiscardModule(mod.name.c_str());
delete &fl;
delete &mod;
console.show();
return;
}
net::Message msg;
//Call pre-reload
if(curMod.callbacks[SC_preReload]) {
Call cl = curMod.call(SC_preReload);
cl.push(&msg);
cl.call();
}
//Replace successful
Module& oldMod = *man->modules[module];
((asCModule*)oldMod.module)->name = (module+"__old").c_str();
((asCModule*)mod.module)->name = module.c_str();
fl.module = module;
mod.name = module;
//Overwrite in manager for ticks
man->files[fl.path] = &fl;
man->modules[module] = &mod;
//Bind imports for this module
bindImports(man, mod);
//Call post-reload
if(mod.callbacks[SC_postReload]) {
Call cl = mod.call(SC_postReload);
cl.push(&msg);
cl.call();
}
else if(mod.callbacks[SC_init]) {
Call cl = mod.call(SC_init);
cl.call();
}
//Special stuff
if(module == "input")
bindInputScripts(GS_Game, devices.scripts.client);
}
void renderRebuildSet(Manager* man, Module& curMod, std::unordered_set<Module*>& totalSet) {
//Queue up dependencies
for(auto it = man->modules.begin(); it != man->modules.end(); ++it) {
Module* mod = it->second;
if(mod->dependencies.find(&curMod) == mod->dependencies.end())
continue;
totalSet.insert(mod);
renderRebuildSet(man, *mod, totalSet);
}
}
void visitRebuildSet(Manager* man, Module& mod, std::unordered_set<Module*>& totalSet, std::vector<Module*>& queue) {
if(totalSet.find(&mod) == totalSet.end())
return;
totalSet.erase(&mod);
foreach(it, mod.dependencies)
visitRebuildSet(man, **it, totalSet, queue);
queue.push_back(&mod);
}
void Manager::reload(const std::string& module) {
//Clear cached stuff
clearCachedScripts();
auto it = modules.find(module);
if(it == modules.end())
return;
Module& curMod = *it->second;
std::unordered_set<Module*> totalSet;
totalSet.insert(&curMod);
renderRebuildSet(this, curMod, totalSet);
std::vector<Module*> queue;
while(!totalSet.empty()) {
Module* mod = *totalSet.begin();
visitRebuildSet(this, *mod, totalSet, queue);
}
foreach(it, queue) {
reloadModule(this, **it);
}
//Rebind all explicit imports
for(auto mod = modules.begin(); mod != modules.end(); ++mod)
bindImports(this, *mod->second);
//Resort hook priorities
sortPriorities(this);
}
void Manager::init() {
foreach(it, priority_init) {
Module& mod = *it->second;
Call cl = mod.call(SC_preInit);
cl.call();
}
foreach(it, priority_init) {
Module& mod = *it->second;
Call cl = mod.call(SC_init);
cl.call();
}
foreach(it, priority_init) {
Module& mod = *it->second;
Call cl = mod.call(SC_postInit);
cl.call();
}
}
void Manager::deinit() {
foreach(it, modules) {
Module& mod = *it->second;
Call cl = mod.call(SC_deinit);
cl.call();
}
}
void Manager::tick(double time) {
foreach(it, priority_tick) {
Module& mod = *it->second;
if(mod.tickFails < SCRIPT_MAX_TICK_FAILS) {
Call cl = mod.call(SC_tick);
cl.push(time);
#ifdef PROFILE_SCRIPT_CALLBACKS
double startTime = devices.driver->getAccurateTime();
#endif
if(!cl.call()) {
mod.tickFails++;
if(mod.tickFails == SCRIPT_MAX_TICK_FAILS)
error("Module '%s' failed its tick too many times, disabling.",
mod.module->GetName());
}
#ifdef PROFILE_SCRIPT_CALLBACKS
double endTime = devices.driver->getAccurateTime();
mod.tickTime = endTime - startTime;
#endif
}
}
}
void Manager::draw() {
foreach(it, priority_draw) {
Module& mod = *it->second;
if(mod.drawFails < SCRIPT_MAX_DRAW_FAILS) {
Call cl = mod.call(SC_draw);
#ifdef PROFILE_SCRIPT_CALLBACKS
double startTime = devices.driver->getAccurateTime();
#endif
if(!cl.call()) {
mod.drawFails++;
if(mod.drawFails == SCRIPT_MAX_DRAW_FAILS)
error("Module '%s' failed its draw too many times, disabling.",
mod.module->GetName());
}
#ifdef PROFILE_SCRIPT_CALLBACKS
double endTime = devices.driver->getAccurateTime();
mod.drawTime = endTime - startTime;
#endif
}
}
}
void Manager::render(double frameTime) {
foreach(it, priority_render) {
Module& mod = *it->second;
if(mod.renderFails < SCRIPT_MAX_RENDER_FAILS) {
Call cl = mod.call(SC_render);
cl.push(frameTime);
#ifdef PROFILE_SCRIPT_CALLBACKS
double startTime = devices.driver->getAccurateTime();
#endif
if(!cl.call()) {
mod.renderFails++;
if(mod.renderFails == SCRIPT_MAX_RENDER_FAILS)
error("Module '%s' failed its render too many times, disabling.",
mod.module->GetName());
}
#ifdef PROFILE_SCRIPT_CALLBACKS
double endTime = devices.driver->getAccurateTime();
mod.renderTime = endTime - startTime;
#endif
}
}
}
#ifdef PROFILE_SCRIPT_CALLBACKS
void Manager::printProfile() {
foreach(it, modules) {
Module& mod = *it->second;
if(mod.tickTime > 0 || mod.drawTime > 0 || mod.renderTime > 0) {
print(" %s -- tick: %.3gms -- draw: %.3gms -- render: %.3gms",
mod.name.c_str(), mod.tickTime * 1000.0,
mod.drawTime * 1000.0, mod.renderTime * 1000.0);
}
}
}
#endif
void Manager::preRender(double frameTime) {
foreach(it, modules) {
Module& mod = *it->second;
if(mod.callbacks[SC_preRender]) {
Call cl = mod.call(SC_preRender);
cl.push(frameTime);
cl.call();
}
}
}
void Manager::save(SaveFile& file) {
foreach(it, modules) {
Module& mod = *it->second;
SaveMessage msg(file);
Call cl = mod.call(SC_save);
cl.push(&msg);
if(cl.call() && msg.size() > 0) {
file << mod.name;
char* pData; net::msize_t size;
msg.getAsPacket(pData, size);
file << size;
file.write(pData, size);
}
}
file << "";
}
void Manager::load(SaveFile& file) {
while(true) {
std::string moduleName;
file >> moduleName;
if(moduleName.empty())
return;
SaveMessage msg(file);
net::msize_t size = file;
if(size > 0) {
char* buffer = (char*)malloc(size);
file.read(buffer, size);
msg.setPacket(buffer, size);
free(buffer);
}
auto iter = modules.find(moduleName);
if(iter != modules.end()) {
Module& mod = *iter->second;
if(mod.callbacks[SC_load]) {
Call cl = mod.call(SC_load);
cl.push(&msg);
cl.call();
}
}
}
}
void Manager::saveIdentifiers(SaveFile& file) {
SaveMessage msg(file);
foreach(it, modules) {
Module& mod = *it->second;
Call cl = mod.call(SC_saveIdentifiers);
cl.push(&msg);
cl.call();
}
}
void Manager::stateChange() {
foreach(it, modules) {
Module& mod = *it->second;
Call cl = mod.call(SC_stateChange);
if(cl.valid())
cl.call();
}
}
#ifdef TRACE_GC_LOCK
void Manager::markGCImpossible() {
if(gcPossible == nullptr)
gcPossible = (bool*)malloc(sizeof(bool));
*gcPossible = false;
}
void Manager::markGCPossible() {
if(gcPossible == nullptr)
gcPossible = (bool*)malloc(sizeof(bool));
*gcPossible = true;
}
#endif
int Manager::garbageCollect(bool full) {
asUINT size, newObjects, detected;
engine->GetGCStatistics(&size, 0, &detected, &newObjects, 0);
if(full/* || size > prevGCSize + (prevGCSize / 2)*/) {
engine->GarbageCollect(asGC_FULL_CYCLE);
engine->GetGCStatistics(&prevGCSize, 0, 0, 0, 0);
return 2;
}
else if(newObjects > 500) {
asUINT runs = (asUINT)(log((double)(newObjects + detected + 2)) / log(2.0)) * 10;
for(asUINT i = 0; i < runs; ++i)
if(engine->GarbageCollect() == 0)
break;
return 1;
}
else {
asUINT runs = (asUINT)(log((double)(newObjects + detected + 2)) / log(2.0)) * 50;
for(asUINT i = 0; i < runs; ++i)
if(engine->GarbageCollect(asGC_ONE_STEP) == 0)
break;
return 0;
}
}
Call Manager::call(int funcID) {
if(funcID <= 0)
return Call();
return call(engine->GetFunctionById(funcID));
}
Call Manager::call(asIScriptFunction* func) {
if(func == 0)
return Call();
asIScriptContext* ctx = asGetActiveContext();
bool nested;
if(ctx && ctx->GetEngine() == engine) {
nested = true;
}
else {
ctx = fetchContext(engine);
nested = ctx->GetState() == asEXECUTION_ACTIVE;
}
if(nested) {
ctx->PushState();
int status = ctx->Prepare(func);
if(status != asSUCCESS) {
ctx->PopState();
ctx = 0;
}
}
else {
ctx->Prepare(func);
}
return Call(this, ctx, nested);
}
Call Manager::call(const char* module, const char* decl) {
return call(getFunction(module, decl));
}
MultiCall::MultiCall() {}
MultiCall Manager::call(ScriptCallback cb) {
MultiCall calls;
for(auto it = modules.begin(), end = modules.end(); it != end; ++it) {
Module& mod = *it->second;
- if(mod.callbacks[cb] > 0)
+ if(mod.callbacks[cb] != nullptr)
calls.calls.push_back(mod.call(cb));
}
return calls;
}
Call Module::call(ScriptCallback cb) {
asIScriptFunction* func = callbacks[cb];
if(func == nullptr)
return Call();
asIScriptEngine* engine = module->GetEngine();
auto* ctx = fetchContext(engine);
ctx->Prepare(func);
return Call(manager, ctx);
}
void Manager::clear() {
for(auto it = modules.begin(), end = modules.end(); it != end; ++it) {
if(engine->DiscardModule(it->first.c_str()) != asSUCCESS)
error("Failed to discard module '%s'", it->first.c_str());
}
foreach(it, modules)
delete it->second;
modules.clear();
foreach(it, files)
delete it->second;
files.clear();
}
Manager::~Manager() {
clear();
delete engine->GetJITCompiler();
}
void Manager::clearScriptThreads() {
clearScripts = true;
pauseScripts = false;
while(scriptThreadsExistent != 0)
threads::idle();
clearScripts = false;
}
void Manager::scriptThreadCreate() {
++scriptThreadsExistent;
}
void Manager::scriptThreadDestroy() {
--scriptThreadsExistent;
}
void Manager::pauseScriptThreads() {
pauseScripts = true;
while(scriptThreadsActive != 0)
threads::idle();
}
void Manager::resumeScriptThreads() {
pauseScripts = false;
}
bool Manager::scriptThreadStart() {
retry:
while(pauseScripts && !clearScripts)
threads::idle();
if(clearScripts)
return false;
++scriptThreadsActive;
if(pauseScripts) {
--scriptThreadsActive;
goto retry;
}
return true;
}
void Manager::scriptThreadEnd() {
--scriptThreadsActive;
}
Call::Call()
: manager(0), ctx(0), arg(0), nested(false) {
}
Call::Call(Manager* man, asIScriptContext* Ctx, bool Nested)
: manager(man), ctx(Ctx), arg(0), nested(Nested) {
}
Call::~Call() {
if(ctx) {
if(nested)
ctx->PopState();
}
}
void Call::setObject(void* obj) {
if(ctx)
ctx->SetObject((void*)obj);
}
void* Call::getReturnObject() {
if(!ctx)
return 0;
return ctx->GetReturnObject();
}
void Call::push(int value) {
if(ctx)
ctx->SetArgDWord(arg++, value);
}
void Call::push(long long value) {
if(ctx)
ctx->SetArgQWord(arg++, value);
}
void Call::push(unsigned value) {
if(ctx)
ctx->SetArgDWord(arg++, value);
}
void Call::push(float value) {
if(ctx)
ctx->SetArgFloat(arg++, value);
}
void Call::push(double value) {
if(ctx)
ctx->SetArgDouble(arg++, value);
}
void Call::push(bool value) {
if(ctx)
ctx->SetArgByte(arg++, value);
}
bool Call::call() {
if(!ctx) {
return false;
}
status = ctx->Execute();
return status == asSUCCESS;
}
bool Call::call(unsigned& value) {
if (!call()) {
value = 0;
return false;
}
value = ctx->GetReturnDWord();
return true;
}
bool Call::call(int& value) {
if (!call()) {
value = 0;
return false;
}
value = ctx->GetReturnDWord();
return true;
}
bool Call::call(long long& value) {
if (!call()) {
value = 0;
return false;
}
value = ctx->GetReturnQWord();
return true;
}
bool Call::call(float& value) {
if (!call()) {
value = 0;
return false;
}
value = ctx->GetReturnFloat();
return true;
}
bool Call::call(double& value) {
if (!call()) {
value = 0;
return false;
}
value = ctx->GetReturnDouble();
return true;
}
bool Call::call(bool& value) {
if (!call()) {
value = 0;
return false;
}
value = ctx->GetReturnByte() != 0;
return true;
}
void MultiCall::setObject(void* obj) {
foreach(call, calls)
call->setObject(obj);
}
void MultiCall::push(int value) {
foreach(call, calls)
call->push(value);
}
void MultiCall::push(float value) {
foreach(call, calls)
call->push(value);
}
void MultiCall::push(double value) {
foreach(call, calls)
call->push(value);
}
void MultiCall::push(bool value) {
foreach(call, calls)
call->push(value);
}
void MultiCall::push(void* value) {
foreach(call, calls)
call->push(value);
}
void MultiCall::call() {
foreach(call, calls)
call->call();
}
struct ReadBytecode : public asIBinaryStream {
std::ifstream& stream;
ReadBytecode(std::ifstream& is)
: stream(is) {
}
void Read(void* ptr, asUINT size) {
if(stream.eof())
memset(ptr, 0, size);
else
stream.read((char*)ptr, size);
}
void Write(const void* ptr, asUINT size) {
}
};
#ifndef DISABLE_SCRIPT_CACHE
static bool loadCached(Module& mod, const File& fl, const std::string& cache_file, unsigned cache_version) {
//Make sure the file exists
if(!fileExists(cache_file))
return false;
//Make sure the cache is new enough
time_t cache_time = getModifiedTime(cache_file);
if(getModifiedTime(fl.path) > cache_time)
return false;
foreach(inc, fl.includes)
if(getModifiedTime(inc->second->path) > cache_time)
return false;
//Open the file
std::ifstream stream(cache_file, std::ios::in | std::ios::binary);
//Read build version from cache
unsigned file_version;
stream.read((char*)&file_version, sizeof(unsigned));
//Needs to be the same version to load the cache
if(cache_version != file_version) {
stream.close();
return false;
}
//Write the bytecode
printf("Load %s from cache\n", cache_file.c_str());
ReadBytecode read(stream);
int code = mod.module->LoadByteCode(&read);
if(code != asSUCCESS) {
//Discard the module and recreate it, we're in
//an inconsistent state otherwise
asIScriptEngine* eng = mod.module->GetEngine();
eng->DiscardModule(mod.module->GetName());
mod.module = eng->GetModule(mod.name.c_str(), asGM_ALWAYS_CREATE);
//Warn the user
warn("WARNING: Failed to load bytecode cache for"
" module '%s' (code %d).", mod.name.c_str(), code);
stream.close();
return false;
}
//Close the file
stream.close();
return true;
}
struct WriteBytecode : public asIBinaryStream {
std::ofstream& stream;
WriteBytecode(std::ofstream& os)
: stream(os) {
}
void Read(void* ptr, asUINT size) {
}
void Write(const void* ptr, asUINT size) {
stream.write((const char*)ptr, size);
}
};
static void saveCached(Module& mod, const std::string& cache_file, unsigned cache_version) {
//Open the file
std::ofstream stream(cache_file, std::ios::out | std::ios::binary);
//Write build version to cache
stream.write((const char*)&cache_version, sizeof(unsigned));
//Write the bytecode
printf("Save %s to cache\n", cache_file.c_str());
WriteBytecode write(stream);
int code = mod.module->SaveByteCode(&write);
if(code < 0)
error("Failed to save bytecode for '%s'", mod.name.c_str());
//Close the file
stream.close();
}
#endif
};

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 13, 12:31 AM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
42974
Default Alt Text
(91 KB)

Event Timeline