Open source Star Ruler 2 source code!
This commit is contained in:
@@ -0,0 +1,848 @@
|
||||
#include "console.h"
|
||||
#include "threads.h"
|
||||
#include "str_util.h"
|
||||
#include "render/driver.h"
|
||||
#include "render/render_state.h"
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
#include "util/format.h"
|
||||
#include "profile/keybinds.h"
|
||||
#include "network/network_manager.h"
|
||||
#include "render/vertexBuffer.h"
|
||||
|
||||
threads::Mutex consoleLock;
|
||||
Console console;
|
||||
|
||||
extern double menuTickTime, serverTickTime, clientTickTime, animationTime;
|
||||
const size_t maxUndoSize = 50;
|
||||
int baseTimer = 0;
|
||||
|
||||
enum LiveStat {
|
||||
LS_Menu = 1,
|
||||
LS_Server = 2,
|
||||
LS_Client = 4,
|
||||
LS_Tick = 8,
|
||||
LS_Buffers = 16,
|
||||
LS_FPS = 32,
|
||||
LS_Bandwidth = 64
|
||||
};
|
||||
|
||||
void asGet(const std::string& strEngine, const std::string& strModule,
|
||||
asIScriptEngine*& sEngine, asIScriptModule*& sModule) {
|
||||
|
||||
//Find engine
|
||||
scripts::Manager* man = 0;
|
||||
if(strEngine == "menu")
|
||||
man = devices.scripts.menu;
|
||||
else if(strEngine == "client")
|
||||
man = devices.scripts.client;
|
||||
else if(strEngine == "server")
|
||||
man = devices.scripts.server;
|
||||
else
|
||||
return;
|
||||
|
||||
//Find module
|
||||
sEngine = man->engine;
|
||||
|
||||
if(strModule.empty()) {
|
||||
sModule = sEngine->GetModule("ConsoleInput", asGM_ALWAYS_CREATE);
|
||||
}
|
||||
else {
|
||||
scripts::Module* module = man->getModule(strModule.c_str());
|
||||
|
||||
if(!module)
|
||||
return;
|
||||
|
||||
sModule = module->module;
|
||||
}
|
||||
}
|
||||
|
||||
void asGlobal(const std::string& strEngine, const std::string& strModule, const std::string& global) {
|
||||
asIScriptEngine* sEngine = 0;
|
||||
asIScriptModule* sModule = 0;
|
||||
|
||||
asGet(strEngine, strModule, sEngine, sModule);
|
||||
|
||||
if(!sEngine || !sModule)
|
||||
return;
|
||||
|
||||
std::string code = global;
|
||||
if(code[code.size() - 1] != ';')
|
||||
code += ";";
|
||||
|
||||
sModule->CompileGlobalVar("ConsoleGlobal", code.c_str(), 0);
|
||||
}
|
||||
|
||||
void asExec(const std::string& strEngine, const std::string& strModule, const std::string& code)
|
||||
{
|
||||
int r;
|
||||
|
||||
asIScriptEngine* sEngine = 0;
|
||||
asIScriptModule* sModule = 0;
|
||||
|
||||
asGet(strEngine, strModule, sEngine, sModule);
|
||||
|
||||
if(!sEngine || !sModule)
|
||||
return;
|
||||
|
||||
//Wrap code
|
||||
std::string fCode;
|
||||
|
||||
if(code.find(";") == std::string::npos) {
|
||||
fCode = "void ConsoleFunction() { print(";
|
||||
fCode += code;
|
||||
fCode += "); }";
|
||||
}
|
||||
else {
|
||||
fCode = "void ConsoleFunction() { ";
|
||||
fCode += code;
|
||||
fCode += "; }";
|
||||
}
|
||||
|
||||
//Compile function
|
||||
asIScriptFunction* sFunc = 0;
|
||||
r = sModule->CompileFunction("ConsoleFunction", fCode.c_str(), -1, 0, &sFunc);
|
||||
if(r < 0)
|
||||
return;
|
||||
|
||||
//Prepare context
|
||||
asIScriptContext* ctx = sEngine->CreateContext();
|
||||
r = ctx->Prepare(sFunc);
|
||||
|
||||
if(r >= 0) {
|
||||
//Execute
|
||||
r = ctx->Execute();
|
||||
}
|
||||
|
||||
//Clean
|
||||
sFunc->Release();
|
||||
ctx->Release();
|
||||
}
|
||||
|
||||
|
||||
Console::Console() : open(false), permaStats(false), bg(new render::RenderState), compact(false), historyItem(history.cbegin()), asMode(false), keybind(0), liveStats(~0) {
|
||||
bg->baseMat = render::MAT_Alpha;
|
||||
bg->lighting = false;
|
||||
bg->culling = render::FC_None;
|
||||
bg->depthTest = render::DT_NoDepthTest;
|
||||
|
||||
addCommand("clear", [this](argList&) {
|
||||
clear();
|
||||
} );
|
||||
|
||||
addCommand("list", [this](argList&) {
|
||||
std::string temp;
|
||||
for(auto i = functions.cbegin(), end = functions.cend(); i != end; ++i) {
|
||||
if(temp.empty()) {
|
||||
temp = i->first;
|
||||
}
|
||||
else {
|
||||
temp.insert(temp.size(), 16 - (temp.size() % 16), ' ');
|
||||
temp += i->first;
|
||||
if(temp.size() >= 16*5) {
|
||||
printLn(temp);
|
||||
temp.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!temp.empty())
|
||||
printLn(temp);
|
||||
} );
|
||||
|
||||
addCommand("resize", [this](argList& args) {
|
||||
if(args.size() != 2) {
|
||||
error("Usage: resize [width] [height]");
|
||||
return;
|
||||
}
|
||||
|
||||
devices.driver->setWindowSize(
|
||||
toNumber<int>(args[0]),
|
||||
toNumber<int>(args[1]));
|
||||
} );
|
||||
|
||||
addCommand("compact", [this](argList& args) {
|
||||
if(args.empty())
|
||||
compact = !compact;
|
||||
else if(streq_nocase(args[0], "true"))
|
||||
compact = true;
|
||||
else if(streq_nocase(args[0], "false"))
|
||||
compact = false;
|
||||
} );
|
||||
|
||||
addCommand("stats", [this](argList& args) {
|
||||
if(args.empty())
|
||||
permaStats = !permaStats;
|
||||
else
|
||||
permaStats = toBool(args[0]);
|
||||
} );
|
||||
|
||||
addCommand("show", [this](argList& args) {
|
||||
if(args.empty()) {
|
||||
printLn("Toggles the showing of live data at the right");
|
||||
printLn("Choose which piece of data to toggle: all, fps, menu, server, client, tick, buffers, bandwidth");
|
||||
return;
|
||||
}
|
||||
|
||||
auto& arg = args[0];
|
||||
if(arg == "all") {
|
||||
//Toggle all off if any are on, otherwise show all
|
||||
liveStats = liveStats ? 0 : ~0;
|
||||
}
|
||||
else if(arg == "fps") {
|
||||
liveStats ^= LS_FPS;
|
||||
}
|
||||
else if(arg == "menu") {
|
||||
liveStats ^= LS_Menu;
|
||||
}
|
||||
else if(arg == "server") {
|
||||
liveStats ^= LS_Server;
|
||||
}
|
||||
else if(arg == "client") {
|
||||
liveStats ^= LS_Client;
|
||||
}
|
||||
else if(arg == "tick") {
|
||||
liveStats ^= LS_Tick;
|
||||
}
|
||||
else if(arg == "buffers") {
|
||||
liveStats ^= LS_Buffers;
|
||||
}
|
||||
else if(arg == "bandwidth") {
|
||||
liveStats ^= LS_Bandwidth;
|
||||
}
|
||||
} );
|
||||
|
||||
addCommand("bind", [this](argList& args) {
|
||||
if(args.size() > 1) {
|
||||
int key = profile::getKeyFromDisplayName(args[0]);
|
||||
if(key == 0) {
|
||||
printLn(format("Unrecognized key '$1'", args[0]));
|
||||
return;
|
||||
}
|
||||
else {
|
||||
std::string command;
|
||||
for(size_t i = 1, cnt = args.size(); i < cnt; ++i) {
|
||||
if(!command.empty())
|
||||
command += " ";
|
||||
if(args[i].find(' ') != std::string::npos) {
|
||||
command += "\"";
|
||||
command += args[i];
|
||||
command += "\"";
|
||||
}
|
||||
else {
|
||||
command += args[i];
|
||||
}
|
||||
}
|
||||
|
||||
binds[key] = command;
|
||||
}
|
||||
}
|
||||
else {
|
||||
printLn("Bind requires a key and a command");
|
||||
}
|
||||
} );
|
||||
|
||||
addCommand("in", [this](argList& args) {
|
||||
if(args.empty()) {
|
||||
asModule = "";
|
||||
asEngine = "client";
|
||||
}
|
||||
else {
|
||||
if(args.size() == 1) {
|
||||
asModule = args[0];
|
||||
if(devices.scripts.menu && devices.scripts.menu->getModule(asModule.c_str())) {
|
||||
asEngine = "menu";
|
||||
}
|
||||
else if(devices.scripts.client && devices.scripts.client->getModule(asModule.c_str())) {
|
||||
asEngine = "client";
|
||||
}
|
||||
else if(devices.scripts.server && devices.scripts.server->getModule(asModule.c_str())) {
|
||||
asEngine = "server";
|
||||
}
|
||||
else {
|
||||
std::string temp;
|
||||
temp += "Error: Could not find angelscript module '";
|
||||
temp += asModule;
|
||||
temp += "' in any engine.";
|
||||
|
||||
printLn(temp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if(args.size() == 2) {
|
||||
asEngine = args[0];
|
||||
asModule = args[1];
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::string temp;
|
||||
temp += "Setting console to angelscript mode for module '";
|
||||
temp += asModule;
|
||||
temp += "' in engine '";
|
||||
temp += asEngine;
|
||||
temp += "'.\n Type 'quit' to return to normal mode.";
|
||||
|
||||
printLn(temp);
|
||||
asMode = true;
|
||||
} );
|
||||
}
|
||||
|
||||
Console::~Console() {
|
||||
delete bg;
|
||||
}
|
||||
|
||||
void Console::addCommand(std::string name, std::function<void(std::vector<std::string>&)> function, bool replace) {
|
||||
toLowercase(name);
|
||||
if(replace || functions.find(name) == functions.end()) {
|
||||
ConsoleFunction& f = functions[name];
|
||||
f.line = nullptr;
|
||||
f.call = function;
|
||||
f.destruct = nullptr;
|
||||
}
|
||||
else
|
||||
error("Duplicate console command '%s'", name.c_str());
|
||||
}
|
||||
|
||||
void Console::addCommand(std::string name, ConsoleFunction& func, bool replace) {
|
||||
toLowercase(name);
|
||||
if(replace || functions.find(name) == functions.end())
|
||||
functions[name] = func;
|
||||
else
|
||||
error("Duplicate console command '%s'", name.c_str());
|
||||
}
|
||||
|
||||
void Console::clearCommands() {
|
||||
for(auto it = functions.begin(); it != functions.end();) {
|
||||
if(it->second.destruct) {
|
||||
if(it->second.destruct()) {
|
||||
it = functions.erase(it);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
bool Console::eraseSelection() {
|
||||
if(length != 0) {
|
||||
if(length > 0)
|
||||
currentLine.erase(caret, length);
|
||||
else {
|
||||
currentLine.erase(caret+length, -length);
|
||||
caret += length;
|
||||
}
|
||||
length = 0;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Console::character(int code) {
|
||||
if(!open)
|
||||
return false;
|
||||
if(devices.keybinds.global.getBind(code) == keybind)
|
||||
return false;
|
||||
if(devices.driver->ctrlKey)
|
||||
return true;
|
||||
|
||||
undo.push_back( std::tuple<std::string, size_t, int>(currentLine, caret, length) );
|
||||
|
||||
eraseSelection();
|
||||
|
||||
currentLine.insert(caret++, 1, (char)code);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Console::globalKey(int code, bool pressed) {
|
||||
if(open)
|
||||
return false;
|
||||
|
||||
auto bind = binds.find(profile::getModifiedKey(code, devices.driver->ctrlKey, devices.driver->altKey, devices.driver->shiftKey));
|
||||
if(bind != binds.end()) {
|
||||
if(pressed)
|
||||
execute(bind->second, false);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Console::key(int code, bool pressed) {
|
||||
if(!open)
|
||||
return false;
|
||||
if(!pressed)
|
||||
return true;
|
||||
std::tuple<std::string, size_t, int> previous(currentLine, caret, length);
|
||||
|
||||
switch(code) {
|
||||
case 'A': //Select all
|
||||
if(devices.driver->ctrlKey) {
|
||||
caret = 0;
|
||||
length = (int)currentLine.size();
|
||||
baseTimer = devices.driver->getTime();
|
||||
}
|
||||
break;
|
||||
case 'K':
|
||||
//Kill line
|
||||
if(devices.driver->ctrlKey) {
|
||||
caret = 0;
|
||||
currentLine = "";
|
||||
}
|
||||
break;
|
||||
case 'C':
|
||||
case 'X':
|
||||
if(devices.driver->ctrlKey) {
|
||||
if(length > 0)
|
||||
devices.driver->setClipboard(currentLine.substr(caret, length));
|
||||
else
|
||||
devices.driver->setClipboard(currentLine.substr(caret+length, -length));
|
||||
if(code == 'X') {
|
||||
eraseSelection();
|
||||
baseTimer = devices.driver->getTime();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'V':
|
||||
if(devices.driver->ctrlKey) {
|
||||
eraseSelection();
|
||||
std::string text = devices.driver->getClipboard();
|
||||
currentLine.insert(caret, text);
|
||||
baseTimer = devices.driver->getTime();
|
||||
caret += text.size();
|
||||
}
|
||||
break;
|
||||
case 'Y':
|
||||
if(devices.driver->ctrlKey) {
|
||||
if(!redo.empty()) {
|
||||
undo.push_back(previous);
|
||||
if(undo.size() > maxUndoSize)
|
||||
undo.pop_front();
|
||||
|
||||
auto& prev = redo.back();
|
||||
currentLine = std::get<0>(prev);
|
||||
caret = std::get<1>(prev);
|
||||
length = std::get<2>(prev);
|
||||
|
||||
redo.pop_back();
|
||||
}
|
||||
baseTimer = devices.driver->getTime();
|
||||
return true; //Return here to prevent handling the line change by the undo system
|
||||
}
|
||||
break;
|
||||
case 'Z':
|
||||
if(devices.driver->ctrlKey) {
|
||||
if(!undo.empty()) {
|
||||
redo.push_back(previous);
|
||||
if(redo.size() > maxUndoSize)
|
||||
redo.pop_front();
|
||||
|
||||
auto& prev = undo.back();
|
||||
currentLine = std::get<0>(prev);
|
||||
caret = std::get<1>(prev);
|
||||
length = std::get<2>(prev);
|
||||
|
||||
undo.pop_back();
|
||||
}
|
||||
baseTimer = devices.driver->getTime();
|
||||
return true; //Return here to prevent handling the line change by the undo system
|
||||
}
|
||||
break;
|
||||
case os::KEY_BACKSPACE:
|
||||
if(!eraseSelection() && caret > 0)
|
||||
currentLine.erase(--caret,1);
|
||||
baseTimer = devices.driver->getTime();
|
||||
break;
|
||||
case os::KEY_DEL:
|
||||
if(!eraseSelection() && caret < currentLine.size())
|
||||
currentLine.erase(caret,1);
|
||||
baseTimer = devices.driver->getTime();
|
||||
break;
|
||||
case os::KEY_LEFT:
|
||||
if(caret > 0) {
|
||||
caret -= 1;
|
||||
if(devices.driver->shiftKey)
|
||||
length += 1;
|
||||
else
|
||||
length = 0;
|
||||
}
|
||||
else if(!devices.driver->shiftKey) {
|
||||
length = 0;
|
||||
}
|
||||
baseTimer = devices.driver->getTime();
|
||||
break;
|
||||
case os::KEY_RIGHT:
|
||||
if(caret < currentLine.size()) {
|
||||
caret += 1;
|
||||
if(devices.driver->shiftKey)
|
||||
length -= 1;
|
||||
else
|
||||
length = 0;
|
||||
}
|
||||
else if(!devices.driver->shiftKey) {
|
||||
length = 0;
|
||||
}
|
||||
baseTimer = devices.driver->getTime();
|
||||
break;
|
||||
case os::KEY_HOME:
|
||||
if(caret > 0) {
|
||||
if(devices.driver->shiftKey)
|
||||
length += (int)caret;
|
||||
else
|
||||
length = 0;
|
||||
caret = 0;
|
||||
}
|
||||
else if(!devices.driver->shiftKey) {
|
||||
length = 0;
|
||||
}
|
||||
baseTimer = devices.driver->getTime();
|
||||
break;
|
||||
case os::KEY_END:
|
||||
if(caret < currentLine.size()) {
|
||||
if(devices.driver->shiftKey)
|
||||
length -= (int)(currentLine.size() - caret);
|
||||
else
|
||||
length = 0;
|
||||
caret = currentLine.size();
|
||||
}
|
||||
else if(!devices.driver->shiftKey) {
|
||||
length = 0;
|
||||
}
|
||||
baseTimer = devices.driver->getTime();
|
||||
break;
|
||||
case os::KEY_ENTER:
|
||||
if(!currentLine.empty()) {
|
||||
execute(currentLine, true);
|
||||
history.push_front(currentLine);
|
||||
historyItem = history.cend();
|
||||
currentLine.clear();
|
||||
caret = 0;
|
||||
length = 0;
|
||||
}
|
||||
baseTimer = devices.driver->getTime();
|
||||
break;
|
||||
case os::KEY_UP:
|
||||
if(!history.empty()) {
|
||||
if(historyItem != history.cend())
|
||||
++historyItem;
|
||||
else
|
||||
historyItem = history.cbegin();
|
||||
|
||||
if(historyItem != history.end())
|
||||
currentLine = *historyItem;
|
||||
else
|
||||
currentLine.clear();
|
||||
caret = currentLine.size();
|
||||
length = 0;
|
||||
}
|
||||
break;
|
||||
case os::KEY_DOWN:
|
||||
if(!history.empty()) {
|
||||
if(historyItem != history.cbegin())
|
||||
--historyItem;
|
||||
else
|
||||
historyItem = history.cend();
|
||||
|
||||
if(historyItem != history.end())
|
||||
currentLine = *historyItem;
|
||||
else
|
||||
currentLine.clear();
|
||||
caret = currentLine.size();
|
||||
length = 0;
|
||||
}
|
||||
break;
|
||||
case os::KEY_TAB:
|
||||
if(!currentLine.empty()) {
|
||||
size_t space = currentLine.find(' ');
|
||||
if(space == std::string::npos) {
|
||||
const std::string* bestMatch = 0;
|
||||
unsigned bestScore = (unsigned)-1;
|
||||
|
||||
for(auto i = functions.cbegin(), end = functions.cend(); i != end; ++i) {
|
||||
size_t p = i->first.find(currentLine);
|
||||
if(p == std::string::npos)
|
||||
continue;
|
||||
unsigned score = (unsigned)((p * 4) + (i->first.size() - currentLine.size()));
|
||||
if(bestScore < score)
|
||||
continue;
|
||||
bestScore = score;
|
||||
bestMatch = &i->first;
|
||||
}
|
||||
|
||||
if(bestMatch) {
|
||||
currentLine = *bestMatch + (char)' ';
|
||||
caret = currentLine.size();
|
||||
length = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//If the line has changed, store the previous state as an undo entry, and clear the redo list
|
||||
if(std::get<0>(previous) != currentLine) {
|
||||
redo.clear();
|
||||
undo.push_back(previous);
|
||||
if(undo.size() > maxUndoSize)
|
||||
undo.pop_front();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Console::printLn(const std::string& line) {
|
||||
consoleLock.lock();
|
||||
if(line.find('\n') == std::string::npos)
|
||||
lines.push_back(line);
|
||||
else {
|
||||
std::vector<std::string> split_lines;
|
||||
split(line, split_lines, '\n');
|
||||
for(unsigned i = 0; i < split_lines.size(); ++i)
|
||||
lines.push_back(split_lines[i]);
|
||||
}
|
||||
consoleLock.release();
|
||||
}
|
||||
|
||||
void Console::clear() {
|
||||
consoleLock.lock();
|
||||
lines.clear();
|
||||
consoleLock.release();
|
||||
}
|
||||
|
||||
void Console::execute(const std::string& command, bool echo) {
|
||||
if(asMode) {
|
||||
if(echo)
|
||||
printLn(std::string("# ") + command);
|
||||
|
||||
if(command == "quit" || command == "q") {
|
||||
asMode = false;
|
||||
}
|
||||
else if(command.compare(0, 7, "global ") == 0) {
|
||||
asGlobal(asEngine, asModule, command.substr(7));
|
||||
}
|
||||
else {
|
||||
asExec(asEngine, asModule, command);
|
||||
}
|
||||
}
|
||||
else {
|
||||
auto argStart = command.find_first_of(" \t");
|
||||
std::string function = command.substr(0, argStart);
|
||||
toLowercase(function);
|
||||
|
||||
auto func = functions.find(function);
|
||||
if(func == functions.end()) {
|
||||
if(echo)
|
||||
printLn(std::string("Unrecognized command '") + function + "'");
|
||||
return;
|
||||
}
|
||||
|
||||
if(echo)
|
||||
printLn(std::string("> ") + command);
|
||||
|
||||
if(func->second.line) {
|
||||
std::string line;
|
||||
if(argStart != std::string::npos)
|
||||
line = command.substr(argStart + 1);
|
||||
|
||||
if(func->second.line(line))
|
||||
return;
|
||||
}
|
||||
|
||||
if(func->second.call) {
|
||||
std::vector<std::string> args;
|
||||
if(argStart != std::string::npos) {
|
||||
auto end = argStart;
|
||||
while(end != std::string::npos) {
|
||||
auto start = command.find_first_not_of(" \t", end);
|
||||
if(start == std::string::npos)
|
||||
break;
|
||||
if(command[start] == '\"') {
|
||||
start += 1;
|
||||
end = command.find('\"', start);
|
||||
}
|
||||
else {
|
||||
end = command.find(' ', start);
|
||||
}
|
||||
|
||||
args.push_back(command.substr(start, end-start));
|
||||
}
|
||||
}
|
||||
|
||||
func->second.call(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Console::executeFile(const std::string& filename) {
|
||||
std::ifstream stream(filename);
|
||||
std::string line;
|
||||
while(stream.is_open() && stream.good()) {
|
||||
std::getline(stream, line);
|
||||
execute(line, false);
|
||||
}
|
||||
}
|
||||
|
||||
void Console::show() {
|
||||
open = true;
|
||||
}
|
||||
|
||||
void Console::toggle() {
|
||||
open = !open;
|
||||
}
|
||||
|
||||
Color bgCols[4] = {Color(0,0,0,230), Color(0,0,0,230), Color(0,0,0,230), Color(0,0,0,230) };
|
||||
Color selCols[4] = {Color(0,0,255,80), Color(0,0,255,80), Color(0,0,255,80), Color(0,0,255,80) };
|
||||
|
||||
void Console::render(render::RenderDriver& driver) {
|
||||
if(!open && !permaStats)
|
||||
return;
|
||||
vec2i screenSize(devices.driver->win_width,devices.driver->win_height);
|
||||
if(screenSize.width == 0 || screenSize.height == 0)
|
||||
return;
|
||||
|
||||
if(open && !compact)
|
||||
driver.drawRectangle(recti(vec2i(0,0), screenSize), bg, 0, bgCols);
|
||||
|
||||
const gui::skin::Skin& skin = devices.library["Debug"];
|
||||
auto& font = skin.getFont(gui::skin::getFontIndex("Normal"));
|
||||
int lineHeight = font.getLineHeight();
|
||||
|
||||
if(!compact && (open || permaStats)) {
|
||||
//FPS indicator
|
||||
const float average_pct = 0.15f;
|
||||
static float fps = 60;
|
||||
static float tps = 4.f;
|
||||
|
||||
extern double realFrameLen, animation_s, render_s, present_s;
|
||||
extern double prevTick_s;
|
||||
|
||||
int y = 1;
|
||||
|
||||
if(liveStats & LS_FPS) {
|
||||
if(realFrameLen > 0.00001) {
|
||||
fps = (1.f - average_pct) * fps + (average_pct * (float)(1.0 / realFrameLen));
|
||||
|
||||
if(prevTick_s > 0.00001) {
|
||||
double gs = devices.driver->getGameSpeed();
|
||||
if(gs > 0)
|
||||
tps = (1.f - average_pct) * tps + (average_pct * (float)(1.0 / (prevTick_s / gs)));
|
||||
}
|
||||
|
||||
char buffer[256];
|
||||
sprintf(buffer, "%.1ftps %.1ffps (a=%4.1fms r=%4.1fms p=%4.1fms)", tps, fps, animation_s*1000.0, render_s*1000.0, present_s*1000.0);
|
||||
font.draw(devices.render, buffer, screenSize.width - font.getDimension(buffer).width, 1);
|
||||
}
|
||||
else {
|
||||
font.draw(devices.render, "inf fps", screenSize.width - font.getDimension("inf fps").width ,1);
|
||||
}
|
||||
|
||||
y += 39;
|
||||
}
|
||||
|
||||
int x = screenSize.width - 260;
|
||||
|
||||
if(liveStats & LS_FPS) {
|
||||
devices.render->drawFPSGraph(recti(vec2i(x,y), vec2i(x + 240, y+60)));
|
||||
y += 60 + lineHeight;
|
||||
}
|
||||
|
||||
asUINT gc_size, gc_destroyed, gc_detected, gc_new, gc_newDestroyed;
|
||||
|
||||
if(devices.scripts.menu && (liveStats & LS_Menu)) {
|
||||
devices.scripts.menu->engine->GetGCStatistics(&gc_size, &gc_destroyed, &gc_detected, &gc_new, &gc_newDestroyed);
|
||||
std::string gc_state = format("Menu GC Entities: $1\n Destroyed: $2\n Detected: $3\n New: $4\n New Destroyed: $5", gc_size, gc_destroyed, gc_detected, gc_new, gc_newDestroyed);
|
||||
font.draw(devices.render, gc_state.c_str(), x, y);
|
||||
y += lineHeight * 6;
|
||||
}
|
||||
|
||||
if(devices.scripts.client && (liveStats & LS_Client)) {
|
||||
devices.scripts.client->engine->GetGCStatistics(&gc_size, &gc_destroyed, &gc_detected, &gc_new, &gc_newDestroyed);
|
||||
std::string gc_state = format("Client GC Entities: $1\n Destroyed: $2\n Detected: $3\n New: $4\n New Destroyed: $5", gc_size, gc_destroyed, gc_detected, gc_new, gc_newDestroyed);
|
||||
font.draw(devices.render, gc_state.c_str(), x, y);
|
||||
y += lineHeight * 6;
|
||||
}
|
||||
|
||||
if(devices.scripts.server && (liveStats & LS_Server)) {
|
||||
devices.scripts.server->engine->GetGCStatistics(&gc_size, &gc_destroyed, &gc_detected, &gc_new, &gc_newDestroyed);
|
||||
std::string gc_state = format("Server GC Entities: $1\n Destroyed: $2\n Detected: $3\n New: $4\n New Destroyed: $5", gc_size, gc_destroyed, gc_detected, gc_new, gc_newDestroyed);
|
||||
font.draw(devices.render, gc_state.c_str(), x, y);
|
||||
y += lineHeight * 6;
|
||||
}
|
||||
|
||||
if(liveStats & LS_Tick) {
|
||||
static double lastServerTick = 0, lastClientTick = 0;
|
||||
if(serverTickTime > 0.0001)
|
||||
lastServerTick = serverTickTime;
|
||||
if(clientTickTime > 0.0001)
|
||||
lastClientTick = clientTickTime;
|
||||
|
||||
std::string tickTimes = format("Server Script Tick: $1ms\nClient Script Tick: $2ms", toString(lastServerTick * 1000.0,2), toString(lastClientTick * 1000.0,2));
|
||||
font.draw(devices.render, tickTimes.c_str(), x, y);
|
||||
y += lineHeight * 3;
|
||||
}
|
||||
|
||||
if(liveStats & LS_Buffers) {
|
||||
extern unsigned drawnSteps, bufferFlushes;
|
||||
std::string vbData = format("VB Steps: $1\nVB Flushes: $2\n Verts= $3\n Steps= $4\n Shader= $5", toString(drawnSteps), toString(bufferFlushes),
|
||||
toString(render::vbFlushCounts[render::FC_VertexLimit]), toString(render::vbFlushCounts[render::FC_StepLimit]), toString(render::vbFlushCounts[render::FC_ShaderLimit]));
|
||||
font.draw(devices.render, vbData.c_str(), x, y);
|
||||
y += lineHeight * 6;
|
||||
}
|
||||
|
||||
if(liveStats & LS_Bandwidth && devices.network->monitorBandwidth) {
|
||||
std::string bwData = format("Incoming: $1/s\nOutgoing: $2/s\nQueued: $3",
|
||||
toSize(devices.network->currentIncoming), toSize(devices.network->currentOutgoing), toString(devices.network->queuedPackets));
|
||||
font.draw(devices.render, bwData.c_str(), x, y);
|
||||
y += lineHeight * 4;
|
||||
}
|
||||
}
|
||||
|
||||
if(!open)
|
||||
return;
|
||||
|
||||
int y = screenSize.height - (2 * lineHeight);
|
||||
|
||||
consoleLock.lock();
|
||||
vec2i lineStart;
|
||||
|
||||
if(asMode) {
|
||||
lineStart = font.getDimension("# ");
|
||||
font.draw(&driver, "# ", 0, y+lineHeight);
|
||||
}
|
||||
else {
|
||||
lineStart = font.getDimension("> ");
|
||||
font.draw(&driver, "> ", 0, y+lineHeight);
|
||||
}
|
||||
|
||||
font.draw(&driver, currentLine.c_str(), lineStart.width, y+lineHeight);
|
||||
if(length != 0) {
|
||||
driver.switchToRenderState(*bg);
|
||||
|
||||
vec2i corner(lineStart.x, y+lineHeight);
|
||||
vec2i size;
|
||||
if(length > 0) {
|
||||
corner.x += font.getDimension(currentLine.substr(0, caret).c_str()).width;
|
||||
size = font.getDimension(currentLine.substr(caret, length).c_str());
|
||||
}
|
||||
else {
|
||||
corner.x += font.getDimension(currentLine.substr(0, caret+length).c_str()).width;
|
||||
size = font.getDimension(currentLine.substr(caret+length, -length).c_str());
|
||||
}
|
||||
|
||||
driver.drawRectangle(recti(corner, corner + size), bg, 0, selCols);
|
||||
}
|
||||
|
||||
if((devices.driver->getTime() - baseTimer) % 2000 < 1000) {
|
||||
int caret_x = font.getDimension(currentLine.substr(0, caret).c_str()).width + lineStart.width;
|
||||
font.drawChar(&driver, '|', '\0', caret_x - 4, y+lineHeight);
|
||||
}
|
||||
|
||||
if(!compact)
|
||||
for(auto line = lines.rbegin(), end = lines.rend(); line != end && (y+lineHeight) > 0; ++line, y-=lineHeight)
|
||||
font.draw(&driver, line->c_str(), 0, y);
|
||||
consoleLock.release();
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
namespace render {
|
||||
class RenderDriver;
|
||||
struct RenderState;
|
||||
};
|
||||
|
||||
namespace profile {
|
||||
struct Keybind;
|
||||
};
|
||||
|
||||
typedef std::vector<std::string> argList;
|
||||
|
||||
class Console {
|
||||
public:
|
||||
struct ConsoleFunction {
|
||||
std::function<bool(std::string&)> line;
|
||||
std::function<void(argList&)> call;
|
||||
std::function<bool()> destruct;
|
||||
};
|
||||
private:
|
||||
std::unordered_map<std::string, ConsoleFunction> functions;
|
||||
std::unordered_map<int, std::string> binds;
|
||||
|
||||
std::list<std::string> lines, history;
|
||||
std::list<std::string>::const_iterator historyItem;
|
||||
|
||||
std::list<std::tuple<std::string, size_t, int>> undo, redo;
|
||||
|
||||
std::string currentLine;
|
||||
size_t caret;
|
||||
int length;
|
||||
|
||||
bool open;
|
||||
bool permaStats;
|
||||
render::RenderState *bg;
|
||||
|
||||
bool compact;
|
||||
unsigned liveStats;
|
||||
|
||||
bool asMode;
|
||||
std::string asModule;
|
||||
std::string asEngine;
|
||||
|
||||
bool eraseSelection();
|
||||
public:
|
||||
void addCommand(std::string name, std::function<void(argList&)> function, bool replace = false);
|
||||
void addCommand(std::string name, ConsoleFunction& func, bool replace = false);
|
||||
void clearCommands();
|
||||
|
||||
void printLn(const std::string& line);
|
||||
void execute(const std::string& command, bool echo);
|
||||
void executeFile(const std::string& filename);
|
||||
|
||||
profile::Keybind* keybind;
|
||||
|
||||
void toggle();
|
||||
void show();
|
||||
void clear();
|
||||
|
||||
Console();
|
||||
~Console();
|
||||
|
||||
bool preRender();
|
||||
void render(render::RenderDriver& driver);
|
||||
|
||||
bool character(int code);
|
||||
bool key(int code, bool pressed);
|
||||
bool globalKey(int code, bool pressed);
|
||||
};
|
||||
|
||||
extern Console console;
|
||||
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
enum ServerAccess {
|
||||
SA_Public,
|
||||
SA_Friends,
|
||||
SA_Private,
|
||||
};
|
||||
|
||||
struct QueueType {
|
||||
std::string id;
|
||||
};
|
||||
|
||||
struct CloudDownload {
|
||||
unsigned long long id;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
struct OwnedItem {
|
||||
long long uid, type;
|
||||
};
|
||||
|
||||
//GamePlatform
|
||||
//Defines various interactions with third party game services (e.g. Steam)
|
||||
class GamePlatform {
|
||||
public:
|
||||
GamePlatform() {}
|
||||
virtual ~GamePlatform() {}
|
||||
|
||||
virtual void update() = 0;
|
||||
|
||||
virtual void logException(unsigned code, void* exception, unsigned version, const char* comment) = 0;
|
||||
|
||||
//Servers
|
||||
virtual void announceServer(unsigned ip4, unsigned short port, const std::string& password) = 0;
|
||||
virtual void announceDisconnect() = 0;
|
||||
virtual void inviteFriend() = 0;
|
||||
virtual std::string getLobbyConnectAddress(const std::string& lobbyID, std::string* pwd = nullptr) = 0;
|
||||
virtual uint64_t getLobby() = 0;
|
||||
virtual void joinLobby(uint64_t id) = 0;
|
||||
|
||||
virtual void enterQueue(const std::string& type, unsigned players, const std::string& version) = 0;
|
||||
virtual bool inQueue() = 0;
|
||||
virtual bool queueRequest() = 0;
|
||||
virtual bool queuePlayerWait(unsigned& ready, unsigned& cap) = 0;
|
||||
virtual bool queueReady() = 0;
|
||||
virtual void leaveQueue() = 0;
|
||||
virtual void acceptQueue() = 0;
|
||||
virtual void rejectQueue() = 0;
|
||||
virtual int remainingTime() const = 0;
|
||||
virtual unsigned queueTotalPlayers() const = 0;
|
||||
|
||||
//Achievements/Stats
|
||||
virtual void modStat(const std::string& id, int delta) = 0;
|
||||
virtual void modStat(const std::string& id, float delta) = 0;
|
||||
virtual bool getStat(const std::string& id, int& value) = 0;
|
||||
virtual bool getStat(const std::string& id, float& value) = 0;
|
||||
virtual bool getGlobalStat(const std::string& id, long long& value) = 0;
|
||||
virtual bool getGlobalStat(const std::string& id, double& value) = 0;
|
||||
virtual void unlockAchievement(const std::string& id) = 0;
|
||||
|
||||
//Cloud Files
|
||||
virtual void addCloudFolder(const std::string& home, const std::string& folder) = 0;
|
||||
virtual void writeCloudFile(const std::string& filename, const std::string& cloudname) = 0;
|
||||
//Syncs changed files down from the cloud
|
||||
virtual void syncCloudFiles(const std::string& home) = 0;
|
||||
//Clear all files for debug purposes
|
||||
virtual void flushCloud() = 0;
|
||||
|
||||
//User Generated Content
|
||||
virtual void createCloudItem(const std::string& folder) = 0;
|
||||
virtual void closeItem() = 0;
|
||||
virtual void setItemTitle(const std::string& title) = 0;
|
||||
virtual void setItemDescription(const std::string& desc) = 0;
|
||||
virtual void setItemTags(const std::vector<std::string>& tags) = 0;
|
||||
virtual void setItemVisibility() = 0;
|
||||
virtual void setItemContents(const std::string& folder) = 0;
|
||||
virtual void setItemImage(const std::string& filename) = 0;
|
||||
virtual void commitItem(const std::string& changelog) = 0;
|
||||
virtual double getUploadProgress() = 0;
|
||||
virtual bool isItemUpdating() = 0;
|
||||
virtual bool isItemActive() = 0;
|
||||
virtual unsigned long long getItemID() = 0;
|
||||
|
||||
virtual bool getDownloadedItem(unsigned index, CloudDownload& download) = 0;
|
||||
virtual unsigned getDownloadedItemCount() = 0;
|
||||
|
||||
virtual bool hasDLC(const std::string& dlcIdent) const = 0;
|
||||
|
||||
//Sell-out API
|
||||
virtual void playtimeHeartbeat() = 0;
|
||||
virtual void rewardPlaytime() = 0;
|
||||
virtual void getOwnedItems(std::vector<OwnedItem>& ids) = 0;
|
||||
virtual void getAwardedItems(std::vector<long long>& types) = 0;
|
||||
virtual bool hasItem(long long typeID) = 0;
|
||||
|
||||
//Friends
|
||||
virtual std::string getNickname() = 0;
|
||||
virtual void openURL(const std::string& url) = 0;
|
||||
|
||||
#ifndef NSTEAM
|
||||
static GamePlatform* acquireSteam();
|
||||
#endif
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
//Initialized global data, should be done once per startup
|
||||
bool initGlobal(bool loadGraphics = true, bool createWindow = true);
|
||||
void destroyGlobal();
|
||||
|
||||
//Creates any thread local variables,
|
||||
//should be called after initializing global
|
||||
void initNewThread();
|
||||
//Frees thread local variables
|
||||
void cleanupThread();
|
||||
//Add a cleanup stage to the current thread
|
||||
void addThreadCleanup(std::function<void()> func);
|
||||
|
||||
//Switches over to a different mod
|
||||
void initMods(const std::vector<std::string>& mods);
|
||||
void destroyMod();
|
||||
bool isPreloading();
|
||||
void finishPreload();
|
||||
void cancelLoad();
|
||||
|
||||
//Switches over to a different locale
|
||||
void switchLocale(const std::string& locale);
|
||||
|
||||
//Initialize game state variables
|
||||
extern bool game_running;
|
||||
extern bool load_resources;
|
||||
extern bool watch_resources;
|
||||
extern bool use_sound;
|
||||
extern bool use_steam;
|
||||
extern bool monitor_files;
|
||||
extern bool fullscreen;
|
||||
extern bool useJIT;
|
||||
extern std::string loadSaveName;
|
||||
void initGame();
|
||||
void destroyGame();
|
||||
|
||||
namespace net {
|
||||
struct Message;
|
||||
};
|
||||
void setGameSettings(net::Message& msg);
|
||||
void clearGameSettings();
|
||||
|
||||
struct GameConfig {
|
||||
size_t count;
|
||||
std::string* names;
|
||||
double* values;
|
||||
double* defaultValues;
|
||||
std::unordered_map<std::string, size_t> indices;
|
||||
|
||||
GameConfig() : count(0), names(nullptr), values(nullptr) {
|
||||
}
|
||||
};
|
||||
extern GameConfig gameConfig;
|
||||
void readGameConfig(net::Message& msg);
|
||||
void writeGameConfig(net::Message& msg);
|
||||
|
||||
class SaveFile;
|
||||
void saveGameConfig(SaveFile& file);
|
||||
void loadGameConfig(SaveFile& file);
|
||||
|
||||
bool hasDLC(const std::string& name);
|
||||
@@ -0,0 +1,328 @@
|
||||
#include "main/references.h"
|
||||
#include "main/input_handling.h"
|
||||
#include "main/initialization.h"
|
||||
#include "main/tick.h"
|
||||
#include "main/console.h"
|
||||
#include "os/driver.h"
|
||||
#include <set>
|
||||
|
||||
#include "processing.h"
|
||||
|
||||
#define func(x) std::function<decltype(x)>(x)
|
||||
|
||||
enum InputCall {
|
||||
IC_MouseWheel,
|
||||
IC_MouseDrag,
|
||||
IC_MouseDragEnd,
|
||||
IC_MouseMove,
|
||||
IC_MouseClick,
|
||||
IC_MouseButton,
|
||||
IC_CharTyped,
|
||||
IC_KeyEvent,
|
||||
IC_DoubleClick,
|
||||
IC_Overlay,
|
||||
|
||||
IC_COUNT
|
||||
};
|
||||
|
||||
const char* InputCallDecls[IC_COUNT] = {
|
||||
"void onMouseWheel(double,double)",
|
||||
"void onMouseDragged(int, int, int, int, int)",
|
||||
"bool onMouseDragEnd(int)",
|
||||
"void onMouseMoved(int, int)",
|
||||
"void onMouseClicked(int)",
|
||||
"bool onMouseButton(int, bool)",
|
||||
"bool onCharTyped(int)",
|
||||
"bool onKeyEvent(int, int)",
|
||||
"void onMouseDoubleClicked(int)",
|
||||
"void onOverlayChanged(bool)",
|
||||
};
|
||||
|
||||
asIScriptFunction* inputCalls[GS_COUNT][IC_COUNT];
|
||||
scripts::Manager* manager[GS_COUNT];
|
||||
|
||||
INIT_FUNC(clear_input) {
|
||||
memset(inputCalls, 0, IC_COUNT * GS_COUNT * sizeof(asIScriptFunction*));
|
||||
memset(manager, 0, GS_COUNT * sizeof(scripts::Manager*));
|
||||
} INIT_FUNC_END;
|
||||
|
||||
void clearInputScripts(GameState state) {
|
||||
memset(inputCalls[state], 0, IC_COUNT * sizeof(asIScriptFunction*));
|
||||
manager[state] = 0;
|
||||
}
|
||||
|
||||
void bindInputScripts(GameState state, scripts::Manager* Manager) {
|
||||
if(Manager)
|
||||
manager[state] = Manager;
|
||||
|
||||
if(!Manager) {
|
||||
clearInputScripts(state);
|
||||
return;
|
||||
}
|
||||
|
||||
for(unsigned i = 0; i < IC_COUNT; ++i)
|
||||
inputCalls[state][i] = manager[state]->getFunction("input", InputCallDecls[i]);
|
||||
}
|
||||
|
||||
void onOverlayToggle(bool state) {
|
||||
if(manager[game_state] && inputCalls[game_state][IC_Overlay]) {
|
||||
auto call = manager[game_state]->call(inputCalls[game_state][IC_Overlay]);
|
||||
call.push(state);
|
||||
call.call();
|
||||
}
|
||||
}
|
||||
|
||||
bool onWindowClose() {
|
||||
game_state = GS_Quit;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onCharTyped(int key) {
|
||||
if(console.character(key))
|
||||
return true;
|
||||
//Inform the script
|
||||
bool ret = true;
|
||||
if(manager[game_state] && inputCalls[game_state][IC_CharTyped]) {
|
||||
auto call = manager[game_state]->call(inputCalls[game_state][IC_CharTyped]);
|
||||
call.push(key);
|
||||
call.call(ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::set<profile::Keybind*> pressedKeys;
|
||||
void onModifiersChanged() {
|
||||
foreach(key, pressedKeys)
|
||||
(*key)->call(false);
|
||||
pressedKeys.clear();
|
||||
}
|
||||
|
||||
void clearPressedKeys() {
|
||||
pressedKeys.clear();
|
||||
}
|
||||
|
||||
bool onKeyEvent(int key, int keyaction) {
|
||||
bool pressed = keyaction & os::KA_Pressed;
|
||||
|
||||
//Figure out if the modifier mask was changed
|
||||
switch(key) {
|
||||
case os::KEY_LCTRL:
|
||||
case os::KEY_RCTRL:
|
||||
case os::KEY_LALT:
|
||||
case os::KEY_RALT:
|
||||
case os::KEY_LSHIFT:
|
||||
case os::KEY_RSHIFT:
|
||||
onModifiersChanged();
|
||||
break;
|
||||
}
|
||||
|
||||
//Build key with modifiers
|
||||
int mod_key = key;
|
||||
if(mod_key >= 'A' && mod_key <= 'Z')
|
||||
mod_key += 'a'-'A';
|
||||
profile::Keybind* bind = devices.keybinds.global.getBind(mod_key);
|
||||
|
||||
if(devices.driver->ctrlKey)
|
||||
mod_key |= profile::Mod_Ctrl;
|
||||
if(devices.driver->altKey)
|
||||
mod_key |= profile::Mod_Alt;
|
||||
if(devices.driver->shiftKey)
|
||||
mod_key |= profile::Mod_Shift;
|
||||
profile::Keybind* mod_bind = devices.keybinds.global.getBind(mod_key);
|
||||
if(mod_bind)
|
||||
bind = mod_bind;
|
||||
|
||||
if(bind != console.keybind && console.key(key, pressed))
|
||||
return true;
|
||||
|
||||
//Inform the script
|
||||
if(manager[game_state] && inputCalls[game_state][IC_KeyEvent]) {
|
||||
bool ret = false;
|
||||
auto call = manager[game_state]->call(inputCalls[game_state][IC_KeyEvent]);
|
||||
call.push(key);
|
||||
call.push(keyaction);
|
||||
call.call(ret);
|
||||
if(ret)
|
||||
return true;
|
||||
}
|
||||
|
||||
if(console.globalKey(key, pressed))
|
||||
return true;
|
||||
|
||||
//Trigger Keybinds
|
||||
if(bind) {
|
||||
bind->call(pressed);
|
||||
if(pressed)
|
||||
pressedKeys.insert(bind);
|
||||
else
|
||||
pressedKeys.erase(bind);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct MouseKey {
|
||||
bool pressed;
|
||||
int pressed_time;
|
||||
|
||||
MouseKey() : pressed(false), pressed_time(0) {}
|
||||
} mouseButtons[3];
|
||||
|
||||
int dragging = 0;
|
||||
int pressed_x = 0, pressed_y = 0;
|
||||
int cur_x = 0, cur_y = 0;
|
||||
int tot_drag = 0;
|
||||
const int dblClickTimeout = 200;
|
||||
|
||||
bool onMouseButton(int button, int pressed) {
|
||||
//When the mouse is being dragged at the system level, don't pass it to the GUI
|
||||
if(dragging) {
|
||||
if(pressed) {
|
||||
dragging |= (0x1 << button);
|
||||
mouseButtons[button].pressed = true;
|
||||
}
|
||||
else {
|
||||
dragging &= ~(0x1 << button);
|
||||
mouseButtons[button].pressed = false;
|
||||
}
|
||||
|
||||
if(!dragging) {
|
||||
devices.driver->setCursorVisible(true);
|
||||
|
||||
if(tot_drag > 4) {
|
||||
tot_drag = 0;
|
||||
bool resetMouse = true;
|
||||
|
||||
if(manager[game_state] && inputCalls[game_state][IC_MouseDragEnd]) {
|
||||
auto call = manager[game_state]->call(inputCalls[game_state][IC_MouseDragEnd]);
|
||||
call.push(button);
|
||||
call.call(resetMouse);
|
||||
}
|
||||
|
||||
if(resetMouse)
|
||||
devices.driver->setMousePos(pressed_x, pressed_y);
|
||||
return true;
|
||||
}
|
||||
|
||||
tot_drag = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(pressed == 0)
|
||||
mouseButtons[button].pressed = false;
|
||||
|
||||
bool eventAbsorbed = false;
|
||||
if(manager[game_state]) {
|
||||
//Pass button event to scripts
|
||||
if(inputCalls[game_state][IC_MouseButton]) {
|
||||
auto call = manager[game_state]->call(inputCalls[game_state][IC_MouseButton]);
|
||||
call.push(button);
|
||||
call.push(pressed == 1);
|
||||
call.call(eventAbsorbed);
|
||||
}
|
||||
|
||||
//Pass click events to scripts
|
||||
if(!pressed) {
|
||||
int time = devices.driver->getTime();
|
||||
if(mouseButtons[button].pressed_time > time - dblClickTimeout) {
|
||||
mouseButtons[button].pressed_time = 0;
|
||||
|
||||
if(inputCalls[game_state][IC_DoubleClick]) {
|
||||
auto call = manager[game_state]->call(inputCalls[game_state][IC_DoubleClick]);
|
||||
call.push(button);
|
||||
call.call();
|
||||
}
|
||||
}
|
||||
else {
|
||||
mouseButtons[button].pressed_time = time;
|
||||
|
||||
if(inputCalls[game_state][IC_MouseClick]) {
|
||||
auto call = manager[game_state]->call(inputCalls[game_state][IC_MouseClick]);
|
||||
call.push(button);
|
||||
call.call();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(pressed != 0 && !eventAbsorbed) {
|
||||
mouseButtons[button].pressed = true;
|
||||
|
||||
if(!dragging) {
|
||||
devices.driver->getMousePos(pressed_x, pressed_y);
|
||||
cur_x = pressed_x;
|
||||
cur_y = pressed_y;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool onMouseMoved(int x, int y) {
|
||||
int prevDragging = dragging;
|
||||
dragging = (mouseButtons[0].pressed ? 0x1 : 0)
|
||||
| (mouseButtons[1].pressed ? 0x2 : 0)
|
||||
| (mouseButtons[2].pressed ? 0x4 : 0);
|
||||
|
||||
if(dragging) {
|
||||
if(!prevDragging)
|
||||
devices.driver->setCursorVisible(false);
|
||||
|
||||
if(manager[game_state] && inputCalls[game_state][IC_MouseDrag]) {
|
||||
int dx = x-cur_x, dy = cur_y-y;
|
||||
tot_drag += abs(dx) + abs(dy);
|
||||
|
||||
if(dx || dy) {
|
||||
auto call = manager[game_state]->call(inputCalls[game_state][IC_MouseDrag]);
|
||||
call.push(dragging);
|
||||
call.push((int)(pressed_x / ui_scale));
|
||||
call.push((int)(pressed_y / ui_scale));
|
||||
call.push(dx);
|
||||
call.push(dy);
|
||||
call.call();
|
||||
|
||||
cur_y = y;
|
||||
cur_x = x;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(manager[game_state] && inputCalls[game_state][IC_MouseMove]) {
|
||||
//Pass move event to scripts
|
||||
auto call = manager[game_state]->call(inputCalls[game_state][IC_MouseMove]);
|
||||
call.push((int)(x / ui_scale));
|
||||
call.push((int)(y / ui_scale));
|
||||
call.call();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onMouseWheel(double x, double y) {
|
||||
if(manager[game_state] && inputCalls[game_state][IC_MouseWheel]) {
|
||||
auto call = manager[game_state]->call(inputCalls[game_state][IC_MouseWheel]);
|
||||
call.push(x);
|
||||
call.push(y);
|
||||
call.call();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void registerInput() {
|
||||
//Add base callbacks for os driver
|
||||
devices.driver->onWindowClose.add(onWindowClose);
|
||||
//devices.driver->onResize.add(onWindowClose);
|
||||
devices.driver->onScroll.add(onMouseWheel);
|
||||
devices.driver->onMouseButton.add(onMouseButton);
|
||||
devices.driver->onMouseMoved.add(onMouseMoved);
|
||||
devices.driver->onCharTyped.add(onCharTyped);
|
||||
devices.driver->onKeyEvent.add(onKeyEvent);
|
||||
}
|
||||
|
||||
void inputTick() {
|
||||
if(dragging) {
|
||||
cur_x = pressed_x;
|
||||
cur_y = pressed_y;
|
||||
devices.driver->setMousePos(pressed_x, pressed_y);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include "main/tick.h"
|
||||
#include "os/driver.h"
|
||||
#include "scripts/manager.h"
|
||||
|
||||
void onOverlayToggle(bool state);
|
||||
|
||||
void registerInput();
|
||||
void inputTick();
|
||||
|
||||
void bindInputScripts(GameState state ,scripts::Manager* manager);
|
||||
void clearInputScripts(GameState state);
|
||||
void clearPressedKeys();
|
||||
@@ -0,0 +1,175 @@
|
||||
#include "main/logging.h"
|
||||
#include "main/console.h"
|
||||
#include "str_util.h"
|
||||
#include "files.h"
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <time.h>
|
||||
|
||||
LogLevel level = LL_Warning;
|
||||
std::ofstream logFile;
|
||||
std::fstream errorLog;
|
||||
|
||||
void createLog() {
|
||||
logFile.open(path_join(getProfileRoot(), "log.txt"), std::ofstream::trunc | std::ofstream::out);
|
||||
errorLog.open(path_join(getProfileRoot(), "errors.log.txt"), std::fstream::out | std::fstream::app);
|
||||
}
|
||||
|
||||
void flushLog() {
|
||||
logFile.flush();
|
||||
errorLog.flush();
|
||||
}
|
||||
|
||||
void logDate() {
|
||||
time_t stamp = time(NULL);
|
||||
struct tm* timeinfo;
|
||||
timeinfo = localtime(&stamp);
|
||||
char buffer[125];
|
||||
strftime(buffer, 124, "%d %b %Y %H:%M:%S\n", timeinfo);
|
||||
logFile << buffer;
|
||||
}
|
||||
|
||||
void appendToErrorLog(const char* errMsg, bool timestamp, bool separateLogs) {
|
||||
if(timestamp) {
|
||||
time_t stamp = time(NULL);
|
||||
struct tm* timeinfo;
|
||||
timeinfo = localtime(&stamp);
|
||||
char buffer[125];
|
||||
strftime(buffer, 124, "%d %b %Y %H:%M:%S\n", timeinfo);
|
||||
errorLog << buffer;
|
||||
}
|
||||
|
||||
errorLog << errMsg;
|
||||
if(separateLogs)
|
||||
errorLog << "\n\n";
|
||||
else
|
||||
errorLog << "\n";
|
||||
errorLog.flush();
|
||||
}
|
||||
|
||||
void appendToErrorLog(const std::string& errMsg, bool timestamp) {
|
||||
appendToErrorLog(errMsg.c_str(), timestamp);
|
||||
}
|
||||
|
||||
void storeLog(const std::string& filename) {
|
||||
//NOTE: This only happens during a crash situation, so we disregard the various possible side effects if something goes wrong
|
||||
logFile.flush();
|
||||
logFile.close();
|
||||
std::string logPath = path_join(getProfileRoot(), "log.txt");
|
||||
rename(logPath.c_str(), filename.c_str());
|
||||
}
|
||||
|
||||
|
||||
void setLogLevel(LogLevel level) {
|
||||
::level = level;
|
||||
}
|
||||
|
||||
LogLevel getLogLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
void print(const std::string& msg) {
|
||||
printf("%s\n", msg.c_str());
|
||||
logFile << msg << std::endl;
|
||||
console.printLn(msg);
|
||||
}
|
||||
|
||||
void print(const char* format, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
printv(format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void printv(const char* format, va_list ap) {
|
||||
char line[2048];
|
||||
vsnprintf(line, 2048, format, ap);
|
||||
printf("%s\n", line);
|
||||
logFile << line << std::endl;
|
||||
console.printLn(line);
|
||||
}
|
||||
|
||||
void info(const std::string& msg) {
|
||||
if(level >= LL_Info)
|
||||
print(msg);
|
||||
}
|
||||
|
||||
void info(const char* format, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
if(level >= LL_Info)
|
||||
printv(format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void warn(const std::string& msg) {
|
||||
if(level >= LL_Warning)
|
||||
print(msg);
|
||||
}
|
||||
|
||||
void warn(const char* format, ...) {
|
||||
if(level >= LL_Warning) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
printv(format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
}
|
||||
|
||||
void error(const std::string& msg) {
|
||||
if(level >= LL_Error)
|
||||
print(msg);
|
||||
}
|
||||
|
||||
void error(const char* format, ...) {
|
||||
if(level >= LL_Error) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
printv(format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
}
|
||||
|
||||
void print(LogLevel lvl, const std::string& msg) {
|
||||
if(level >= lvl)
|
||||
print(msg);
|
||||
}
|
||||
|
||||
void print(LogLevel lvl, const char* format, ...) {
|
||||
if(level >= lvl) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
printv(format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
}
|
||||
|
||||
const char* sectionNames[NS_COUNT] = {
|
||||
"Unknown",
|
||||
"Network",
|
||||
"Processing",
|
||||
"Loading",
|
||||
"Script Tick",
|
||||
"Render",
|
||||
"Startup",
|
||||
"Message Thread",
|
||||
"Animation"
|
||||
};
|
||||
|
||||
Threaded(NativeSection) threadSection = NS_Unknown;
|
||||
|
||||
NativeSection enterSection(NativeSection section) {
|
||||
auto& sect = threadSection;
|
||||
NativeSection n = sect;
|
||||
sect = section;
|
||||
return n;
|
||||
}
|
||||
|
||||
const char* getSectionName() {
|
||||
if(threadSection < NS_COUNT)
|
||||
return sectionNames[threadSection];
|
||||
else
|
||||
return sectionNames[NS_Unknown];
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
#include "threads.h"
|
||||
#include <string>
|
||||
|
||||
enum LogLevel {
|
||||
LL_Error,
|
||||
LL_Warning,
|
||||
LL_Info,
|
||||
};
|
||||
|
||||
void setLogLevel(LogLevel level);
|
||||
LogLevel getLogLevel();
|
||||
void createLog();
|
||||
void flushLog();
|
||||
void storeLog(const std::string& filename);
|
||||
|
||||
void info(const std::string& msg);
|
||||
void warn(const std::string& msg);
|
||||
void error(const std::string& msg);
|
||||
|
||||
void info(const char* format, ...);
|
||||
void warn(const char* format, ...);
|
||||
void error(const char* format, ...);
|
||||
|
||||
void print(const std::string& msg);
|
||||
void print(const char* format, ...);
|
||||
|
||||
void print(LogLevel lvl, const std::string& msg);
|
||||
void print(LogLevel lvl, const char* format, ...);
|
||||
|
||||
void printv(const char* format, va_list ap);
|
||||
|
||||
void appendToErrorLog(const std::string& errMsg, bool timestamp = true);
|
||||
void appendToErrorLog(const char* errMsg, bool timestamp = true, bool separateLogs = true);
|
||||
|
||||
void logDate();
|
||||
|
||||
enum NativeSection {
|
||||
NS_Unknown,
|
||||
NS_Network,
|
||||
NS_Processing,
|
||||
NS_Loading,
|
||||
NS_ScriptTick,
|
||||
NS_Render,
|
||||
NS_Startup,
|
||||
NS_MessageThread,
|
||||
NS_Animation,
|
||||
|
||||
NS_COUNT
|
||||
};
|
||||
|
||||
//Sets the current section, returning the previous section
|
||||
NativeSection enterSection(NativeSection section);
|
||||
const char* getSectionName();
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "profiler.h"
|
||||
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
|
||||
namespace profiler {
|
||||
|
||||
double profileStart() {
|
||||
return devices.driver->getAccurateTime();
|
||||
}
|
||||
|
||||
void profileEnd(double startTime, const char* section) {
|
||||
double delta = devices.driver->getAccurateTime() - startTime;
|
||||
|
||||
if(delta >= 5e-1)
|
||||
print("%s: %.1fs\n", section, delta);
|
||||
else if(delta >= 5e-4)
|
||||
print("%s: %.1fms\n", section, delta * 1e3);
|
||||
else if(delta >= 5e-7)
|
||||
print("%s: %.1fus\n", section, delta * 1e6);
|
||||
else
|
||||
print("%s: %.1fns\n", section, delta * 1e9);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
namespace profiler {
|
||||
|
||||
//Returns a value to pass to a call to 'profileEnd'
|
||||
double profileStart();
|
||||
|
||||
//Prints out the time elapsed since 'startTime' as returned by 'profileStart'
|
||||
void profileEnd(double startTime, const char* section);
|
||||
|
||||
#define profile( x, name ) { double _t = profiler::profileStart(); x; profiler::profileEnd(_t, name); }
|
||||
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
#include "main/references.h"
|
||||
|
||||
references devices;
|
||||
|
||||
references::references() : physics(nullptr), nodePhysics(nullptr), cloud(nullptr) {
|
||||
}
|
||||
|
||||
namespace audio {
|
||||
bool disableSFX = false;
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
#include "render/driver.h"
|
||||
#include "render/camera.h"
|
||||
#include "resource/library.h"
|
||||
#include "scripts/manager.h"
|
||||
#include "os/driver.h"
|
||||
#include "mods/mod_manager.h"
|
||||
#include "profile/keybinds.h"
|
||||
#include "profile/settings.h"
|
||||
#include "resource/locale.h"
|
||||
|
||||
class Universe;
|
||||
class NetworkManager;
|
||||
class PhysicsWorld;
|
||||
class GamePlatform;
|
||||
|
||||
namespace audio {
|
||||
class ISoundDevice;
|
||||
extern bool disableSFX;
|
||||
};
|
||||
|
||||
struct references {
|
||||
os::OSDriver* driver;
|
||||
|
||||
resource::Library library;
|
||||
|
||||
NetworkManager* network;
|
||||
|
||||
GamePlatform* cloud;
|
||||
|
||||
render::RenderDriver* render;
|
||||
scene::Node* scene;
|
||||
|
||||
audio::ISoundDevice* sound;
|
||||
|
||||
Universe* universe;
|
||||
|
||||
PhysicsWorld* physics, *nodePhysics;
|
||||
|
||||
resource::Locale locale;
|
||||
|
||||
mods::Manager mods;
|
||||
|
||||
profile::Keybinds keybinds;
|
||||
struct {
|
||||
profile::Settings mod;
|
||||
profile::Settings engine;
|
||||
} settings;
|
||||
|
||||
struct {
|
||||
scripts::Manager* server;
|
||||
scripts::Manager* client;
|
||||
scripts::Manager* menu;
|
||||
scripts::Manager* cache_server;
|
||||
scripts::Manager* cache_shadow;
|
||||
} scripts;
|
||||
|
||||
struct {
|
||||
asIScriptEngine* server;
|
||||
asIScriptEngine* client;
|
||||
asIScriptEngine* menu;
|
||||
} engines;
|
||||
|
||||
references();
|
||||
};
|
||||
|
||||
extern references devices;
|
||||
@@ -0,0 +1,329 @@
|
||||
#include "save_load.h"
|
||||
#include "util/save_file.h"
|
||||
#include "references.h"
|
||||
#include "logging.h"
|
||||
#include "obj/universe.h"
|
||||
#include "obj/obj_group.h"
|
||||
#include "empire.h"
|
||||
#include "physics/physics_world.h"
|
||||
#include "design/projectiles.h"
|
||||
#include "main/tick.h"
|
||||
#include "design/hull.h"
|
||||
#include "design/subsystem.h"
|
||||
#include "design/effector.h"
|
||||
#include "main/initialization.h"
|
||||
#include <map>
|
||||
#include <assert.h>
|
||||
|
||||
extern std::map<int,ObjectGroup*> groups;
|
||||
extern int SAVE_VERSION, START_VERSION;
|
||||
|
||||
void addSubsystemIdentifiers(SaveFile& file) {
|
||||
unsigned cnt = getSubsystemDefCount();
|
||||
for(unsigned i = 0; i < cnt; ++i) {
|
||||
const SubsystemDef* def = getSubsystemDef(i);
|
||||
file.addIdentifier(SI_Subsystem, def->index, def->id);
|
||||
|
||||
for(unsigned j = 0, jcnt = def->modules.size(); j < jcnt; ++j) {
|
||||
auto* mod = def->modules[j];
|
||||
file.addIdentifier(SI_SubsystemModule, mod->umodid, mod->umodident);
|
||||
}
|
||||
|
||||
foreach(it, def->modifierIds) {
|
||||
auto* mod = it->second;
|
||||
file.addIdentifier(SI_SubsystemModifier, mod->umodifid, def->id+"::"+it->first);
|
||||
}
|
||||
}
|
||||
|
||||
for(auto it = shipVariableIndices.begin(), end = shipVariableIndices.end(); it != end; ++it)
|
||||
file.addIdentifier(SI_ShipVar, it->second, it->first);
|
||||
|
||||
for(auto it = variableIndices.begin(), end = variableIndices.end(); it != end; ++it)
|
||||
file.addIdentifier(SI_SubsystemVar, it->second, it->first);
|
||||
|
||||
for(auto it = hexVariableIndices.begin(), end = hexVariableIndices.end(); it != end; ++it)
|
||||
file.addIdentifier(SI_HexVar, it->second, it->first);
|
||||
|
||||
for(auto it = variableIndices.begin(), end = variableIndices.end(); it != end; ++it)
|
||||
file.addIdentifier(SI_SubsystemVar, it->second, it->first);
|
||||
|
||||
for(size_t i = 0, cnt = getEffectorDefinitionCount(); i < cnt; ++i) {
|
||||
auto* effdef = getEffectorDefinition(i);
|
||||
file.addIdentifier(SI_Effector, effdef->index, effdef->name);
|
||||
}
|
||||
|
||||
for(size_t i = 0, cnt = getEffectDefinitionCount(); i < cnt; ++i) {
|
||||
auto* effdef = getEffectDefinition(i);
|
||||
file.addIdentifier(SI_Effect, effdef->id, effdef->name);
|
||||
}
|
||||
}
|
||||
|
||||
void addHullIdentifiers(SaveFile& file) {
|
||||
unsigned cnt = getHullCount();
|
||||
for(unsigned i = 0; i < cnt; ++i) {
|
||||
const HullDef* hull = getHullDefinition(i);
|
||||
file.addIdentifier(SI_Hull, hull->id, hull->ident);
|
||||
}
|
||||
}
|
||||
|
||||
void addShipsetIdentifiers(SaveFile& file) {
|
||||
unsigned cnt = getShipsetCount();
|
||||
for(unsigned i = 0; i < cnt; ++i) {
|
||||
auto* set = getShipset(i);
|
||||
file.addIdentifier(SI_Shipset, set->id, set->ident);
|
||||
}
|
||||
}
|
||||
|
||||
void addDummyIdentifiers(SaveFile& file) {
|
||||
if(file < SFV_0007) {
|
||||
unsigned i = 0;
|
||||
file.addDummyLoadIdentifier(SI_Effector, i++, "CarpetBomb");
|
||||
file.addDummyLoadIdentifier(SI_Effector, i++, "Railgun");
|
||||
file.addDummyLoadIdentifier(SI_Effector, i++, "Laser");
|
||||
file.addDummyLoadIdentifier(SI_Effector, i++, "PurpleLaser");
|
||||
file.addDummyLoadIdentifier(SI_Effector, i++, "Rockets");
|
||||
file.addDummyLoadIdentifier(SI_Effector, i++, "Missile");
|
||||
file.addDummyLoadIdentifier(SI_Effector, i++, "Torpedo");
|
||||
file.addDummyLoadIdentifier(SI_Effector, i++, "PopulationBomb");
|
||||
file.addDummyLoadIdentifier(SI_Effector, i++, "WaveBeam");
|
||||
file.addDummyLoadIdentifier(SI_Effector, i++, "StationArtillery");
|
||||
}
|
||||
}
|
||||
|
||||
bool saveGame(const std::string& filename) {
|
||||
//TODO: Handle errors gracefully, or guarantee they cannot occur
|
||||
try {
|
||||
SaveFile* pfile = SaveFile::open(filename, SM_Write);
|
||||
SaveFile& file = *pfile;
|
||||
if(&file == 0) {
|
||||
error("Unable to open file '%s'", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
addSubsystemIdentifiers(file);
|
||||
addHullIdentifiers(file);
|
||||
addShipsetIdentifiers(file);
|
||||
|
||||
devices.scripts.server->saveIdentifiers(file);
|
||||
|
||||
file.saveIdentifiers();
|
||||
file << file.scriptVersion;
|
||||
file << file.startVersion;
|
||||
file << (unsigned)devices.mods.activeMods.size();
|
||||
foreach(it, devices.mods.activeMods) {
|
||||
file << (*it)->ident;
|
||||
file << (*it)->version;
|
||||
}
|
||||
|
||||
file.boundary();
|
||||
|
||||
saveGameConfig(file);
|
||||
|
||||
file.boundary();
|
||||
|
||||
file << devices.driver->getGameTime();
|
||||
|
||||
Empire::saveEmpires(file);
|
||||
|
||||
file.boundary();
|
||||
|
||||
saveEffectors(file);
|
||||
|
||||
file.boundary();
|
||||
|
||||
//Save physics
|
||||
if(devices.physics) {
|
||||
file << true;
|
||||
devices.physics->writeSetup(file);
|
||||
}
|
||||
else {
|
||||
file << false;
|
||||
}
|
||||
|
||||
if(devices.nodePhysics) {
|
||||
file << true;
|
||||
devices.nodePhysics->writeSetup(file);
|
||||
}
|
||||
else {
|
||||
file << false;
|
||||
}
|
||||
|
||||
file.boundary();
|
||||
|
||||
//Save object id sequences
|
||||
unsigned typeCount = getScriptObjectTypeCount();
|
||||
file << typeCount;
|
||||
for(unsigned i = 0; i < typeCount; ++i) {
|
||||
auto* type = getScriptObjectType(i);
|
||||
file << type->nextID.get();
|
||||
}
|
||||
|
||||
file.boundary();
|
||||
|
||||
//Save object groups
|
||||
unsigned groupCount = (unsigned)groups.size();
|
||||
file << groupCount;
|
||||
for(auto i = groups.begin(), end = groups.end(); i != end; ++i)
|
||||
i->second->save(file);
|
||||
|
||||
file.boundary();
|
||||
|
||||
//Save objects
|
||||
unsigned objectCount = (unsigned)devices.universe->children.size();
|
||||
file << objectCount;
|
||||
|
||||
for(unsigned i = 0; i < objectCount; ++i)
|
||||
devices.universe->children[i]->save(file);
|
||||
|
||||
file.boundary();
|
||||
|
||||
saveProjectiles(file);
|
||||
|
||||
file.boundary();
|
||||
|
||||
devices.scripts.server->save(file);
|
||||
|
||||
if(devices.scripts.client)
|
||||
devices.scripts.client->save(file);
|
||||
else
|
||||
file << "";
|
||||
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
catch(SaveFileError& err) {
|
||||
error("Failed to save: %s", err.text);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool loadGame(const std::string& filename) {
|
||||
#ifndef _DEBUG
|
||||
try {
|
||||
#endif
|
||||
SaveFile& file = *SaveFile::open(filename, SM_Read);
|
||||
if(&file == 0) {
|
||||
error("Unable to open file '%s'", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
file.loadIdentifiers();
|
||||
addDummyIdentifiers(file);
|
||||
|
||||
SaveFileInfo info;
|
||||
readSaveFileInfo(file, info);
|
||||
file.scriptVersion = info.version;
|
||||
file.startVersion = info.startVersion;
|
||||
SAVE_VERSION = file.scriptVersion;
|
||||
START_VERSION = file.startVersion;
|
||||
|
||||
//Initialize server scripts
|
||||
devices.scripts.server->init();
|
||||
|
||||
//Load identifiers
|
||||
addSubsystemIdentifiers(file);
|
||||
addHullIdentifiers(file);
|
||||
addShipsetIdentifiers(file);
|
||||
|
||||
devices.scripts.server->saveIdentifiers(file);
|
||||
file.scriptVersion = info.version;
|
||||
file.startVersion = info.startVersion;
|
||||
|
||||
file.finalizeIdentifiers();
|
||||
|
||||
file.boundary();
|
||||
|
||||
loadGameConfig(file);
|
||||
file.boundary();
|
||||
|
||||
devices.driver->resetGameTime(file);
|
||||
resetGameTime();
|
||||
|
||||
Empire::loadEmpires(file);
|
||||
|
||||
file.boundary();
|
||||
|
||||
loadEffectors(file);
|
||||
|
||||
file.boundary();
|
||||
|
||||
//Load physics
|
||||
if(file.read<bool>())
|
||||
devices.physics = PhysicsWorld::fromSave(file);
|
||||
if(file.read<bool>())
|
||||
devices.nodePhysics = PhysicsWorld::fromSave(file);
|
||||
|
||||
file.boundary();
|
||||
|
||||
//Load object id sequences
|
||||
unsigned typeCount = file;
|
||||
for(unsigned i = 0; i < typeCount; ++i) {
|
||||
auto* type = getScriptObjectType(i);
|
||||
type->nextID = file.read<int>();
|
||||
}
|
||||
|
||||
file.boundary();
|
||||
|
||||
//Load object groups
|
||||
unsigned groupCount = file;
|
||||
for(unsigned i = 0; i < groupCount; ++i)
|
||||
new ObjectGroup(file);
|
||||
|
||||
file.boundary();
|
||||
|
||||
//Load objects
|
||||
unsigned objectCount = file;
|
||||
for(unsigned i = 0; i < objectCount; ++i) {
|
||||
Object* obj = file;
|
||||
obj->load(file);
|
||||
devices.universe->children.push_back( obj ); obj->grab();
|
||||
}
|
||||
|
||||
file.boundary();
|
||||
|
||||
//Perform post init for groups
|
||||
for(auto i = groups.begin(), end = groups.end(); i != end;) {
|
||||
ObjectGroup* group = i->second;
|
||||
if(group->postLoad()) {
|
||||
group->postInit();
|
||||
++i;
|
||||
}
|
||||
else {
|
||||
i = groups.erase(i);
|
||||
group->drop();
|
||||
}
|
||||
}
|
||||
|
||||
loadProjectiles(file);
|
||||
|
||||
file.boundary();
|
||||
|
||||
devices.scripts.server->load(file);
|
||||
|
||||
//Objects that weren't loaded yet at this point are not valid
|
||||
invalidateUninitializedObjects();
|
||||
|
||||
//Perform post load on objects
|
||||
for(unsigned i = 0; i < objectCount; ++i)
|
||||
devices.universe->children[i]->postLoad();
|
||||
|
||||
//Post load on effectors
|
||||
postLoadEffectors();
|
||||
|
||||
//Initialize client scripts
|
||||
devices.scripts.client->init();
|
||||
|
||||
//TODO: If we don't have a client, there could still be data to load (if we load it last, we don't have to care)
|
||||
if(devices.scripts.client)
|
||||
devices.scripts.client->load(file);
|
||||
|
||||
file.close();
|
||||
return true;
|
||||
#ifndef _DEBUG
|
||||
}
|
||||
catch(SaveFileError& err) {
|
||||
error("Failed to load save '%s':\n %s", filename.c_str(), err.text);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
bool saveGame(const std::string& file);
|
||||
bool loadGame(const std::string& file);
|
||||
@@ -0,0 +1,862 @@
|
||||
#include "main/tick.h"
|
||||
#include "main/input_handling.h"
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
#include "processing.h"
|
||||
#include "obj/object.h"
|
||||
#include "scene/animation/anim_node_sync.h"
|
||||
#include "main/console.h"
|
||||
#include "main/initialization.h"
|
||||
#include "network/network_manager.h"
|
||||
#include "ISoundDevice.h"
|
||||
#include "files.h"
|
||||
#include "design/projectiles.h"
|
||||
#include "render/vertexBuffer.h"
|
||||
#include "render/gl_framebuffer.h"
|
||||
#include "save_load.h"
|
||||
#include <cmath>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
extern unsigned drawnSteps, bufferFlushes;
|
||||
|
||||
GameState game_state = GS_Menu;
|
||||
std::string game_locale;
|
||||
bool game_running = false;
|
||||
bool reload_gui = false;
|
||||
|
||||
bool fullGC = false;
|
||||
|
||||
threads::Mutex texDestroyLock;
|
||||
std::vector<const render::Texture*> queuedDestroyTextures;
|
||||
void queueDestroyTexture(const render::Texture* tex) {
|
||||
threads::Lock lock(texDestroyLock);
|
||||
queuedDestroyTextures.push_back(tex);
|
||||
}
|
||||
|
||||
void destroyQueuedTextures() {
|
||||
if(queuedDestroyTextures.empty())
|
||||
return;
|
||||
|
||||
threads::Lock lock(texDestroyLock);
|
||||
for(unsigned i = 0; i < queuedDestroyTextures.size(); ++i)
|
||||
delete queuedDestroyTextures[i];
|
||||
queuedDestroyTextures.clear();
|
||||
}
|
||||
|
||||
const render::Shader* fsShader = nullptr;
|
||||
bool hide_ui = false;
|
||||
double ui_scale = 1.0;
|
||||
double scale_3d = 1.0;
|
||||
double pixelSizeRatio = 1.0;
|
||||
double* maxfps = nullptr;
|
||||
|
||||
//Seconds since last autosave
|
||||
double autosaveTimer = 0.0;
|
||||
extern double autosaveInterval;
|
||||
|
||||
threads::Signal scriptTickSignal;
|
||||
|
||||
extern bool queuedModSwitch;
|
||||
extern std::vector<std::string> modSetup;
|
||||
|
||||
namespace scripts {
|
||||
void logException();
|
||||
};
|
||||
|
||||
class RunScriptTick : public processing::Action {
|
||||
double time;
|
||||
double& logTime;
|
||||
bool gcLock;
|
||||
scripts::Manager* manager;
|
||||
public:
|
||||
RunScriptTick(scripts::Manager* Manager, double Time, bool Gc, double& LogTime)
|
||||
: time(Time), gcLock(Gc), manager(Manager), logTime(LogTime) {
|
||||
}
|
||||
|
||||
bool run() {
|
||||
auto prevSection = enterSection(NS_ScriptTick);
|
||||
double start = devices.driver->getAccurateTime();
|
||||
#ifndef _DEBUG
|
||||
try {
|
||||
#endif
|
||||
#ifdef TRACE_GC_LOCK
|
||||
manager->markGCImpossible();
|
||||
#endif
|
||||
manager->tick(time);
|
||||
devices.network->managerNetworking(manager, time);
|
||||
scriptTickSignal.signalDown();
|
||||
#ifdef TRACE_GC_LOCK
|
||||
manager->markGCPossible();
|
||||
#endif
|
||||
#ifndef _DEBUG
|
||||
}
|
||||
catch(...) {
|
||||
scripts::logException();
|
||||
throw;
|
||||
}
|
||||
#endif
|
||||
logTime = devices.driver->getAccurateTime() - start;
|
||||
enterSection(prevSection);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class RunScriptGC : public processing::Action {
|
||||
scripts::Manager* manager;
|
||||
bool full;
|
||||
public:
|
||||
RunScriptGC(scripts::Manager* Manager, bool FullCycle = false)
|
||||
: manager(Manager), full(FullCycle) {
|
||||
}
|
||||
|
||||
bool run() {
|
||||
double start = devices.driver->getAccurateTime();
|
||||
manager->pauseScriptThreads();
|
||||
double gcStart = devices.driver->getAccurateTime();
|
||||
int gcMode = manager->garbageCollect(full);
|
||||
double end = devices.driver->getAccurateTime();
|
||||
manager->resumeScriptThreads();
|
||||
|
||||
#ifdef PROFILE_PROCESSING
|
||||
if(end - start > 0.016) {
|
||||
const char* name = "Server";
|
||||
if(manager == devices.scripts.client)
|
||||
name = "Client";
|
||||
else if(manager == devices.scripts.menu)
|
||||
name = "Menu";
|
||||
error("%s GC took %dms (Mode %d)", name, (int)((end - gcStart) * 1.0e3), gcMode);
|
||||
if(gcStart - start > 0.01)
|
||||
error("%s script pausing took %dms", name, (int)((gcStart - start) * 1.0e3));
|
||||
}
|
||||
#endif
|
||||
|
||||
scriptTickSignal.signalDown();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
unsigned animationThreadCount = 6;
|
||||
threads::Signal animationSignal, activeAnimCount;
|
||||
threads::Mutex animDataLock;
|
||||
double nearestNode = 9.0e35, furthestNode = 0.0;
|
||||
double animation_s = 0.0, render_s = 0.0, present_s = 0.0;
|
||||
threads::atomic_int animIndex(-1), animProcessed;
|
||||
|
||||
//Animates some nodes, returning how long it processed
|
||||
double animateSomeNodes(int maxNodes) {
|
||||
if(animIndex < 0)
|
||||
return 0.0;
|
||||
|
||||
double furthest = 0.0;
|
||||
double start = devices.driver->getAccurateTime();
|
||||
|
||||
auto* nodes = &devices.scene->children.front();
|
||||
int count = (int)devices.scene->children.size();
|
||||
int animCount = count/(animationThreadCount*16);
|
||||
if(animCount == 0)
|
||||
animCount = 1;
|
||||
|
||||
int index = (animIndex -= animCount);
|
||||
while(index >= 0) {
|
||||
int animated = 0;
|
||||
for(int i = index; i > index - animCount && i >= 0; --i) {
|
||||
auto* node = nodes[i];
|
||||
node->animate();
|
||||
++animated;
|
||||
|
||||
double d = node->sortDistance + node->abs_scale;
|
||||
if(d > furthest)
|
||||
furthest = d;
|
||||
}
|
||||
|
||||
if(animated != 0)
|
||||
animProcessed += animated;
|
||||
|
||||
maxNodes -= animated;
|
||||
if(maxNodes < 0)
|
||||
break;
|
||||
|
||||
index = (animIndex -= animCount);
|
||||
}
|
||||
|
||||
animDataLock.lock();
|
||||
//if(nearest < nearestNode)
|
||||
// nearestNode = nearest;
|
||||
if(furthest > furthestNode)
|
||||
furthestNode = furthest;
|
||||
animDataLock.release();
|
||||
|
||||
return devices.driver->getAccurateTime() - start;
|
||||
}
|
||||
|
||||
volatile bool EndAnimationThreads = false;
|
||||
threads::threadreturn threadcall animateNodes(void* arg) {
|
||||
enterSection(NS_Animation);
|
||||
double& logTime = *(double*)arg;
|
||||
initNewThread();
|
||||
activeAnimCount.signalUp();
|
||||
//threads::setThreadPriority(threads::TP_High);
|
||||
|
||||
#ifdef TRACE_GC_LOCK
|
||||
devices.scripts.menu->markGCImpossible();
|
||||
devices.scripts.client->markGCImpossible();
|
||||
#endif
|
||||
while(true) {
|
||||
while(animIndex < 0 && !EndAnimationThreads)
|
||||
threads::sleep(1);
|
||||
if(EndAnimationThreads)
|
||||
break;
|
||||
|
||||
animationSignal.signalUp();
|
||||
|
||||
double /*nearest = 9.0e35,*/ furthest = 0.0;
|
||||
double start = devices.driver->getAccurateTime();
|
||||
|
||||
if(!devices.scene->children.empty()) {
|
||||
auto* nodes = &devices.scene->children.front();
|
||||
int animCount = (int)devices.scene->children.size()/(animationThreadCount*16);
|
||||
if(animCount == 0)
|
||||
animCount = 1;
|
||||
|
||||
int index = (animIndex -= animCount);
|
||||
while(index >= 0) {
|
||||
int animated = 0;
|
||||
for(int i = index; i > index - animCount && i >= 0; --i) {
|
||||
auto* node = nodes[i];
|
||||
node->animate();
|
||||
++animated;
|
||||
|
||||
double d = node->sortDistance + node->abs_scale;
|
||||
if(d > furthest)
|
||||
furthest = d;
|
||||
}
|
||||
|
||||
if(animated != 0)
|
||||
animProcessed += animated;
|
||||
|
||||
index = (animIndex -= animCount);
|
||||
}
|
||||
}
|
||||
|
||||
logTime = devices.driver->getAccurateTime() - start;
|
||||
|
||||
animDataLock.lock();
|
||||
//if(nearest < nearestNode)
|
||||
// nearestNode = nearest;
|
||||
if(furthest > furthestNode)
|
||||
furthestNode = furthest;
|
||||
animDataLock.release();
|
||||
|
||||
animationSignal.signalDown();
|
||||
threads::sleep(1);
|
||||
}
|
||||
#ifdef TRACE_GC_LOCK
|
||||
devices.scripts.menu->markGCPossible();
|
||||
devices.scripts.client->markGCPossible();
|
||||
#endif
|
||||
|
||||
activeAnimCount.signalDown();
|
||||
cleanupThread();
|
||||
return 0;
|
||||
}
|
||||
|
||||
double frameLen_s = 0, frameTime_s = 0;
|
||||
double realFrameLen = 0, realFrameTime;
|
||||
int frameTime_ms = 0, frameLen_ms = 0;
|
||||
unsigned frameNumber = 0;
|
||||
|
||||
std::vector<double> frames;
|
||||
unsigned max_frames = 120;
|
||||
|
||||
void shader_gameTime(float* pFloats,unsigned short n,void*) {
|
||||
do {
|
||||
*pFloats = (float)frameTime_s;
|
||||
++pFloats;
|
||||
} while(--n);
|
||||
}
|
||||
|
||||
void shader_frameTime(float* pFloats,unsigned short n,void*) {
|
||||
do {
|
||||
*pFloats = (float)realFrameTime;
|
||||
++pFloats;
|
||||
} while(--n);
|
||||
}
|
||||
|
||||
void shader_gameTime_cycle(float* pFloats,unsigned short n,void* pArgs) {
|
||||
float* period = (float*)pArgs;
|
||||
do {
|
||||
*pFloats = (float)(std::fmod(frameTime_s, double(*period)) / double(*period));
|
||||
++period; ++pFloats;
|
||||
} while(--n);
|
||||
}
|
||||
|
||||
void shader_frameTime_cycle(float* pFloats,unsigned short n,void* pArgs) {
|
||||
float* period = (float*)pArgs;
|
||||
do {
|
||||
*pFloats = (float)(std::fmod(realFrameTime, double(*period)) / double(*period));
|
||||
++period; ++pFloats;
|
||||
} while(--n);
|
||||
}
|
||||
|
||||
void shader_gameTime_cycle_abs(float* pFloats,unsigned short n,void* pArgs) {
|
||||
float* period = (float*)pArgs;
|
||||
do {
|
||||
*pFloats = (float)std::fmod(frameTime_s, double(*period));
|
||||
++period; ++pFloats;
|
||||
} while(--n);
|
||||
}
|
||||
|
||||
void shader_frameTime_cycle_abs(float* pFloats,unsigned short n,void* pArgs) {
|
||||
float* period = (float*)pArgs;
|
||||
do {
|
||||
*pFloats = (float)std::fmod(realFrameTime, double(*period));
|
||||
++period; ++pFloats;
|
||||
} while(--n);
|
||||
}
|
||||
|
||||
void shader_pixelRatio(float* pFloats,unsigned short n,void* pArgs) {
|
||||
*pFloats = (float)pixelSizeRatio;
|
||||
}
|
||||
|
||||
#ifdef PROFILE_LOCKS
|
||||
double lockProfileTimer = 0.0;
|
||||
bool printLockProfile = false;
|
||||
bool requireObserved = false;
|
||||
#endif
|
||||
|
||||
double menuTickTime = 0, serverTickTime = 0, clientTickTime = 0, animationTime = 0;
|
||||
double* animTimes = nullptr;
|
||||
|
||||
extern double lastLockGlobalUpdate, nextTargetUpdateTime, nextScriptGCTime;
|
||||
|
||||
void resetGameTime() {
|
||||
nextTargetUpdateTime = lastLockGlobalUpdate = devices.driver->getGameTime();
|
||||
frameTime_s = devices.driver->getGameTime() - 0.25;
|
||||
nextScriptGCTime = devices.driver->getFrameTime();
|
||||
}
|
||||
|
||||
void endAnimation() {
|
||||
if(animTimes) {
|
||||
EndAnimationThreads = true;
|
||||
activeAnimCount.wait(0);
|
||||
EndAnimationThreads = false;
|
||||
delete[] animTimes;
|
||||
animTimes = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void idleLoadResources(int maxPriority) {
|
||||
if(devices.library.processTextures(maxPriority, true))
|
||||
return;
|
||||
if(devices.library.processMeshes(maxPriority, 1))
|
||||
return;
|
||||
if(devices.library.processTextures(INT_MIN, true))
|
||||
return;
|
||||
if(devices.library.processMeshes(INT_MIN, 1))
|
||||
return;
|
||||
}
|
||||
|
||||
GameState prev_state = GS_Menu;
|
||||
void tickGlobal(bool hasScripts) {
|
||||
#ifdef TRACE_GC_LOCK
|
||||
devices.scripts.menu->markGCImpossible();
|
||||
devices.scripts.client->markGCImpossible();
|
||||
#endif
|
||||
|
||||
//Get frame timings
|
||||
static int lastRender = 0;
|
||||
|
||||
if(queuedModSwitch) {
|
||||
queuedModSwitch = false;
|
||||
destroyMod();
|
||||
initMods(modSetup);
|
||||
game_state = GS_Menu;
|
||||
}
|
||||
|
||||
if(game_state != prev_state) {
|
||||
if(hasScripts) {
|
||||
if(game_running && devices.scripts.client)
|
||||
devices.scripts.client->stateChange();
|
||||
if(devices.scripts.menu)
|
||||
devices.scripts.menu->stateChange();
|
||||
}
|
||||
prev_state = game_state;
|
||||
}
|
||||
|
||||
++frameNumber;
|
||||
|
||||
double frame_time = devices.driver->getGameTime() - 0.25;
|
||||
frameTime_ms = devices.driver->getTime();
|
||||
|
||||
double real_frame = devices.driver->getFrameTime();
|
||||
realFrameLen = real_frame - realFrameTime;
|
||||
realFrameTime = real_frame;
|
||||
|
||||
int frame_ms = frameTime_ms - lastRender;
|
||||
lastRender = frameTime_ms;
|
||||
|
||||
double frame_s = frame_time - frameTime_s;
|
||||
frameTime_s = frame_time;
|
||||
|
||||
if(frame_ms > 250 || frame_s > 0.25) {
|
||||
frame_ms = 250;
|
||||
frame_s = 0.25;
|
||||
}
|
||||
else if(frame_ms < 0 || frame_s < 0.0) {
|
||||
frame_ms = 0;
|
||||
frame_s = 0.0;
|
||||
}
|
||||
|
||||
frameLen_s = frame_s;
|
||||
frameLen_ms = frame_ms;
|
||||
|
||||
frames.push_back(realFrameLen);
|
||||
if(frames.size() > max_frames)
|
||||
frames.erase(frames.begin());
|
||||
|
||||
//Run network manager tick
|
||||
devices.network->tick(realFrameLen);
|
||||
|
||||
//Do lock profiling
|
||||
#ifdef PROFILE_LOCKS
|
||||
lockProfileTimer += frame_s;
|
||||
if(lockProfileTimer >= 1.0) {
|
||||
lockProfileTimer = 0.0;
|
||||
|
||||
if(printLockProfile) {
|
||||
threads::profileMutexCycle([](threads::Mutex* mtx) {
|
||||
if((mtx->observed || !requireObserved) && mtx->profileCount > 0) {
|
||||
print("%s: %d locks", mtx->name.c_str(), mtx->profileCount);
|
||||
}
|
||||
});
|
||||
threads::profileReadWriteMutexCycle([](threads::ReadWriteMutex* mtx) {
|
||||
if((mtx->observed || !requireObserved) && (mtx->profileReadCount > 0 || mtx->profileWriteCount > 0)) {
|
||||
print("%s: %d read, %d write", mtx->name.c_str(), mtx->profileReadCount, mtx->profileWriteCount);
|
||||
}
|
||||
});
|
||||
|
||||
printLockProfile = false;
|
||||
}
|
||||
else {
|
||||
threads::profileMutexCycle(0);
|
||||
threads::profileReadWriteMutexCycle(0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
idleLoadResources(isPreloading() ? -10 : INT_MIN);
|
||||
|
||||
//Do script GCs
|
||||
#ifdef TRACE_GC_LOCK
|
||||
devices.scripts.menu->markGCPossible();
|
||||
devices.scripts.client->markGCPossible();
|
||||
#endif
|
||||
|
||||
if(hasScripts) {
|
||||
scriptTickSignal.signal(1);
|
||||
processing::queueAction(new RunScriptGC(devices.scripts.menu, fullGC));
|
||||
|
||||
if(game_running) {
|
||||
scriptTickSignal.signalUp(1);
|
||||
processing::queueAction(new RunScriptGC(devices.scripts.client, fullGC));
|
||||
}
|
||||
|
||||
fullGC = false;
|
||||
|
||||
while(!scriptTickSignal.check(0)) {
|
||||
processing::run();
|
||||
threads::sleep(0);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TRACE_GC_LOCK
|
||||
devices.scripts.menu->markGCImpossible();
|
||||
devices.scripts.client->markGCImpossible();
|
||||
#endif
|
||||
|
||||
//Run all the script ticks
|
||||
if(hasScripts) {
|
||||
scriptTickSignal.signal(1);
|
||||
processing::queueAction(new RunScriptTick(devices.scripts.menu, realFrameLen, false, menuTickTime));
|
||||
|
||||
if(game_running) {
|
||||
scriptTickSignal.signalUp(2);
|
||||
processing::queueAction(new RunScriptTick(devices.scripts.server, frameLen_s, true, serverTickTime));
|
||||
processing::queueAction(new RunScriptTick(devices.scripts.client, realFrameLen, false, clientTickTime));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TRACE_GC_LOCK
|
||||
devices.scripts.menu->markGCPossible();
|
||||
devices.scripts.client->markGCPossible();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unordered_map<std::string,time_t> guiModuleTimes;
|
||||
void tickGuiReload() {
|
||||
if(!devices.scripts.client)
|
||||
return;
|
||||
for(auto it = devices.scripts.client->modules.begin(); it != devices.scripts.client->modules.end(); ++it) {
|
||||
scripts::Module& mod = *it->second;
|
||||
scripts::File& fl = *mod.file;
|
||||
|
||||
time_t mtime = getModifiedTime(fl.path);
|
||||
auto f = guiModuleTimes.find(mod.name);
|
||||
if(f == guiModuleTimes.end()) {
|
||||
guiModuleTimes[mod.name] = mtime;
|
||||
}
|
||||
else {
|
||||
if(f->second < mtime)
|
||||
devices.scripts.client->reload(mod.name);
|
||||
guiModuleTimes[mod.name] = mtime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static render::Texture* renderTarget = nullptr;
|
||||
void getFrameRender(Image& img) {
|
||||
if(!game_running || !devices.scripts.client)
|
||||
return;
|
||||
|
||||
auto prev_state = game_state;
|
||||
game_state = GS_Menu;
|
||||
|
||||
devices.scripts.client->preRender(0.0);
|
||||
devices.scripts.client->render(0.0);
|
||||
|
||||
renderTarget->save(img);
|
||||
devices.render->setRenderTarget(nullptr);
|
||||
|
||||
game_state = prev_state;
|
||||
}
|
||||
|
||||
void renderFrame(scripts::Manager* uiScript, scripts::Manager* renderScript) {
|
||||
drawnSteps = 0;
|
||||
bufferFlushes = 0;
|
||||
for(unsigned i = 0; i < render::FC_COUNT; ++i)
|
||||
render::vbFlushCounts[i] = 0;
|
||||
|
||||
destroyQueuedTextures();
|
||||
|
||||
#ifdef TRACE_GC_LOCK
|
||||
devices.scripts.menu->markGCImpossible();
|
||||
devices.scripts.client->markGCImpossible();
|
||||
#endif
|
||||
|
||||
vec2i screenSize(devices.driver->win_width,devices.driver->win_height);
|
||||
bool render = screenSize.width != 0 && screenSize.height != 0;
|
||||
|
||||
if(render) {
|
||||
if(!renderTarget)
|
||||
renderTarget = devices.render->createRenderTarget(screenSize * scale_3d);
|
||||
if(renderTarget->size != screenSize * scale_3d) {
|
||||
delete renderTarget;
|
||||
renderTarget = devices.render->createRenderTarget(screenSize * scale_3d);
|
||||
}
|
||||
}
|
||||
|
||||
devices.sound->setListenerData(vec3f(devices.render->cam_pos), vec3f(), vec3f(devices.render->cam_pos + devices.render->cam_facing), vec3f(devices.render->cam_up));
|
||||
|
||||
if(!Object::GALAXY_CREATION)
|
||||
scene::processNodeEvents();
|
||||
|
||||
if(renderScript)
|
||||
renderScript->preRender(realFrameLen);
|
||||
|
||||
nearestNode = 9.0e35; furthestNode = 0.0;
|
||||
|
||||
static bool dumpAnimTimes = false;
|
||||
if(!animTimes) {
|
||||
animationThreadCount = std::max(1u,devices.driver->getProcessorCount()-1);
|
||||
animTimes = new double[animationThreadCount]();
|
||||
for(unsigned i = 0; i < animationThreadCount; ++i)
|
||||
threads::createThread(animateNodes, &animTimes[i]);
|
||||
#ifdef PROFILE_ANIMATION
|
||||
console.addCommand("anim_times", [](argList& args) {
|
||||
dumpAnimTimes = true;
|
||||
}, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
render::Texture* rt = nullptr;
|
||||
const render::Shader* shad = nullptr;
|
||||
if(fsShader || scale_3d != 1.0) {
|
||||
rt = renderTarget;
|
||||
shad = fsShader;
|
||||
}
|
||||
|
||||
devices.library.reloadWatchedResources();
|
||||
|
||||
if(render) {
|
||||
devices.render->setScreenSize(screenSize.x, screenSize.y);
|
||||
devices.render->setRenderTarget(rt);
|
||||
}
|
||||
|
||||
if(screenSize.y != 0)
|
||||
pixelSizeRatio = (double)screenSize.y / 1024.0;
|
||||
|
||||
double animTotal = 0.0;
|
||||
{
|
||||
int nodeCount = (int)devices.scene->children.size();
|
||||
animProcessed = 0;
|
||||
|
||||
//Animate nodes, signaling the animation threads to work if they get time
|
||||
animIndex = nodeCount-1;
|
||||
unsigned loops = 0;
|
||||
while(true) {
|
||||
bool waiting = false;
|
||||
|
||||
if(!scriptTickSignal.check(0)) {
|
||||
waiting = true;
|
||||
processing::run(true);
|
||||
}
|
||||
if(animProcessed != nodeCount) {
|
||||
waiting = true;
|
||||
animTotal += animateSomeNodes(128);
|
||||
}
|
||||
|
||||
if(!waiting)
|
||||
break;
|
||||
if(++loops % 100 == 0)
|
||||
threads::sleep(0);
|
||||
}
|
||||
|
||||
//Sort the catch-all parent
|
||||
devices.scene->sortChildren();
|
||||
|
||||
//Wait for all animation threads to report in
|
||||
while(!animationSignal.check(0))
|
||||
threads::sleep(0);
|
||||
|
||||
for(unsigned i = 0; i < animationThreadCount; ++i) {
|
||||
double t = animTimes[i];
|
||||
animTotal += t;
|
||||
}
|
||||
#ifdef PROFILE_ANIMATION
|
||||
if(dumpAnimTimes)
|
||||
scene::dumpAnimationProfile();
|
||||
dumpAnimTimes = false;
|
||||
#else
|
||||
(void)dumpAnimTimes;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef TRACE_GC_LOCK
|
||||
devices.scripts.menu->markGCImpossible();
|
||||
devices.scripts.client->markGCImpossible();
|
||||
#endif
|
||||
|
||||
double animation_end = devices.driver->getAccurateTime();
|
||||
animation_s = animTotal;
|
||||
|
||||
auto prevSection = enterSection(NS_Render);
|
||||
|
||||
if(render) {
|
||||
devices.render->setDefaultRenderState();
|
||||
|
||||
//Animation system tells us the near and far distances of everything that gets rendered
|
||||
devices.render->setNearFarPlanes(1.0, furthestNode * 1.1);
|
||||
|
||||
//3D prepare is done by scripts, since it needs the camera
|
||||
devices.render->clearRenderPrepared();
|
||||
if(renderScript)
|
||||
renderScript->render(realFrameLen);
|
||||
|
||||
if(rt) {
|
||||
devices.render->setRenderTarget(0);
|
||||
|
||||
if(devices.render->isRenderPrepared()) {
|
||||
render::RenderState rs;
|
||||
if(shad)
|
||||
rs.shader = shad;
|
||||
else
|
||||
rs.shader = devices.library["Fullscreen"];
|
||||
rs.lighting = false;
|
||||
rs.culling = render::FC_None;
|
||||
rs.depthWrite = false;
|
||||
rs.depthTest = render::DT_NoDepthTest;
|
||||
rs.constant = false;
|
||||
rs.textures[0] = rt;
|
||||
rs.textures[7] = ((render::glFrameBuffer*)rt)->depthTexture; //fuck you framebuffers aren't textures
|
||||
|
||||
auto* buffer = render::VertexBufferTCV::fetch(&rs);
|
||||
auto* verts = buffer->request(1, render::PT_Quads);
|
||||
|
||||
verts[0].set(vec2f(0,0));
|
||||
verts[1].set(vec2f(1,0));
|
||||
verts[2].set(vec2f(1,1));
|
||||
verts[3].set(vec2f(0,1));
|
||||
|
||||
buffer->draw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Check if we should scale the 2D interface
|
||||
if(render && !hide_ui) {
|
||||
static render::Texture* uirt = 0;
|
||||
vec2i uiArea;
|
||||
if(ui_scale != 1.0) {
|
||||
uiArea = vec2d(devices.driver->win_width, devices.driver->win_height) / ui_scale;
|
||||
|
||||
if(uirt == 0) {
|
||||
uirt = devices.render->createRenderTarget(uiArea);
|
||||
}
|
||||
else {
|
||||
if(uirt->size != uiArea) {
|
||||
delete uirt;
|
||||
uirt = devices.render->createRenderTarget(uiArea);
|
||||
}
|
||||
}
|
||||
|
||||
devices.render->setScreenSize(uiArea.x, uiArea.y);
|
||||
devices.render->setRenderTarget(uirt, true);
|
||||
}
|
||||
|
||||
//2D prepare is done before scripts
|
||||
devices.render->prepareRender2D();
|
||||
if(uiScript)
|
||||
uiScript->draw();
|
||||
|
||||
//Scale the 2D interface
|
||||
if(uiArea.x != 0) {
|
||||
devices.render->setScreenSize(screenSize.x, screenSize.y);
|
||||
devices.render->setRenderTarget(0);
|
||||
devices.render->prepareRender2D();
|
||||
|
||||
render::RenderState rs;
|
||||
rs.lighting = false;
|
||||
rs.constant = false;
|
||||
rs.culling = render::FC_None;
|
||||
rs.baseMat = render::MAT_Overlay;
|
||||
rs.shader = devices.library["Fullscreen"];
|
||||
rs.depthWrite = false;
|
||||
rs.depthTest = render::DT_NoDepthTest;
|
||||
rs.filterMin = render::TF_Linear;
|
||||
rs.filterMag = render::TF_Linear;
|
||||
rs.textures[0] = uirt;
|
||||
|
||||
auto* buffer = render::VertexBufferTCV::fetch(&rs);
|
||||
auto* verts = buffer->request(1, render::PT_Quads);
|
||||
|
||||
verts[0].set(vec2f(0,0));
|
||||
verts[1].set(vec2f(1,0));
|
||||
verts[2].set(vec2f(1,1));
|
||||
verts[3].set(vec2f(0,1));
|
||||
|
||||
buffer->draw();
|
||||
}
|
||||
}
|
||||
|
||||
if(render) {
|
||||
if(hide_ui)
|
||||
devices.render->prepareRender2D();
|
||||
console.render(*devices.render);
|
||||
render::renderVertexBuffers();
|
||||
}
|
||||
else {
|
||||
threads::sleep(1);
|
||||
}
|
||||
|
||||
double render_end = devices.driver->getAccurateTime();
|
||||
|
||||
devices.driver->swapBuffers(maxfps && *maxfps > 0 ? 1.0 / *maxfps : 0.0);
|
||||
|
||||
double present_end = devices.driver->getAccurateTime();
|
||||
|
||||
render_s = render_end - animation_end;
|
||||
present_s = present_end - render_end;
|
||||
|
||||
#ifdef TRACE_GC_LOCK
|
||||
devices.scripts.menu->markGCPossible();
|
||||
devices.scripts.client->markGCPossible();
|
||||
#endif
|
||||
|
||||
#ifdef _DEBUG
|
||||
devices.render->reportErrors();
|
||||
#endif
|
||||
|
||||
enterSection(prevSection);
|
||||
}
|
||||
|
||||
void tickConsole() {
|
||||
tickGlobal(false);
|
||||
renderFrame(0, 0);
|
||||
}
|
||||
|
||||
void tickGame() {
|
||||
//General tick
|
||||
tickGlobal();
|
||||
|
||||
if(devices.driver->getGameSpeed() > 0 && !devices.network->isClient) {
|
||||
autosaveTimer += realFrameLen;
|
||||
|
||||
auto* interval = devices.settings.engine.getSetting("dAutosaveMinutes");
|
||||
if(interval && interval->getDouble() >= 1.0 && autosaveTimer > interval->getDouble() * 60.0) {
|
||||
int maxAutosaves = 1;
|
||||
auto* count = devices.settings.engine.getSetting("iAutosaveCount");
|
||||
if(count)
|
||||
maxAutosaves = count->getInteger();
|
||||
std::string prevName;
|
||||
if(maxAutosaves > 1) {
|
||||
for(int index = maxAutosaves; index > 0; --index) {
|
||||
std::string fname = "autosave";
|
||||
if(index != 1)
|
||||
fname += toString(index);
|
||||
fname += ".sr2";
|
||||
fname = path_join(devices.mods.getGlobalProfile("saves"), fname);
|
||||
|
||||
if(fileExists(fname)) {
|
||||
if(index == maxAutosaves)
|
||||
remove(fname.c_str());
|
||||
else
|
||||
rename(fname.c_str(), prevName.c_str());
|
||||
}
|
||||
prevName = fname;
|
||||
}
|
||||
}
|
||||
|
||||
processing::pause();
|
||||
saveGame(path_join(devices.mods.getGlobalProfile("saves"), "autosave.sr2"));
|
||||
processing::resume();
|
||||
|
||||
autosaveTimer = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
if(reload_gui)
|
||||
tickGuiReload();
|
||||
|
||||
renderFrame(devices.scripts.client, devices.scripts.client);
|
||||
inputTick();
|
||||
processing::runIsolation();
|
||||
}
|
||||
|
||||
void tickMenu() {
|
||||
//General tick
|
||||
tickGlobal();
|
||||
renderFrame(devices.scripts.menu, devices.scripts.menu);
|
||||
inputTick();
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, time_t> monitoredFiles;
|
||||
void monitorFile(const std::string& filename) {
|
||||
if(monitor_files)
|
||||
monitoredFiles[filename] = getModifiedTime(filename);
|
||||
}
|
||||
|
||||
bool tickMonitor() {
|
||||
if(isPreloading())
|
||||
return false;
|
||||
|
||||
foreach(it, monitoredFiles) {
|
||||
time_t mtime = getModifiedTime(it->first);
|
||||
if(mtime > it->second) {
|
||||
monitoredFiles.clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
extern double animation_s, render_s, present_s;
|
||||
|
||||
namespace render {
|
||||
class Shader;
|
||||
class Camera;
|
||||
};
|
||||
struct Image;
|
||||
|
||||
extern const render::Shader* fsShader;
|
||||
|
||||
extern double frameLen_s, frameTime_s, realFrameLen;
|
||||
extern int frameTime_ms, frameLen_ms;
|
||||
extern bool reload_gui;
|
||||
|
||||
extern std::vector<double> frames;
|
||||
extern unsigned max_frames;
|
||||
|
||||
enum GameState {
|
||||
GS_Menu,
|
||||
GS_Game,
|
||||
GS_Test_Scripts,
|
||||
GS_Monitor_Scripts,
|
||||
GS_Console_Wait,
|
||||
GS_Quit,
|
||||
GS_Load_Prep,
|
||||
|
||||
GS_COUNT,
|
||||
};
|
||||
|
||||
extern GameState game_state;
|
||||
extern std::string game_locale;
|
||||
extern bool game_running;
|
||||
extern bool hide_ui;
|
||||
extern double ui_scale;
|
||||
|
||||
void resetGameTime();
|
||||
void getFrameRender(Image& img);
|
||||
void tickGlobal(bool hasScripts = true);
|
||||
void tickMenu();
|
||||
void tickGame();
|
||||
void tickConsole();
|
||||
|
||||
void monitorFile(const std::string& filename);
|
||||
bool tickMonitor();
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#define GAME_VERSION 1
|
||||
#define GAME_VERSION_NAME "0.0.1"
|
||||
#define ENGINE_BUILD 1
|
||||
#define SERVER_SCRIPT_BUILD 1
|
||||
#define CLIENT_SCRIPT_BUILD 1
|
||||
#define MENU_SCRIPT_BUILD 1
|
||||
#ifdef _MSC_VER
|
||||
#define ARCH_NAME "W"
|
||||
#ifdef _M_AMD64
|
||||
#define ARCH_BITS 64
|
||||
#else
|
||||
#define ARCH_BITS 32
|
||||
#endif
|
||||
#else
|
||||
#ifdef __APPLE__
|
||||
#define ARCH_NAME "A"
|
||||
#else
|
||||
#define ARCH_NAME "L"
|
||||
#endif
|
||||
#ifdef __amd64__
|
||||
#define ARCH_BITS 64
|
||||
#else
|
||||
#define ARCH_BITS 32
|
||||
#endif
|
||||
#endif
|
||||
#define BUILD_VERSION "DEV"
|
||||
Reference in New Issue
Block a user