Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F86521
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
91 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
R80 StarRuler2-Source
Attached
Detach File
Event Timeline
Log In to Comment