Open source Star Ruler 2 source code!
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
#include "as_binding_print.h"
|
||||
|
||||
#include "angelscript.h"
|
||||
#include <fstream>
|
||||
|
||||
using std::endl;
|
||||
|
||||
//Prints all enums, global variables, global functions, and object declarations in the engine to the specified file
|
||||
void printBindings(asIScriptEngine* engine, const char* filename) {
|
||||
//TODO
|
||||
/*std::ofstream file(filename);
|
||||
if(!file.is_open() || !file.good())
|
||||
return;
|
||||
|
||||
file << "Enums:" << endl << "======" << endl << endl;
|
||||
|
||||
for(asUINT i = 0, cnt = engine->GetEnumCount(); i < cnt; ++i) {
|
||||
int id;
|
||||
file << engine->GetEnumByIndex(i, &id) << " {" << endl;
|
||||
|
||||
int prevV = -1;
|
||||
bool showNums = false;
|
||||
for(asUINT v = 0, vCnt = engine->GetEnumValueCount(id); v < vCnt; ++v) {
|
||||
int value;
|
||||
file << "\t" << engine->GetEnumValueByIndex(id, v, &value);
|
||||
|
||||
//Show it like a traditional enum if it starts at 0. If it ever breaks from that, begin to always show the numbers
|
||||
if(!showNums && value == prevV + 1) {
|
||||
file << "," << endl;
|
||||
prevV = value;
|
||||
}
|
||||
else {
|
||||
showNums = true;
|
||||
file << " = " << value << endl;
|
||||
}
|
||||
}
|
||||
|
||||
file << "}" << endl << endl;
|
||||
}
|
||||
|
||||
file << "Globals:" << endl << "========" << endl << endl;
|
||||
|
||||
for(asUINT i = 0, cnt = engine->GetGlobalPropertyCount(); i < cnt; ++i) {
|
||||
const char* name;
|
||||
int typeID;
|
||||
bool isConst;
|
||||
int id = engine->GetGlobalPropertyByIndex(i, &name, 0, &typeID, &isConst);
|
||||
|
||||
file << "\t";
|
||||
if(isConst)
|
||||
file << "const ";
|
||||
file << engine->GetTypeDeclaration(typeID) << " " << name << endl;
|
||||
}
|
||||
|
||||
file << endl << "Global Functions:" << endl << "=================" << endl << endl;
|
||||
|
||||
for(asUINT i = 0, cnt = engine->GetGlobalFunctionCount(); i < cnt; ++i) {
|
||||
asIScriptFunction* func = engine->GetGlobalFunctionByIndex(i);
|
||||
file << func->GetDeclaration() << endl;
|
||||
}
|
||||
|
||||
file << endl << "Classes:" << endl << "========" << endl << endl;
|
||||
|
||||
for(asUINT i = 0, cnt = engine->GetObjectTypeCount(); i < cnt; ++i) {
|
||||
asITypeInfo* obj = engine->GetObjectTypeByIndex(i);
|
||||
file << obj->GetName();
|
||||
if(asITypeInfo* base = obj->GetBaseType())
|
||||
file << " : " << base->GetName();
|
||||
file << " {" << endl;
|
||||
|
||||
for(asUINT p = 0, pCnt = obj->GetPropertyCount(); p < pCnt; ++p) {
|
||||
const char* name;
|
||||
bool isRef;
|
||||
int typeID;
|
||||
obj->GetProperty(p, &name, &typeID, 0, 0, &isRef);
|
||||
|
||||
file << "\t" << engine->GetTypeDeclaration(typeID);
|
||||
if(isRef)
|
||||
file << "@ ";
|
||||
else
|
||||
file << " ";
|
||||
|
||||
file << name << endl;
|
||||
}
|
||||
|
||||
if(obj->GetPropertyCount() != 0 && obj->GetMethodCount() != 0)
|
||||
file << endl;
|
||||
|
||||
for(asUINT m = 0, mCnt = obj->GetMethodCount(); m < mCnt; ++m) {
|
||||
asIScriptFunction* func = obj->GetMethodByIndex(m);
|
||||
|
||||
file << "\t" << func->GetDeclaration(false) << endl;
|
||||
}
|
||||
|
||||
file << "}" << endl << endl;
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class asIScriptEngine;
|
||||
|
||||
void printBindings(asIScriptEngine* engine, const char* filename);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
#include "angelscript.h"
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace assembler {
|
||||
struct CodePage;
|
||||
struct CriticalSection;
|
||||
};
|
||||
|
||||
enum JITSettings {
|
||||
//Should the JIT attempt to suspend? (Slightly faster, but makes suspension very rare if it occurs at all)
|
||||
JIT_NO_SUSPEND = 0x01,
|
||||
//Should the JIT reset the FPU entering System calls? (Slightly faster, may not work on all platforms)
|
||||
JIT_SYSCALL_FPU_NORESET = 0x02,
|
||||
//Should the JIT support error events from System calls? (Faster, but exceptions will generally be ignored, possibly leading to crashes)
|
||||
JIT_SYSCALL_NO_ERRORS = 0x04,
|
||||
//Do allocation/deallocation functions inspect the script context? (Faster, but won't work correctly if you try to get information about the script system during allocations)
|
||||
JIT_ALLOC_SIMPLE = 0x08,
|
||||
//Fall back to AngelScript to perform switch logic? (Slower, but uses less memory)
|
||||
JIT_NO_SWITCHES = 0x10,
|
||||
//Fall back to AngelScript to perform script calls
|
||||
// Slower, but can be used as a temporary workaround for angelscript changes
|
||||
JIT_NO_SCRIPT_CALLS = 0x20,
|
||||
//Make calling reference counting functions faster in common situations
|
||||
// Reference counting functions which access the script context will produce undefined results
|
||||
JIT_FAST_REFCOUNT = 0x40,
|
||||
};
|
||||
|
||||
class asCJITCompiler : public asIJITCompiler {
|
||||
assembler::CodePage* activePage;
|
||||
std::multimap<asJITFunction,assembler::CodePage*> pages;
|
||||
|
||||
assembler::CriticalSection* lock;
|
||||
|
||||
unsigned flags;
|
||||
|
||||
std::multimap<asJITFunction,unsigned char**> jumpTables;
|
||||
unsigned char** activeJumpTable;
|
||||
unsigned currentTableSize;
|
||||
|
||||
struct DeferredCodePointer {
|
||||
void** jitFunction;
|
||||
void** jitEntry;
|
||||
};
|
||||
std::multimap<asIScriptFunction*,DeferredCodePointer> deferredPointers;
|
||||
public:
|
||||
asCJITCompiler(unsigned Flags = 0);
|
||||
~asCJITCompiler();
|
||||
int CompileFunction(asIScriptFunction *function, asJITFunction *output);
|
||||
void ReleaseJITFunction(asJITFunction func);
|
||||
void finalizePages();
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
#include <GL/glew.h>
|
||||
#include "GLFW/glfw3.h"
|
||||
@@ -0,0 +1,7 @@
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#define PREFETCH(x) _mm_prefetch(x, _MM_HINT_T0)
|
||||
#endif
|
||||
#ifdef __GNUC__
|
||||
#define PREFETCH(x) __builtin_prefetch(x, 0, 3)
|
||||
#endif
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
#if defined(_MSC_VER)
|
||||
#ifdef _DEBUG
|
||||
#define NO_DEFAULT default: throw 0;
|
||||
#define UNREACHABLE throw 0;
|
||||
#else
|
||||
#define NO_DEFAULT default: __assume(0);
|
||||
#define UNREACHABLE __assume(0);
|
||||
#endif
|
||||
#else
|
||||
#ifdef _DEBUG
|
||||
#define NO_DEFAULT default: throw 0;
|
||||
#define UNREACHABLE throw 0;
|
||||
#else
|
||||
#define NO_DEFAULT default: __builtin_unreachable();
|
||||
#define UNREACHABLE __builtin_unreachable();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define foreach(var, cont) for(auto var = cont.begin(), end = cont.end(); var != end; ++var)
|
||||
|
||||
#ifdef WIN_MODE
|
||||
#define unsigned_enum(name) enum name : unsigned
|
||||
#else
|
||||
#ifdef LIN_MODE
|
||||
#define unsigned_enum(name) enum name
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define umap std::unordered_map
|
||||
#define uset std::unordered_set
|
||||
|
||||
#define INIT_FUNC(name) namespace __init__##name { struct init { init()
|
||||
|
||||
#define INIT_FUNC_END } v; };
|
||||
|
||||
#define INIT_VAR(var) var; namespace __init__##var { struct init { init()
|
||||
|
||||
#define INIT_VAR_END } v; };
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifdef WIN_MODE
|
||||
#include <regex>
|
||||
typedef std::regex regex;
|
||||
typedef std::match_results<std::string::const_iterator> reg_result;
|
||||
|
||||
#define reg_compile(r, c)\
|
||||
static regex r = std::regex(c)
|
||||
|
||||
#define reg_match(str, m, r)\
|
||||
std::regex_match(str.cbegin(), str.cend(), m, r)
|
||||
|
||||
#define reg_str(str, m, i)\
|
||||
m[i]
|
||||
|
||||
#else
|
||||
#include <regex.h>
|
||||
typedef regex_t* regex;
|
||||
typedef regmatch_t reg_result[16];
|
||||
|
||||
#define reg_compile(r, c) \
|
||||
static regex r = 0;\
|
||||
if(!r) {\
|
||||
r = new regex_t();\
|
||||
regcomp(r, c, REG_EXTENDED);\
|
||||
}
|
||||
|
||||
#define reg_match(str, m, r)\
|
||||
!regexec(r, str.c_str(), 16, m, 0)
|
||||
|
||||
#define reg_str(str, m, i)\
|
||||
str.substr(m[i].rm_so, m[i].rm_eo - m[i].rm_so)
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,164 @@
|
||||
#pragma once
|
||||
#include "design/subsystem.h"
|
||||
#include "design/hull.h"
|
||||
#include "util/refcount.h"
|
||||
#include "image.h"
|
||||
#include <unordered_set>
|
||||
|
||||
class SaveFile;
|
||||
|
||||
namespace net {
|
||||
struct Message;
|
||||
};
|
||||
|
||||
struct DesignClass {
|
||||
unsigned id;
|
||||
std::string name;
|
||||
std::vector<const Design*> designs;
|
||||
};
|
||||
|
||||
struct DesignError {
|
||||
bool fatal;
|
||||
std::string text;
|
||||
const Subsystem* subsys;
|
||||
const SubsystemDef::ModuleDesc* module;
|
||||
vec2i hex;
|
||||
|
||||
DesignError(bool Fatal, std::string Text,
|
||||
const Subsystem* Subsys = 0, const SubsystemDef::ModuleDesc* Module = 0,
|
||||
vec2i Hex = vec2i(-1, -1))
|
||||
: fatal(Fatal), text(Text), subsys(Subsys), module(Module), hex(Hex) {
|
||||
}
|
||||
};
|
||||
|
||||
class Empire;
|
||||
class Design : public AtomicRefCounted {
|
||||
public:
|
||||
//Metadata
|
||||
const HullDef* hull;
|
||||
std::string name;
|
||||
double size;
|
||||
double hexSize;
|
||||
unsigned interiorHexes;
|
||||
unsigned exteriorHexes;
|
||||
unsigned stateCount;
|
||||
unsigned effectorStateCount;
|
||||
unsigned effectorCount;
|
||||
unsigned dataCount;
|
||||
bool initialized;
|
||||
double totalHP;
|
||||
double quadrantTotalHP[4];
|
||||
mutable bool outdated;
|
||||
Color color;
|
||||
Color dullColor;
|
||||
|
||||
//Errors
|
||||
std::vector<DesignError> errors;
|
||||
bool hasFatalErrors() const;
|
||||
bool hasTag(const std::string& tag) const;
|
||||
|
||||
std::unordered_set<int> numTags;
|
||||
bool hasTag(int index) const;
|
||||
|
||||
//Subsystems
|
||||
std::vector<Subsystem> subsystems;
|
||||
std::vector<Subsystem*> damageOrder;
|
||||
std::vector<SubsystemDef::ShipModifier> modifiers;
|
||||
HexGrid<int> grid;
|
||||
HexGrid<int> hexIndex;
|
||||
HexGrid<int> hexStatusIndex;
|
||||
std::vector<vec2u> hexes;
|
||||
std::unordered_set<uint64_t> errorHexes;
|
||||
unsigned usedHexCount;
|
||||
float* shipVariables;
|
||||
vec2u cropMin;
|
||||
vec2u cropMax;
|
||||
|
||||
//Ownership and usage data
|
||||
mutable unsigned id;
|
||||
mutable Empire* owner;
|
||||
mutable bool used;
|
||||
mutable bool obsolete;
|
||||
mutable int revision;
|
||||
mutable threads::atomic_int built;
|
||||
mutable threads::atomic_int active;
|
||||
mutable render::Sprite icon;
|
||||
mutable render::Sprite distantIcon;
|
||||
mutable render::Sprite fleetIcon;
|
||||
bool forceHull;
|
||||
|
||||
mutable net::Message* data;
|
||||
mutable asIScriptObject* clientData;
|
||||
mutable asIScriptObject* serverData;
|
||||
|
||||
//Manual updates bump revision numbers and
|
||||
//use these 'horizontal' lists.
|
||||
mutable heldPointer<const Design> newer;
|
||||
const Design* newest() const;
|
||||
const Design* next() const;
|
||||
|
||||
//Automatic updates do not bump revisions and
|
||||
//use this 'vertical' list of designs.
|
||||
mutable heldPointer<const Design> original;
|
||||
mutable heldPointer<const Design> updated;
|
||||
const Design* mostUpdated() const;
|
||||
const Design* base() const;
|
||||
|
||||
mutable DesignClass* cls;
|
||||
|
||||
//Designs are created via descriptors
|
||||
struct Descriptor {
|
||||
struct System {
|
||||
const SubsystemDef* type;
|
||||
vec3d direction;
|
||||
std::vector<vec2u> hexes;
|
||||
std::vector<const SubsystemDef::ModuleDesc*> modules;
|
||||
|
||||
System() : type(nullptr), direction(vec3d::front()) {
|
||||
}
|
||||
};
|
||||
|
||||
Empire* owner;
|
||||
const HullDef* hull;
|
||||
std::string name;
|
||||
std::string className;
|
||||
std::string hullName;
|
||||
double size;
|
||||
vec2u gridSize;
|
||||
std::vector<Descriptor::System> systems;
|
||||
std::vector<const SubsystemDef*> appliedSystems;
|
||||
bool staticHull;
|
||||
bool forceHull;
|
||||
asIScriptObject* settings;
|
||||
|
||||
Descriptor() : owner(0), hull(0), size(1), staticHull(false), forceHull(false), gridSize(1,1), settings(nullptr) {
|
||||
}
|
||||
|
||||
~Descriptor() {
|
||||
if(hull)
|
||||
hull->drop();
|
||||
if(settings)
|
||||
settings->Release();
|
||||
}
|
||||
};
|
||||
|
||||
Design(const Design::Descriptor& desc);
|
||||
Design(net::Message& msg);
|
||||
Design(SaveFile& file);
|
||||
Design();
|
||||
~Design();
|
||||
|
||||
void init(net::Message& msg);
|
||||
void init(const Design::Descriptor& desc);
|
||||
void write(net::Message& msg) const;
|
||||
void save(SaveFile& file) const;
|
||||
void toDescriptor(Design::Descriptor& desc) const;
|
||||
void makeDistanceMap(Image& img, vec2i pos, vec2i size) const;
|
||||
void bindData();
|
||||
void buildDamageOrder();
|
||||
|
||||
unsigned getQuadrant(const vec2u& pos) const;
|
||||
|
||||
void writeData(net::Message& msg) const;
|
||||
void initData(net::Message& msg);
|
||||
};
|
||||
@@ -0,0 +1,378 @@
|
||||
#include "design/effect.h"
|
||||
#include "compat/misc.h"
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
#include "network/message.h"
|
||||
#include "str_util.h"
|
||||
#include <unordered_map>
|
||||
|
||||
static std::vector<EffectDef*> effectDefinitions;
|
||||
static umap<std::string, int> effectIndices;
|
||||
|
||||
static const char* callback_retval[EH_COUNT] = {
|
||||
"void",
|
||||
"void",
|
||||
"void",
|
||||
"void",
|
||||
"void",
|
||||
"void",
|
||||
"void",
|
||||
"DamageEventStatus",
|
||||
"DamageEventStatus",
|
||||
"void",
|
||||
"void",
|
||||
"void",
|
||||
"void",
|
||||
"void",
|
||||
};
|
||||
|
||||
static const char* callback_basedecl[EH_COUNT] = {
|
||||
"Event&",
|
||||
"Event&",
|
||||
"Event&",
|
||||
"Event&",
|
||||
"Event&",
|
||||
"Event&",
|
||||
"Event&",
|
||||
"DamageEvent&, const vec2u&",
|
||||
"DamageEvent&, vec2u&, vec2d&",
|
||||
"Event&",
|
||||
"Event&",
|
||||
"Event&, Empire@, Empire@",
|
||||
"Event&",
|
||||
"Event&",
|
||||
};
|
||||
|
||||
void clearEffectDefinitions() {
|
||||
effectIndices.clear();
|
||||
foreach(it, effectDefinitions)
|
||||
delete *it;
|
||||
effectDefinitions.clear();
|
||||
}
|
||||
|
||||
void loadEffectDefinitions(const std::string& filename) {
|
||||
EffectDef* def = 0;
|
||||
|
||||
DataHandler datahandler;
|
||||
|
||||
datahandler("Effect", [&](std::string& value) {
|
||||
auto it = effectIndices.find(value);
|
||||
if(it != effectIndices.end()) {
|
||||
def = 0;
|
||||
error("Error: Duplicate effect %s.", value.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
def = new EffectDef();
|
||||
def->name = value;
|
||||
def->id = (unsigned)effectDefinitions.size();
|
||||
def->valueCount = 0;
|
||||
|
||||
effectDefinitions.push_back(def);
|
||||
effectIndices[def->name] = def->id;
|
||||
});
|
||||
|
||||
datahandler("Value", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
|
||||
if(def->valueCount >= EFFECT_MAX_VALUES) {
|
||||
error("(Effect %s): Error: Maximum of 6 values allowed per effect.", def->name.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
EffectDef::ValueDesc desc;
|
||||
auto pos = value.find('=');
|
||||
if(pos != std::string::npos) {
|
||||
if(pos < value.size() - 1)
|
||||
desc.defaultValue = Formula::fromInfix(value.substr(pos+1).c_str());
|
||||
value = trim(value.substr(0, pos));
|
||||
}
|
||||
|
||||
auto it = def->valueNames.find(value);
|
||||
if(it != def->valueNames.end()) {
|
||||
error("(Effect %s): Error: Duplicate effect value %s.", def->name.c_str(), value.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
def->valueNames[value] = def->valueCount;
|
||||
def->values.push_back(desc);
|
||||
++def->valueCount;
|
||||
});
|
||||
|
||||
datahandler("Start", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_Start] = value;
|
||||
});
|
||||
|
||||
datahandler("Destroy", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_Destroy] = value;
|
||||
});
|
||||
|
||||
datahandler("End", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_End] = value;
|
||||
});
|
||||
|
||||
datahandler("Suspend", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_Suspend] = value;
|
||||
});
|
||||
|
||||
datahandler("Continue", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_Continue] = value;
|
||||
});
|
||||
|
||||
datahandler("Change", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_Change] = value;
|
||||
});
|
||||
|
||||
datahandler("Damage", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_Damage] = value;
|
||||
});
|
||||
|
||||
datahandler("Tick", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_Tick] = value;
|
||||
});
|
||||
|
||||
datahandler("GlobalDamage", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_GlobalDamage] = value;
|
||||
});
|
||||
|
||||
datahandler("RetrofitPre", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_Retrofit_Pre] = value;
|
||||
});
|
||||
|
||||
datahandler("RetrofitPost", [&](std::string& value) {
|
||||
if(def)
|
||||
def->hookDefinitions[EH_Retrofit_Post] = value;
|
||||
});
|
||||
|
||||
datahandler.read(filename);
|
||||
}
|
||||
|
||||
EffectDef::EffectDef() : id(-1), valueCount(0), hooks() {
|
||||
}
|
||||
|
||||
void EffectDef::setHook(EffectHook hook, const std::string& ref) {
|
||||
std::vector<std::string> args;
|
||||
split(ref, args, "::");
|
||||
|
||||
if(args.size() != 2) {
|
||||
error("(Effect %s): Error: Invalid script function reference.", name.c_str());
|
||||
hooks[hook] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
//Find the module the function is in
|
||||
scripts::Module* modu = devices.scripts.server->getModule(args[0].c_str());
|
||||
if(!modu) {
|
||||
error("(Effect %s): Error: Invalid script module '%s'.", name.c_str(), args[0].c_str());
|
||||
hooks[hook] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
//Build declaration for function
|
||||
std::string def;
|
||||
def = callback_retval[hook];
|
||||
def += " "+args[1]+"(";
|
||||
def += callback_basedecl[hook];
|
||||
for(unsigned i = 0; i < valueCount; ++i) {
|
||||
def += ",double";
|
||||
}
|
||||
def += ")";
|
||||
|
||||
//Find the function
|
||||
asIScriptFunction* func = modu->getFunction(def.c_str());
|
||||
if(!func) {
|
||||
error("(Effect %s): Error: Could not find script function '%s'.", name.c_str(), def.c_str());
|
||||
hooks[hook] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
hooks[hook] = func;
|
||||
}
|
||||
|
||||
Effect::Effect() : type(0) {
|
||||
}
|
||||
|
||||
Effect::Effect(const EffectDef* Type) : type(Type) {
|
||||
}
|
||||
|
||||
void Effect::call(EffectHook hook, EffectEvent& event) const {
|
||||
if(!type)
|
||||
return;
|
||||
auto* func = type->hooks[hook];
|
||||
if(!func)
|
||||
return;
|
||||
scripts::Call cl = devices.scripts.server->call(func);
|
||||
if(cl.ctx) {
|
||||
cl.push((void*)&event);
|
||||
for(unsigned i = 0; i < type->valueCount; ++i)
|
||||
cl.push(values[i]);
|
||||
cl.call();
|
||||
}
|
||||
}
|
||||
|
||||
DamageEventStatus Effect::damage(DamageEvent& event, const vec2u& position) const {
|
||||
if(!type || type->hooks[EH_Damage] == nullptr)
|
||||
return DE_Continue;
|
||||
scripts::Call cl = devices.scripts.server->call(type->hooks[EH_Damage]);
|
||||
unsigned status = DE_Continue;
|
||||
if(cl.ctx) {
|
||||
cl.push((void*)&event);
|
||||
cl.push((void*)&position);
|
||||
for(unsigned i = 0; i < type->valueCount; ++i)
|
||||
cl.push(values[i]);
|
||||
cl.call(status);
|
||||
}
|
||||
return (DamageEventStatus)status;
|
||||
}
|
||||
|
||||
void Effect::ownerChange(EffectEvent& event, Empire* prevEmpire, Empire* newEmpire) const {
|
||||
if(!type || type->hooks[EH_Owner_Change] == nullptr)
|
||||
return;
|
||||
scripts::Call cl = devices.scripts.server->call(type->hooks[EH_Owner_Change]);
|
||||
if(cl.ctx) {
|
||||
cl.push((void*)&event);
|
||||
cl.push((void*)prevEmpire);
|
||||
cl.push((void*)newEmpire);
|
||||
for(unsigned i = 0; i < type->valueCount; ++i)
|
||||
cl.push(values[i]);
|
||||
cl.call();
|
||||
}
|
||||
}
|
||||
|
||||
DamageEventStatus Effect::globalDamage(DamageEvent& event, vec2u& position, vec2d& endPoint) const {
|
||||
if(!type || type->hooks[EH_GlobalDamage] == nullptr)
|
||||
return DE_Continue;
|
||||
scripts::Call cl = devices.scripts.server->call(type->hooks[EH_GlobalDamage]);
|
||||
unsigned status = DE_Continue;
|
||||
if(cl.ctx) {
|
||||
cl.push((void*)&event);
|
||||
cl.push((void*)&position);
|
||||
cl.push((void*)&endPoint);
|
||||
for(unsigned i = 0; i < type->valueCount; ++i)
|
||||
cl.push(values[i]);
|
||||
cl.call(status);
|
||||
}
|
||||
return (DamageEventStatus)status;
|
||||
}
|
||||
|
||||
void Effect::writeData(net::Message& msg) const {
|
||||
if(type) {
|
||||
msg.write1();
|
||||
msg.writeLimited(type->id, (unsigned)effectDefinitions.size()-1);
|
||||
}
|
||||
else {
|
||||
msg.write0();
|
||||
}
|
||||
for(size_t i = 0; i < EFFECT_MAX_VALUES; ++i)
|
||||
msg << (float)values[i];
|
||||
}
|
||||
|
||||
void Effect::readData(net::Message& msg) {
|
||||
if(msg.readBit()) {
|
||||
unsigned typeId = msg.readLimited((unsigned)effectDefinitions.size()-1);
|
||||
type = getEffectDefinition(typeId);
|
||||
}
|
||||
else {
|
||||
type = nullptr;
|
||||
}
|
||||
for(size_t i = 0; i < EFFECT_MAX_VALUES; ++i) {
|
||||
float v;
|
||||
msg >> v;
|
||||
values[i] = v;
|
||||
}
|
||||
}
|
||||
|
||||
TimedEffect::TimedEffect()
|
||||
: remaining(0.0) {
|
||||
}
|
||||
|
||||
TimedEffect::TimedEffect(const TimedEffect& other)
|
||||
: remaining(other.remaining) {
|
||||
event = other.event;
|
||||
effect = other.effect;
|
||||
}
|
||||
|
||||
TimedEffect::~TimedEffect() {
|
||||
}
|
||||
|
||||
TimedEffect::TimedEffect(const EffectDef* Type, double Time)
|
||||
: effect(Type), remaining(Time) {
|
||||
}
|
||||
|
||||
TimedEffect::TimedEffect(const Effect& Effect, double Time)
|
||||
: remaining(Time) {
|
||||
effect = Effect;
|
||||
}
|
||||
|
||||
void TimedEffect::call(EffectHook hook) {
|
||||
effect.call(hook, event);
|
||||
}
|
||||
|
||||
void TimedEffect::tick(double time) {
|
||||
event.time = time;
|
||||
effect.call(EH_Tick, event);
|
||||
|
||||
if(event.status != ES_Suspended) {
|
||||
remaining -= time;
|
||||
|
||||
if(remaining <= 0.0)
|
||||
event.status = ES_Ended;
|
||||
}
|
||||
}
|
||||
|
||||
EffectEvent::EffectEvent()
|
||||
: time(0.0), efficiency(1.f), partiality(1.f), source(-1), destination(-1), obj(0),
|
||||
target(0), status(ES_Active), custom1(0.f), custom2(0.f) {
|
||||
}
|
||||
|
||||
EffectEvent::~EffectEvent() {
|
||||
}
|
||||
|
||||
DamageEvent::DamageEvent()
|
||||
: damage(0.0), pierce(0.f), partiality(1.f), flags(0), source(-1), destination(-1), obj(0),
|
||||
target(0), custom1(0), custom2(0), spillable(true) {
|
||||
}
|
||||
|
||||
DamageEvent::~DamageEvent() {
|
||||
}
|
||||
|
||||
const EffectDef* getEffectDefinition(const std::string& name) {
|
||||
auto it = effectIndices.find(name);
|
||||
if(it == effectIndices.end())
|
||||
return 0;
|
||||
return effectDefinitions[it->second];
|
||||
}
|
||||
|
||||
const EffectDef* getEffectDefinition(int index) {
|
||||
if(index < 0 || index >= (int)effectDefinitions.size())
|
||||
return 0;
|
||||
return effectDefinitions[index];
|
||||
}
|
||||
|
||||
unsigned getEffectDefinitionCount() {
|
||||
return effectDefinitions.size();
|
||||
}
|
||||
|
||||
void enumerateEffectDefinitions(void (*cb)(const std::string&,int)) {
|
||||
foreach(it, effectDefinitions)
|
||||
cb((*it)->name, (*it)->id);
|
||||
}
|
||||
|
||||
void bindEffectHooks() {
|
||||
foreach(it, effectDefinitions) {
|
||||
foreach(h, (*it)->hookDefinitions) {
|
||||
(*it)->setHook(h->first, h->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
#pragma once
|
||||
#include "util/formula.h"
|
||||
#include "compat/misc.h"
|
||||
#include "obj/object.h"
|
||||
#include "util/refcount.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
#ifndef EFFECT_MAX_VALUES
|
||||
#define EFFECT_MAX_VALUES 6
|
||||
#endif
|
||||
|
||||
class asIScriptFunction;
|
||||
namespace net {
|
||||
struct Message;
|
||||
};
|
||||
|
||||
enum EffectHook {
|
||||
EH_Start,
|
||||
EH_Tick,
|
||||
EH_Suspend,
|
||||
EH_Continue,
|
||||
EH_Destroy,
|
||||
EH_End,
|
||||
EH_Change,
|
||||
EH_Damage,
|
||||
EH_GlobalDamage,
|
||||
EH_Retrofit_Pre,
|
||||
EH_Retrofit_Post,
|
||||
EH_Owner_Change,
|
||||
EH_Save,
|
||||
EH_Load,
|
||||
EH_COUNT
|
||||
};
|
||||
|
||||
class EffectDef {
|
||||
public:
|
||||
std::string name;
|
||||
int id;
|
||||
|
||||
umap<std::string, unsigned> valueNames;
|
||||
unsigned valueCount;
|
||||
struct ValueDesc {
|
||||
Formula* defaultValue;
|
||||
|
||||
ValueDesc() : defaultValue(nullptr) {}
|
||||
};
|
||||
std::vector<ValueDesc> values;
|
||||
|
||||
std::map<EffectHook, std::string> hookDefinitions;
|
||||
asIScriptFunction* hooks[EH_COUNT];
|
||||
void setHook(EffectHook hook, const std::string& ref);
|
||||
|
||||
EffectDef();
|
||||
};
|
||||
|
||||
enum EffectStatus {
|
||||
ES_Active,
|
||||
ES_Suspended,
|
||||
ES_Ended,
|
||||
};
|
||||
|
||||
class Subsystem;
|
||||
class EffectEvent {
|
||||
public:
|
||||
vec3d impact;
|
||||
vec2d direction;
|
||||
heldPointer<Object> obj;
|
||||
heldPointer<Object> target;
|
||||
double time;
|
||||
float efficiency;
|
||||
float partiality;
|
||||
int source;
|
||||
int destination;
|
||||
float custom1;
|
||||
float custom2;
|
||||
EffectStatus status;
|
||||
|
||||
EffectEvent();
|
||||
~EffectEvent();
|
||||
};
|
||||
|
||||
enum DamageEventStatus {
|
||||
DE_Continue,
|
||||
DE_SkipHex,
|
||||
DE_EndDamage,
|
||||
};
|
||||
|
||||
class DamageEvent {
|
||||
public:
|
||||
vec3d impact;
|
||||
double damage;
|
||||
float pierce;
|
||||
float partiality;
|
||||
float custom1;
|
||||
float custom2;
|
||||
unsigned flags;
|
||||
bool spillable;
|
||||
|
||||
int source;
|
||||
int destination;
|
||||
heldPointer<Object> obj;
|
||||
heldPointer<Object> target;
|
||||
|
||||
DamageEvent();
|
||||
~DamageEvent();
|
||||
};
|
||||
|
||||
class Effect {
|
||||
public:
|
||||
const EffectDef* type;
|
||||
double values[EFFECT_MAX_VALUES];
|
||||
|
||||
Effect();
|
||||
Effect(const EffectDef* Type);
|
||||
|
||||
void call(EffectHook hook, EffectEvent& event) const;
|
||||
DamageEventStatus damage(DamageEvent& event, const vec2u& position) const;
|
||||
DamageEventStatus globalDamage(DamageEvent& event, vec2u& position, vec2d& endPoint) const;
|
||||
void ownerChange(EffectEvent& event, Empire* prevEmpire, Empire* newEmpire) const;
|
||||
|
||||
void writeData(net::Message& msg) const;
|
||||
void readData(net::Message& msg);
|
||||
};
|
||||
|
||||
class TimedEffect {
|
||||
public:
|
||||
EffectEvent event;
|
||||
Effect effect;
|
||||
double remaining;
|
||||
|
||||
TimedEffect();
|
||||
TimedEffect(const TimedEffect& other);
|
||||
TimedEffect(const EffectDef* Type, double Time);
|
||||
TimedEffect(const Effect& Effect, double Time);
|
||||
~TimedEffect();
|
||||
void call(EffectHook hook);
|
||||
void tick(double time);
|
||||
};
|
||||
|
||||
void loadEffectDefinitions(const std::string& filename);
|
||||
const EffectDef* getEffectDefinition(const std::string& name);
|
||||
const EffectDef* getEffectDefinition(int index);
|
||||
unsigned getEffectDefinitionCount();
|
||||
void enumerateEffectDefinitions(void (*cb)(const std::string&,int));
|
||||
void bindEffectHooks();
|
||||
void clearEffectDefinitions();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,261 @@
|
||||
#pragma once
|
||||
#include "util/formula.h"
|
||||
#include "scripts/manager.h"
|
||||
#include "design/effect.h"
|
||||
#include "render/render_state.h"
|
||||
#include "threads.h"
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
class Object;
|
||||
class Effector;
|
||||
class Design;
|
||||
struct EffectorTarget;
|
||||
class HullDef;
|
||||
|
||||
namespace net {
|
||||
struct Message;
|
||||
};
|
||||
|
||||
namespace resource {
|
||||
class Sound;
|
||||
};
|
||||
|
||||
namespace scene {
|
||||
class Node;
|
||||
struct ParticleSystemDesc;
|
||||
};
|
||||
|
||||
typedef Object*(*nativeTargetAlgorithm)(const Effector*, Object*, EffectorTarget* targ);
|
||||
struct TargetAlgorithm {
|
||||
union {
|
||||
nativeTargetAlgorithm native;
|
||||
asIScriptFunction* script;
|
||||
};
|
||||
bool isNative;
|
||||
};
|
||||
|
||||
typedef double(*nativeTargetWeighter)(const Effector*, Object*, Object*, void*);
|
||||
struct TargetWeighter {
|
||||
union {
|
||||
nativeTargetWeighter native;
|
||||
asIScriptFunction* script;
|
||||
};
|
||||
bool isNative;
|
||||
void* arg;
|
||||
|
||||
TargetWeighter() {
|
||||
script = 0;
|
||||
isNative = false;
|
||||
arg = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct EffectorTarget;
|
||||
|
||||
enum EffectorActivationType {
|
||||
EAT_Inactive,
|
||||
EAT_Activate,
|
||||
EAT_Repeat
|
||||
};
|
||||
|
||||
typedef EffectorActivationType(*nativeEffectorActivation)(const Effector*, Object*, EffectorTarget& targ, double&, double*, double*);
|
||||
struct EffectorActivation {
|
||||
union {
|
||||
nativeEffectorActivation native;
|
||||
asIScriptFunction* script;
|
||||
};
|
||||
bool isNative;
|
||||
};
|
||||
|
||||
enum EffectorPhysicalType {
|
||||
EPT_Instant,
|
||||
EPT_Projectile,
|
||||
EPT_Missile,
|
||||
EPT_AimedMissile,
|
||||
EPT_Beam,
|
||||
};
|
||||
|
||||
enum EffectorGraphicType {
|
||||
EGT_Sprite,
|
||||
EGT_Beam,
|
||||
EGT_Line
|
||||
};
|
||||
|
||||
enum EffectorEfficiencyMode {
|
||||
EEM_Normal,
|
||||
EEM_Reload_Partial,
|
||||
EEM_Duration_Partial,
|
||||
EEM_Reload,
|
||||
EEM_Duration,
|
||||
};
|
||||
|
||||
struct EffectorSkin {
|
||||
EffectorGraphicType graphicType;
|
||||
double graphicSize;
|
||||
double length;
|
||||
std::string trailMatID;
|
||||
const render::RenderState* trailMat;
|
||||
Color trailStart, trailEnd, color;
|
||||
|
||||
std::string def_material;
|
||||
const render::RenderState* material;
|
||||
|
||||
std::string def_impact;
|
||||
const scene::ParticleSystemDesc* impact;
|
||||
|
||||
float fire_pitch_variance;
|
||||
std::vector<std::string> fire_sound_names;
|
||||
std::vector<const resource::Sound*> fire_sounds;
|
||||
|
||||
std::string def_impact_sound;
|
||||
const resource::Sound* impact_sound;
|
||||
|
||||
EffectorSkin();
|
||||
};
|
||||
|
||||
class EffectorDef {
|
||||
public:
|
||||
unsigned index;
|
||||
std::string name;
|
||||
std::unordered_map<std::string, unsigned> valueNames;
|
||||
|
||||
Formula* range, *lifetime, *tracking, *speed, *spread, *capTarget;
|
||||
Formula* fireArc, *targetTolerance, *fireTolerance;
|
||||
|
||||
std::string def_algorithm;
|
||||
std::string def_activation;
|
||||
std::string def_canTarget;
|
||||
std::string def_autoTarget;
|
||||
std::string def_onTrigger;
|
||||
|
||||
TargetAlgorithm algorithm;
|
||||
EffectorActivation activation;
|
||||
asIScriptFunction* onTrigger;
|
||||
|
||||
std::vector<TargetWeighter> canTargetWeighters;
|
||||
Formula* canTarget;
|
||||
std::vector<TargetWeighter> autoTargetWeighters;
|
||||
Formula* autoTarget;
|
||||
|
||||
//Whether the projectile should hit a target according to physical behaviors, or only the chosen target
|
||||
bool physicalImpact;
|
||||
bool passthroughInvalid;
|
||||
|
||||
bool pierces;
|
||||
float recoverTime;
|
||||
|
||||
EffectorEfficiencyMode efficiencyMode;
|
||||
EffectorPhysicalType physicalType;
|
||||
double physicalSize;
|
||||
|
||||
std::unordered_map<std::string, unsigned> skinNames;
|
||||
|
||||
unsigned valueCount;
|
||||
struct ValueDesc {
|
||||
Formula* defaultValue;
|
||||
|
||||
ValueDesc() : defaultValue(nullptr) {}
|
||||
};
|
||||
std::vector<ValueDesc> values;
|
||||
|
||||
unsigned stateCount;
|
||||
std::vector<Formula*> arguments;
|
||||
std::vector<Formula*> triggerArguments;
|
||||
|
||||
const EffectDef* effect;
|
||||
std::vector<Formula*> effectValues;
|
||||
|
||||
std::vector<EffectorSkin> skins;
|
||||
|
||||
void triggerGraphics(Object* obj, EffectorTarget& targ, const Effector* effector, double* time = 0, vec2d* direction = 0, float efficiency = 1.f, double tOffset = 0) const;
|
||||
EffectorDef();
|
||||
};
|
||||
|
||||
enum TargetFlags {
|
||||
TF_Target = 0,
|
||||
TF_Preference = 0x1,
|
||||
TF_Group = 0x2,
|
||||
TF_Firing = 0x4,
|
||||
TF_Retarget = 0x8,
|
||||
TF_TrackingProgress = 0x10,
|
||||
TF_ClearTracking = 0x20,
|
||||
TF_WithinFireTolerance = 0x40,
|
||||
};
|
||||
|
||||
struct EffectorTarget {
|
||||
Object* target;
|
||||
unsigned flags;
|
||||
unsigned char hits;
|
||||
vec3d tracking;
|
||||
};
|
||||
|
||||
class Effector {
|
||||
mutable threads::atomic_int refs;
|
||||
public:
|
||||
const Design* inDesign;
|
||||
unsigned subsysIndex;
|
||||
unsigned effectorIndex;
|
||||
mutable unsigned effectorId;
|
||||
|
||||
unsigned skinIndex;
|
||||
|
||||
vec3d relativePosition;
|
||||
vec3d turretAngle;
|
||||
double relativeSize;
|
||||
bool enabled;
|
||||
|
||||
const EffectorDef& type;
|
||||
double* values;
|
||||
double range, lifetime, tracking, speed, spread;
|
||||
double fireArc, targetTolerance, fireTolerance;
|
||||
unsigned capTarget;
|
||||
unsigned stateOffset;
|
||||
Effect effect;
|
||||
|
||||
Effector(const EffectorDef& def);
|
||||
~Effector();
|
||||
void initValues();
|
||||
|
||||
void load(SaveFile& file);
|
||||
void save(SaveFile& file) const;
|
||||
|
||||
static const Effector* receiveUpdate(net::Message& msg);
|
||||
void sendUpdate(net::Message& msg) const;
|
||||
void sendDestruction(net::Message& msg) const;
|
||||
|
||||
void grab() const;
|
||||
void drop() const;
|
||||
|
||||
bool isInRange(Object* obj, Object* target, bool considerArc = true) const;
|
||||
bool canTarget(Object* obj, Object* target) const;
|
||||
bool autoTarget(Object* obj, Object* target) const;
|
||||
double getTargetWeight(Object* obj, Object* target) const;
|
||||
|
||||
void trigger(Object* obj, EffectorTarget& targ, float efficiency, double tOffset = 0) const;
|
||||
void triggerEffect(Object* obj, Object* target, const vec3d& impactOffset, float efficiency, float partiality, double delay = 0.0) const;
|
||||
void update(Object* obj, double time, double* states, EffectorTarget& target, float efficiency, bool holdFire = false) const;
|
||||
|
||||
void setRelativePosition(vec2u hex, const HullDef* hull, vec3d direction);
|
||||
|
||||
void writeData(net::Message& msg) const;
|
||||
Effector(net::Message& msg);
|
||||
};
|
||||
|
||||
void clearEffectorDefinitions();
|
||||
void loadEffectorDefinitions(const std::string& filename);
|
||||
unsigned getEffectorDefinitionCount();
|
||||
const EffectorDef* getEffectorDefinition(const std::string& name);
|
||||
const EffectorDef* getEffectorDefinition(unsigned index);
|
||||
void bindEffectorHooks(bool shadow = false);
|
||||
void bindEffectorResources();
|
||||
|
||||
extern std::unordered_map<unsigned, const Effector*> effectorMap;
|
||||
void registerEffector(const Effector* eff);
|
||||
void unregisterEffector(const Effector* eff);
|
||||
const Effector* getEffector(unsigned id);
|
||||
void clearEffectors();
|
||||
|
||||
void saveEffectors(SaveFile& file);
|
||||
void loadEffectors(SaveFile& file);
|
||||
void postLoadEffectors();
|
||||
@@ -0,0 +1,305 @@
|
||||
#include "effector_functions.h"
|
||||
#include "obj/object.h"
|
||||
#include "util/random.h"
|
||||
#include "threads.h"
|
||||
#include "empire.h"
|
||||
#include "obj/blueprint.h"
|
||||
#include "assert.h"
|
||||
#include "main/logging.h"
|
||||
|
||||
#ifndef TARGET_SINGLE_TARGET_DEPTH
|
||||
#define TARGET_SINGLE_TARGET_DEPTH 3
|
||||
#endif
|
||||
|
||||
//* Algorithms
|
||||
struct SingleTargetFinder {
|
||||
const Effector* eff;
|
||||
EffectorTarget* efftarg;
|
||||
Object* obj;
|
||||
Object* best;
|
||||
double maxWeight;
|
||||
|
||||
bool result(Object* targ) {
|
||||
//Ignore ourselves
|
||||
if(targ == obj)
|
||||
return false;
|
||||
|
||||
//Ignore objects out of range
|
||||
if(!eff->isInRange(obj, targ))
|
||||
return false;
|
||||
|
||||
double weight = eff->getTargetWeight(obj, targ);
|
||||
|
||||
//Weights under zero mean to ignore this object
|
||||
if(weight <= 0.0)
|
||||
return false;
|
||||
|
||||
//Modify weight with targeting preference
|
||||
if(efftarg->target) {
|
||||
if(efftarg->flags & TF_Group) {
|
||||
if(efftarg->target->group != targ->group)
|
||||
weight /= 10.0;
|
||||
} else if(efftarg->flags & TF_Preference) {
|
||||
if(efftarg->target != targ)
|
||||
weight /= 10.0;
|
||||
}
|
||||
}
|
||||
|
||||
//Weights of at least one mean to immediately target this
|
||||
if(weight >= 1.0) {
|
||||
best = targ;
|
||||
return true;
|
||||
}
|
||||
|
||||
//Store the object with the highest weight, so
|
||||
//we have something to target if nothing is
|
||||
//randomed.
|
||||
if(weight > maxWeight) {
|
||||
best = targ;
|
||||
maxWeight = weight;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
static Object* SingleTarget(const Effector* eff, Object* obj, EffectorTarget* efftarg) {
|
||||
SingleTargetFinder finder;
|
||||
finder.efftarg = efftarg;
|
||||
finder.eff = eff;
|
||||
finder.obj = obj;
|
||||
finder.best = 0;
|
||||
finder.maxWeight = 0;
|
||||
|
||||
obj->findTargets(finder, TARGET_SINGLE_TARGET_DEPTH, Object::RANDOMIZE_TARGETS);
|
||||
return finder.best;
|
||||
}
|
||||
|
||||
//* Weighters
|
||||
static double NotOurs(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(obj->owner == targ->owner)
|
||||
return 0.0;
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
static double Ours(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(obj->owner != targ->owner)
|
||||
return 0.0;
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
static double isEnemy(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(targ->owner && obj->owner != targ->owner && targ->owner->valid()) {
|
||||
if(obj->owner && (obj->owner->hostileMask & targ->owner->mask) != 0)
|
||||
return 1.0;
|
||||
else
|
||||
return 0.0;
|
||||
}
|
||||
else
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
static double isDamageable(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(targ->getFlag(objNoDamage))
|
||||
return 0.0;
|
||||
else
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
static double isAttackable(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(obj->owner == targ->owner || targ->owner == nullptr || !targ->owner->valid())
|
||||
return 1.0;
|
||||
return isEnemy(eff, obj, targ, arg);
|
||||
}
|
||||
|
||||
static double hasDamagedBlueprint(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(obj != nullptr && obj->type->blueprintOffset != 0) {
|
||||
auto* blueprint = (Blueprint*)(((size_t)obj) + obj->type->blueprintOffset);
|
||||
return blueprint->currentHP < blueprint->design->totalHP - 0.0001 ? 1.0 : 0.0;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double isType(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if((void*)targ->type != arg)
|
||||
return 0.0;
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
static double Distance(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
return obj->position.distanceTo(targ->position);
|
||||
}
|
||||
|
||||
double hasTag(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(targ != nullptr && targ->isValid() && targ->type->blueprintOffset != 0) {
|
||||
auto* blueprint = (Blueprint*)(((size_t)targ) + targ->type->blueprintOffset);
|
||||
if(!blueprint || !blueprint->design)
|
||||
return 0.0;
|
||||
return blueprint->design->hasTag((int)(size_t)arg) ? 1.0 : 0.0;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
static double targRadius(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(targ == nullptr)
|
||||
return 1.0;
|
||||
return targ->radius;
|
||||
}
|
||||
|
||||
static double originRadius(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(obj == nullptr)
|
||||
return 1.0;
|
||||
return obj->radius;
|
||||
}
|
||||
|
||||
static double sizeDifference(const Effector* eff, Object* obj, Object* targ, void* arg) {
|
||||
if(obj == nullptr || targ == nullptr)
|
||||
return 1.0;
|
||||
if(obj->radius > targ->radius)
|
||||
return obj->radius / targ->radius;
|
||||
else
|
||||
return targ->radius / obj->radius;
|
||||
}
|
||||
|
||||
//* Activation
|
||||
static EffectorActivationType Always(const Effector* eff, Object* obj, EffectorTarget& targ, double& time, double* args, double* states) {
|
||||
return EAT_Activate;
|
||||
}
|
||||
|
||||
static EffectorActivationType Timed(const Effector* eff, Object* obj, EffectorTarget& targ, double& time, double* args, double* states) {
|
||||
if(!(targ.flags & TF_WithinFireTolerance)) {
|
||||
if(states[0] > 0.0)
|
||||
states[0] = std::max(states[0] - time, 0.0);
|
||||
return EAT_Inactive;
|
||||
}
|
||||
else if(states[0] <= time) {
|
||||
time = states[0];
|
||||
states[0] = args[0];
|
||||
if(args[0] <= 0.000001)
|
||||
return EAT_Inactive;
|
||||
return EAT_Repeat;
|
||||
}
|
||||
|
||||
states[0] -= time;
|
||||
return EAT_Inactive;
|
||||
}
|
||||
|
||||
static EffectorActivationType VariableTimed(const Effector* eff, Object* obj, EffectorTarget& targ, double& time, double* args, double* states) {
|
||||
if(!(targ.flags & TF_WithinFireTolerance)) {
|
||||
if(states[0] > 0.0)
|
||||
states[0] = std::max(states[0] - time, 0.0);
|
||||
return EAT_Inactive;
|
||||
}
|
||||
else if(states[0] <= time) {
|
||||
time = states[0];
|
||||
if(targ.target)
|
||||
states[0] = args[0] * randomd(1.0 - args[1], 1.0 + args[1]);
|
||||
else
|
||||
states[0] = args[0];
|
||||
if(args[0] <= 0.000001)
|
||||
return EAT_Inactive;
|
||||
return EAT_Repeat;
|
||||
}
|
||||
|
||||
states[0] -= time;
|
||||
return EAT_Inactive;
|
||||
}
|
||||
|
||||
static EffectorActivationType StaggeredTimed(const Effector* eff, Object* obj, EffectorTarget& targ, double& time, double* args, double* states) {
|
||||
if(!(targ.flags & TF_WithinFireTolerance)) {
|
||||
double stagger = args[1] * args[0] * (-eff->relativePosition.x + 1.0) * 0.5;
|
||||
states[0] = std::max(states[0] - time, stagger);
|
||||
return EAT_Inactive;
|
||||
}
|
||||
else if(states[0] <= time) {
|
||||
time = states[0];
|
||||
states[0] = args[0];
|
||||
if(args[0] <= 0.000001)
|
||||
return EAT_Inactive;
|
||||
return EAT_Repeat;
|
||||
}
|
||||
|
||||
states[0] -= time;
|
||||
return EAT_Inactive;
|
||||
}
|
||||
|
||||
//Fires arg[1] shots at arg[0] second intervals, then reloads over arg[2] seconds
|
||||
static EffectorActivationType Magazine(const Effector* eff, Object* obj, EffectorTarget& targ, double& time, double* args, double* states) {
|
||||
if(!(targ.flags & TF_WithinFireTolerance) || states[1] <= 0.0) {
|
||||
if(states[0] > 0.0) {
|
||||
states[0] = states[0] - time;
|
||||
if(states[0] <= 0.0) {
|
||||
states[1] = args[1];
|
||||
states[0] = 0.0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
states[1] = args[1];
|
||||
}
|
||||
return EAT_Inactive;
|
||||
}
|
||||
else if(states[0] <= time) {
|
||||
time = states[0];
|
||||
states[1] -= 1.0;
|
||||
if(states[1] <= 0.000001)
|
||||
states[0] = args[2];
|
||||
else
|
||||
states[0] = args[0];
|
||||
if(args[0] <= 0.000001)
|
||||
return EAT_Inactive;
|
||||
return EAT_Repeat;
|
||||
}
|
||||
|
||||
states[0] -= time;
|
||||
return EAT_Inactive;
|
||||
}
|
||||
|
||||
//Build maps
|
||||
decltype(TargetAlgorithms) makeAlgoList() {
|
||||
decltype(TargetAlgorithms) list;
|
||||
|
||||
list["SingleTarget"] = SingleTarget;
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
decltype(TargetWeighters) makeWeighterList() {
|
||||
decltype(TargetWeighters) list;
|
||||
|
||||
list["NotOurs"] = NotOurs;
|
||||
list["Ours"] = Ours;
|
||||
list["isEnemy"] = isEnemy;
|
||||
list["isAttackable"] = isAttackable;
|
||||
list["hasDamagedBlueprint"] = hasDamagedBlueprint;
|
||||
list["isDamageable"] = isDamageable;
|
||||
list["targRadius"] = targRadius;
|
||||
list["originRadius"] = originRadius;
|
||||
list["sizeDifference"] = sizeDifference;
|
||||
list["Distance"] = Distance;
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
decltype(EffectorActivation) makeActivationList() {
|
||||
decltype(EffectorActivation) list;
|
||||
|
||||
auto addFunc = [&](std::string name, nativeEffectorActivation f, unsigned stateCount, unsigned argCount)
|
||||
{
|
||||
auto& cb = list[name];
|
||||
cb.func = f;
|
||||
cb.stateCount = stateCount;
|
||||
cb.argCount = argCount;
|
||||
};
|
||||
|
||||
addFunc("Always", Always, 0, 0);
|
||||
addFunc("Timed", Timed, 1, 1);
|
||||
addFunc("VariableTimed", VariableTimed, 1, 2);
|
||||
addFunc("StaggeredTimed", StaggeredTimed, 1, 2);
|
||||
addFunc("Magazine", Magazine, 2, 3);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
umap<std::string, nativeTargetAlgorithm> TargetAlgorithms = makeAlgoList();
|
||||
umap<std::string, nativeTargetWeighter> TargetWeighters = makeWeighterList();
|
||||
umap<std::string, ActivationCB> EffectorActivation = makeActivationList();
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "design/effector.h"
|
||||
#include "compat/misc.h"
|
||||
|
||||
extern umap<std::string, nativeTargetAlgorithm> TargetAlgorithms;
|
||||
extern umap<std::string, nativeTargetWeighter> TargetWeighters;
|
||||
|
||||
struct ActivationCB {
|
||||
nativeEffectorActivation func;
|
||||
unsigned stateCount, argCount;
|
||||
};
|
||||
|
||||
extern umap<std::string, ActivationCB> EffectorActivation;
|
||||
|
||||
double isType(const Effector* eff, Object* obj, Object* targ, void* arg);
|
||||
double hasTag(const Effector* eff, Object* obj, Object* targ, void* arg);
|
||||
@@ -0,0 +1,912 @@
|
||||
#include "design/hull.h"
|
||||
#include "str_util.h"
|
||||
#include "main/logging.h"
|
||||
#include "main/references.h"
|
||||
#include "compat/misc.h"
|
||||
#include "design/design.h"
|
||||
#include "design/subsystem.h"
|
||||
#include "design/subsystem.h"
|
||||
#include "threads.h"
|
||||
#include "vec3.h"
|
||||
#include "line3d.h"
|
||||
#include <algorithm>
|
||||
#include "files.h"
|
||||
#include <math.h>
|
||||
#include <cmath>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static std::vector<Shipset*> shipsets;
|
||||
static umap<std::string, unsigned> shipsetIndices;
|
||||
|
||||
static std::vector<HullDef*> hullDefinitions;
|
||||
static umap<std::string, unsigned> hullIndices;
|
||||
|
||||
static unsigned computedHulls = 0;
|
||||
void computeHulls(unsigned amount) {
|
||||
unsigned offset = randomi(0, hullDefinitions.size()-1);
|
||||
for(unsigned n = 0, cnt = hullDefinitions.size(); n < cnt; ++n) {
|
||||
if(hullDefinitions[(offset+n)%cnt]->calculateImpacts()) {
|
||||
++computedHulls;
|
||||
if(computedHulls >= hullDefinitions.size())
|
||||
return;
|
||||
--amount;
|
||||
if(amount == 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isFinishedComputingHulls() {
|
||||
return computedHulls >= hullDefinitions.size();
|
||||
}
|
||||
|
||||
unsigned getHullCount() {
|
||||
return (unsigned)hullDefinitions.size();
|
||||
}
|
||||
|
||||
const HullDef* getHullDefinition(unsigned id) {
|
||||
if(id >= hullDefinitions.size())
|
||||
return 0;
|
||||
return hullDefinitions[id];
|
||||
}
|
||||
|
||||
const HullDef* getHullDefinition(const std::string& name) {
|
||||
auto it = hullIndices.find(name);
|
||||
if(it == hullIndices.end())
|
||||
return 0;
|
||||
return hullDefinitions[it->second];
|
||||
}
|
||||
|
||||
HullDef::HullDef()
|
||||
: id(-1), background(0), material(0), mesh(0),
|
||||
iconSheet(0), iconIndex(0), gridSize(16, 8), backgroundScale(1.0), modelScale(1.0),
|
||||
active(gridSize), exterior(gridSize), activeCount(0), exteriorCount(0),
|
||||
minSize(1.0), maxSize(-1.0), baseHull(this), shape(nullptr), special(false)
|
||||
{
|
||||
active.clear(false);
|
||||
exterior.clear(false);
|
||||
}
|
||||
|
||||
HullDef::HullDef(const HullDef& other) {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
void clearHullDefinitions() {
|
||||
foreach(it, hullDefinitions) {
|
||||
auto* hull = *it;
|
||||
hull->baseHull = nullptr;
|
||||
hull->drop();
|
||||
}
|
||||
hullDefinitions.clear();
|
||||
hullIndices.clear();
|
||||
computedHulls = 0;
|
||||
}
|
||||
|
||||
void loadHullDefinitions(const std::string& filename) {
|
||||
std::vector<HullDef*> defs;
|
||||
readHullDefinitions(filename, defs);
|
||||
|
||||
foreach(it, defs) {
|
||||
(*it)->calculateDist();
|
||||
(*it)->calculateExterior();
|
||||
(*it)->id = (unsigned)hullDefinitions.size();
|
||||
hullDefinitions.push_back(*it);
|
||||
hullIndices[(*it)->ident] = (*it)->id;
|
||||
}
|
||||
}
|
||||
|
||||
bool meshIntersect(const Mesh& mesh, const line3dd& line, vec3d& output) {
|
||||
double closestDist = 1e100;
|
||||
vec3d closestIntersect;
|
||||
|
||||
for(unsigned i = 0, cnt = mesh.faces.size(); i < cnt; ++i) {
|
||||
const Mesh::Face& face = mesh.faces[i];
|
||||
|
||||
vec3d v1 = vec3d(mesh.vertices[face.a].position);
|
||||
vec3d v2 = vec3d(mesh.vertices[face.b].position);
|
||||
vec3d v3 = vec3d(mesh.vertices[face.c].position);
|
||||
|
||||
vec3d point;
|
||||
if(line.intersectTriangle(v1, v2, v3, point)) {
|
||||
double dist = line.start.distanceToSQ(point);
|
||||
if(dist < closestDist) {
|
||||
closestIntersect = point;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!closestIntersect.zero()) {
|
||||
output = closestIntersect;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HullDef::calculateImpacts() {
|
||||
if(!impacts.empty())
|
||||
return false;
|
||||
if(!this->mesh)
|
||||
return true;
|
||||
const Mesh& mesh = this->mesh->getMesh();
|
||||
if(mesh.faces.empty())
|
||||
return false;
|
||||
|
||||
//double t = devices.driver->getAccurateTime();
|
||||
|
||||
std::vector<vec3d> lineImpacts;
|
||||
lineImpacts.resize(8 * 64);
|
||||
impacts.resize(8 * 64);
|
||||
|
||||
//Trace the impact lines
|
||||
for(unsigned n = 0; n < 8; ++n) {
|
||||
double theta = (double)n / 8.0 * pi;
|
||||
for(unsigned i = 0; i < 64; ++i) {
|
||||
double phi = (double)i / 64.0 * twopi;
|
||||
|
||||
double st = sin(theta);
|
||||
vec3d start = vec3d(
|
||||
st * cos(phi),
|
||||
cos(theta),
|
||||
st * sin(phi)
|
||||
);
|
||||
|
||||
line3dd ray(start, vec3d(0.0));
|
||||
|
||||
vec3d point;
|
||||
if(meshIntersect(mesh, ray, point))
|
||||
lineImpacts[n*64 + i] = point;
|
||||
else
|
||||
lineImpacts[n*64 + i] = vec3d();
|
||||
}
|
||||
}
|
||||
|
||||
//Pre-calculate the closest impact points for angles
|
||||
for(unsigned n = 0; n < 8; ++n) {
|
||||
double theta = (double)n / 8.0 * pi;
|
||||
for(unsigned i = 0; i < 64; ++i) {
|
||||
double phi = (double)i / 64.0 * twopi;
|
||||
|
||||
double st = sin(theta);
|
||||
vec3d point = vec3d(
|
||||
st * cos(phi),
|
||||
cos(theta),
|
||||
st * sin(phi)
|
||||
);
|
||||
|
||||
double d = 1e80;
|
||||
vec3d imp;
|
||||
for(unsigned j = 0, jcnt = lineImpacts.size(); j < jcnt; ++j) {
|
||||
double dist = lineImpacts[j].distanceToSQ(point);
|
||||
if(dist < d) {
|
||||
d = dist;
|
||||
imp = lineImpacts[j];
|
||||
}
|
||||
}
|
||||
|
||||
impacts[n*64 + i] = imp;
|
||||
}
|
||||
}
|
||||
|
||||
//double tend = devices.driver->getAccurateTime();
|
||||
//print("Impacts %s - %gms", ident.c_str(), (tend-t)*1000.0);
|
||||
return true;
|
||||
}
|
||||
|
||||
vec3d HullDef::getImpact(const vec3d& offset, double radius, bool constant) const {
|
||||
if(baseHull != nullptr && baseHull != this)
|
||||
return baseHull->getImpact(offset, radius);
|
||||
|
||||
if(impacts.size() == 0)
|
||||
return offset;
|
||||
|
||||
vec3d uoffset = offset / radius;
|
||||
|
||||
double theta = std::fmod(twopi + acos(uoffset.y / uoffset.getLength()), twopi);
|
||||
double phi = std::fmod(twopi + atan2(uoffset.z, uoffset.x), twopi);
|
||||
|
||||
unsigned row = (unsigned)floor(theta / pi * 8.0 + 0.5) % 8;
|
||||
unsigned index;
|
||||
if(constant)
|
||||
index = ((unsigned)floor(phi / twopi * 64.0 + 0.5)) % 64;
|
||||
else
|
||||
index = ((unsigned)floor(phi / twopi * 64.0 + 0.5) + randomi(-1,1)) % 64;
|
||||
|
||||
vec3d imp = impacts[row*64 + index];
|
||||
if(imp.zero())
|
||||
return offset;
|
||||
|
||||
return imp * radius;
|
||||
}
|
||||
|
||||
vec3d HullDef::getClosestImpact(const vec3d& offset) const {
|
||||
if(baseHull != nullptr && baseHull != this)
|
||||
return baseHull->getClosestImpact(offset);
|
||||
|
||||
if(impacts.size() == 0)
|
||||
return offset;
|
||||
|
||||
double d = 1e80;
|
||||
vec3d imp;
|
||||
for(unsigned j = 0, jcnt = impacts.size(); j < jcnt; ++j) {
|
||||
double dist = impacts[j].distanceToSQ(offset);
|
||||
if(dist < d) {
|
||||
d = dist;
|
||||
imp = impacts[j];
|
||||
}
|
||||
}
|
||||
|
||||
return imp;
|
||||
}
|
||||
|
||||
void HullDef::calculateExterior() {
|
||||
//Top & Bottom Lines
|
||||
for(unsigned i = 0, cnt = gridSize.x; i < cnt; ++i) {
|
||||
if(i % 2 == 0) {
|
||||
calculateExterior(vec2u(i, 0), HEX_DownLeft);
|
||||
calculateExterior(vec2u(i, 0), HEX_Down);
|
||||
calculateExterior(vec2u(i, 0), HEX_DownRight);
|
||||
|
||||
calculateExterior(vec2u(i, gridSize.y-1), HEX_Up);
|
||||
}
|
||||
else {
|
||||
calculateExterior(vec2u(i, 0), HEX_Down);
|
||||
|
||||
calculateExterior(vec2u(i, gridSize.y-1), HEX_UpLeft);
|
||||
calculateExterior(vec2u(i, gridSize.y-1), HEX_Up);
|
||||
calculateExterior(vec2u(i, gridSize.y-1), HEX_UpRight);
|
||||
}
|
||||
}
|
||||
|
||||
//Right & Left Lines
|
||||
for(unsigned i = 0, cnt = gridSize.y; i < cnt; ++i) {
|
||||
calculateExterior(vec2u(0, i), HEX_DownRight);
|
||||
calculateExterior(vec2u(0, i), HEX_UpRight);
|
||||
|
||||
calculateExterior(vec2u(gridSize.x-1, i), HEX_DownLeft);
|
||||
calculateExterior(vec2u(gridSize.x-1, i), HEX_UpLeft);
|
||||
}
|
||||
}
|
||||
|
||||
void HullDef::calculateExterior(vec2u pos, unsigned direction) {
|
||||
unsigned mask = (1<<((direction+3)%6));
|
||||
do {
|
||||
if((mask & flagExteriorFaux) && !(exterior[pos] & (flagExteriorFaux | flagExteriorPass)) && active[pos])
|
||||
break;
|
||||
exterior[pos] |= mask;
|
||||
if(active[pos]) {
|
||||
if(exterior[pos] & flagExteriorPass)
|
||||
continue;
|
||||
if(exterior[pos] & flagExteriorFaux) {
|
||||
mask |= flagExteriorFaux;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
while(exterior.advance(pos, (HexGridAdjacency)direction));
|
||||
}
|
||||
|
||||
void HullDef::calculateDist() {
|
||||
if(shape == nullptr || shape->format != FMT_RGBA)
|
||||
return;
|
||||
|
||||
if(shapeMapped)
|
||||
return;
|
||||
|
||||
//First, make it binary. This is a distance map, so every active
|
||||
//pixel should be 0 and every inactive should be full distance.
|
||||
for(unsigned x = 0; x < shape->width; ++x) {
|
||||
for(unsigned y = 0; y < shape->height; ++y) {
|
||||
Color& col = shape->get_rgba(x, y);
|
||||
if(col.a != 0)
|
||||
col.a = 0;
|
||||
else
|
||||
col.a = 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
//Calculate distances
|
||||
for(unsigned x = 0; x < shape->width; ++x) {
|
||||
for(unsigned y = 0; y < shape->height; ++y) {
|
||||
if(shape->get_rgba(x, y).a == 0)
|
||||
calculateDist(vec2u(x,y), 0);
|
||||
}
|
||||
}
|
||||
|
||||
saveImage(shape, shapeMap.c_str());
|
||||
}
|
||||
|
||||
void HullDef::calculateDist(vec2u pos, int dist) {
|
||||
assert(dist <= shape->get_rgba(pos.x, pos.y).a);
|
||||
shape->get_rgba(pos.x, pos.y).a = std::min(dist, 255);
|
||||
|
||||
int wmod = std::max(512 / (int)shape->width, 1);
|
||||
int hmod = std::max(512 / (int)shape->height, 1);
|
||||
|
||||
if(pos.x > 0) {
|
||||
if(shape->get_rgba(pos.x-1, pos.y).a > dist+wmod)
|
||||
calculateDist(vec2u(pos.x-1, pos.y), dist+wmod);
|
||||
}
|
||||
if(pos.x < shape->width-1) {
|
||||
if(shape->get_rgba(pos.x+1, pos.y).a > dist+wmod)
|
||||
calculateDist(vec2u(pos.x+1, pos.y), dist+wmod);
|
||||
}
|
||||
if(pos.y > 0) {
|
||||
if(shape->get_rgba(pos.x, pos.y-1).a > dist+hmod)
|
||||
calculateDist(vec2u(pos.x, pos.y-1), dist+hmod);
|
||||
}
|
||||
if(pos.y < shape->height-1) {
|
||||
if(shape->get_rgba(pos.x, pos.y+1).a > dist+hmod)
|
||||
calculateDist(vec2u(pos.x, pos.y+1), dist+hmod);
|
||||
}
|
||||
}
|
||||
|
||||
double HullDef::getMatchDistance(const vec2d& pos) const {
|
||||
if(shape == nullptr)
|
||||
return 128.0;
|
||||
return shape->getTexel(pos.x, pos.y).a;
|
||||
}
|
||||
|
||||
double HullDef::getMatchDistance(void* descPtr) const {
|
||||
auto& desc = *(Design::Descriptor*)descPtr;
|
||||
double dist = 0.0;
|
||||
|
||||
vec2u grid = desc.gridSize;
|
||||
unsigned count = grid.x * grid.y;
|
||||
uint8_t* cache;
|
||||
if(count < 2500)
|
||||
cache = (uint8_t*)alloca(count * sizeof(uint8_t));
|
||||
else
|
||||
cache = (uint8_t*)malloc(count * sizeof(uint8_t));
|
||||
memset(cache, 0, count * sizeof(uint8_t));
|
||||
|
||||
for(size_t i = 0, cnt = desc.systems.size(); i < cnt; ++i) {
|
||||
auto& sys = desc.systems[i];
|
||||
for(size_t j = 0, jcnt = sys.hexes.size(); j < jcnt; ++j) {
|
||||
vec2u hex = sys.hexes[j];
|
||||
if(hex.x < grid.x && hex.y < grid.y) {
|
||||
unsigned index = hex.y * grid.x + hex.x;
|
||||
cache[index] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned x = 0; x < grid.x; ++x) {
|
||||
for(unsigned y = 0; y < grid.y; ++y) {
|
||||
vec2d pctPos = HexGrid<>::getEffectivePosition(vec2u(x, y));
|
||||
pctPos.x += 0.75 * 0.5;
|
||||
pctPos.y += 0.5;
|
||||
pctPos.x /= ((double)grid.x) * 0.75;
|
||||
pctPos.y /= (double)grid.y;
|
||||
|
||||
double d = getMatchDistance(pctPos);
|
||||
|
||||
unsigned index = y * grid.x + x;
|
||||
if(cache[index])
|
||||
dist += d*d;
|
||||
else
|
||||
dist += 0.5 * (255.0 - d);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(count >= 2500)
|
||||
free(cache);
|
||||
|
||||
return dist;
|
||||
}
|
||||
|
||||
bool HullDef::checkConnected() {
|
||||
HexGrid<bool> connected(active.width, active.height);
|
||||
connected.clear(false);
|
||||
|
||||
bool found = false;
|
||||
for(unsigned x = 0; x < active.width && !found; ++x) {
|
||||
for(unsigned y = 0; y < active.height && !found; ++y) {
|
||||
if(active.get(x,y)) {
|
||||
fillConnected(connected, vec2u(x,y));
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned x = 0; x < active.width; ++x) {
|
||||
for(unsigned y = 0; y < active.height; ++y) {
|
||||
if(active.get(x,y) && !connected.get(x,y))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void HullDef::fillConnected(HexGrid<bool>& connected, vec2u pos) {
|
||||
connected[pos] = true;
|
||||
for(unsigned i = 0; i < 6; ++i) {
|
||||
vec2u hex = pos;
|
||||
if(active.advance(hex, (HexGridAdjacency)i)) {
|
||||
if(active[hex] && !connected[hex])
|
||||
fillConnected(connected, hex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HullDef::isExterior(const vec2u& hex) const {
|
||||
if(!exterior.valid(hex))
|
||||
return false;
|
||||
return exterior.get(hex) & 0x00ffffff;
|
||||
}
|
||||
|
||||
bool HullDef::isExteriorInDirection(const vec2u& hex, unsigned dir) const {
|
||||
if(!exterior.valid(hex))
|
||||
return false;
|
||||
return exterior.get(hex) & (1<<dir);
|
||||
}
|
||||
|
||||
void readHullDefinitions(const std::string& filename, std::vector<HullDef*>& hulls) {
|
||||
HullDef* def = 0;
|
||||
int x = 0, y = 0;
|
||||
|
||||
DataHandler datahandler;
|
||||
datahandler.lineHandler([&](std::string& line) {
|
||||
if(!def)
|
||||
return;
|
||||
|
||||
if(y >= def->gridSize.height) {
|
||||
error("Error: Too many rows for hull '%s'.", def->ident.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
x = 0;
|
||||
line = trim(line, "\r\n\t");
|
||||
unsigned cnt = (unsigned)line.size();
|
||||
for(unsigned i = 0; i < cnt; ++i) {
|
||||
if(x >= def->gridSize.width) {
|
||||
error("Error: Too many characters on row for hull '%s'.", def->ident.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
bool& active = def->active.get(x, y);
|
||||
int& exterior = def->exterior.get(x, y);
|
||||
|
||||
switch(line[i]) {
|
||||
case '-':
|
||||
active = false;
|
||||
exterior = 0;
|
||||
++x;
|
||||
break;
|
||||
case 'X':
|
||||
case 'x':
|
||||
active = true;
|
||||
exterior = 0;
|
||||
++def->activeCount;
|
||||
++x;
|
||||
break;
|
||||
case '#':
|
||||
active = true;
|
||||
exterior = 0xff;
|
||||
++def->activeCount;
|
||||
++def->exteriorCount;
|
||||
++x;
|
||||
break;
|
||||
case ' ':
|
||||
break;
|
||||
default:
|
||||
error("Error: Invalid character '%c' on row for hull '%s'.", line[i], def->ident.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
++y;
|
||||
});
|
||||
|
||||
datahandler("Hull", [&](std::string& value) {
|
||||
def = new HullDef();
|
||||
def->ident = value;
|
||||
def->name = "__"+def->ident+"__";
|
||||
hulls.push_back(def);
|
||||
|
||||
x = 0;
|
||||
y = 0;
|
||||
});
|
||||
|
||||
datahandler("Name", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
def->name = devices.locale.localize(value);
|
||||
});
|
||||
|
||||
datahandler("Tags", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
split(value, def->tags, ',', true);
|
||||
for(size_t i = 0, cnt = def->tags.size(); i < cnt; ++i)
|
||||
def->numTags.insert(getSysTagIndex(def->tags[i], true));
|
||||
});
|
||||
|
||||
datahandler("Subsystem", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
def->subsystems.push_back(value);
|
||||
});
|
||||
|
||||
datahandler("Background", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
|
||||
const render::RenderState* bg = &devices.library.getMaterial(value);
|
||||
if(!bg) {
|
||||
error("(Hull %s): Error: Unknown material %s.", def->ident.c_str(), value.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
def->background = bg;
|
||||
def->backgroundName = value;
|
||||
});
|
||||
|
||||
datahandler("BackgroundScale", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
def->backgroundScale = toNumber<double>(value);
|
||||
});
|
||||
|
||||
datahandler("ModelScale", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
def->modelScale = toNumber<double>(value);
|
||||
});
|
||||
|
||||
datahandler("Material", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
|
||||
const render::RenderState* mat = &devices.library.getMaterial(value);
|
||||
if(!mat) {
|
||||
error("(Hull %s): Error: Unknown material %s.", def->ident.c_str(), value.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
def->materialName = value;
|
||||
def->material = mat;
|
||||
});
|
||||
|
||||
datahandler("Model", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
|
||||
const render::RenderMesh* mesh = &devices.library.getMesh(value);
|
||||
if(!mesh) {
|
||||
if(devices.render)
|
||||
error("(Hull %s): Error: Unknown model %s.", def->ident.c_str(), value.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
def->meshName = value;
|
||||
def->mesh = mesh;
|
||||
});
|
||||
|
||||
datahandler("Shape", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
|
||||
std::string fname = devices.mods.resolve(value);
|
||||
def->shapeMap = fname+".map.png";
|
||||
if(fileExists(def->shapeMap)) {
|
||||
def->shape = loadImage(def->shapeMap.c_str());
|
||||
def->shapeMapped = true;
|
||||
}
|
||||
else {
|
||||
def->shape = loadImage(fname.c_str());
|
||||
def->shapeMapped = false;
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("GridSize", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
|
||||
std::vector<std::string> args;
|
||||
split(value, args, ',');
|
||||
|
||||
if(args.size() != 2) {
|
||||
error("(Hull %s): Error: Invalid grid size specification.", def->ident.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
def->gridSize = vec2i(toNumber<int>(args[0]), toNumber<int>(args[1]));
|
||||
|
||||
def->active.resize(def->gridSize);
|
||||
def->active.clear(false);
|
||||
|
||||
def->exterior.resize(def->gridSize);
|
||||
def->exterior.clear(0);
|
||||
});
|
||||
|
||||
datahandler("GridOffset", [&](std::string& value) {
|
||||
if(!def)
|
||||
return;
|
||||
|
||||
std::vector<std::string> args;
|
||||
split(value, args, ',');
|
||||
|
||||
if(args.size() != 4) {
|
||||
error("(Hull %s): Error: Invalid grid offset specification.", def->ident.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
def->gridOffset = recti(toNumber<int>(args[0]), toNumber<int>(args[1]),
|
||||
toNumber<int>(args[2]), toNumber<int>(args[3]));
|
||||
});
|
||||
|
||||
datahandler("GuiIcon", [&](std::string& value) {
|
||||
if(def) {
|
||||
def->guiIcon = devices.library.getSprite(value);
|
||||
def->guiIconName = value;
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("FleetIcon", [&](std::string& value) {
|
||||
if(def) {
|
||||
def->fleetIcon = devices.library.getSprite(value);
|
||||
def->fleetIconName = value;
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("IconSheet", [&](std::string& value) {
|
||||
if(def) {
|
||||
def->iconSheet = &devices.library.getSpriteSheet(value);
|
||||
def->iconName = value;
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("IconIndex", [&](std::string& value) {
|
||||
if(def)
|
||||
def->iconIndex = toNumber<unsigned>(value);
|
||||
});
|
||||
|
||||
datahandler("MinSize", [&](std::string& value) {
|
||||
if(def)
|
||||
def->minSize = toNumber<double>(value);
|
||||
});
|
||||
|
||||
datahandler("MaxSize", [&](std::string& value) {
|
||||
if(def)
|
||||
def->maxSize = toNumber<double>(value);
|
||||
});
|
||||
|
||||
datahandler("Special", [&](std::string& value) {
|
||||
if(def)
|
||||
def->special = toBool(value);
|
||||
});
|
||||
|
||||
datahandler.read(filename);
|
||||
}
|
||||
|
||||
bool HullDef::hasTag(const std::string& tag) const {
|
||||
auto it = std::find(tags.begin(), tags.end(), tag);
|
||||
return it != tags.end();
|
||||
}
|
||||
|
||||
void writeHullDefinitions(const std::string& filename, std::vector<HullDef*>& hulls) {
|
||||
std::ofstream file(filename);
|
||||
foreach(it, hulls) {
|
||||
const HullDef* def = *it;
|
||||
file << "Hull: " << def->ident << "\n";
|
||||
file << "\tName: " << def->name << "\n";
|
||||
file << "\tBackground: " << def->backgroundName << "\n";
|
||||
if(def->backgroundScale != 1.0)
|
||||
file << "\tBackgroundScale: " << def->backgroundScale << "\n";
|
||||
file << "\tMaterial: " << def->materialName << "\n";
|
||||
file << "\tModel: " << def->meshName << "\n";
|
||||
file << "\tGuiIcon: " << def->guiIconName << "\n";
|
||||
if(!def->fleetIconName.empty())
|
||||
file << "\tFleetIcon: " << def->fleetIconName << "\n";
|
||||
file << "\tIconSheet: " << def->iconName << "\n";
|
||||
file << "\tIconIndex: " << def->iconIndex << "\n";
|
||||
file << "\n";
|
||||
|
||||
if(!def->tags.empty()) {
|
||||
file << "\tTags: ";
|
||||
bool first = true;
|
||||
for(auto t = def->tags.begin(), tend = def->tags.end(); t != tend; ++t) {
|
||||
if(!first)
|
||||
file << ", ";
|
||||
first = false;
|
||||
file << *t;
|
||||
}
|
||||
file << "\n\n";
|
||||
}
|
||||
|
||||
file << "\tGridSize: " << def->gridSize.x << ", " << def->gridSize.y << "\n";
|
||||
file << "\tGridOffset: " << def->gridOffset.topLeft.x << "," << def->gridOffset.topLeft.y;
|
||||
file << ", " << def->gridOffset.botRight.x << "," << def->gridOffset.botRight.y << "\n";
|
||||
file << "\n";
|
||||
|
||||
for(int y = 0; y < def->gridSize.y; ++y) {
|
||||
file << "\t";
|
||||
|
||||
for(int x = 0; x < def->gridSize.x; ++x) {
|
||||
if(x != 0)
|
||||
file << " ";
|
||||
if(def->active.get(x, y)) {
|
||||
if(def->exterior.get(x, y)) {
|
||||
file << "#";
|
||||
}
|
||||
else {
|
||||
file << "X";
|
||||
}
|
||||
}
|
||||
else {
|
||||
file << "-";
|
||||
}
|
||||
}
|
||||
|
||||
file << "\n";
|
||||
}
|
||||
|
||||
file << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
unsigned Shipset::getHullCount() const {
|
||||
return hulls.size();
|
||||
}
|
||||
|
||||
ShipSkin* Shipset::getSkin(const std::string& name) const {
|
||||
auto it = skins.find(name);
|
||||
if(it == skins.end())
|
||||
return nullptr;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
Shipset::~Shipset() {
|
||||
for(auto it = skins.begin(); it != skins.end(); ++it)
|
||||
delete it->second;
|
||||
}
|
||||
|
||||
const HullDef* Shipset::getHull(unsigned index) const {
|
||||
if(index >= hulls.size())
|
||||
return nullptr;
|
||||
return hulls[index];
|
||||
}
|
||||
|
||||
const HullDef* Shipset::getHull(const std::string& ident) const {
|
||||
for(auto i = hulls.begin(), end = hulls.end(); i != end; ++i)
|
||||
if((*i)->ident == ident)
|
||||
return *i;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Shipset::hasHull(const HullDef* hull) const {
|
||||
for(auto i = hulls.begin(), end = hulls.end(); i != end; ++i)
|
||||
if(*i == hull)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void loadShipset(const std::string& filename) {
|
||||
Shipset* set = nullptr;
|
||||
ShipSkin* skin = nullptr;
|
||||
|
||||
DataHandler datahandler;
|
||||
|
||||
datahandler("Shipset", [&](std::string& value) {
|
||||
if(shipsetIndices.find(value) != shipsetIndices.end())
|
||||
error("Duplicate Shipset ID: %s", value.c_str());
|
||||
set = new Shipset();
|
||||
set->ident = value;
|
||||
set->available = true;
|
||||
set->id = (unsigned)shipsets.size();
|
||||
shipsetIndices[value] = set->id;
|
||||
shipsets.push_back(set);
|
||||
});
|
||||
|
||||
datahandler("Name", [&](std::string& value) {
|
||||
if(set)
|
||||
set->name = value;
|
||||
});
|
||||
|
||||
datahandler("DLC", [&](std::string& value) {
|
||||
if(set)
|
||||
set->dlc = value;
|
||||
});
|
||||
|
||||
datahandler("Available", [&](std::string& value) {
|
||||
if(set)
|
||||
set->available = toBool(value, true);
|
||||
});
|
||||
|
||||
datahandler("Hull", [&](std::string& value) {
|
||||
if(set) {
|
||||
auto* hull = getHullDefinition(value);
|
||||
if(hull)
|
||||
set->hulls.push_back(hull);
|
||||
else
|
||||
error("Could not find hull %s for shipset %s", value.c_str(), set->ident.c_str());
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("Skin", [&](std::string& value) {
|
||||
if(set) {
|
||||
skin = new ShipSkin();
|
||||
skin->ident = value;
|
||||
set->skins[value] = skin;
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("Model", [&](std::string& value) {
|
||||
if(skin && set) {
|
||||
const render::RenderMesh* mesh = &devices.library.getMesh(value);
|
||||
if(!mesh) {
|
||||
if(devices.render)
|
||||
error("(%s Ship Skin %s): Error: Unknown model %s.", set->ident.c_str(), skin->ident.c_str(), value.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
skin->mesh = mesh;
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("Material", [&](std::string& value) {
|
||||
if(skin && set) {
|
||||
const render::RenderState* mat = &devices.library.getMaterial(value);
|
||||
if(!mat) {
|
||||
if(devices.render)
|
||||
error("(%s Ship Skin %s): Error: Unknown material %s.", set->ident.c_str(), skin->ident.c_str(), value.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
skin->material = mat;
|
||||
}
|
||||
});
|
||||
|
||||
datahandler("Icon", [&](std::string& value) {
|
||||
if(skin && set) {
|
||||
skin->icon = devices.library.getSprite(value);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
datahandler.read(filename);
|
||||
}
|
||||
|
||||
void initAllShipset() {
|
||||
auto* set = new Shipset();
|
||||
set->ident = "ALL";
|
||||
set->available = false;
|
||||
set->id = (unsigned)shipsets.size();
|
||||
|
||||
for(size_t i = 0, cnt = shipsets.size(); i < cnt; ++i) {
|
||||
auto* other = shipsets[i];
|
||||
if(!other->available)
|
||||
continue;
|
||||
for(size_t n = 0, ncnt = other->hulls.size(); n < ncnt; ++n)
|
||||
set->hulls.push_back(other->hulls[n]);
|
||||
}
|
||||
|
||||
shipsetIndices[set->ident] = set->id;
|
||||
shipsets.push_back(set);
|
||||
}
|
||||
|
||||
void clearShipsets() {
|
||||
shipsets.clear(); shipsets.shrink_to_fit();
|
||||
shipsetIndices.clear();
|
||||
}
|
||||
|
||||
unsigned getShipsetCount() {
|
||||
return (unsigned)shipsets.size();
|
||||
}
|
||||
|
||||
const Shipset* getShipset(unsigned id) {
|
||||
if(id >= (unsigned)shipsets.size())
|
||||
return nullptr;
|
||||
return shipsets[id];
|
||||
}
|
||||
|
||||
const Shipset* getShipset(const std::string& ident) {
|
||||
auto i = shipsetIndices.find(ident);
|
||||
if(i == shipsetIndices.end())
|
||||
return nullptr;
|
||||
return shipsets[i->second];
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include "util/hex_grid.h"
|
||||
#include "util/refcount.h"
|
||||
#include "vec2.h"
|
||||
#include "rect.h"
|
||||
#include "render/render_state.h"
|
||||
#include "render/render_mesh.h"
|
||||
#include "render/spritesheet.h"
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
||||
class Shipset;
|
||||
class HullDef : public AtomicRefCounted {
|
||||
public:
|
||||
static const unsigned flagExteriorPass = 1 << 31;
|
||||
static const unsigned flagExteriorFaux = 1 << 30;
|
||||
|
||||
unsigned id;
|
||||
std::string ident;
|
||||
std::string name;
|
||||
std::string backgroundName;
|
||||
std::string meshName;
|
||||
std::string materialName;
|
||||
std::string iconName;
|
||||
std::string guiIconName;
|
||||
std::string fleetIconName;
|
||||
std::vector<std::string> tags;
|
||||
std::vector<std::string> subsystems;
|
||||
std::unordered_set<int> numTags;
|
||||
const render::RenderState* background;
|
||||
const render::RenderState* material;
|
||||
const render::RenderMesh* mesh;
|
||||
const render::SpriteSheet* iconSheet;
|
||||
render::Sprite guiIcon;
|
||||
render::Sprite fleetIcon;
|
||||
unsigned iconIndex;
|
||||
vec2i gridSize;
|
||||
recti gridOffset;
|
||||
double backgroundScale;
|
||||
double modelScale;
|
||||
HexGrid<bool> active;
|
||||
HexGrid<int> exterior;
|
||||
unsigned activeCount;
|
||||
unsigned exteriorCount;
|
||||
double minSize;
|
||||
double maxSize;
|
||||
mutable heldPointer<const HullDef> baseHull;
|
||||
Image* shape;
|
||||
std::string shapeMap;
|
||||
bool shapeMapped;
|
||||
bool special;
|
||||
|
||||
HullDef();
|
||||
HullDef(const HullDef& other);
|
||||
|
||||
std::vector<vec3d> impacts;
|
||||
bool calculateImpacts();
|
||||
vec3d getImpact(const vec3d& offset, double radius, bool constant = false) const;
|
||||
vec3d getClosestImpact(const vec3d& offset) const;
|
||||
|
||||
void calculateDist();
|
||||
void calculateDist(vec2u pos, int dist);
|
||||
void calculateExterior();
|
||||
void calculateExterior(vec2u pos, unsigned direction);
|
||||
|
||||
bool checkConnected();
|
||||
void fillConnected(HexGrid<bool>& connected, vec2u hex);
|
||||
|
||||
double getMatchDistance(const vec2d& pos) const;
|
||||
double getMatchDistance(void* desc) const;
|
||||
|
||||
bool isExterior(const vec2u& hex) const;
|
||||
bool isExteriorInDirection(const vec2u& hex, unsigned dir) const;
|
||||
|
||||
bool hasTag(const std::string& tag) const;
|
||||
};
|
||||
|
||||
struct ShipSkin {
|
||||
std::string ident;
|
||||
const render::RenderState* material;
|
||||
const render::RenderMesh* mesh;
|
||||
render::Sprite icon;
|
||||
};
|
||||
|
||||
class Shipset : public AtomicRefCounted {
|
||||
public:
|
||||
unsigned id;
|
||||
std::vector<const HullDef*> hulls;
|
||||
bool available;
|
||||
std::string ident, name;
|
||||
std::string dlc;
|
||||
|
||||
std::unordered_map<std::string,ShipSkin*> skins;
|
||||
|
||||
~Shipset();
|
||||
ShipSkin* getSkin(const std::string& name) const;
|
||||
|
||||
bool hasHull(const HullDef* hull) const;
|
||||
|
||||
unsigned getHullCount() const;
|
||||
const HullDef* getHull(unsigned index) const;
|
||||
const HullDef* getHull(const std::string& ident) const;
|
||||
};
|
||||
|
||||
void loadHullDefinitions(const std::string& filename);
|
||||
void clearHullDefinitions();
|
||||
|
||||
void readHullDefinitions(const std::string& filename, std::vector<HullDef*>& hulls);
|
||||
void writeHullDefinitions(const std::string& filename, std::vector<HullDef*>& hulls);
|
||||
|
||||
unsigned getHullCount();
|
||||
const HullDef* getHullDefinition(unsigned id);
|
||||
const HullDef* getHullDefinition(const std::string& ident);
|
||||
|
||||
void computeHulls(unsigned amount);
|
||||
bool isFinishedComputingHulls();
|
||||
|
||||
void loadShipset(const std::string& filename);
|
||||
void initAllShipset();
|
||||
void clearShipsets();
|
||||
|
||||
unsigned getShipsetCount();
|
||||
const Shipset* getShipset(unsigned id);
|
||||
const Shipset* getShipset(const std::string& ident);
|
||||
@@ -0,0 +1,749 @@
|
||||
#include "projectiles.h"
|
||||
#include "line3d.h"
|
||||
#include "main/references.h"
|
||||
#include "processing.h"
|
||||
#include "physics/physics_world.h"
|
||||
#include "obj/object.h"
|
||||
#include "empire.h"
|
||||
#include "design/effector.h"
|
||||
#include "threads.h"
|
||||
#include "scene/node.h"
|
||||
#include "scene/particle_system.h"
|
||||
#include "memory/AllocOnlyPool.h"
|
||||
#include "threads.h"
|
||||
#include "obj/lock.h"
|
||||
#include "scene/animation/anim_projectile.h"
|
||||
#include "util/save_file.h"
|
||||
#include "design/design.h"
|
||||
#include "obj/blueprint.h"
|
||||
#include "network/network_manager.h"
|
||||
#include "ISound.h"
|
||||
#include "ISoundDevice.h"
|
||||
#include <climits>
|
||||
#include <algorithm>
|
||||
|
||||
memory::AllocOnlyPool<Projectile,threads::Mutex> projPool(8192);
|
||||
|
||||
void initNewThread();
|
||||
void cleanupThread();
|
||||
|
||||
struct ProjEffect : public ObjectMessage {
|
||||
Object* source;
|
||||
const Effector* effector;
|
||||
vec3d relImpact;
|
||||
float effectiveness;
|
||||
float partiality;
|
||||
float delay;
|
||||
|
||||
ProjEffect(const Effector* fctr, Object* target, Object* src, vec3d relImpactPt, float eff, float part, float del) : ObjectMessage(target), source(src), effector(fctr), relImpact(relImpactPt), effectiveness(eff), partiality(part), delay(del) {
|
||||
source->grab();
|
||||
effector->grab();
|
||||
}
|
||||
|
||||
~ProjEffect() {
|
||||
object->drop();
|
||||
source->drop();
|
||||
effector->drop();
|
||||
}
|
||||
|
||||
void process() override {
|
||||
effector->triggerEffect(source, object, relImpact, effectiveness, partiality, delay);
|
||||
}
|
||||
};
|
||||
|
||||
struct ProjImpactEffect : public scene::NodeEvent {
|
||||
const scene::ParticleSystemDesc* system;
|
||||
vec3d from, vel;
|
||||
float scale;
|
||||
float delay;
|
||||
|
||||
ProjImpactEffect(scene::Node* parentNode, const scene::ParticleSystemDesc* sys, const vec3d& From, const vec3d& Vel, float Scale, float Delay) : NodeEvent(parentNode), system(sys), from(From), vel(Vel), scale(Scale), delay(Delay) {
|
||||
}
|
||||
|
||||
void process() override {
|
||||
vec3d pos;
|
||||
quaterniond rot;
|
||||
if(node) {
|
||||
vec3d d = (from - node->abs_position).normalized();
|
||||
pos = (from - node->abs_position);
|
||||
rot = quaterniond::fromImpliedTransform(vec3d::front(), d);
|
||||
}
|
||||
else {
|
||||
pos = from;
|
||||
}
|
||||
auto* pSys = scene::playParticleSystem(system, node, pos, rot, vel, scale, delay);
|
||||
if(pSys)
|
||||
pSys->drop();
|
||||
}
|
||||
|
||||
~ProjImpactEffect() {
|
||||
}
|
||||
};
|
||||
|
||||
double clamp(double v, double low, double high) {
|
||||
if(v <= low)
|
||||
return low;
|
||||
else if(v >= high)
|
||||
return high;
|
||||
else
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline double sqr(double x) {
|
||||
return x * x;
|
||||
}
|
||||
|
||||
//Look for a new objec hostile to the owner
|
||||
Object* findTarget(Empire* owner, const vec3d& position, double radius, const void* ofType = nullptr) {
|
||||
if(!owner)
|
||||
return nullptr;
|
||||
|
||||
Object* obj = nullptr;
|
||||
|
||||
devices.physics->findInBox(AABBoxd::fromCircle(position, radius), [&obj,owner,&position,radius,ofType](const PhysicsItem& item) {
|
||||
if(obj)
|
||||
return;
|
||||
if(item.type == PIT_Object) {
|
||||
Object* target = item.object;
|
||||
if(!target->isVisibleTo(owner) || !target->isValid())
|
||||
return;
|
||||
if(ofType && target->type != ofType)
|
||||
return;
|
||||
if(position.distanceToSQ(target->position) > (radius + target->radius) * (radius + target->radius))
|
||||
return;
|
||||
|
||||
obj = target;
|
||||
obj->grab();
|
||||
}
|
||||
}, owner->hostileMask);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool Projectile::tick(double time) {
|
||||
//Rotate to track the target
|
||||
if(type == PT_Missile) {
|
||||
if(target && target->isValid()) {
|
||||
if(recover <= 0.f) {
|
||||
vec3d dir = (target->position - position).normalized();
|
||||
vec3d projImpact = target->position + dir;
|
||||
if(target->type->blueprintOffset != 0) {
|
||||
auto* blueprint = (Blueprint*)(((size_t)target) + target->type->blueprintOffset);
|
||||
const Design* dsg = blueprint->design;
|
||||
|
||||
if(dsg != nullptr) {
|
||||
vec3d impactOffset = target->rotation.inverted() * dir;
|
||||
projImpact = target->rotation * dsg->hull->getImpact(impactOffset, target->radius, true) + target->position;
|
||||
}
|
||||
}
|
||||
|
||||
dir = (projImpact - position).normalized();
|
||||
double speed = velocity.getLength();
|
||||
vec3d vdir = velocity / speed;
|
||||
|
||||
double angle = acos(clamp(vdir.dot(dir),-1,1)), rot = tracking * time;
|
||||
if(angle < rot) {
|
||||
velocity = dir * speed;
|
||||
}
|
||||
else if(angle >= pi * 0.9) {
|
||||
quaterniond dirq = quaterniond::fromImpliedTransform(vec3d::front(), dir);
|
||||
quaterniond vdirq = quaterniond::fromImpliedTransform(vec3d::front(), vdir);
|
||||
quaterniond result = vdirq.slerp(dirq, rot / angle);
|
||||
velocity = (result * vec3d::front()).normalized(speed);
|
||||
}
|
||||
else {//if(angle < 0.05) {
|
||||
//Lerp is a good enough approximation
|
||||
// -- It really is not :( stupid missiles can't even turn 180 degrees
|
||||
velocity = vdir.interpolate(dir, rot / angle).normalized(speed);
|
||||
}
|
||||
|
||||
if(graphics) {
|
||||
auto* anim = (scene::ProjectileAnim*)graphics->animator.ptr;
|
||||
anim->velocity = velocity;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(source && source->isValid()) {
|
||||
if(target) {
|
||||
auto* prevTarget = target;
|
||||
target = findTarget(source->owner, target->position, 50.0, target->type);
|
||||
prevTarget->drop();
|
||||
}
|
||||
else {
|
||||
target = findTarget(source->owner, position, 50.0);
|
||||
}
|
||||
|
||||
//If we didn't quickly find a target, we die twice as fast
|
||||
if(target == 0 && !effector->type.pierces)
|
||||
lifetime *= 0.5f;
|
||||
}
|
||||
else {
|
||||
//Become a dummy when the target is already dead
|
||||
//Also live only half the remaining time
|
||||
lifetime *= 0.5f;
|
||||
if(target) {
|
||||
target->drop();
|
||||
target = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(type == PT_Beam) {
|
||||
//Beams can't fire when the source is dead
|
||||
if(!source->isValid())
|
||||
return true;
|
||||
|
||||
if(target) {
|
||||
if(target->isValid()) {
|
||||
if(tracking > 0) {
|
||||
vec3d targDir = (target->position - (source->position + position)).normalize();
|
||||
double range = velocity.getLength();
|
||||
|
||||
vec3d curDir = velocity / range;
|
||||
|
||||
double dot = std::max(std::min(targDir.dot(curDir), 1.0), -1.0);
|
||||
double angDiff = acos(dot);
|
||||
double track = tracking * time;
|
||||
|
||||
if(angDiff <= track)
|
||||
velocity = targDir * range;
|
||||
else
|
||||
velocity = curDir.slerp(targDir,track/angDiff) * range;
|
||||
}
|
||||
}
|
||||
else {
|
||||
//Our current target is dead, find a new one
|
||||
auto* prevTarget = target;
|
||||
if(source)
|
||||
target = findTarget(source->owner, target->position, 50.0, target->type);
|
||||
else
|
||||
target = 0;
|
||||
prevTarget->drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
line3dd line(position, vec3d());
|
||||
|
||||
if(type != PT_Beam) {
|
||||
line.end = position + velocity * time;
|
||||
}
|
||||
else {
|
||||
line.start += source->position;
|
||||
line.end = line.start + velocity;
|
||||
}
|
||||
|
||||
Object* other = 0;
|
||||
vec3d impactPt;
|
||||
|
||||
double prevDistSQ = 0;
|
||||
double projScale = scale;
|
||||
|
||||
//If our line still collides with our last impacted object, we can check for only intervening objects
|
||||
if(lastImpact && lastImpact->isValid()) {
|
||||
vec3d closePt = line.getClosestPoint(lastImpact->position,false);
|
||||
double distSQ = closePt.distanceToSQ(lastImpact->position);
|
||||
double width = lastImpact->radius + scale;
|
||||
if(distSQ <= width * width) {
|
||||
other = lastImpact;
|
||||
other->grab();
|
||||
impactPt = closePt - line.getDirection() * sqrt(width*width - distSQ);
|
||||
if(!effector || !effector->type.pierces)
|
||||
line.end = impactPt;
|
||||
prevDistSQ = closePt.distanceToSQ(line.start);
|
||||
}
|
||||
}
|
||||
|
||||
if(mode != PM_OnlyHitsTarget) {
|
||||
if(mode == PM_PassthroughInvalid) {
|
||||
const Effector* eff = effector;
|
||||
Object* src = source;
|
||||
devices.physics->findInBox(AABBoxd(line, scale), [&other,&line,&prevDistSQ,&impactPt,projScale,eff,src](const PhysicsItem& item) {
|
||||
//if(item.type != PIT_Object)
|
||||
// return;
|
||||
Object* obj = item.object;
|
||||
vec3d closePt = line.getClosestPoint(obj->position,false);
|
||||
double distSQ = closePt.distanceToSQ(obj->position);
|
||||
double width = obj->radius + projScale;
|
||||
if(distSQ <= width * width && (!eff || eff->canTarget(src, obj))) {
|
||||
double objDistSQ = closePt.distanceToSQ(line.start);
|
||||
if(!other || objDistSQ < prevDistSQ) {
|
||||
if(other)
|
||||
other->drop();
|
||||
other = obj;
|
||||
other->grab();
|
||||
prevDistSQ = objDistSQ;
|
||||
impactPt = closePt - line.getDirection() * sqrt(width*width - distSQ);
|
||||
}
|
||||
}
|
||||
}, source->owner ? (source->owner->hostileMask | 0x1) : ~0x0);
|
||||
}
|
||||
else {
|
||||
devices.physics->findInBox(AABBoxd(line, scale), [&other,&line,&prevDistSQ,&impactPt,projScale](const PhysicsItem& item) {
|
||||
//if(item.type != PIT_Object)
|
||||
// return;
|
||||
Object* obj = item.object;
|
||||
vec3d closePt = line.getClosestPoint(obj->position,false);
|
||||
double distSQ = closePt.distanceToSQ(obj->position);
|
||||
double width = obj->radius + projScale;
|
||||
if(distSQ <= width * width) {
|
||||
double objDistSQ = closePt.distanceToSQ(line.start);
|
||||
if(!other || objDistSQ < prevDistSQ) {
|
||||
if(other)
|
||||
other->drop();
|
||||
other = obj;
|
||||
other->grab();
|
||||
prevDistSQ = objDistSQ;
|
||||
impactPt = closePt - line.getDirection() * sqrt(width*width - distSQ);
|
||||
}
|
||||
}
|
||||
}, source->owner ? (source->owner->hostileMask | 0x1) : ~0x0);
|
||||
}
|
||||
}
|
||||
else if(target && target->isValid()){
|
||||
vec3d closePt = line.getClosestPoint(target->position,false);
|
||||
double distSQ = closePt.distanceToSQ(target->position);
|
||||
double width = target->radius + projScale;
|
||||
if(distSQ <= width * width) {
|
||||
if(other)
|
||||
other->drop();
|
||||
other = target;
|
||||
other->grab();
|
||||
impactPt = closePt - line.getDirection() * sqrt(width*width - distSQ);
|
||||
}
|
||||
}
|
||||
|
||||
if(other != nullptr && other->type->blueprintOffset != 0) {
|
||||
auto* blueprint = (Blueprint*)(((size_t)other) + other->type->blueprintOffset);
|
||||
const Design* dsg = blueprint->design;
|
||||
|
||||
if(dsg != nullptr) {
|
||||
vec3d impactOffset = impactPt - other->position;
|
||||
impactOffset = other->rotation.inverted() * impactOffset;
|
||||
impactPt = other->rotation * dsg->hull->getImpact(impactOffset, other->radius, type == PT_Beam) + other->position;
|
||||
}
|
||||
}
|
||||
|
||||
if(recover > 0.f) {
|
||||
if(other && other == lastImpact) {
|
||||
other->drop();
|
||||
other = nullptr;
|
||||
}
|
||||
recover -= time;
|
||||
if(recover <= 0.f)
|
||||
recover = 0.f;
|
||||
}
|
||||
|
||||
if(other) {
|
||||
if(impact)
|
||||
*impact = impactPt;
|
||||
if(type == PT_Beam || effector->type.pierces) {
|
||||
other->grab();
|
||||
if(lastImpact)
|
||||
lastImpact->drop();
|
||||
lastImpact = other;
|
||||
}
|
||||
|
||||
auto* player = Empire::getPlayerEmpire();
|
||||
|
||||
if(effector) {
|
||||
if(player && other->isVisibleTo(player)) {
|
||||
if(auto* sfx = effector->type.skins[effector->skinIndex].impact_sound) {
|
||||
if(sfx->loaded && !audio::disableSFX) {
|
||||
auto* sound = devices.sound->play3D(sfx->source, snd_vec(other->position), false, true);
|
||||
if(sound) {
|
||||
if(source)
|
||||
sound->setVolume((float)((scale + 1.0) / (scale + 7.0)));
|
||||
sound->setPitch((float)randomd(0.95,1.05));
|
||||
float dist = other->position.distanceTo(devices.render->cam_pos);
|
||||
float lo = dist / (dist + scale * 500.0);
|
||||
if(lo > 0.05)
|
||||
sound->setLowPass(lo);
|
||||
sound->resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LockGroup* lockGroup = other->lockGroup;
|
||||
float delay = 0.f;
|
||||
if(effector && lockGroup) {
|
||||
if(type == PT_Missile && missileData) {
|
||||
double speed = velocity.getLength();
|
||||
delay = impactPt.distanceTo(position) / speed * 1.5f;
|
||||
}
|
||||
|
||||
if(effector->type.skins[effector->skinIndex].impact && (!player || other->isVisibleTo(player)))
|
||||
scene::queueNodeEvent(new ProjImpactEffect(other->node, effector->type.skins[effector->skinIndex].impact, impactPt, other->velocity, (float)sqrt(effector->relativeSize * (source ? source->radius : 1.0)), delay));
|
||||
if(!devices.network->isClient)
|
||||
lockGroup->addMessage(new ProjEffect(effector, other, source, impactPt - other->position, efficiency, type == PT_Beam ? (float)time : 1.f, delay));
|
||||
else
|
||||
other->drop();
|
||||
}
|
||||
else {
|
||||
other->drop();
|
||||
}
|
||||
|
||||
if(type != PT_Beam) {
|
||||
if(effector && effector->type.pierces) {
|
||||
recover = effector->type.recoverTime;
|
||||
if(target && other == target) {
|
||||
target->drop();
|
||||
target = nullptr;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(type == PT_Missile && missileData) {
|
||||
auto& data = **missileData;
|
||||
double gameTime = devices.driver->getGameTime();
|
||||
double speed = velocity.getLength();
|
||||
|
||||
data.lastUpdate = gameTime;
|
||||
data.aliveUntil = gameTime + delay;
|
||||
data.pos = impactPt;
|
||||
data.vel = vec3f((impactPt - position).normalized(speed));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(impact) {
|
||||
*impact = line.end;
|
||||
}
|
||||
|
||||
if(type != PT_Beam) {
|
||||
position = line.end;
|
||||
if(type == PT_Missile && missileData) {
|
||||
auto& data = **missileData;
|
||||
data.lastUpdate = devices.driver->getGameTime();
|
||||
data.pos = position;
|
||||
data.vel = vec3f(velocity);
|
||||
}
|
||||
}
|
||||
lifetime -= (float)time;
|
||||
return lifetime <= 0;
|
||||
}
|
||||
|
||||
void* Projectile::operator new(size_t size) {
|
||||
return projPool.alloc();
|
||||
}
|
||||
|
||||
void Projectile::operator delete(void* p) {
|
||||
return projPool.dealloc((Projectile*)p);
|
||||
}
|
||||
|
||||
Projectile::Projectile(ProjType Type) : lastTick(devices.driver->getGameTime()), source(0), target(0), lastImpact(0), graphics(0), type(Type), impact(0), efficiency(1.f), endNotice(0), mode(PM_Normal), recover(0.f) {
|
||||
}
|
||||
|
||||
Projectile::~Projectile() {
|
||||
if(graphics) {
|
||||
graphics->markForDeletion();
|
||||
graphics->drop();
|
||||
}
|
||||
|
||||
if(type != PT_Missile) {
|
||||
if(endNotice) {
|
||||
**endNotice = true;
|
||||
endNotice->drop();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(missileData) {
|
||||
if((*missileData)->aliveUntil < 0)
|
||||
(*missileData)->aliveUntil = 0;
|
||||
missileData->drop();
|
||||
}
|
||||
}
|
||||
|
||||
if(source)
|
||||
source->drop();
|
||||
if(target)
|
||||
target->drop();
|
||||
if(lastImpact)
|
||||
lastImpact->drop();
|
||||
if(effector)
|
||||
effector->drop();
|
||||
}
|
||||
|
||||
extern double frameTime_s;
|
||||
Projectile* Projectile::load(SaveFile& file) {
|
||||
const Effector* efftr = nullptr;
|
||||
|
||||
unsigned char empID;
|
||||
int dsgId;
|
||||
unsigned subsysIndex, effectorIndex, effId;
|
||||
if((bool)file) {
|
||||
empID = file;
|
||||
if(empID == INVALID_EMPIRE)
|
||||
return 0;
|
||||
|
||||
dsgId = file;
|
||||
subsysIndex = file;
|
||||
effectorIndex = file;
|
||||
|
||||
Empire* dsgOwner = Empire::getEmpireByID(empID);
|
||||
const Design* dsg = dsgOwner->getDesign(dsgId);
|
||||
if(dsg == nullptr || subsysIndex >= dsg->subsystems.size() || effectorIndex >= dsg->subsystems[subsysIndex].type->effectors.size()) {
|
||||
//throw "Invalid projectile effector.";
|
||||
// Just cancel the projectile later so we can continue the load.
|
||||
}
|
||||
else {
|
||||
efftr = &dsg->subsystems[subsysIndex].effectors[effectorIndex]; //No refcounting on design effectors
|
||||
}
|
||||
}
|
||||
else {
|
||||
effId = file;
|
||||
efftr = getEffector(effId); //Reference is transfered to projectile
|
||||
}
|
||||
|
||||
//Find effector
|
||||
Projectile* proj = new Projectile((ProjType)file.read<unsigned char>());
|
||||
proj->effector = efftr;
|
||||
|
||||
//Load data
|
||||
file >> proj->lastTick;
|
||||
file >> proj->lifetime;
|
||||
proj->source = file.readExistingObject();
|
||||
proj->target = file.readExistingObject();
|
||||
file >> proj->position;
|
||||
file >> proj->velocity;
|
||||
file >> proj->scale;
|
||||
file >> proj->tracking;
|
||||
file >> proj->efficiency;
|
||||
|
||||
//Actually screw this, these things cause nothing but problems
|
||||
delete proj;
|
||||
return 0;
|
||||
|
||||
if(!proj->source || !proj->effector) {
|
||||
//This can happen if the object that fired the projectile
|
||||
//was already destroyed when the save occured. We have no way
|
||||
//of instantiating these or their effects at the moment, because
|
||||
//we know absolutely nothing about the source.
|
||||
|
||||
//TODO: Preserve these in some way, they can still impact and matter
|
||||
//quite a bit.
|
||||
delete proj;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//TODO: The effects are non-trivial, figure out how to restore them
|
||||
//Create graphics
|
||||
/*proj->graphics = proj->effector->type.createGraphics(proj->effector, proj->scale);
|
||||
if(!proj->graphics) {
|
||||
delete proj;
|
||||
throw "Invalid projectile effector graphics.";
|
||||
}
|
||||
|
||||
proj->graphics->position = proj->position;
|
||||
proj->graphics->rebuildTransformation();
|
||||
proj->graphics->animator = new scene::ProjectileAnim(proj->velocity, 1.f);
|
||||
|
||||
registerProjectile(proj);
|
||||
proj->graphics->queueReparent(devices.scene);
|
||||
return proj;*/
|
||||
registerProjectile(proj);
|
||||
return proj;
|
||||
}
|
||||
|
||||
void Projectile::save(SaveFile& file) {
|
||||
if(effector && effector->effectorId != 0) {
|
||||
file << false;
|
||||
file << effector->effectorId;
|
||||
}
|
||||
else {
|
||||
file << true;
|
||||
if(!effector || !effector->inDesign || !effector->inDesign->owner) {
|
||||
file << INVALID_EMPIRE;
|
||||
return;
|
||||
}
|
||||
|
||||
file << effector->inDesign->owner->id;
|
||||
file << effector->inDesign->id;
|
||||
file << effector->subsysIndex;
|
||||
file << effector->effectorIndex;
|
||||
}
|
||||
|
||||
file << (unsigned char)type;
|
||||
file << lastTick;
|
||||
file << lifetime;
|
||||
file << source;
|
||||
file << target;
|
||||
file << position;
|
||||
file << velocity;
|
||||
file << scale;
|
||||
file << tracking;
|
||||
file << efficiency;
|
||||
}
|
||||
|
||||
threads::Mutex projAddLock, projfillLock;
|
||||
threads::Signal activeProjThreads;
|
||||
threads::atomic_int pull_index, push_index, processed_count;
|
||||
std::vector<double> projThreadTimes;
|
||||
|
||||
std::vector<Projectile*>* source = new std::vector<Projectile*>, *dest = new std::vector<Projectile*>;
|
||||
std::vector<Projectile*> queuedProjs;
|
||||
|
||||
static void fillProjectiles() {
|
||||
dest->resize(push_index);
|
||||
|
||||
if(!queuedProjs.empty()) {
|
||||
projAddLock.lock();
|
||||
dest->insert(dest->end(), queuedProjs.begin(), queuedProjs.end());
|
||||
queuedProjs.clear();
|
||||
projAddLock.release();
|
||||
}
|
||||
|
||||
std::swap(source, dest);
|
||||
dest->resize(source->size());
|
||||
|
||||
processed_count = 0;
|
||||
push_index = 0;
|
||||
pull_index = 0;
|
||||
}
|
||||
|
||||
volatile bool ProjectilesActive = false;
|
||||
volatile bool EndProjectiles = false;
|
||||
volatile bool PauseProjectiles = false;
|
||||
volatile bool ProjectilesPaused = false;
|
||||
class ProcessProjectiles : public processing::Action {
|
||||
bool host, active;
|
||||
public:
|
||||
ProcessProjectiles(bool Host) : host(Host), active(true) {
|
||||
}
|
||||
|
||||
~ProcessProjectiles() {
|
||||
//This happens when the game is ending. We may not complete an entire cycle at the right time.
|
||||
if(host)
|
||||
ProjectilesActive = false;
|
||||
else if(active)
|
||||
activeProjThreads.signalDown();
|
||||
}
|
||||
|
||||
bool run() {
|
||||
if(host && activeProjThreads.check(0)) {
|
||||
if(EndProjectiles)
|
||||
return true;
|
||||
ProjectilesActive = true;
|
||||
|
||||
if(PauseProjectiles) {
|
||||
ProjectilesActive = false;
|
||||
ProjectilesPaused = true;
|
||||
return false;
|
||||
}
|
||||
else if(ProjectilesPaused) {
|
||||
ProjectilesPaused = false;
|
||||
}
|
||||
else {
|
||||
fillProjectiles();
|
||||
}
|
||||
|
||||
if(!source->empty()) {
|
||||
activeProjThreads.signal(4);
|
||||
for(unsigned i = 0; i < 4; ++i)
|
||||
processing::queueAction(new ProcessProjectiles(false));
|
||||
}
|
||||
else {
|
||||
ProjectilesActive = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
double curTime = devices.driver->getGameTime();
|
||||
unsigned tickMax = 1000;
|
||||
while(tickMax--) {
|
||||
int index = pull_index++;
|
||||
if(index >= (int)source->size()) {
|
||||
if(host) {
|
||||
ProjectilesActive = false;
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
active = false;
|
||||
activeProjThreads.signalDown();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//Take the next projectile, tick it, and put it on the output stack (unless it needs deleted)
|
||||
Projectile* proj = source->at(index);
|
||||
double t = curTime - proj->lastTick;
|
||||
|
||||
if(t < 0.125) {
|
||||
int outIndex = push_index++;
|
||||
dest->at(outIndex) = proj;
|
||||
}
|
||||
else {
|
||||
if(!proj->tick(t)) {
|
||||
proj->lastTick = curTime;
|
||||
int outIndex = push_index++;
|
||||
dest->at(outIndex) = proj;
|
||||
}
|
||||
else {
|
||||
delete proj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
void registerProjectile(Projectile* proj) {
|
||||
projAddLock.lock();
|
||||
queuedProjs.push_back(proj);
|
||||
projAddLock.release();
|
||||
}
|
||||
|
||||
void initProjectiles() {
|
||||
EndProjectiles = false;
|
||||
PauseProjectiles = false;
|
||||
processing::queueAction(new ProcessProjectiles(true));
|
||||
}
|
||||
|
||||
void stopProjectiles() {
|
||||
EndProjectiles = true;
|
||||
while(ProjectilesActive)
|
||||
threads::sleep(1);
|
||||
activeProjThreads.wait(0);
|
||||
source->clear(); source->shrink_to_fit();
|
||||
dest->clear(); dest->shrink_to_fit();
|
||||
for(auto i = queuedProjs.begin(), end = queuedProjs.end(); i != end; ++i)
|
||||
delete *i;
|
||||
queuedProjs.clear();
|
||||
pull_index = 0;
|
||||
push_index = 0;
|
||||
processed_count = 0;
|
||||
}
|
||||
|
||||
void saveProjectiles(SaveFile& file) {
|
||||
unsigned cnt = (unsigned)(queuedProjs.size() + push_index);
|
||||
file << cnt;
|
||||
|
||||
cnt = (unsigned)queuedProjs.size();
|
||||
for(unsigned i = 0; i < cnt; ++i)
|
||||
queuedProjs[i]->save(file);
|
||||
|
||||
cnt = (unsigned)push_index;
|
||||
for(unsigned i = 0; i < cnt; ++i)
|
||||
(*dest)[i]->save(file);
|
||||
}
|
||||
|
||||
void loadProjectiles(SaveFile& file) {
|
||||
unsigned cnt = file;
|
||||
for(unsigned i = 0; i < cnt; ++i)
|
||||
Projectile::load(file);
|
||||
}
|
||||
|
||||
void pauseProjectiles() {
|
||||
PauseProjectiles = true;
|
||||
if(!ProjectilesActive)
|
||||
return;
|
||||
activeProjThreads.wait(0);
|
||||
while(!ProjectilesPaused && ProjectilesActive)
|
||||
threads::sleep(1);
|
||||
}
|
||||
|
||||
void resumeProjectiles() {
|
||||
PauseProjectiles = false;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
#include "vec3.h"
|
||||
#include "threads.h"
|
||||
#include <stddef.h>
|
||||
|
||||
namespace scene {
|
||||
class Node;
|
||||
};
|
||||
|
||||
struct MissileData {
|
||||
vec3d pos;
|
||||
double lastUpdate;
|
||||
vec3f vel;
|
||||
double aliveUntil;
|
||||
};
|
||||
|
||||
class Effector;
|
||||
class Object;
|
||||
class SaveFile;
|
||||
|
||||
enum ProjType {
|
||||
PT_Bullet,
|
||||
PT_Beam,
|
||||
PT_Missile
|
||||
};
|
||||
|
||||
enum ProjMode {
|
||||
PM_Normal,
|
||||
PM_OnlyHitsTarget,
|
||||
PM_PassthroughInvalid
|
||||
};
|
||||
|
||||
struct Projectile {
|
||||
//NOTE: Velocity is reused to specify a beam's target
|
||||
vec3d position, velocity;
|
||||
|
||||
double lastTick;
|
||||
|
||||
vec3d* impact;
|
||||
const Effector* effector;
|
||||
Object* source, *target, *lastImpact;
|
||||
scene::Node* graphics;
|
||||
union {
|
||||
threads::SharedData<bool>* endNotice;
|
||||
threads::SharedData<MissileData>* missileData;
|
||||
};
|
||||
|
||||
float lifetime;
|
||||
float scale;
|
||||
|
||||
//radians per second of turning
|
||||
float tracking;
|
||||
|
||||
//efficiency of the subsystem
|
||||
float efficiency;
|
||||
|
||||
//recovery time in which nothing can be hit
|
||||
float recover;
|
||||
|
||||
ProjType type;
|
||||
ProjMode mode;
|
||||
|
||||
//Advances projectile and performs collision
|
||||
//Returns true if the projectile is dead
|
||||
bool tick(double time);
|
||||
|
||||
Projectile(ProjType Type);
|
||||
~Projectile();
|
||||
|
||||
static Projectile* load(SaveFile& file);
|
||||
void save(SaveFile& file);
|
||||
|
||||
void* operator new(size_t size);
|
||||
void operator delete(void*);
|
||||
};
|
||||
|
||||
void registerProjectile(Projectile* proj);
|
||||
void initProjectiles();
|
||||
void stopProjectiles();
|
||||
|
||||
void saveProjectiles(SaveFile& file);
|
||||
void loadProjectiles(SaveFile& file);
|
||||
|
||||
void pauseProjectiles();
|
||||
void resumeProjectiles();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,398 @@
|
||||
#pragma once
|
||||
#include "design/effect.h"
|
||||
#include "design/effector.h"
|
||||
#include "vec2.h"
|
||||
#include "util/formula.h"
|
||||
#include "util/basic_type.h"
|
||||
#include "compat/misc.h"
|
||||
#include "render/spritesheet.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include "util/hex_grid.h"
|
||||
|
||||
class SaveFile;
|
||||
|
||||
#ifndef MODIFY_STAGE_MAXARGS
|
||||
#define MODIFY_STAGE_MAXARGS 8
|
||||
#endif
|
||||
|
||||
enum SysVariableType {
|
||||
SVT_SubsystemVariable,
|
||||
SVT_HexVariable,
|
||||
SVT_ShipVariable,
|
||||
};
|
||||
|
||||
enum TemplateCondition {
|
||||
TC_Tag,
|
||||
TC_Modifier,
|
||||
TC_Variable,
|
||||
TC_HexVariable,
|
||||
TC_ShipVariable,
|
||||
TC_Subsystem,
|
||||
|
||||
TC_NOT = 1<<20,
|
||||
};
|
||||
|
||||
class Design;
|
||||
class Subsystem;
|
||||
class HullDef;
|
||||
class SubsystemDef {
|
||||
public:
|
||||
struct Variable {
|
||||
Formula* formula;
|
||||
std::string str_formula;
|
||||
std::string name;
|
||||
int index;
|
||||
int globalId;
|
||||
SysVariableType type;
|
||||
bool dependent;
|
||||
};
|
||||
|
||||
struct Effect {
|
||||
const EffectDef* type;
|
||||
std::vector<Formula*> values;
|
||||
std::vector<std::string> str_values;
|
||||
};
|
||||
|
||||
struct Effector {
|
||||
const EffectorDef* type;
|
||||
bool enabled;
|
||||
unsigned skinIndex;
|
||||
std::string skinName;
|
||||
std::vector<Formula*> values;
|
||||
std::vector<std::string> str_values;
|
||||
};
|
||||
|
||||
struct StateDesc {
|
||||
BasicTypes type;
|
||||
Formula* formula;
|
||||
std::string str_formula;
|
||||
};
|
||||
|
||||
struct HookDesc {
|
||||
std::string name;
|
||||
std::vector<std::string> str_args;
|
||||
std::vector<Formula*> formulas;
|
||||
mutable std::vector<double> argValues;
|
||||
|
||||
HookDesc() {}
|
||||
HookDesc(const std::string& str);
|
||||
};
|
||||
|
||||
struct ModifyStage {
|
||||
unsigned index;
|
||||
int stage;
|
||||
int umodifid;
|
||||
std::unordered_map<std::string, int> argumentNames;
|
||||
std::vector<std::pair<int, std::string>> str_variables;
|
||||
std::vector<std::pair<int, std::string>> str_hexVariables;
|
||||
std::vector<std::pair<int, std::string>> str_shipVariables;
|
||||
std::unordered_map<int, Formula*> variables;
|
||||
std::unordered_map<int, Formula*> hexVariables;
|
||||
std::unordered_map<int, Formula*> shipVariables;
|
||||
|
||||
ModifyStage() : stage(0), umodifid(-1), index(0) {}
|
||||
|
||||
void applyVariables(Subsystem* sys) const;
|
||||
void applyHexVariables(Subsystem* sys, int hexIndex) const;
|
||||
void applyShipVariables(Design* dsg, Subsystem* sys) const;
|
||||
};
|
||||
|
||||
struct AppliedStage {
|
||||
const ModifyStage* stage;
|
||||
float arguments[MODIFY_STAGE_MAXARGS];
|
||||
Formula* formulas[MODIFY_STAGE_MAXARGS];
|
||||
|
||||
void clear() {
|
||||
for(unsigned i = 0; i < MODIFY_STAGE_MAXARGS; ++i) {
|
||||
if(formulas[i]) {
|
||||
delete formulas[i];
|
||||
formulas[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppliedStage() {
|
||||
memset(this, 0, sizeof(AppliedStage));
|
||||
}
|
||||
};
|
||||
|
||||
struct ShipModifier {
|
||||
AppliedStage stage;
|
||||
std::string modifyName;
|
||||
std::vector<std::string> str_arguments;
|
||||
std::vector<std::pair<int,std::string>> conditions;
|
||||
};
|
||||
|
||||
struct Assert {
|
||||
std::string str_formula;
|
||||
Formula* formula;
|
||||
std::string message;
|
||||
bool fatal;
|
||||
bool unique;
|
||||
};
|
||||
|
||||
struct ModuleDesc {
|
||||
int index;
|
||||
std::string id;
|
||||
std::string name;
|
||||
std::string description;
|
||||
Color color;
|
||||
|
||||
std::string umodident;
|
||||
int umodid;
|
||||
|
||||
std::string def_onEnable;
|
||||
std::string def_onDisable;
|
||||
|
||||
asIScriptFunction* scr_onEnable;
|
||||
asIScriptFunction* scr_onDisable;
|
||||
|
||||
std::vector<ModifyStage> modifiers;
|
||||
std::vector<ModifyStage> uniqueModifiers;
|
||||
|
||||
std::vector<AppliedStage> appliedStages;
|
||||
std::vector<std::string> str_appliedStages;
|
||||
|
||||
std::vector<AppliedStage> uniqueAppliedStages;
|
||||
std::vector<std::string> str_uniqueAppliedStages;
|
||||
|
||||
std::vector<AppliedStage> hexAppliedStages;
|
||||
std::vector<std::string> str_hexAppliedStages;
|
||||
|
||||
std::vector<SubsystemDef::ShipModifier> adjacentModifiers;
|
||||
std::vector<SubsystemDef::Effect> effects;
|
||||
|
||||
std::unordered_set<std::string> tags;
|
||||
std::unordered_set<int> numTags;
|
||||
std::unordered_map<int, std::vector<std::string>> tagValues;
|
||||
|
||||
bool hasTag(const std::string& tag) const;
|
||||
bool hasTag(int index) const;
|
||||
const std::string& getTagValue(int index, unsigned num = 0) const;
|
||||
unsigned getTagValueCount(int index) const;
|
||||
bool hasTagValue(int index, const std::string& value) const;
|
||||
|
||||
std::string def_onCheckErrors;
|
||||
asIScriptFunction* scr_onCheckErrors;
|
||||
|
||||
bool onCheckErrors(Design* design, Subsystem* sys, const vec2u& hex) const;
|
||||
|
||||
std::vector<HookDesc> hooks;
|
||||
std::vector<Assert> asserts;
|
||||
|
||||
std::string spriteMat;
|
||||
render::Sprite sprite;
|
||||
int drawMode;
|
||||
|
||||
bool required;
|
||||
bool unique;
|
||||
bool vital;
|
||||
bool defaultUnlock;
|
||||
|
||||
void onEnable(EffectEvent& evt, const vec2u& position) const;
|
||||
void onDisable(EffectEvent& evt, const vec2u& position) const;
|
||||
|
||||
ModuleDesc()
|
||||
: index(-1), scr_onEnable(0), scr_onDisable(0),
|
||||
drawMode(0), required(false), unique(false), vital(false),
|
||||
defaultUnlock(false), scr_onCheckErrors(nullptr) {
|
||||
}
|
||||
};
|
||||
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::string id;
|
||||
int index;
|
||||
int ordering;
|
||||
int damageOrder;
|
||||
|
||||
int elevation;
|
||||
Color baseColor;
|
||||
Color typeColor;
|
||||
|
||||
std::string hexMat;
|
||||
std::string picMat;
|
||||
render::Sprite picture;
|
||||
|
||||
std::vector<int> variableIndices;
|
||||
std::vector<SubsystemDef::Variable> variables;
|
||||
std::vector<int> hexVariableIndices;
|
||||
std::vector<SubsystemDef::Variable> hexVariables;
|
||||
std::vector<int> shipVariableIndices;
|
||||
std::vector<SubsystemDef::Variable> shipVariables;
|
||||
|
||||
std::vector<SubsystemDef::Effect> effects;
|
||||
std::vector<SubsystemDef::Effector> effectors;
|
||||
std::vector<SubsystemDef::StateDesc> states;
|
||||
std::vector<SubsystemDef::ModuleDesc*> modules;
|
||||
std::vector<SubsystemDef::ShipModifier> shipModifiers;
|
||||
std::vector<SubsystemDef::ShipModifier> postModifiers;
|
||||
std::vector<SubsystemDef::ShipModifier> adjacentModifiers;
|
||||
std::unordered_map<std::string, int> moduleIndices;
|
||||
const SubsystemDef::ModuleDesc* defaultModule;
|
||||
const SubsystemDef::ModuleDesc* coreModule;
|
||||
|
||||
std::vector<ModifyStage*> modifiers;
|
||||
std::unordered_map<std::string, ModifyStage*> modifierIds;
|
||||
std::vector<Assert> asserts;
|
||||
|
||||
uset<std::string> tags;
|
||||
std::vector<std::string> hullTags;
|
||||
|
||||
uset<int> numTags;
|
||||
std::unordered_map<int, std::vector<std::string>> tagValues;
|
||||
|
||||
void finalize();
|
||||
bool hasTag(const std::string& tag) const;
|
||||
bool hasTag(int index) const;
|
||||
const std::string& getTagValue(int index, unsigned num = 0) const;
|
||||
unsigned getTagValueCount(int index) const;
|
||||
bool hasTagValue(int index, const std::string& value) const;
|
||||
|
||||
std::string def_onCheckErrors;
|
||||
asIScriptFunction* scr_onCheckErrors;
|
||||
|
||||
std::vector<HookDesc> hooks;
|
||||
|
||||
bool onCheckErrors(Design* design, Subsystem* sys) const;
|
||||
bool canUseOn(const HullDef* hull) const;
|
||||
bool hasHullTag(const std::string& tag) const;
|
||||
|
||||
bool hasCore;
|
||||
bool isContiguous;
|
||||
bool exteriorCore;
|
||||
bool defaultUnlock;
|
||||
bool isHull;
|
||||
bool isApplied;
|
||||
bool hexLimitArc;
|
||||
bool passExterior;
|
||||
bool fauxExterior;
|
||||
bool alwaysTakeDamage;
|
||||
|
||||
SubsystemDef();
|
||||
~SubsystemDef();
|
||||
};
|
||||
|
||||
extern int SV_Size, HV_Resistance, HV_HP, ShV_HexSize;
|
||||
extern umap<std::string, int> subsystemIndices;
|
||||
extern umap<std::string, int> variableIndices;
|
||||
extern umap<std::string, int> hexVariableIndices;
|
||||
extern umap<std::string, int> shipVariableIndices;
|
||||
|
||||
void clearSubsystemDefinitions();
|
||||
void loadSubsystemDefinitions(const std::string& filename);
|
||||
const SubsystemDef* getSubsystemDef(const std::string& name);
|
||||
const SubsystemDef* getSubsystemDef(int id);
|
||||
int getSubsystemDefCount();
|
||||
void enumerateVariables(std::function<void(const std::string&,int)>);
|
||||
int getVariableIndex(const std::string& name);
|
||||
const std::string& getVariableId(int index);
|
||||
void enumerateHexVariables(std::function<void(const std::string&,int)>);
|
||||
int getHexVariableIndex(const std::string& name);
|
||||
const std::string& getHexVariableId(int index);
|
||||
void enumerateShipVariables(std::function<void(const std::string&,int)>);
|
||||
unsigned getShipVariableCount();
|
||||
int getShipVariableIndex(const std::string& name);
|
||||
const std::string& getShipVariableId(int index);
|
||||
void enumerateSysTags(std::function<void(const std::string&,int)>);
|
||||
int getSysTagIndex(const std::string& name, bool create = false);
|
||||
|
||||
Formula* parseFormula(const std::string& str, const SubsystemDef* def = 0, const SubsystemDef::ModifyStage* modifier = 0);
|
||||
|
||||
void bindSubsystemMaterials();
|
||||
void bindSubsystemHooks();
|
||||
void finalizeSubsystems();
|
||||
void executeSubsystemTemplates();
|
||||
|
||||
class Blueprint;
|
||||
struct SubsystemEvent {
|
||||
const Subsystem* subsystem;
|
||||
const Design* design;
|
||||
Object* obj;
|
||||
Blueprint* blueprint;
|
||||
void* data;
|
||||
float efficiency;
|
||||
float partiality;
|
||||
};
|
||||
|
||||
class asIScriptFunction;
|
||||
class asIScriptObject;
|
||||
class SaveMessage;
|
||||
class Subsystem {
|
||||
public:
|
||||
const SubsystemDef* type;
|
||||
std::vector<vec2u> hexes;
|
||||
std::vector<const SubsystemDef::ModuleDesc*> modules;
|
||||
std::vector<SubsystemDef::ShipModifier> adjacentModifiers;
|
||||
std::vector<std::vector<SubsystemDef::ShipModifier>> hexAdjacentModifiers;
|
||||
std::vector<int> moduleCounts;
|
||||
vec2u core;
|
||||
vec3d direction;
|
||||
int exteriorHexes;
|
||||
bool hasErrors;
|
||||
|
||||
static asIScriptFunction* ScriptInitFunction;
|
||||
static asIScriptFunction* ScriptHookFunctions[EH_COUNT];
|
||||
|
||||
Effector* effectors;
|
||||
float* variables;
|
||||
float* baseVariables;
|
||||
float* hexVariables;
|
||||
float* hexBaseVariables;
|
||||
BasicType* defaults;
|
||||
unsigned stateOffset;
|
||||
unsigned effectorOffset;
|
||||
unsigned dataOffset;
|
||||
|
||||
std::vector<Effect> effects;
|
||||
std::vector<std::vector<unsigned>> hexEffects;
|
||||
|
||||
std::vector<asIScriptObject*> hookClasses;
|
||||
void addHook(Design* design, const SubsystemDef::HookDesc& desc);
|
||||
|
||||
const Design* inDesign;
|
||||
unsigned index;
|
||||
|
||||
int getModuleCount(int index);
|
||||
|
||||
float* variable(int index);
|
||||
const float* variable(int index) const;
|
||||
float* hexVariable(int index, int hexIndex);
|
||||
const float* hexVariable(int index, int hexIndex) const;
|
||||
|
||||
Subsystem();
|
||||
Subsystem(const SubsystemDef& def);
|
||||
Subsystem(SaveFile& file);
|
||||
~Subsystem();
|
||||
void save(SaveFile& file) const;
|
||||
void postLoad(Design* design);
|
||||
|
||||
void init(const SubsystemDef& def);
|
||||
void init(SaveFile& file);
|
||||
|
||||
void initVariables(Design* design);
|
||||
void initEffects(Design* design);
|
||||
void initLinks(Design* design);
|
||||
void evaluatePost(Design* design);
|
||||
void evaluateAsserts(Design* design);
|
||||
void skinEffectors(Empire& emp);
|
||||
void applyAdjacencies(Design* design);
|
||||
|
||||
DamageEventStatus damage(DamageEvent& event, const vec2u& position) const;
|
||||
DamageEventStatus globalDamage(DamageEvent& event, vec2u& position, vec2d& endPoint) const;
|
||||
bool hasGlobalDamage() const;
|
||||
|
||||
void ownerChange(EffectEvent& event, Empire* prevEmpire, Empire* newEmpire) const;
|
||||
void call(EffectHook hook, EffectEvent& event) const;
|
||||
void tick(EffectEvent& event) const;
|
||||
|
||||
void save(EffectEvent& event, SaveMessage& msg) const;
|
||||
void load(EffectEvent& event, SaveMessage& msg) const;
|
||||
|
||||
void markConnected(HexGrid<bool>& grid, vec2u hex);
|
||||
|
||||
void writeData(net::Message& msg) const;
|
||||
Subsystem(net::Message& msg);
|
||||
};
|
||||
@@ -0,0 +1,114 @@
|
||||
#ifdef BREAKPAD
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
|
||||
static bool dumpCallback(const char* dump_path,
|
||||
const char* minidump_id,
|
||||
void* context,
|
||||
bool succeeded) {
|
||||
printf(
|
||||
"-- NUCLEAR CRASH DETECTED --\n"
|
||||
" Crash dump created at: %s/%s.dmp\n"
|
||||
" Please included this file in your bug report.\n"
|
||||
"----------------------------\n",
|
||||
dump_path, minidump_id);
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
void initCrashDump() {
|
||||
google_breakpad::ExceptionHandler eh("/tmp", NULL, dumpCallback, NULL, true);
|
||||
*(int*)0 = 2;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <stdio.h>
|
||||
#include <execinfo.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "main/logging.h"
|
||||
#include "scripts/context_cache.h"
|
||||
void signal_basic(int sig) {
|
||||
void* btarray[64];
|
||||
size_t btsize;
|
||||
btsize = backtrace(btarray, 64);
|
||||
fprintf(stderr, "Error: caught signal %d:\n", sig);
|
||||
backtrace_symbols_fd(btarray, btsize, 2);
|
||||
abort();
|
||||
}
|
||||
|
||||
void print_trace() {
|
||||
void* btarray[64];
|
||||
size_t btsize;
|
||||
btsize = backtrace(btarray, 64);
|
||||
error("\nStack trace:");
|
||||
char** symbols = backtrace_symbols(btarray, btsize);
|
||||
for(unsigned i = 0; i < btsize; ++i)
|
||||
error(" %s", symbols[i]);
|
||||
}
|
||||
|
||||
//First try logging the stack trace and the script position
|
||||
//that crashed (if applicable). Fall back on basic handler
|
||||
//if this does not work.
|
||||
void signal_ext(int signum, siginfo_t* info, void* arg) {
|
||||
signal(SIGSEGV, signal_basic);
|
||||
|
||||
//Trace
|
||||
error("\nCaught Segfault at %p", info->si_addr);
|
||||
print_trace();
|
||||
|
||||
//Script stack
|
||||
error("");
|
||||
scripts::logException();
|
||||
abort();
|
||||
}
|
||||
|
||||
void exceptionThrown() {
|
||||
static bool first_throw = true;
|
||||
const char* exceptionText = nullptr;
|
||||
|
||||
//Find the exception to try
|
||||
try {
|
||||
if(!first_throw) {
|
||||
exceptionText = "empty exception";
|
||||
}
|
||||
else {
|
||||
first_throw = false;
|
||||
throw; //Haaax
|
||||
}
|
||||
}
|
||||
catch(const char* text) {
|
||||
exceptionText = text;
|
||||
}
|
||||
catch(std::exception const& exc) {
|
||||
exceptionText = exc.what();
|
||||
}
|
||||
catch(...) {
|
||||
exceptionText = "unknown exception";
|
||||
}
|
||||
|
||||
//Print exception text
|
||||
error("\nUnexpected Exception: %s", exceptionText);
|
||||
|
||||
//Trace
|
||||
print_trace();
|
||||
|
||||
//Script stack
|
||||
error("");
|
||||
scripts::logException();
|
||||
abort();
|
||||
}
|
||||
|
||||
void initCrashDump() {
|
||||
struct sigaction act;
|
||||
memset(&act, 0, sizeof(struct sigaction));
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_sigaction = &signal_ext;
|
||||
act.sa_flags = SA_SIGINFO;
|
||||
sigaction(SIGSEGV, &act, nullptr);
|
||||
std::set_terminate(exceptionThrown);
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,193 @@
|
||||
#pragma once
|
||||
#include "threads.h"
|
||||
#include "general_states.h"
|
||||
#include "design/subsystem.h"
|
||||
#include "network/address.h"
|
||||
#include "color.h"
|
||||
#include <deque>
|
||||
#include <stdint.h>
|
||||
#include <unordered_map>
|
||||
|
||||
//Represents the controlling force behind any number of units
|
||||
//Also stores global resources for controlled objects
|
||||
//
|
||||
//Notes:
|
||||
// Creating an empire automatically registers it to the global list
|
||||
class Design;
|
||||
struct DesignClass;
|
||||
class Shipset;
|
||||
typedef uint32_t EmpMask;
|
||||
typedef std::unordered_map<std::string, const Design*> designMap;
|
||||
typedef std::unordered_map<std::string, DesignClass*> designClassMap;
|
||||
typedef designMap::iterator designIterator;
|
||||
typedef designClassMap::iterator designClassIterator;
|
||||
|
||||
extern unsigned validEmpireCount;
|
||||
extern EmpMask currentVision[32];
|
||||
|
||||
namespace render {
|
||||
struct RenderState;
|
||||
};
|
||||
|
||||
namespace net {
|
||||
struct Message;
|
||||
};
|
||||
|
||||
class Empire;
|
||||
|
||||
struct EmpireMessage {
|
||||
virtual ~EmpireMessage() {}
|
||||
|
||||
virtual void process(Empire* emp) {
|
||||
}
|
||||
};
|
||||
|
||||
struct Player;
|
||||
struct StatEntry;
|
||||
class StatHistory;
|
||||
|
||||
class Object;
|
||||
|
||||
const unsigned char INVALID_EMPIRE = 0xff;
|
||||
const unsigned char DEFAULT_EMPIRE = 0xfe;
|
||||
const unsigned char SPECTATOR_EMPIRE = 0xfd;
|
||||
const unsigned char UNLISTED_EMPIRE = 0xfc;
|
||||
|
||||
class Empire {
|
||||
public:
|
||||
struct SubsystemData {
|
||||
bool unlocked;
|
||||
std::vector<bool> modulesUnlocked;
|
||||
std::unordered_map<unsigned, SubsystemDef::AppliedStage> stages;
|
||||
unsigned nextStageId;
|
||||
bool delta;
|
||||
|
||||
SubsystemData() : unlocked(false), nextStageId(0), delta(false) {
|
||||
}
|
||||
|
||||
void write(net::Message& msg);
|
||||
void read(const SubsystemDef* def, net::Message& msg);
|
||||
};
|
||||
|
||||
std::vector<Object*> objects;
|
||||
std::vector<SubsystemData> subsysData;
|
||||
|
||||
threads::Mutex msgLock, processLock;
|
||||
std::deque<EmpireMessage*> messages;
|
||||
void processMessages(unsigned maxMessages = 0xffffffff);
|
||||
void queueMessage(EmpireMessage* msg);
|
||||
|
||||
unsigned char id;
|
||||
int index;
|
||||
unsigned validEmpIndex;
|
||||
std::string name;
|
||||
|
||||
//Bit-mask that represents ownership of an object
|
||||
threads::Mutex maskMutex;
|
||||
EmpMask mask, visionMask, hostileMask;
|
||||
|
||||
//Color used to represent this empire
|
||||
Color color;
|
||||
|
||||
const render::RenderState *background, *portrait, *flag;
|
||||
std::string backgroundDef, portraitDef, flagDef;
|
||||
unsigned flagID;
|
||||
net::Address lastPlayer;
|
||||
Player* player;
|
||||
|
||||
//Stats
|
||||
std::vector<StatHistory*> statHistories;
|
||||
std::vector<threads::ReadWriteMutex> statLocks;
|
||||
void recordStat(unsigned id, int value);
|
||||
void recordStatDelta(unsigned id, int delta);
|
||||
void recordStat(unsigned id, float value);
|
||||
void recordStatDelta(unsigned id, float delta);
|
||||
void recordEvent(unsigned id, unsigned short type, const std::string& name);
|
||||
|
||||
const StatHistory* lockStatHistory(unsigned id);
|
||||
void unlockStatHistory(unsigned id);
|
||||
|
||||
std::vector<render::SpriteSheet*> hullIcons;
|
||||
std::vector<render::SpriteSheet*> hullDistantIcons;
|
||||
std::vector<render::SpriteSheet*> hullFleetIcons;
|
||||
std::vector<Image*> hullImages;
|
||||
unsigned hullIconIndex;
|
||||
|
||||
//Designs used by this empire
|
||||
heldPointer<const Shipset> shipset;
|
||||
std::string effectorSkin;
|
||||
|
||||
threads::ReadWriteMutex designMutex;
|
||||
threads::ReadWriteMutex subsystemDataMutex;
|
||||
std::vector<const Design*> designIds;
|
||||
designMap designs;
|
||||
|
||||
std::vector<DesignClass*> designClassIds;
|
||||
designClassMap designClasses;
|
||||
|
||||
threads::ReadWriteMutex objectLock;
|
||||
void registerObject(Object* obj);
|
||||
void unregisterObject(Object* obj);
|
||||
unsigned objectCount();
|
||||
Object* findObject(unsigned i);
|
||||
|
||||
Empire(unsigned char id = INVALID_EMPIRE);
|
||||
~Empire();
|
||||
|
||||
bool valid();
|
||||
|
||||
void cacheVision();
|
||||
|
||||
//Add a design to the empire's list, cannot
|
||||
//accept duplicate names
|
||||
bool addDesign(DesignClass* cls, const Design* design);
|
||||
bool changeDesign(const Design* older, const Design* newer, DesignClass* cls = nullptr);
|
||||
void setDesign(const Design* older, const Design* newer, DesignClass* cls = nullptr);
|
||||
const Design* updateDesign(const Design* design, bool onlyOutdated);
|
||||
void setDesignUpdate(const Design* older, const Design* newer);
|
||||
void flagDesignOld(const Design* design);
|
||||
void makeDesignIcon(const Design* design);
|
||||
|
||||
//Get the design by name
|
||||
DesignClass* getDesignClass(int id);
|
||||
DesignClass* getDesignClass(const std::string& name, bool add = true);
|
||||
const Design* getDesign(const std::string& name, bool grab = false);
|
||||
const Design* getDesign(unsigned id, bool grab = false);
|
||||
Design* getDesignMake(unsigned id);
|
||||
|
||||
SubsystemData* getSubsystemData(const SubsystemDef* def);
|
||||
|
||||
//Saving and loading
|
||||
Empire(SaveFile& file);
|
||||
void save(SaveFile& file);
|
||||
|
||||
static void saveEmpires(SaveFile& file);
|
||||
static void loadEmpires(SaveFile& file);
|
||||
|
||||
//Network syncing
|
||||
void sendInitial(net::Message& msg);
|
||||
void readDelta(net::Message& msg);
|
||||
void writeDelta(net::Message& msg);
|
||||
|
||||
void sendDesign(net::Message& msg, const Design* dsg, bool fromServer = false);
|
||||
Design* recvDesign(net::Message& msg, bool fromServer = false);
|
||||
|
||||
//Global management
|
||||
static Empire* getDefaultEmpire();
|
||||
static Empire* getSpectatorEmpire();
|
||||
static Empire* getPlayerEmpire();
|
||||
static Empire* getEmpireByIndex(unsigned index);
|
||||
static Empire* getEmpireByID(unsigned char id);
|
||||
|
||||
static unsigned getEmpireCount();
|
||||
|
||||
static void setPlayerEmpire(Empire* emp);
|
||||
static void initEmpires();
|
||||
static void clearEmpires();
|
||||
static void setEmpireStates(const StateDefinition* def);
|
||||
static const StateDefinition* getEmpireStates();
|
||||
|
||||
void* operator new(size_t size);
|
||||
};
|
||||
|
||||
Empire* recvEmpireInitial(net::Message& msg);
|
||||
@@ -0,0 +1,74 @@
|
||||
#include "empire.h"
|
||||
#include "str_util.h"
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
static unsigned nextIndex = 0;
|
||||
std::unordered_map<std::string,unsigned> statIndices;
|
||||
static std::unordered_map<unsigned,std::string> indexNames;
|
||||
|
||||
struct StatType {
|
||||
bool isInt;
|
||||
|
||||
StatType() : isInt(true) {}
|
||||
};
|
||||
|
||||
static std::vector<StatType> types;
|
||||
|
||||
unsigned getEmpireStatCount() {
|
||||
return (unsigned)statIndices.size();
|
||||
}
|
||||
|
||||
std::string getEmpireStatName(unsigned id) {
|
||||
auto iter = indexNames.find(id);
|
||||
if(iter != indexNames.end())
|
||||
return iter->second;
|
||||
else
|
||||
return "<invalid>";
|
||||
}
|
||||
|
||||
unsigned getStatID(const std::string& name) {
|
||||
auto iter = statIndices.find(name);
|
||||
if(iter != statIndices.end())
|
||||
return iter->second;
|
||||
else
|
||||
return 0xffffffff;
|
||||
}
|
||||
|
||||
bool statIsint(unsigned id) {
|
||||
if(id < types.size())
|
||||
return types[id].isInt;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void clearEmpireStats() {
|
||||
nextIndex = 0;
|
||||
statIndices.clear();
|
||||
indexNames.clear();
|
||||
types.clear();
|
||||
}
|
||||
|
||||
void loadEmpireStats(const std::string& filename) {
|
||||
DataHandler data;
|
||||
|
||||
data("Stat", [&](std::string& value) {
|
||||
unsigned index = nextIndex++;
|
||||
statIndices[value] = index;
|
||||
indexNames[index] = value;
|
||||
types.push_back(StatType());
|
||||
});
|
||||
|
||||
data("Type", [&](std::string& value) {
|
||||
if(statIndices.empty())
|
||||
return;
|
||||
|
||||
if(streq_nocase(value, "integer"))
|
||||
types.back().isInt = true;
|
||||
else if(streq_nocase(value, "float"))
|
||||
types.back().isInt = false;
|
||||
});
|
||||
|
||||
data.read(filename);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
unsigned getEmpireStatCount();
|
||||
|
||||
std::string getEmpireStatName(unsigned id);
|
||||
unsigned getStatID(const std::string& name);
|
||||
bool statIsint(unsigned id);
|
||||
|
||||
void clearEmpireStats();
|
||||
void loadEmpireStats(const std::string& filename);
|
||||
@@ -0,0 +1,831 @@
|
||||
#include "general_states.h"
|
||||
#include "str_util.h"
|
||||
#include "main/logging.h"
|
||||
#include "compat/misc.h"
|
||||
#include "util/locked_type.h"
|
||||
#include "util/lockless_type.h"
|
||||
#include "obj/object.h"
|
||||
#include "obj/blueprint.h"
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include "network.h"
|
||||
#include "empire.h"
|
||||
#include "scripts/script_components.h"
|
||||
#include "scripts/generic_call.h"
|
||||
|
||||
StateDefinition errorStateDefinition;
|
||||
std::unordered_map<std::string, StateDefinition*> definitions;
|
||||
std::vector<StateDefinition*> stateDefinitions;
|
||||
|
||||
template<class T>
|
||||
void copySimple(void* dest, void* src) {
|
||||
if(src)
|
||||
*(T*)dest = *(T*)src;
|
||||
else
|
||||
new(dest) T();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void* parseNumber(const std::string& str) {
|
||||
return (void*) new T(toNumber<T>(str));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void defaultConstruct(void* dest, void* src) {
|
||||
new(dest) T();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void destruct(void* mem) {
|
||||
if(mem)
|
||||
((T*)mem)->~T();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void defaultWrite(net::Message& msg, void* mem) {
|
||||
msg << *(T*)mem;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void defaultRead(net::Message& msg, void* mem) {
|
||||
msg >> *(T*)mem;
|
||||
}
|
||||
|
||||
//In bind_network.cpp
|
||||
namespace scripts {
|
||||
extern net::Message& readObjectScr(net::Message& msg, Object** obj);
|
||||
extern net::Message& writeObjectScr(net::Message& msg, Object* obj);
|
||||
extern net::Message& readEmpire(net::Message& msg, Empire** emp);
|
||||
extern net::Message& writeEmpire(net::Message& msg, Empire* emp);
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, StateValueDefinition> stateValueTypes;
|
||||
|
||||
void resetStateValueTypes() {
|
||||
stateValueTypes.clear();
|
||||
|
||||
stateValueTypes["int"].setup(sizeof(int), "int",
|
||||
copySimple<int>,
|
||||
parseNumber<int>,
|
||||
nullptr,
|
||||
defaultWrite<int>,
|
||||
defaultRead<int>,
|
||||
[](asIScriptGeneric* f, void* mem) {
|
||||
if(mem)
|
||||
f->SetReturnDWord(*(asDWORD*)mem);
|
||||
else
|
||||
f->SetReturnDWord((asDWORD)0);
|
||||
});
|
||||
|
||||
stateValueTypes["uint"].setup(sizeof(unsigned), "uint",
|
||||
copySimple<unsigned>,
|
||||
parseNumber<unsigned>,
|
||||
nullptr,
|
||||
defaultWrite<unsigned>,
|
||||
defaultRead<unsigned>,
|
||||
[](asIScriptGeneric* f, void* mem) {
|
||||
if(mem)
|
||||
f->SetReturnDWord(*(asDWORD*)mem);
|
||||
else
|
||||
f->SetReturnDWord((asDWORD)0);
|
||||
});
|
||||
|
||||
stateValueTypes["double"].setup(sizeof(double), "double",
|
||||
copySimple<double>,
|
||||
parseNumber<double>,
|
||||
nullptr,
|
||||
defaultWrite<double>,
|
||||
defaultRead<double>,
|
||||
[](asIScriptGeneric* f, void* mem) {
|
||||
if(mem)
|
||||
f->SetReturnDouble(*(double*)mem);
|
||||
else
|
||||
f->SetReturnDouble((double)0.0);
|
||||
});
|
||||
|
||||
stateValueTypes["float"].setup(sizeof(double), "float",
|
||||
copySimple<float>,
|
||||
parseNumber<float>,
|
||||
nullptr,
|
||||
defaultWrite<float>,
|
||||
defaultRead<float>,
|
||||
[](asIScriptGeneric* f, void* mem) {
|
||||
if(mem)
|
||||
f->SetReturnDouble(*(float*)mem);
|
||||
else
|
||||
f->SetReturnDouble((float)0.0);
|
||||
});
|
||||
|
||||
stateValueTypes["bool"].setup(sizeof(bool), "bool",
|
||||
copySimple<bool>,
|
||||
[](const std::string& str) -> void* {
|
||||
return new bool(toBool(str));
|
||||
},
|
||||
nullptr, defaultWrite<bool>, defaultRead<bool>,
|
||||
[&](asIScriptGeneric* f, void* mem) {
|
||||
if(mem)
|
||||
f->SetReturnByte(*(bool*)mem);
|
||||
else
|
||||
f->SetReturnByte(false);
|
||||
});
|
||||
|
||||
stateValueTypes["locked_int"].setup(sizeof(LocklessInt), "locked_int",
|
||||
[](void* dest, void* src) {
|
||||
if(src)
|
||||
new(dest) LocklessInt(*(LocklessInt*)src);
|
||||
else
|
||||
new(dest) LocklessInt();
|
||||
},
|
||||
[](const std::string& str) -> void* {
|
||||
return new LocklessInt(toNumber<int>(str));
|
||||
},
|
||||
[](void* memory) {
|
||||
((LocklessInt*)memory)->~LocklessInt();
|
||||
},
|
||||
[](net::Message& msg, void* mem) {
|
||||
msg << ((LocklessInt*)mem)->value;
|
||||
},
|
||||
[](net::Message& msg, void* mem) {
|
||||
msg >> ((LocklessInt*)mem)->value;
|
||||
},
|
||||
[](asIScriptGeneric* f, void* mem) {
|
||||
f->SetReturnObject(mem);
|
||||
});
|
||||
|
||||
stateValueTypes["locked_double"].setup(sizeof(LocklessDouble), "locked_double",
|
||||
[](void* dest, void* src) {
|
||||
if(src)
|
||||
new(dest) LocklessDouble(*(LocklessDouble*)src);
|
||||
else
|
||||
new(dest) LocklessDouble();
|
||||
},
|
||||
[](const std::string& str) -> void* {
|
||||
return new LocklessDouble(toNumber<double>(str));
|
||||
},
|
||||
[](void* memory) {
|
||||
((LocklessDouble*)memory)->~LocklessDouble();
|
||||
},
|
||||
[](net::Message& msg, void* mem) {
|
||||
msg << ((LocklessDouble*)mem)->value;
|
||||
},
|
||||
[](net::Message& msg, void* mem) {
|
||||
msg >> ((LocklessDouble*)mem)->value;
|
||||
},
|
||||
[](asIScriptGeneric* f, void* mem) {
|
||||
f->SetReturnObject(mem);
|
||||
});
|
||||
|
||||
//TODO: Support default initialization
|
||||
stateValueTypes["vec3d"].setup(sizeof(vec3d), "vec3d",
|
||||
copySimple<vec3d>,
|
||||
nullptr);
|
||||
|
||||
stateValueTypes["quaterniond"].setup(sizeof(quaterniond), "quaterniond",
|
||||
copySimple<quaterniond>,
|
||||
nullptr);
|
||||
|
||||
stateValueTypes["string"].setup(sizeof(std::string), "string",
|
||||
[](void* dest, void* src) {
|
||||
if(src)
|
||||
new(dest) std::string(*(std::string*)src);
|
||||
else
|
||||
new(dest) std::string();
|
||||
},
|
||||
[](const std::string& str) -> void* {
|
||||
return new std::string(str);
|
||||
},
|
||||
[](void* mem) {
|
||||
((std::string*)mem)->~basic_string();
|
||||
} );
|
||||
|
||||
stateValueTypes["Empire"].setup(sizeof(Empire*), "Empire@",
|
||||
copySimple<Empire*>,
|
||||
nullptr,
|
||||
nullptr,
|
||||
[](net::Message& msg, void* mem) {
|
||||
scripts::writeEmpire(msg, *(Empire**)mem);
|
||||
},
|
||||
[](net::Message& msg, void* mem) {
|
||||
scripts::readEmpire(msg, (Empire**)mem);
|
||||
},
|
||||
[](asIScriptGeneric* f, void* mem) {
|
||||
if(mem) {
|
||||
Empire* emp = *(Empire**)mem;
|
||||
f->SetReturnAddress(emp);
|
||||
}
|
||||
else {
|
||||
f->SetReturnAddress(nullptr);
|
||||
}
|
||||
});
|
||||
|
||||
stateValueTypes["Object$"].setup(sizeof(Object*), "Object@",
|
||||
[](void* dest, void* src) {
|
||||
Object*& d = *(Object**)dest;
|
||||
if(src) {
|
||||
Object* s = *(Object**)src;
|
||||
if(s)
|
||||
s->grab();
|
||||
if(d)
|
||||
d->drop();
|
||||
d = s;
|
||||
}
|
||||
else {
|
||||
if(d)
|
||||
d->drop();
|
||||
d = 0;
|
||||
}
|
||||
}, nullptr, //Object@ cannot have a default value
|
||||
[](void* mem) {
|
||||
Object* obj = *(Object**)mem;
|
||||
if(obj)
|
||||
obj->drop();
|
||||
},
|
||||
[](net::Message& msg, void* mem) {
|
||||
scripts::writeObjectScr(msg, *(Object**)mem);
|
||||
},
|
||||
[](net::Message& msg, void* mem) {
|
||||
scripts::readObjectScr(msg, (Object**)mem);
|
||||
},
|
||||
[](asIScriptGeneric* f, void* mem) {
|
||||
if(mem) {
|
||||
Object* obj = *(Object**)mem;
|
||||
if(obj)
|
||||
obj->grab();
|
||||
f->SetReturnAddress(obj);
|
||||
} else {
|
||||
f->SetReturnAddress(nullptr);
|
||||
}
|
||||
},
|
||||
[](void* mem) {
|
||||
Object* obj = *(Object**)mem;
|
||||
if(obj) {
|
||||
obj->drop();
|
||||
*(void**)mem = nullptr;
|
||||
}
|
||||
});
|
||||
|
||||
stateValueTypes["Object"].setup(sizeof(LockedHandle<Object>), "Object@",
|
||||
[](void* dest, void* src) {
|
||||
if(src)
|
||||
new(dest) LockedHandle<Object>(*(LockedHandle<Object>*)src);
|
||||
else
|
||||
new(dest) LockedHandle<Object>();
|
||||
}, nullptr, //Object@ cannot have a default value
|
||||
[](void* mem) {
|
||||
((LockedHandle<Object>*)mem)->~LockedHandle();
|
||||
},
|
||||
[](net::Message& msg, void* mem) {
|
||||
Object* obj = ((LockedHandle<Object>*)mem)->get();
|
||||
scripts::writeObjectScr(msg, obj);
|
||||
if(obj)
|
||||
obj->drop();
|
||||
},
|
||||
[](net::Message& msg, void* mem) {
|
||||
Object* obj = nullptr;
|
||||
scripts::readObjectScr(msg, &obj);
|
||||
((LockedHandle<Object>*)mem)->set(obj);
|
||||
if(obj)
|
||||
obj->drop();
|
||||
},
|
||||
[](asIScriptGeneric* f, void* mem) {
|
||||
Object* obj = ((LockedHandle<Object>*)mem)->get();
|
||||
f->SetReturnAddress(obj);
|
||||
},
|
||||
[](void* mem) {
|
||||
((LockedHandle<Object>*)mem)->set(nullptr);
|
||||
},
|
||||
[](asIScriptGeneric* f, void* mem) {
|
||||
Object* obj = (Object*)f->GetArgAddress(0);
|
||||
((LockedHandle<Object>*)mem)->set(obj);
|
||||
});
|
||||
stateValueTypes["Object"].returnType = "Object@";
|
||||
|
||||
stateValueTypes["Blueprint"].setup(sizeof(Blueprint), "Blueprint",
|
||||
defaultConstruct<Blueprint>,
|
||||
nullptr,
|
||||
destruct<Blueprint>,
|
||||
nullptr, nullptr,
|
||||
[](asIScriptGeneric* f, void* mem) {
|
||||
f->SetReturnAddress(mem);
|
||||
}, [](void* mem) {
|
||||
Blueprint* bp = (Blueprint*)mem;
|
||||
if(bp)
|
||||
bp->preClear();
|
||||
});
|
||||
stateValueTypes["Blueprint"].returnType = "Blueprint@";
|
||||
}
|
||||
|
||||
void StateValueDefinition::setup(unsigned Size, std::string Type,
|
||||
decltype(init) Init, decltype(parser) Parse, decltype(clear) Clear,
|
||||
decltype(writeSync) Write, decltype(readSync) Read, decltype(returnFunc) Return,
|
||||
decltype(clearRefs) ClearRefs, decltype(paramSetFunc) ParamSet)
|
||||
{
|
||||
size = Size; type = Type;
|
||||
init = Init; parser = Parse;
|
||||
clear = Clear;
|
||||
writeSync = Write;
|
||||
readSync = Read;
|
||||
returnFunc = Return;
|
||||
clearRefs = ClearRefs;
|
||||
paramSetFunc = ParamSet;
|
||||
|
||||
//Align to the size of the largest primitve that could fit into the type
|
||||
if(size >= sizeof(void*))
|
||||
alignment = sizeof(void*);
|
||||
else if(size >= 4)
|
||||
alignment = 4;
|
||||
else if(size >= 2)
|
||||
alignment = 2;
|
||||
else
|
||||
alignment = 1;
|
||||
}
|
||||
|
||||
void* StateValueDefinition::parse(const std::string& str) const {
|
||||
if(parser)
|
||||
return parser(str);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
void StateValueDefinition::alloc(void* memory, void* arg) const {
|
||||
memset(memory, 0, size);
|
||||
if(init)
|
||||
init(memory, arg);
|
||||
}
|
||||
|
||||
void StateValueDefinition::free(void* memory) const {
|
||||
if(clear)
|
||||
clear(memory);
|
||||
}
|
||||
|
||||
void StateValueDefinition::preClear(void* memory) const {
|
||||
if(clearRefs)
|
||||
clearRefs(memory);
|
||||
}
|
||||
|
||||
bool StateValueDefinition::syncable() const {
|
||||
return writeSync != 0 && readSync != 0;
|
||||
}
|
||||
|
||||
bool StateValueDefinition::returnable() const {
|
||||
return returnFunc != 0;
|
||||
}
|
||||
|
||||
void StateValueDefinition::syncWrite(net::Message& file, void* memory) const {
|
||||
if(writeSync)
|
||||
writeSync(file, memory);
|
||||
}
|
||||
|
||||
void StateValueDefinition::syncRead(net::Message& file, void* memory) const {
|
||||
if(readSync)
|
||||
readSync(file, memory);
|
||||
}
|
||||
|
||||
void StateValueDefinition::setReturn(asIScriptGeneric* gen, void* memory) const {
|
||||
if(returnFunc)
|
||||
returnFunc(gen, memory);
|
||||
}
|
||||
|
||||
bool StateValueDefinition::isParam() const {
|
||||
return (bool)paramSetFunc;
|
||||
}
|
||||
|
||||
void StateValueDefinition::setFromParam(asIScriptGeneric* gen, void* memory) const {
|
||||
if(paramSetFunc)
|
||||
paramSetFunc(gen, memory);
|
||||
}
|
||||
|
||||
const StateValueDefinition* getStateValueType(const std::string& type) {
|
||||
auto iter = stateValueTypes.find(type);
|
||||
if(iter != stateValueTypes.end())
|
||||
return &iter->second;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void clearStateDefinitions() {
|
||||
foreach(it, definitions)
|
||||
delete it->second;
|
||||
definitions.clear();
|
||||
stateDefinitions.clear();
|
||||
}
|
||||
|
||||
void loadStateDefinitions(const std::string& filename, const std::string& sharedBase) {
|
||||
StateDefinition* def = nullptr;
|
||||
std::unordered_set<std::string> used_names;
|
||||
|
||||
scripts::MethodFlags flags;
|
||||
flags.restricted = true;
|
||||
|
||||
auto finishDefinition = [&def,&used_names,&flags]() {
|
||||
if(def == nullptr)
|
||||
return;
|
||||
definitions[def->name] = def;
|
||||
stateDefinitions.push_back(def);
|
||||
|
||||
StateDefinition* type = def;
|
||||
def->asVar.setup(def->totalDataSize, def->name.c_str(),
|
||||
[type](void* dest,void* src) {
|
||||
if(src)
|
||||
type->copy(dest, src);
|
||||
else
|
||||
type->prepare(dest);
|
||||
},
|
||||
nullptr,
|
||||
[type](void* mem) {
|
||||
type->unprepare(mem);
|
||||
});
|
||||
|
||||
used_names.clear();
|
||||
def = nullptr;
|
||||
flags.reset();
|
||||
flags.restricted = true;
|
||||
};
|
||||
|
||||
std::ifstream file(filename);
|
||||
if(!file.is_open()) {
|
||||
error("Could not open '%s'", filename.c_str());
|
||||
return;
|
||||
}
|
||||
skipBOM(file);
|
||||
|
||||
bool inType = false;
|
||||
|
||||
char name[81], dtype[25], value[81];
|
||||
char bracket, bracket2;
|
||||
|
||||
while(true) {
|
||||
std::string line;
|
||||
std::getline(file, line);
|
||||
if(file.fail())
|
||||
break;
|
||||
|
||||
std::string parse = line.substr(0, line.find("//"));
|
||||
if(parse.find_first_not_of(" \t\n\r") == std::string::npos)
|
||||
continue;
|
||||
|
||||
if(!inType) {
|
||||
finishDefinition();
|
||||
|
||||
int args = sscanf(parse.c_str(), " %80s %c %80s %c", name, &bracket, value, &bracket2);
|
||||
if((args != 2 && args != 4) ||
|
||||
(args == 2 && bracket != '{') ||
|
||||
(args == 4 && (bracket != ':' || bracket2 != '{'))) {
|
||||
error("Unrecognized line '%s'", line.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string typeName = name;
|
||||
auto previous = definitions.find(typeName);
|
||||
if(previous != definitions.end()) {
|
||||
error("Duplicate type '%s'", name);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto baseType = definitions.find(sharedBase);
|
||||
if(baseType == definitions.end()) {
|
||||
if(!sharedBase.empty() && sharedBase != name)
|
||||
error("Expected definition of base type '%s'", sharedBase.c_str());
|
||||
def = new StateDefinition();
|
||||
}
|
||||
else {
|
||||
def = new StateDefinition(*baseType->second);
|
||||
}
|
||||
|
||||
if(args == 4)
|
||||
def->scriptClass = value;
|
||||
|
||||
used_names.clear();
|
||||
|
||||
def->name = name;
|
||||
inType = true;
|
||||
}
|
||||
else if(def) {
|
||||
//Parse flag lines (e.g. restricted:)
|
||||
std::string trimmed = trim(parse);
|
||||
if(!trimmed.empty() && trimmed.back() == ':') {
|
||||
trimmed.back() = ' ';
|
||||
flags.reset();
|
||||
flags.parse(trimmed);
|
||||
continue;
|
||||
}
|
||||
|
||||
int args = sscanf(parse.c_str(), " %c", &bracket);
|
||||
if(args == 1 && bracket == '}') {
|
||||
inType = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
parse = trimmed;
|
||||
|
||||
//Parse methods
|
||||
if(parse.find('(') != std::string::npos) {
|
||||
StateDefinition::method method;
|
||||
method.flags = flags;
|
||||
method.flags.parse(parse);
|
||||
method.decl = parse;
|
||||
method.wrapped = nullptr;
|
||||
def->methods.push_back(method);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool synced = false;
|
||||
if(parse.compare(0, 7, "synced ") == 0) {
|
||||
parse = parse.substr(7);
|
||||
synced = true;
|
||||
}
|
||||
|
||||
bool attribute = false;
|
||||
if(parse.compare(0, 10, "attribute ") == 0) {
|
||||
parse = parse.substr(10);
|
||||
attribute = true;
|
||||
}
|
||||
|
||||
args = sscanf(parse.c_str(), " %24s %80s = %80s", dtype, name, value);
|
||||
|
||||
if(args < 2) {
|
||||
error("Unrecognized line '%s'", line.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
if(args < 3)
|
||||
value[0] = '\0';
|
||||
|
||||
if(used_names.find(name) != used_names.end()) {
|
||||
error("Duplicate member '%s'", name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!isIdentifier(name)) {
|
||||
error("'%s' is not a valid identifier", name);
|
||||
continue;
|
||||
}
|
||||
|
||||
StateDefinition::stateDefMember mem;
|
||||
mem.name = name;
|
||||
mem.defText = value;
|
||||
mem.typeName = dtype;
|
||||
mem.synced = synced;
|
||||
mem.attribute = attribute;
|
||||
if(flags.restricted)
|
||||
mem.access = SR_Restricted;
|
||||
else if(flags.visible)
|
||||
mem.access = SR_Visible;
|
||||
else
|
||||
mem.access = SR_Invisible;
|
||||
def->types.push_back(mem);
|
||||
}
|
||||
else {
|
||||
if(parse.find('}') != std::string::npos)
|
||||
inType = false;
|
||||
}
|
||||
}
|
||||
|
||||
finishDefinition();
|
||||
}
|
||||
|
||||
void finalizeStateDefinitions() {
|
||||
for(auto i = stateDefinitions.begin(), end = stateDefinitions.end(); i != end; ++i) {
|
||||
auto& def = **i;
|
||||
|
||||
unsigned offset = 0;
|
||||
|
||||
//Determine data types of all members
|
||||
for(auto t = def.types.begin(), tend = def.types.end(); t != tend;) {
|
||||
auto& member = *t;
|
||||
|
||||
const StateValueDefinition* valueType = getStateValueType(member.typeName);
|
||||
if(valueType == 0 || (member.synced && !valueType->syncable())) {
|
||||
if(valueType)
|
||||
error("%s error: Type '%s' for member '%s' cannot be synced", def.name.c_str(), member.typeName.c_str(), member.name.c_str());
|
||||
else
|
||||
error("%s error: Unknown type '%s' for member '%s'", def.name.c_str(), member.typeName.c_str(), member.name.c_str());
|
||||
t = def.types.erase(t);
|
||||
tend = def.types.end();
|
||||
continue;
|
||||
}
|
||||
|
||||
member.def = valueType;
|
||||
member.defaultValue = valueType->parse(member.defText);
|
||||
if(offset & (valueType->alignment - 1)) {
|
||||
offset &= ~(unsigned)(valueType->alignment - 1);
|
||||
offset += (unsigned)valueType->alignment;
|
||||
}
|
||||
member.offset = offset;
|
||||
offset += valueType->size;
|
||||
|
||||
++t;
|
||||
}
|
||||
|
||||
def.totalDataSize = offset;
|
||||
|
||||
//Parse through methods
|
||||
unsigned methodIndex = 0;
|
||||
for(auto m = def.methods.begin(), mend = def.methods.end(); m != mend; ++m, ++methodIndex) {
|
||||
auto& method = *m;
|
||||
scripts::GenericCallDesc desc;
|
||||
|
||||
if(method.flags.server || method.flags.shadow)
|
||||
desc.parse(method.decl, true, true);
|
||||
else
|
||||
desc.parse(method.decl, true, false);
|
||||
|
||||
if(!method.flags.local && desc.returnType.type) {
|
||||
error("Error: '%s': non-local function cannot return a value.", method.decl.c_str());
|
||||
continue;
|
||||
}
|
||||
else if(desc.returnType == scripts::GT_Custom_Handle) {
|
||||
error("Error: '%s': script types cannot be return values.", method.decl.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
scripts::WrappedMethod* wm = new scripts::WrappedMethod();
|
||||
wm->fullDeclaration = method.decl;
|
||||
wm->index = methodIndex;
|
||||
wm->local = method.flags.local;
|
||||
wm->server = method.flags.server;
|
||||
wm->shadow = method.flags.shadow;
|
||||
wm->wrapped = desc;
|
||||
wm->restricted = method.flags.restricted;
|
||||
wm->async = method.flags.async;
|
||||
wm->safe = method.flags.safe;
|
||||
wm->relocking = method.flags.relocking;
|
||||
wm->isComponentMethod = false;
|
||||
|
||||
if(wm->wrapped.returnsArray) {
|
||||
wm->wrapped.returnType.type = scripts::GT_Custom_Handle;
|
||||
wm->wrapped.returnType.customName = "DataList";
|
||||
}
|
||||
|
||||
wm->desc.append(desc);
|
||||
wm->desc.name = desc.name;
|
||||
wm->desc.constFunction = false;
|
||||
wm->desc.returnType = desc.returnType;
|
||||
wm->desc.returnsArray = wm->wrapped.returnsArray;
|
||||
|
||||
method.wrapped = wm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StateDefinition::StateDefinition() : totalDataSize(0), base(0) {}
|
||||
|
||||
StateDefinition::StateDefinition(const StateDefinition& other) : types(other.types), totalDataSize(other.totalDataSize), base(&other), methods(other.methods) {
|
||||
//TODO: default args are not deleted, so we'll just copy the pointers blindly for now
|
||||
}
|
||||
|
||||
StateDefinition::~StateDefinition() {
|
||||
//TODO: default args are not yet deleted, see above
|
||||
}
|
||||
|
||||
void StateDefinition::align(unsigned& offset) {
|
||||
unsigned off = offset % sizeof(void*);
|
||||
if(off != 0)
|
||||
offset += sizeof(void*) - off;
|
||||
}
|
||||
|
||||
void StateDefinition::align(void*& memory) {
|
||||
size_t off = (size_t)memory % sizeof(void*);
|
||||
if(off != 0)
|
||||
memory = (char*)memory + (sizeof(void*) - off);
|
||||
}
|
||||
|
||||
const StateDefinition& getStateDefinition(const std::string& name) {
|
||||
auto it = definitions.find(name);
|
||||
if(it == definitions.end())
|
||||
return errorStateDefinition;
|
||||
return *it->second;
|
||||
}
|
||||
|
||||
unsigned StateDefinition::getSize(unsigned atOffset) const {
|
||||
unsigned off = atOffset % sizeof(void*);
|
||||
if(off == 0)
|
||||
return totalDataSize;
|
||||
else
|
||||
return totalDataSize + (sizeof(void*) - off);
|
||||
}
|
||||
|
||||
void StateDefinition::prepare(void*& memory) const {
|
||||
align(memory);
|
||||
|
||||
char* data = (char*)memory;
|
||||
for(auto v = types.begin(), end = types.end(); v != end; ++v) {
|
||||
auto& type = *v->def;
|
||||
type.alloc(data + v->offset, v->defaultValue);
|
||||
}
|
||||
|
||||
memory = data + totalDataSize;
|
||||
}
|
||||
|
||||
void StateDefinition::copy(void*& memory, void*& from) const {
|
||||
align(memory);
|
||||
align(from);
|
||||
|
||||
char* data = (char*)memory, *source = (char*)from;
|
||||
for(auto v = types.begin(), end = types.end(); v != end; ++v) {
|
||||
auto& type = *v->def;
|
||||
type.alloc(data + v->offset, source + v->offset);
|
||||
}
|
||||
|
||||
memory = data + totalDataSize;
|
||||
from = source + totalDataSize;
|
||||
}
|
||||
|
||||
void StateDefinition::preClear(void*& memory) const {
|
||||
align(memory);
|
||||
|
||||
char* data = (char*)memory;
|
||||
for(auto v = types.begin(), end = types.end(); v != end; ++v) {
|
||||
auto& type = *v->def;
|
||||
type.preClear(data + v->offset);
|
||||
}
|
||||
|
||||
memory = data + totalDataSize;
|
||||
}
|
||||
|
||||
void StateDefinition::unprepare(void*& memory) const {
|
||||
align(memory);
|
||||
|
||||
char* data = (char*)memory;
|
||||
for(auto v = types.begin(), end = types.end(); v != end; ++v) {
|
||||
auto& type = *v->def;
|
||||
type.free(data + v->offset);
|
||||
}
|
||||
|
||||
memory = data + totalDataSize;
|
||||
}
|
||||
|
||||
void StateDefinition::syncWrite(net::Message& msg, void* memory) const {
|
||||
align(memory);
|
||||
|
||||
char* data = (char*)memory;
|
||||
for(auto i = types.begin(), end = types.end(); i != end; ++i) {
|
||||
auto& state = *i->def;
|
||||
if(i->synced)
|
||||
state.syncWrite(msg, data + i->offset);
|
||||
}
|
||||
}
|
||||
|
||||
void StateDefinition::syncRead(net::Message& msg, void* memory) const {
|
||||
align(memory);
|
||||
|
||||
char* data = (char*)memory;
|
||||
for(auto i = types.begin(), end = types.end(); i != end; ++i) {
|
||||
auto& state = *i->def;
|
||||
if(i->synced)
|
||||
state.syncRead(msg, data + i->offset);
|
||||
}
|
||||
}
|
||||
|
||||
StateList::StateList() : def(&errorStateDefinition), values(0) {
|
||||
}
|
||||
|
||||
StateList::StateList(const StateDefinition& Def) : def(0), values(0) {
|
||||
change(Def);
|
||||
}
|
||||
|
||||
StateList::~StateList() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void StateList::change(const StateDefinition& Def) {
|
||||
clear();
|
||||
def = &Def;
|
||||
if(def->totalDataSize == 0)
|
||||
return;
|
||||
|
||||
values = new char[def->totalDataSize];
|
||||
void* mem = values;
|
||||
def->prepare(mem);
|
||||
}
|
||||
|
||||
void StateList::clear() {
|
||||
if(values) {
|
||||
void* mem = values;
|
||||
def->unprepare(mem);
|
||||
delete[] values;
|
||||
values = 0;
|
||||
}
|
||||
|
||||
def = 0;
|
||||
}
|
||||
|
||||
StateList& StateList::operator=(StateList& other) {
|
||||
clear();
|
||||
def = other.def;
|
||||
if(def == 0)
|
||||
return *this;
|
||||
|
||||
values = new char[def->totalDataSize];
|
||||
void* mem = values, *otherMem = other.values;;
|
||||
def->copy(mem, otherMem);
|
||||
return *this;
|
||||
}
|
||||
|
||||
unsigned StateList::count() const{
|
||||
return (unsigned)def->types.size();
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
#pragma once
|
||||
#include "threads.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include "scripts/script_components.h"
|
||||
|
||||
namespace net {
|
||||
struct Message;
|
||||
};
|
||||
|
||||
class asIScriptGeneric;
|
||||
class StateValueDefinition {
|
||||
std::function<void(void*,void*)> init;
|
||||
std::function<void*(const std::string&)> parser;
|
||||
std::function<void(void*)> clear;
|
||||
std::function<void(void*)> clearRefs;
|
||||
std::function<void(net::Message&,void*)> writeSync;
|
||||
std::function<void(net::Message&,void*)> readSync;
|
||||
std::function<void(asIScriptGeneric*,void*)> returnFunc;
|
||||
std::function<void(asIScriptGeneric*,void*)> paramSetFunc;
|
||||
public:
|
||||
unsigned size;
|
||||
size_t alignment;
|
||||
std::string type;
|
||||
std::string returnType;
|
||||
|
||||
void setup(unsigned Size, std::string Type,
|
||||
decltype(init) Init, decltype(parser) Parse,
|
||||
decltype(clear) Clear = nullptr,
|
||||
decltype(writeSync) Write = nullptr,
|
||||
decltype(readSync) Read = nullptr,
|
||||
decltype(returnFunc) Return = nullptr,
|
||||
decltype(clearRefs) ClearRefs = nullptr,
|
||||
decltype(paramSetFunc) ParamSet = nullptr);
|
||||
|
||||
void* parse(const std::string& str) const;
|
||||
|
||||
void alloc(void* memory, void* arg) const;
|
||||
|
||||
void preClear(void* memory) const;
|
||||
void free(void* memory) const;
|
||||
|
||||
bool syncable() const;
|
||||
bool returnable() const;
|
||||
|
||||
void syncWrite(net::Message& file, void* memory) const;
|
||||
|
||||
void syncRead(net::Message& file, void* memory) const;
|
||||
|
||||
void setReturn(asIScriptGeneric* gen, void* memory) const;
|
||||
|
||||
bool isParam() const;
|
||||
void setFromParam(asIScriptGeneric* gen, void* memory) const;
|
||||
};
|
||||
|
||||
extern std::unordered_map<std::string, StateValueDefinition> stateValueTypes;
|
||||
const StateValueDefinition* getStateValueType(const std::string& type);
|
||||
void resetStateValueTypes();
|
||||
|
||||
enum StateRestriction {
|
||||
SR_Visible,
|
||||
SR_Restricted,
|
||||
SR_Invisible,
|
||||
};
|
||||
|
||||
struct StateDefinition {
|
||||
public:
|
||||
struct stateDefMember {
|
||||
std::string name, typeName, defText;
|
||||
const StateValueDefinition* def;
|
||||
void* defaultValue;
|
||||
bool synced;
|
||||
bool attribute;
|
||||
StateRestriction access;
|
||||
unsigned offset;
|
||||
|
||||
stateDefMember() : def(0), defaultValue(0),
|
||||
synced(false), attribute(false), access(SR_Restricted), offset(0) {
|
||||
}
|
||||
};
|
||||
|
||||
struct method {
|
||||
std::string decl;
|
||||
scripts::MethodFlags flags;
|
||||
scripts::WrappedMethod* wrapped;
|
||||
};
|
||||
|
||||
const StateDefinition* base;
|
||||
static void align(void*& mem);
|
||||
std::string name;
|
||||
|
||||
std::string scriptClass;
|
||||
|
||||
//Relatively indexes
|
||||
std::vector<stateDefMember> types;
|
||||
unsigned totalDataSize;
|
||||
|
||||
std::vector<method> methods;
|
||||
|
||||
StateValueDefinition asVar;
|
||||
|
||||
unsigned getSize(unsigned atOffset) const;
|
||||
static void align(unsigned& offset);
|
||||
|
||||
void prepare(void*& memory) const;
|
||||
void copy(void*& memory, void*& from) const;
|
||||
void preClear(void*& memory) const;
|
||||
void unprepare(void*& memory) const;
|
||||
|
||||
void syncWrite(net::Message& msg, void* memory) const;
|
||||
void syncRead(net::Message& msg, void* memory) const;
|
||||
|
||||
StateDefinition();
|
||||
StateDefinition(const StateDefinition& other);
|
||||
~StateDefinition();
|
||||
};
|
||||
|
||||
struct StateList {
|
||||
const StateDefinition* def;
|
||||
char* values;
|
||||
|
||||
StateList& operator=(StateList& other);
|
||||
void change(const StateDefinition& def);
|
||||
void clear();
|
||||
unsigned count() const;
|
||||
|
||||
StateList();
|
||||
StateList(const StateDefinition& def);
|
||||
~StateList();
|
||||
};
|
||||
|
||||
void loadStateDefinitions(const std::string& filename, const std::string& sharedBase = "");
|
||||
void finalizeStateDefinitions();
|
||||
const StateDefinition& getStateDefinition(const std::string& name);
|
||||
void clearStateDefinitions();
|
||||
|
||||
extern StateDefinition errorStateDefinition;
|
||||
extern std::vector<StateDefinition*> stateDefinitions;
|
||||
@@ -0,0 +1,480 @@
|
||||
#include "gui/skin.h"
|
||||
#include "num_util.h"
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
#include <map>
|
||||
|
||||
const gui::skin::Element* currentDrawnElement = 0;
|
||||
vec2i eleDestSize;
|
||||
|
||||
void shader_skin_margin_src(float* margin,unsigned short,void*) {
|
||||
if(currentDrawnElement) {
|
||||
margin[0] = (float)currentDrawnElement->margin.topLeft.x;
|
||||
margin[1] = (float)currentDrawnElement->margin.topLeft.y;
|
||||
margin[2] = (float)currentDrawnElement->margin.botRight.x;
|
||||
margin[3] = (float)currentDrawnElement->margin.botRight.y;
|
||||
}
|
||||
}
|
||||
|
||||
void shader_skin_margin_dest(float* margin,unsigned short,void*) {
|
||||
if(currentDrawnElement) {
|
||||
recti destMargin = currentDrawnElement->getDestinationMargin(eleDestSize);
|
||||
margin[0] = (float)destMargin.topLeft.x;
|
||||
margin[1] = (float)destMargin.topLeft.y;
|
||||
margin[2] = (float)destMargin.botRight.x;
|
||||
margin[3] = (float)destMargin.botRight.y;
|
||||
}
|
||||
}
|
||||
|
||||
void shader_skin_src_pos(float* pos,unsigned short,void*) {
|
||||
if(currentDrawnElement) {
|
||||
pos[0] = (float)currentDrawnElement->area.topLeft.x;
|
||||
pos[1] = (float)currentDrawnElement->area.topLeft.y;
|
||||
}
|
||||
}
|
||||
|
||||
void shader_skin_src_size(float* size,unsigned short,void*) {
|
||||
if(currentDrawnElement) {
|
||||
size[0] = (float)currentDrawnElement->area.getWidth();
|
||||
size[1] = (float)currentDrawnElement->area.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
void shader_skin_dst_size(float* size,unsigned short,void*) {
|
||||
if(currentDrawnElement) {
|
||||
size[0] = (float)eleDestSize.x;
|
||||
size[1] = (float)eleDestSize.y;
|
||||
}
|
||||
}
|
||||
|
||||
void shader_skin_mode(float* modes,unsigned short,void*) {
|
||||
if(currentDrawnElement) {
|
||||
modes[0] = (float)currentDrawnElement->horizMode;
|
||||
modes[1] = (float)currentDrawnElement->vertMode;
|
||||
}
|
||||
}
|
||||
|
||||
void shader_skin_gradientCount(float* count,unsigned short,void*) {
|
||||
if(currentDrawnElement) {
|
||||
*count = (float)currentDrawnElement->gradients.size();
|
||||
}
|
||||
}
|
||||
|
||||
void shader_skin_gradientMode(float* count,unsigned short,void*) {
|
||||
if(currentDrawnElement) {
|
||||
*count = (float)currentDrawnElement->gradMode;
|
||||
}
|
||||
}
|
||||
|
||||
void shader_skin_gradientRects(float* rects, unsigned short n,void*) {
|
||||
if(currentDrawnElement) {
|
||||
unsigned short cnt = (unsigned short)currentDrawnElement->gradients.size();
|
||||
if(cnt < n)
|
||||
n = cnt;
|
||||
if(n == 0)
|
||||
return;
|
||||
|
||||
recti baseRect(0,0, eleDestSize.width, eleDestSize.height);
|
||||
|
||||
for(unsigned short i = 0; i < n; ++i) {
|
||||
auto& gradient = currentDrawnElement->gradients[i];
|
||||
recti rect = gradient.area.evaluate(baseRect);
|
||||
rects[i*4+0] = (float)rect.topLeft.x;
|
||||
rects[i*4+1] = (float)rect.topLeft.y;
|
||||
rects[i*4+2] = (float)rect.botRight.x;
|
||||
rects[i*4+3] = (float)rect.botRight.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void shader_skin_gradientCols(float* cols, unsigned short n, void*) {
|
||||
if(currentDrawnElement) {
|
||||
n /= 4;
|
||||
unsigned short cnt = (unsigned short)currentDrawnElement->gradients.size();
|
||||
if(cnt < n)
|
||||
n = cnt;
|
||||
if(n == 0)
|
||||
return;
|
||||
|
||||
Colorf* fcols = (Colorf*)cols;
|
||||
for(unsigned short i = 0; i < n; ++i, fcols += 4) {
|
||||
auto& gradient = currentDrawnElement->gradients[i];
|
||||
new(fcols+0) Colorf(gradient.colors[0]);
|
||||
new(fcols+1) Colorf(gradient.colors[1]);
|
||||
new(fcols+2) Colorf(gradient.colors[2]);
|
||||
new(fcols+3) Colorf(gradient.colors[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace gui {
|
||||
namespace skin {
|
||||
|
||||
std::map<std::string,unsigned> dynStyleIndices;
|
||||
std::map<std::string,unsigned> dynElementFlags;
|
||||
std::map<std::string,unsigned> dynFontIndices;
|
||||
std::map<std::string,unsigned> dynColorIndices;
|
||||
std::map<unsigned,std::string> dynFlagNames;
|
||||
unsigned nextElementFlag = 1;
|
||||
|
||||
bool elementLower(Element* x, Element* y) {
|
||||
return x->flags < y->flags;
|
||||
}
|
||||
|
||||
int getDynamicIndex(std::map<std::string, unsigned>& dyn, const std::string& name, bool addIfMissing) {
|
||||
auto index = dyn.find(name);
|
||||
if(index == dyn.end()) {
|
||||
if(addIfMissing) {
|
||||
unsigned newIndex = (unsigned)dyn.size();
|
||||
dyn[name] = newIndex;
|
||||
return newIndex;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return index->second;
|
||||
}
|
||||
|
||||
unsigned getStyleCount() {
|
||||
return (unsigned)dynStyleIndices.size();
|
||||
}
|
||||
|
||||
int getStyleIndex(const std::string& name, bool addIfMissing) {
|
||||
return getDynamicIndex(dynStyleIndices, name, addIfMissing);
|
||||
}
|
||||
|
||||
int getElementFlag(const std::string& name, bool addIfMissing) {
|
||||
if(name == "Normal")
|
||||
return 0;
|
||||
auto index = dynElementFlags.find(name);
|
||||
if(index == dynElementFlags.end()) {
|
||||
if(addIfMissing) {
|
||||
if(nextElementFlag == 0) {
|
||||
nextElementFlag = 1;
|
||||
error("Error: Skin exceeded limit of 32 unique element flags.");
|
||||
}
|
||||
|
||||
unsigned flag = nextElementFlag;
|
||||
dynElementFlags[name] = flag;
|
||||
dynFlagNames[flag] = name;
|
||||
|
||||
if(flag == 0x100000000)
|
||||
nextElementFlag = 0;
|
||||
else
|
||||
nextElementFlag = nextElementFlag << 1;
|
||||
|
||||
return flag;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return index->second;
|
||||
}
|
||||
|
||||
std::string getElementFlagName(unsigned flag) {
|
||||
if(flag == 0)
|
||||
return "Normal";
|
||||
unsigned check = 0x1;
|
||||
unsigned cnt = 0;
|
||||
std::string name;
|
||||
for(unsigned i = 0; i < 32; ++i, check <<= 1) {
|
||||
if((flag & check) != 0) {
|
||||
if(cnt != 0)
|
||||
name += ", ";
|
||||
auto it = dynFlagNames.find(check);
|
||||
if(it != dynFlagNames.end())
|
||||
name += it->second;
|
||||
else
|
||||
name += "???";
|
||||
++cnt;
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
int getColorIndex(const std::string& name, bool addIfMissing) {
|
||||
return getDynamicIndex(dynColorIndices, name, addIfMissing);
|
||||
}
|
||||
|
||||
int getFontIndex(const std::string& name, bool addIfMissing) {
|
||||
return getDynamicIndex(dynFontIndices, name, addIfMissing);
|
||||
}
|
||||
|
||||
void clearDynamicIndices() {
|
||||
dynElementFlags.clear();
|
||||
dynFlagNames.clear();
|
||||
dynFontIndices.clear();
|
||||
dynColorIndices.clear();
|
||||
dynStyleIndices.clear();
|
||||
nextElementFlag = 1;
|
||||
}
|
||||
|
||||
void enumerateStyleIndices(std::function<void(const std::string&,unsigned)> callback) {
|
||||
foreach(i, dynStyleIndices)
|
||||
callback(i->first,i->second);
|
||||
}
|
||||
|
||||
void enumerateElementFlags(void (*cb)(const std::string&,unsigned)) {
|
||||
(*cb)("Normal", 0);
|
||||
foreach(i, dynElementFlags)
|
||||
(*cb)(i->first,i->second);
|
||||
}
|
||||
|
||||
void enumerateColorIndices(void (*cb)(const std::string&,unsigned)) {
|
||||
foreach(i, dynColorIndices)
|
||||
(*cb)(i->first,i->second);
|
||||
}
|
||||
|
||||
void enumerateFontIndices(void (*cb)(const std::string&,unsigned)) {
|
||||
foreach(i, dynFontIndices)
|
||||
(*cb)(i->first,i->second);
|
||||
}
|
||||
|
||||
void Style::addElement(Element* ele) {
|
||||
auto at = std::lower_bound(elements.begin(), elements.end(), ele, elementLower);
|
||||
if(at == elements.end())
|
||||
elements.push_back(ele);
|
||||
else
|
||||
elements.insert(at, ele);
|
||||
}
|
||||
|
||||
Element* Style::getExactElement(unsigned flags) const {
|
||||
foreach(it, elements) {
|
||||
if((*it)->flags == flags)
|
||||
return *it;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Element* Style::getElement(unsigned flags) const {
|
||||
if(elements.empty())
|
||||
return nullptr;
|
||||
|
||||
Element ele;
|
||||
ele.flags = flags;
|
||||
|
||||
auto at = std::lower_bound(elements.begin(), elements.end(), &ele, elementLower);
|
||||
size_t index;
|
||||
|
||||
if(at == elements.end())
|
||||
index = elements.size() - 1;
|
||||
else
|
||||
index = at - elements.begin();
|
||||
|
||||
if(elements[index]->flags == flags)
|
||||
return elements[index];
|
||||
|
||||
while(index > 0 && (~flags & elements[index]->flags))
|
||||
--index;
|
||||
|
||||
return elements[index];
|
||||
}
|
||||
|
||||
bool Skin::hasStyle(unsigned index) const {
|
||||
if(index >= styles.size())
|
||||
return false;
|
||||
if(!styles[index])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const Style& Skin::getStyle(unsigned index) const {
|
||||
if(index >= styles.size())
|
||||
return *errorStyle;
|
||||
if(!styles[index])
|
||||
return *errorStyle;
|
||||
return *styles[index];
|
||||
}
|
||||
|
||||
const Element& Skin::getElement(unsigned index, unsigned flags) const {
|
||||
if(index < styles.size() && styles[index]) {
|
||||
const Element* ele = styles[index]->getElement(flags);
|
||||
if(ele)
|
||||
return *ele;
|
||||
}
|
||||
return *errorElement;
|
||||
}
|
||||
|
||||
const render::Font& Skin::getFont(unsigned index) const {
|
||||
if(index < (unsigned)fonts.size() && fonts[index])
|
||||
return *fonts[index];
|
||||
return *errorFont;
|
||||
}
|
||||
|
||||
Color Skin::getColor(unsigned index) const {
|
||||
if(index < (unsigned)colors.size())
|
||||
return colors[index];
|
||||
return Color();
|
||||
}
|
||||
|
||||
void Skin::setStyle(unsigned index, Style* style) {
|
||||
if(index >= (unsigned)styles.size())
|
||||
styles.resize(index+1, 0);
|
||||
styles[index] = style;
|
||||
}
|
||||
|
||||
void Skin::setColor(unsigned index, Color color) {
|
||||
if(index >= (unsigned)colors.size())
|
||||
colors.resize(index+1);
|
||||
colors[index] = color;
|
||||
}
|
||||
|
||||
void Skin::setFont(unsigned index, const render::Font* font) {
|
||||
if(index >= (unsigned)fonts.size())
|
||||
fonts.resize(index+1, 0);
|
||||
fonts[index] = font;
|
||||
}
|
||||
|
||||
Skin::Skin() : fonts(), material(0) {
|
||||
errorFont = render::Font::createDummyFont();
|
||||
errorElement = new Element();
|
||||
errorStyle = new Style();
|
||||
}
|
||||
|
||||
void Gradient::draw(render::RenderDriver& driver, recti pos,
|
||||
const recti* clip) const {
|
||||
|
||||
recti absPos = area.evaluate(pos);
|
||||
driver.drawRectangle(absPos, 0, 0, colors, clip);
|
||||
}
|
||||
|
||||
void Layer::draw(render::RenderDriver& driver, recti pos,
|
||||
const recti* clip, Color color) const {
|
||||
|
||||
if(!ele)
|
||||
return;
|
||||
|
||||
recti absPos = area.evaluate(pos);
|
||||
ele->draw(driver, absPos, clip, hasOverride ? override : color);
|
||||
}
|
||||
|
||||
void Element::clear() {
|
||||
layers.clear();
|
||||
gradients.clear();
|
||||
|
||||
margin = recti();
|
||||
filled = true;
|
||||
horizMode = DM_Uniform;
|
||||
vertMode = DM_Uniform;
|
||||
aspectMargin = AMM_None;
|
||||
}
|
||||
|
||||
void Element::draw(render::RenderDriver& driver, recti pos,
|
||||
const recti* clip, Color color) const {
|
||||
Color colors[4] = {color, color, color, color};
|
||||
|
||||
foreach(it, layers)
|
||||
(*it).draw(driver, pos, clip, color);
|
||||
|
||||
//TODO: Support no fill
|
||||
|
||||
if(!area.empty() || gradMode == GM_Overlay) {
|
||||
currentDrawnElement = this;
|
||||
eleDestSize = pos.getSize();
|
||||
driver.drawRectangle(pos, material, 0, colors, clip);
|
||||
currentDrawnElement = 0;
|
||||
}
|
||||
}
|
||||
|
||||
recti Element::getDestinationMargin(vec2i size) const {
|
||||
recti destMargin;
|
||||
switch(aspectMargin) {
|
||||
case AMM_None:
|
||||
destMargin.topLeft.x = margin.topLeft.x;
|
||||
destMargin.topLeft.y = margin.topLeft.y;
|
||||
destMargin.botRight.x = margin.botRight.x;
|
||||
destMargin.botRight.y = margin.botRight.y;
|
||||
break;
|
||||
case AMM_Horizontal: {
|
||||
float l_ratio = ((float)margin.topLeft.x) / ((float)area.getHeight());
|
||||
float r_ratio = ((float)margin.botRight.x) / ((float)area.getHeight());
|
||||
|
||||
destMargin.topLeft.x = (int)(l_ratio * (float)size.height);
|
||||
destMargin.topLeft.y = margin.topLeft.y;
|
||||
destMargin.botRight.x = (int)(r_ratio * (float)size.height);
|
||||
destMargin.botRight.y = margin.botRight.y;
|
||||
} break;
|
||||
case AMM_Vertical: {
|
||||
float l_ratio = ((float)margin.topLeft.y) / ((float)area.getWidth());
|
||||
float r_ratio = ((float)margin.botRight.y) / ((float)area.getWidth());
|
||||
|
||||
destMargin.topLeft.x = margin.topLeft.x;
|
||||
destMargin.topLeft.y = (int)(l_ratio * (float)size.width);
|
||||
destMargin.botRight.x = margin.botRight.x;
|
||||
destMargin.botRight.y = (int)(r_ratio * (float)size.width);
|
||||
} break;
|
||||
}
|
||||
return destMargin;
|
||||
}
|
||||
|
||||
bool Element::isPixelActive(recti box, vec2i px) const {
|
||||
if(!material)
|
||||
return true;
|
||||
auto* tex = material->textures[0];
|
||||
if(!tex)
|
||||
return true;
|
||||
|
||||
recti dmargin = getDestinationMargin(box.getSize());
|
||||
int tx = 0;
|
||||
switch(horizMode) {
|
||||
case DM_Uniform:
|
||||
tx = px.x + area.topLeft.x;
|
||||
break;
|
||||
case DM_Scaled:
|
||||
case DM_Tiled:
|
||||
if(px.x < dmargin.topLeft.x) {
|
||||
tx = (int)((float)px.x * (float)margin.topLeft.x / (float)dmargin.topLeft.x) + area.topLeft.x;
|
||||
}
|
||||
else if(px.x >= box.getWidth() - dmargin.botRight.x) {
|
||||
tx = area.botRight.x - (box.getWidth() -
|
||||
(int)((float)px.x * (float)margin.botRight.x / (float)dmargin.botRight.x));
|
||||
}
|
||||
else {
|
||||
tx = area.topLeft.x + margin.topLeft.x;
|
||||
if(horizMode == DM_Scaled) {
|
||||
tx += (int)(((double)(px.x - dmargin.topLeft.x)/
|
||||
(double)(box.getWidth() - dmargin.topLeft.x - dmargin.botRight.x))
|
||||
* (double)(area.getWidth() - margin.topLeft.x - margin.botRight.x));
|
||||
}
|
||||
else {
|
||||
tx += (px.x - dmargin.topLeft.x) % (area.getWidth() - margin.topLeft.x - margin.botRight.x);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int ty = 0;
|
||||
switch(vertMode) {
|
||||
case DM_Uniform:
|
||||
ty = px.y + area.topLeft.y;
|
||||
break;
|
||||
case DM_Scaled:
|
||||
case DM_Tiled:
|
||||
if(px.y < dmargin.topLeft.y) {
|
||||
ty = (int)((float)px.y * (float)margin.topLeft.y / (float)dmargin.topLeft.y) + area.topLeft.y;
|
||||
}
|
||||
else if(px.y >= box.getWidth() - dmargin.botRight.y) {
|
||||
ty = area.botRight.y - (box.getHeight() -
|
||||
(int)((float)px.y * (float)margin.botRight.y / (float)dmargin.botRight.y));
|
||||
}
|
||||
else {
|
||||
ty = area.topLeft.y + margin.topLeft.y;
|
||||
if(horizMode == DM_Scaled) {
|
||||
ty += (int)(((double)(px.y - dmargin.topLeft.y)/
|
||||
(double)(box.getHeight() - dmargin.topLeft.y - dmargin.botRight.y))
|
||||
* (double)(area.getHeight() - margin.topLeft.y - margin.botRight.y));
|
||||
}
|
||||
else {
|
||||
ty += (px.y - dmargin.topLeft.y) % (area.getHeight() - margin.topLeft.y - margin.botRight.y);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return tex->isPixelActive(vec2i(tx, ty));
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
#include "render/driver.h"
|
||||
#include "render/font.h"
|
||||
#include "render/render_state.h"
|
||||
#include "color.h"
|
||||
#include "rect.h"
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
enum DimensionMode {
|
||||
DM_Uniform,
|
||||
DM_Scaled,
|
||||
DM_Tiled
|
||||
};
|
||||
|
||||
enum AspectMarginMode {
|
||||
AMM_None,
|
||||
AMM_Horizontal,
|
||||
AMM_Vertical,
|
||||
};
|
||||
|
||||
enum GradientMode {
|
||||
GM_Normal,
|
||||
GM_Overlay,
|
||||
};
|
||||
|
||||
namespace gui {
|
||||
namespace skin {
|
||||
|
||||
class Element;
|
||||
|
||||
struct Gradient {
|
||||
Color colors[4];
|
||||
relrecti area;
|
||||
|
||||
void draw(render::RenderDriver& driver, recti pos,
|
||||
const recti* clip = 0) const;
|
||||
};
|
||||
|
||||
|
||||
struct Layer {
|
||||
relrecti area;
|
||||
const Element* ele;
|
||||
Color override;
|
||||
bool hasOverride;
|
||||
|
||||
Layer() : ele(0), hasOverride(false) {
|
||||
}
|
||||
|
||||
void draw(render::RenderDriver& driver, recti pos,
|
||||
const recti* clip = 0, Color color = Color(0xffffffff)) const;
|
||||
};
|
||||
|
||||
class Element {
|
||||
public:
|
||||
unsigned flags;
|
||||
|
||||
const render::RenderState* material;
|
||||
recti area;
|
||||
recti margin;
|
||||
AspectMarginMode aspectMargin;
|
||||
bool filled;
|
||||
DimensionMode horizMode, vertMode;
|
||||
GradientMode gradMode;
|
||||
|
||||
std::vector<Gradient> gradients;
|
||||
std::vector<Layer> layers;
|
||||
|
||||
Element() : material(0), aspectMargin(AMM_None), filled(true),
|
||||
horizMode(DM_Uniform), vertMode(DM_Uniform), gradMode(GM_Normal) {}
|
||||
|
||||
void clear();
|
||||
recti getDestinationMargin(vec2i size) const;
|
||||
bool isPixelActive(recti box, vec2i px) const;
|
||||
void draw(render::RenderDriver& driver, recti pos,
|
||||
const recti* clip = 0, Color color = Color(0xffffffff)) const;
|
||||
};
|
||||
|
||||
class Style {
|
||||
public:
|
||||
std::vector<Element*> elements;
|
||||
bool irregular;
|
||||
|
||||
Style() : irregular(false) {}
|
||||
|
||||
void addElement(Element* ele);
|
||||
Element* getExactElement(unsigned flags) const;
|
||||
const Element* getElement(unsigned flags) const;
|
||||
};
|
||||
|
||||
class Skin {
|
||||
const render::Font* errorFont;
|
||||
const Style* errorStyle;
|
||||
const Element* errorElement;
|
||||
public:
|
||||
const render::RenderState* material;
|
||||
std::string materialName;
|
||||
|
||||
std::vector<Style*> styles;
|
||||
std::vector<Color> colors;
|
||||
std::vector<const render::Font*> fonts;
|
||||
|
||||
void setStyle(unsigned index, Style* style);
|
||||
void setColor(unsigned index, Color color);
|
||||
void setFont(unsigned index, const render::Font* font);
|
||||
|
||||
bool hasStyle(unsigned index) const;
|
||||
const Style& getStyle(unsigned index) const;
|
||||
const Element& getElement(unsigned index, unsigned flags) const;
|
||||
const render::Font& getFont(unsigned index) const;
|
||||
Color getColor(unsigned index) const;
|
||||
|
||||
Skin();
|
||||
};
|
||||
|
||||
unsigned getStyleCount();
|
||||
int getStyleIndex(const std::string& name, bool addIfMissing = false);
|
||||
int getColorIndex(const std::string& name, bool addIfMissing = false);
|
||||
int getFontIndex(const std::string& name, bool addIfMissing = false);
|
||||
|
||||
int getElementFlag(const std::string& name, bool addIfMissing = false);
|
||||
std::string getElementFlagName(unsigned flag);
|
||||
|
||||
void enumerateStyleIndices(std::function<void(const std::string&,unsigned)> callback);
|
||||
void enumerateColorIndices(void (*cb)(const std::string&,unsigned));
|
||||
void enumerateFontIndices(void (*cb)(const std::string&,unsigned));
|
||||
|
||||
void enumerateElementFlags(void (*cb)(const std::string&,unsigned));
|
||||
|
||||
void clearDynamicIndices();
|
||||
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,891 @@
|
||||
#ifdef _MSC_VER
|
||||
#include <Windows.h>
|
||||
#include <DbgHelp.h>
|
||||
#endif
|
||||
#include "main/input_handling.h"
|
||||
#include "main/initialization.h"
|
||||
#include "main/tick.h"
|
||||
#include "main/logging.h"
|
||||
#include "main/version.h"
|
||||
#include "main/save_load.h"
|
||||
#include "main/game_platform.h"
|
||||
#include "util/format.h"
|
||||
#include "util/save_file.h"
|
||||
#include "main/references.h"
|
||||
#include "scripts/script_bind.h"
|
||||
#include "scripts/context_cache.h"
|
||||
#include "obj/lock.h"
|
||||
#include "files.h"
|
||||
#include "processing.h"
|
||||
#include "threads.h"
|
||||
#include "network/network_manager.h"
|
||||
#include "util/locked_type.h"
|
||||
#include "util/lockless_type.h"
|
||||
#include "network/init.h"
|
||||
#include "main/version.h"
|
||||
#include "render/vertexBuffer.h"
|
||||
#include <stdint.h>
|
||||
#ifdef __GNUC__
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
bool launchPatcher = false;
|
||||
|
||||
namespace scripts {
|
||||
void updateGame();
|
||||
};
|
||||
|
||||
unsigned reportVersion = 2;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
MINIDUMP_TYPE mdumpType = (MINIDUMP_TYPE)(MiniDumpWithProcessThreadData | MiniDumpWithIndirectlyReferencedMemory);
|
||||
|
||||
LONG WINAPI CrashCallback( LPEXCEPTION_POINTERS pException ) {
|
||||
print("Unhandled exception in thread %d", threads::getThreadID());
|
||||
flushLog();
|
||||
|
||||
SYSTEMTIME timeStamp;
|
||||
GetLocalTime(&timeStamp);
|
||||
char buffer[512];
|
||||
sprintf_s(buffer, 512, "%.420s\\SR2_%i-%i-%i_%i-%i-%i.mdmp", getProfileRoot().c_str(), timeStamp.wYear, timeStamp.wMonth, timeStamp.wDay, timeStamp.wHour, timeStamp.wMinute, timeStamp.wSecond);
|
||||
HANDLE file = CreateFile(buffer, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
|
||||
if(file != INVALID_HANDLE_VALUE) {
|
||||
print("Building minidump %s", buffer);
|
||||
|
||||
MINIDUMP_EXCEPTION_INFORMATION exception;
|
||||
exception.ClientPointers = FALSE;
|
||||
exception.ExceptionPointers = pException;
|
||||
exception.ThreadId = GetCurrentThreadId();
|
||||
|
||||
if(MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), file, mdumpType, (pException != 0) ? &exception : 0, 0, 0) == FALSE) {
|
||||
DWORD err = GetLastError();
|
||||
LPVOID lpMsgBuf;
|
||||
DWORD dw = GetLastError();
|
||||
|
||||
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(LPTSTR) &lpMsgBuf, 0, NULL );
|
||||
appendToErrorLog("Failed to generate Minidump:", true, false);
|
||||
appendToErrorLog((char*)lpMsgBuf, false);
|
||||
print("Failed to generate Minidump: %s", (char*)lpMsgBuf);
|
||||
|
||||
LocalFree(lpMsgBuf);
|
||||
}
|
||||
CloseHandle(file);
|
||||
}
|
||||
else {
|
||||
DWORD err = GetLastError();
|
||||
print("Could not write out minidump '%' (%d)", buffer, err);
|
||||
appendToErrorLog(std::string("Could not open minidump file: ") + toString(err));
|
||||
}
|
||||
|
||||
auto* ctx = asGetActiveContext();
|
||||
char cloudBuffer[512];
|
||||
if(ctx) {
|
||||
if(ctx->GetState() == asEXECUTION_EXCEPTION) {
|
||||
const char* pSection = nullptr;
|
||||
int column = 0;
|
||||
int line = ctx->GetExceptionLineNumber(&column,&pSection);
|
||||
sprintf_s(cloudBuffer, 512, "%s Script Exception: %.400s (%i:%i)", getSectionName(), pSection, line, column);
|
||||
}
|
||||
else if(ctx->GetState() == asEXECUTION_ACTIVE) {
|
||||
auto* f = ctx->GetFunction();
|
||||
if(f) {
|
||||
const char* pSection = nullptr;
|
||||
int column = 0;
|
||||
int line = ctx->GetLineNumber(0, &column, &pSection);
|
||||
sprintf_s(cloudBuffer, 512, "%s Script Active: %.400s (%i:%i)", getSectionName(), pSection, line, column);
|
||||
}
|
||||
else {
|
||||
sprintf_s(cloudBuffer, 512, "%s Script Active, Unknown state",getSectionName());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(threads::getThreadID() != 0 || !scene::renderingNode) {
|
||||
sprintf_s(cloudBuffer, 512, "Native: %s", getSectionName());
|
||||
}
|
||||
else {
|
||||
sprintf_s(cloudBuffer, 512, "Native: %s\nRendering %s", getSectionName(), scene::renderingNode->getName());
|
||||
}
|
||||
|
||||
print("%s", cloudBuffer);
|
||||
|
||||
if(devices.cloud) {
|
||||
devices.cloud->logException(pException->ExceptionRecord->ExceptionCode, pException, reportVersion, cloudBuffer);
|
||||
print("Attempted to upload exception to the cloud");
|
||||
}
|
||||
|
||||
if(ctx) {
|
||||
print("Exception occurred inner to script execution:");
|
||||
flushLog();
|
||||
scripts::logException();
|
||||
}
|
||||
|
||||
storeLog(std::string(buffer) + ".txt");
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
void initCrashDump() {
|
||||
SetUnhandledExceptionFilter(CrashCallback);
|
||||
}
|
||||
#else
|
||||
#include "dump_lin.h"
|
||||
#endif
|
||||
|
||||
#include "util/formula.h"
|
||||
|
||||
#include "main/console.h"
|
||||
|
||||
#include "render/render_mesh.h"
|
||||
|
||||
#include "ISoundDevice.h"
|
||||
#include "SLoadError.h"
|
||||
|
||||
#include "scripts/context_cache.h"
|
||||
#include "../as_addons/include/scripthelper.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#ifdef PROFILE_LOCKS
|
||||
extern bool printLockProfile, requireObserved;
|
||||
#endif
|
||||
|
||||
#ifdef PROFILE_EXECUTION
|
||||
namespace scripts {
|
||||
void logScriptProfile(asIScriptEngine* engine);
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
extern "C" {
|
||||
__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace scripts {
|
||||
void takeScreenshot(const std::string& fname, bool increment = true);
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, double> formulaTestVars;
|
||||
double formulaTest(void*,const std::string* var) {
|
||||
auto v = formulaTestVars.find(*var);
|
||||
if(v != formulaTestVars.end())
|
||||
return v->second;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
void netErrorMessage(const char* err, int code) {
|
||||
error("Network Error: %s (%i)", err, code);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
std::string launchConnect, launchPassword, launchLobby;
|
||||
|
||||
//Create profile directories
|
||||
{
|
||||
std::vector<std::string> paths;
|
||||
path_split(getProfileRoot(), paths);
|
||||
|
||||
std::string path;
|
||||
for(auto i = paths.begin(), end = paths.end(); i != end; ++i) {
|
||||
path += *i + "/";
|
||||
makeDirectory(path);
|
||||
}
|
||||
}
|
||||
|
||||
createLog();
|
||||
logDate();
|
||||
|
||||
enterSection(NS_Startup);
|
||||
net::setErrorCallback(netErrorMessage);
|
||||
|
||||
console.addCommand("volume", [](argList& args) {
|
||||
if(!devices.sound)
|
||||
return;
|
||||
if(args.empty())
|
||||
console.printLn(format("Volume: $1", devices.sound->getVolume()));
|
||||
else
|
||||
devices.sound->setVolume(toNumber<float>(args[0].c_str()));
|
||||
} );
|
||||
|
||||
console.addCommand("quit", [](argList&) {
|
||||
game_state = GS_Quit;
|
||||
} );
|
||||
|
||||
console.addCommand("menu", [](argList&) {
|
||||
game_state = GS_Menu;
|
||||
} );
|
||||
|
||||
console.addCommand("crash", [](argList&) {
|
||||
//Legit.
|
||||
*(volatile int*)nullptr = 42;
|
||||
} );
|
||||
|
||||
console.addCommand("game", [](argList&) {
|
||||
game_state = GS_Game;
|
||||
} );
|
||||
|
||||
console.addCommand("update", [](argList&) {
|
||||
scripts::updateGame();
|
||||
} );
|
||||
|
||||
console.addCommand("host", [](argList&) {
|
||||
devices.network->host("", 2048, 0, true);
|
||||
if(game_running)
|
||||
devices.network->signalServerReady();
|
||||
} );
|
||||
|
||||
console.addCommand("connect", [](argList& args) {
|
||||
if(args.size() == 0)
|
||||
devices.network->connect("127.0.0.1", 2048);
|
||||
else if(args.size() == 2)
|
||||
devices.network->connect(args[0], toNumber<int>(args[1]));
|
||||
else
|
||||
devices.network->connect(args[0], 2048);
|
||||
} );
|
||||
|
||||
console.addCommand("bw_monitor", [](argList& args) {
|
||||
if(args.size() == 0)
|
||||
devices.network->monitorBandwidth = true;
|
||||
else
|
||||
devices.network->monitorBandwidth = toBool(args[0]);
|
||||
});
|
||||
|
||||
console.addCommand("bw_incoming", [](argList&) {
|
||||
devices.network->dumpIncomingMonitor();
|
||||
});
|
||||
|
||||
console.addCommand("bw_outgoing", [](argList&) {
|
||||
devices.network->dumpOutgoingMonitor();
|
||||
});
|
||||
|
||||
console.addCommand("bw_incoming_total", [](argList&) {
|
||||
devices.network->dumpIncomingTotals();
|
||||
});
|
||||
|
||||
console.addCommand("bw_outgoing_total", [](argList&) {
|
||||
devices.network->dumpOutgoingTotals();
|
||||
});
|
||||
|
||||
console.addCommand("clear_cloud", [](argList&) {
|
||||
if(devices.cloud)
|
||||
devices.cloud->flushCloud();
|
||||
});
|
||||
|
||||
console.addCommand("full_gc", [](argList&) {
|
||||
extern bool fullGC;
|
||||
fullGC = true;
|
||||
});
|
||||
|
||||
console.addCommand("async_times", [](argList&) {
|
||||
void logAsyncLoad();
|
||||
logAsyncLoad();
|
||||
});
|
||||
|
||||
#ifdef PROFILE_LOCKS
|
||||
console.addCommand("print_locks", [](argList& args) {
|
||||
requireObserved = args.size() > 0 && toBool(args[0]);
|
||||
printLockProfile = true;
|
||||
});
|
||||
#endif
|
||||
#ifdef PROFILE_PROCESSING
|
||||
console.addCommand("print_processing", [](argList& args) {
|
||||
processing::printProcessingProfile();
|
||||
});
|
||||
#endif
|
||||
#ifdef PROFILE_SCRIPT_CALLBACKS
|
||||
console.addCommand("print_script_cb", [](argList& args) {
|
||||
print("Server:");
|
||||
if(devices.scripts.server)
|
||||
devices.scripts.server->printProfile();
|
||||
print("Client:");
|
||||
if(devices.scripts.client)
|
||||
devices.scripts.client->printProfile();
|
||||
print("Menu:");
|
||||
if(devices.scripts.menu)
|
||||
devices.scripts.menu->printProfile();
|
||||
});
|
||||
#endif
|
||||
#ifdef PROFILE_EXECUTION
|
||||
console.addCommand("log_script_profile", [](argList& args) {
|
||||
std::string engine;
|
||||
if(args.size() != 0)
|
||||
engine = args[0];
|
||||
|
||||
if(devices.scripts.menu && (engine.empty() || engine == "menu")) {
|
||||
print("Menu Script Profile:");
|
||||
scripts::logScriptProfile(devices.scripts.menu->engine);
|
||||
}
|
||||
|
||||
if(devices.scripts.client && (engine.empty() || engine == "client")) {
|
||||
print("Client Script Profile:");
|
||||
scripts::logScriptProfile(devices.scripts.client->engine);
|
||||
}
|
||||
|
||||
if(devices.scripts.server && (engine.empty() || engine == "server")) {
|
||||
print("Server Script Profile:");
|
||||
scripts::logScriptProfile(devices.scripts.server->engine);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
console.addCommand("print_groups", [](argList& args) {
|
||||
printLockStats();
|
||||
});
|
||||
|
||||
console.addCommand("export_bindings", [](argList& args) {
|
||||
if(args.size() == 0)
|
||||
console.printLn("Please specify an engine: menu, client, or server");
|
||||
else if(args[0] == "menu") {
|
||||
if(devices.scripts.menu)
|
||||
WriteConfigToFile(devices.scripts.menu->engine, "menu_config.txt");
|
||||
}
|
||||
else if(args[0] == "client") {
|
||||
if(devices.scripts.client)
|
||||
WriteConfigToFile(devices.scripts.client->engine, "client_config.txt");
|
||||
}
|
||||
else if(args[0] == "server") {
|
||||
if(devices.scripts.server)
|
||||
WriteConfigToFile(devices.scripts.server->engine, "server_config.txt");
|
||||
}
|
||||
else {
|
||||
console.printLn("Please specify an engine: menu, client, or server");
|
||||
}
|
||||
});
|
||||
|
||||
console.addCommand("gc_types", [](argList& args) {
|
||||
if(args.empty()) {
|
||||
console.printLn("Please specify an engine: menu, client, or server");
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned threshold = 5;
|
||||
|
||||
scripts::Manager* manager = 0;
|
||||
if(args[0] == "menu")
|
||||
manager = devices.scripts.menu;
|
||||
else if(args[0] == "client")
|
||||
manager = devices.scripts.client;
|
||||
else if(args[0] == "server")
|
||||
manager = devices.scripts.server;
|
||||
|
||||
if(manager == 0) {
|
||||
console.printLn("Engine is not active or is invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
if(args.size() >= 2)
|
||||
threshold = toNumber<unsigned>(args[1]);
|
||||
|
||||
std::map<asITypeInfo*,unsigned> counts;
|
||||
asUINT index = 0;
|
||||
asITypeInfo* type = 0;
|
||||
|
||||
asUINT end = ~0;
|
||||
if(args.size() >= 3 && args[2] == "new") {
|
||||
asUINT dummy;
|
||||
manager->engine->GetGCStatistics(&dummy,
|
||||
nullptr, nullptr, &end);
|
||||
}
|
||||
|
||||
while(index < end && manager->engine->GetObjectInGC(index++, 0, 0, &type) == asSUCCESS) {
|
||||
if(type != 0)
|
||||
counts[type] += 1;
|
||||
}
|
||||
|
||||
//Use a multimap to sort the data by count
|
||||
std::multimap<unsigned,asITypeInfo*> results;
|
||||
for(auto i = counts.begin(), end = counts.end(); i != end; ++i)
|
||||
if(i->second >= threshold)
|
||||
results.insert(std::pair<unsigned,asITypeInfo*>(i->second, i->first));
|
||||
|
||||
for(auto i = results.rbegin(), end = results.rend(); i != end; ++i) {
|
||||
auto* subtype = i->second->GetSubType();
|
||||
std::string line = i->second->GetName();
|
||||
if(subtype) {
|
||||
line += "<";
|
||||
line += subtype->GetName();
|
||||
line += ">";
|
||||
}
|
||||
line += ": ";
|
||||
line += toString(i->first,0);
|
||||
console.printLn(line);
|
||||
}
|
||||
});
|
||||
|
||||
console.addCommand("formula_var", [](argList& args) {
|
||||
if(args.size() == 2) {
|
||||
double value = toNumber<double>(args[1]);
|
||||
formulaTestVars[args[0]] = value;
|
||||
console.printLn(args[0] + " = " + toString(value,5));
|
||||
}
|
||||
});
|
||||
|
||||
console.addCommand("formula", [](argList& args) {
|
||||
try {
|
||||
if(!args.empty()) {
|
||||
std::string content;
|
||||
for(auto i = args.begin(), end = args.end(); i != end; ++i) {
|
||||
if(!content.empty())
|
||||
content += " ";
|
||||
content += *i;
|
||||
}
|
||||
Formula* formula = Formula::fromInfix(content.c_str());
|
||||
console.printLn(std::string(" = ") + toString(formula->evaluate(formulaTest, 0),5));
|
||||
}
|
||||
}
|
||||
catch(FormulaError err) {
|
||||
console.printLn(err.msg);
|
||||
}
|
||||
});
|
||||
|
||||
console.addCommand("screenshot", [](argList& args) {
|
||||
scripts::takeScreenshot(args.empty() ? "screenshot" : args[0]);
|
||||
});
|
||||
|
||||
console.addCommand("spherize", [](argList& args) {
|
||||
if(args.empty()) {
|
||||
console.printLn("Need image path and optional output path");
|
||||
return;
|
||||
}
|
||||
|
||||
Image* source = loadImage(args[0].c_str());
|
||||
if(!source) {
|
||||
console.printLn("Could not locate file");
|
||||
return;
|
||||
}
|
||||
|
||||
Image* out = source->sphereDistort();
|
||||
|
||||
try {
|
||||
if(!saveImage(out,args.size() > 1 ? args[1].c_str() : args[0].c_str()))
|
||||
throw 0;
|
||||
console.printLn("Spherized image");
|
||||
}
|
||||
catch(...) {
|
||||
console.printLn("Could not write image");
|
||||
}
|
||||
|
||||
delete out;
|
||||
});
|
||||
|
||||
console.addCommand("rand_image", [](argList& args) {
|
||||
if(args.size() < 3) {
|
||||
console.printLn("Need width, height, and output path");
|
||||
return;
|
||||
}
|
||||
|
||||
Image* out = Image::random(vec2u(toNumber<unsigned>(args[0]),toNumber<unsigned>(args[1])), randomi);
|
||||
|
||||
try {
|
||||
if(!saveImage(out,args[2].c_str()))
|
||||
throw 0;
|
||||
console.printLn("Output image");
|
||||
}
|
||||
catch(...) {
|
||||
console.printLn("Could not write image");
|
||||
}
|
||||
|
||||
delete out;
|
||||
});
|
||||
|
||||
/*console.addCommand("genetica_planet", [](argList& args) {
|
||||
if(args.empty()) {
|
||||
console.printLn("Need image path and optional output path");
|
||||
return;
|
||||
}
|
||||
|
||||
Image* source = loadImage(args[0].c_str());
|
||||
if(!source) {
|
||||
console.printLn("Could not locate file");
|
||||
return;
|
||||
}
|
||||
|
||||
//Hack: We need the image to be twice as wide as tall, so we completely violate all sense of sanity to do it easily
|
||||
source->height = source->width / 2;
|
||||
|
||||
Image* out = source->sphereDistort();
|
||||
|
||||
try {
|
||||
if(!saveImage(out,args.size() > 1 ? args[1].c_str() : args[0].c_str()))
|
||||
throw 0;
|
||||
console.printLn("Exported spherized genetica planet");
|
||||
}
|
||||
catch(...) {
|
||||
console.printLn("Could not write image");
|
||||
}
|
||||
|
||||
delete out;
|
||||
});*/
|
||||
|
||||
console.addCommand("save", [](argList& args) {
|
||||
if(game_state != GS_Game) {
|
||||
error("No game to save.");
|
||||
return;
|
||||
}
|
||||
if(devices.network->isClient) {
|
||||
error("Cannot save game from multiplayer clients.");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string savePath = devices.mods.getGlobalProfile("saves");
|
||||
std::string filename = path_join(savePath, args.empty() ? "quicksave.sr2" : args[0]);
|
||||
if(!match(filename.c_str(),"*.sr2"))
|
||||
filename += ".sr2";
|
||||
|
||||
double start = devices.driver->getAccurateTime();
|
||||
|
||||
processing::pause();
|
||||
bool success = saveGame(filename);
|
||||
processing::resume();
|
||||
|
||||
if(success) {
|
||||
double end = devices.driver->getAccurateTime();
|
||||
console.printLn(format("Saved '$1' (Took $2ms)", filename, (end - start) * 1000.0));
|
||||
}
|
||||
});
|
||||
|
||||
console.addCommand("3d_scale", [](argList& args) {
|
||||
extern double scale_3d;
|
||||
if(!args.empty()) {
|
||||
double scale = toNumber<double>(args[0]);
|
||||
if(scale > 0 && scale <= 2.0) {
|
||||
scale_3d = scale;
|
||||
}
|
||||
else {
|
||||
console.printLn("3D Render scale must be within (0,2]");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.printLn(format("3D Scale: $1", toString(scale_3d, 2)));
|
||||
});
|
||||
|
||||
console.addCommand("gpu_mem", [](argList& args) {
|
||||
unsigned long long texMem = 0, meshMem = 0;
|
||||
|
||||
auto& textures = devices.library.textures;
|
||||
for(size_t i = 0; i < textures.size(); ++i)
|
||||
texMem += textures[i]->getTextureBytes();
|
||||
|
||||
auto& meshes = devices.library.meshes;
|
||||
for(auto i = meshes.begin(), end = meshes.end(); i != end; ++i)
|
||||
meshMem += i->second->getMeshBytes();
|
||||
|
||||
unsigned long long MB_Bytes = 1024 * 1024;
|
||||
console.printLn(format(" GPU Total Mem Estimate: $1MB", (unsigned)((texMem + meshMem) / MB_Bytes)));
|
||||
console.printLn(format(" GPU Tex Mem Estimate: $1MB", (unsigned)(texMem / MB_Bytes)));
|
||||
console.printLn(format(" GPU Mesh Mem Estimate: $1MB", (unsigned)(meshMem / MB_Bytes)));
|
||||
});
|
||||
|
||||
console.addCommand("vb_float_limit", [](argList& args) {
|
||||
if(!args.empty())
|
||||
render::vbFloatLimit = toNumber<unsigned>(args.front());
|
||||
console.printLn(toString(render::vbFloatLimit));
|
||||
});
|
||||
|
||||
console.addCommand("vb_step_limit", [](argList& args) {
|
||||
if(!args.empty())
|
||||
render::vbMaxSteps = toNumber<unsigned>(args.front());
|
||||
console.printLn(toString(render::vbMaxSteps));
|
||||
});
|
||||
|
||||
console.addCommand("reload_gui", [](argList& args) {
|
||||
reload_gui = true;
|
||||
});
|
||||
|
||||
//Get command line arguments
|
||||
std::vector<std::string> mods;
|
||||
std::string localeName = "";
|
||||
bool loadGraphics = true;
|
||||
bool createWindow = true;
|
||||
|
||||
bool handleCrashes = true;
|
||||
|
||||
for(int argi = 1; argi < argc; ++argi) {
|
||||
std::string arg = argv[argi];
|
||||
if(arg.size() == 1) {
|
||||
error("Found '%c', accidental space?", arg[0]);
|
||||
continue;
|
||||
}
|
||||
|
||||
//Compatibility for switches
|
||||
if(arg[0] == '-' && arg[1] == '-')
|
||||
arg = arg.substr(2, arg.size() - 2);
|
||||
else if(arg[0] == '-' || arg[0] == '+' || arg[0] == '/')
|
||||
arg = arg.substr(1, arg.size() - 1);
|
||||
|
||||
//Options
|
||||
if(arg == "mod") {
|
||||
++argi;
|
||||
if(argi < argc)
|
||||
mods.push_back(argv[argi]);
|
||||
else
|
||||
error("Error: mod option requires a mod name.");
|
||||
}
|
||||
else if(arg == "locale") {
|
||||
++argi;
|
||||
if(argi < argc)
|
||||
localeName = argv[argi];
|
||||
else
|
||||
error("Error: locale option requires a locale name.");
|
||||
}
|
||||
else if(arg == "load") {
|
||||
++argi;
|
||||
if(argi < argc) {
|
||||
std::string fname = devices.mods.getGlobalProfile("saves");
|
||||
fname = path_join(fname, argv[argi]);
|
||||
if(!match(fname.c_str(),"*.sr2"))
|
||||
fname += ".sr2";
|
||||
|
||||
SaveFileInfo info;
|
||||
if(getSaveFileInfo(fname, info)) {
|
||||
mods.clear();
|
||||
for(auto i = info.mods.begin(), end = info.mods.end(); i != end; ++i) {
|
||||
auto* m = devices.mods.getMod(i->id);
|
||||
if(m) m = m->getFallback(i->version);
|
||||
|
||||
mods.push_back(m ? m->ident : i->id);
|
||||
}
|
||||
loadSaveName = fname;
|
||||
}
|
||||
game_state = GS_Game;
|
||||
}
|
||||
else
|
||||
error("Error: load option requires a save name.");
|
||||
}
|
||||
else if(arg == "quickstart") {
|
||||
game_state = GS_Game;
|
||||
}
|
||||
else if(arg == "watch-resources") {
|
||||
watch_resources = true;
|
||||
}
|
||||
else if(arg == "reload-gui") {
|
||||
reload_gui = true;
|
||||
}
|
||||
else if(arg == "test-scripts") {
|
||||
game_state = GS_Test_Scripts;
|
||||
loadGraphics = false;
|
||||
}
|
||||
else if(arg == "monitor-scripts") {
|
||||
game_state = GS_Monitor_Scripts;
|
||||
loadGraphics = false;
|
||||
monitor_files = true;
|
||||
}
|
||||
else if(arg == "no-window") {
|
||||
createWindow = false;
|
||||
}
|
||||
else if(arg == "no-sound") {
|
||||
use_sound = false;
|
||||
}
|
||||
else if(arg == "no-steam") {
|
||||
use_steam = false;
|
||||
}
|
||||
else if(arg == "verbose") {
|
||||
setLogLevel(LL_Info);
|
||||
}
|
||||
else if(arg == "chatty") {
|
||||
extern bool LOG_CHATTY;
|
||||
LOG_CHATTY = true;
|
||||
}
|
||||
else if(arg == "no-errorlog") {
|
||||
extern bool LOG_ERRORLOG;
|
||||
LOG_ERRORLOG = false;
|
||||
}
|
||||
else if(arg == "fullscreen") {
|
||||
fullscreen = true;
|
||||
}
|
||||
else if(arg == "nojit") {
|
||||
useJIT = false;
|
||||
}
|
||||
else if(arg == "nodump") {
|
||||
handleCrashes = false;
|
||||
}
|
||||
else if(arg == "nomodelcache") {
|
||||
extern bool useModelCache;
|
||||
useModelCache = false;
|
||||
}
|
||||
else if(arg == "connect") {
|
||||
//NOTE: Added by steam if the player connected through steam
|
||||
++argi;
|
||||
if(argi < argc)
|
||||
launchConnect = argv[argi];
|
||||
}
|
||||
else if(arg == "password") {
|
||||
//NOTE: Added by steam if the player connected through steam to a passworded server
|
||||
++argi;
|
||||
if(argi < argc)
|
||||
launchPassword = argv[argi];
|
||||
}
|
||||
else if(arg == "connect_lobby") {
|
||||
//NOTE: Added by steam if the player connected on a steam lobby
|
||||
++argi;
|
||||
if(argi < argc)
|
||||
launchLobby = argv[argi];
|
||||
}
|
||||
else if(arg == "version") {
|
||||
printf(
|
||||
"-- Star Ruler 2 v%s --\nEngine Build: %d\n"
|
||||
"Server Script Build: %d\nClient Script Build: %d\n"
|
||||
"Menu Script Build: %d\n",
|
||||
GAME_VERSION_NAME, ENGINE_BUILD, SERVER_SCRIPT_BUILD,
|
||||
CLIENT_SCRIPT_BUILD, MENU_SCRIPT_BUILD);
|
||||
return 0;
|
||||
}
|
||||
else if(arg == "help") {
|
||||
printf("Usage: %s [OPTIONS]\n\n"
|
||||
"--mod [MOD] Start the game with a specific mod active.\n"
|
||||
"--locale [LANGUAGE] Start the game in a specific language.\n"
|
||||
"--load [SAVEGAME] Load the specified savegame on start.\n"
|
||||
"--quickstart Immediately start a new game, skipping the menu.\n"
|
||||
"--watch-resources Automatically reload certain data files if changed.\n"
|
||||
"--test-scripts Compile scripts, then exit, logging any errors.\n"
|
||||
"--monitor-scripts Monitor scripts and run --test-scripts if changed.\n"
|
||||
"--no-window Run --test/monitor-scripts in console mode.\n"
|
||||
"--no-sound Disable the sound system from ever running.\n"
|
||||
"--no-steam Disable all steam functionality from running.\n"
|
||||
"--verbose Log more information about the game's state.\n"
|
||||
"--fullscreen Run the game in full screen mode.\n"
|
||||
"--version Display version information of the binary.\n"
|
||||
"--help Display this help message.\n"
|
||||
, argv[0]);
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
error("Unhandled argument: '%s'", arg.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
//Initialize dumping crash data
|
||||
if(handleCrashes)
|
||||
initCrashDump();
|
||||
|
||||
//Initialize global stuff
|
||||
if(!initGlobal(loadGraphics, createWindow))
|
||||
return 1;
|
||||
|
||||
//Initialize threaded variables for this thread
|
||||
initNewThread();
|
||||
|
||||
//Use the default locale
|
||||
if(!localeName.empty()) {
|
||||
game_locale = localeName;
|
||||
}
|
||||
else {
|
||||
auto* loc = devices.settings.engine.getSetting("sLocale");
|
||||
if(loc)
|
||||
game_locale = loc->toString();
|
||||
}
|
||||
|
||||
//Run the default mod
|
||||
print("Loading mod(s)");
|
||||
initMods(mods);
|
||||
|
||||
//Check for initialization options
|
||||
switch(game_state) {
|
||||
case GS_Game:
|
||||
initGame();
|
||||
break;
|
||||
case GS_Test_Scripts:
|
||||
if(createWindow) {
|
||||
game_state = GS_Console_Wait;
|
||||
}
|
||||
else {
|
||||
finishPreload();
|
||||
game_state = GS_Quit;
|
||||
}
|
||||
case GS_Monitor_Scripts:
|
||||
processing::end();
|
||||
processing::clear();
|
||||
break;
|
||||
}
|
||||
|
||||
threads::setThreadPriority(threads::TP_High);
|
||||
|
||||
//NOTE: Only supports ipv4 address:port or domain names with implied port; ipv6 will break
|
||||
if(!launchLobby.empty()) {
|
||||
if(devices.cloud)
|
||||
launchConnect = devices.cloud->getLobbyConnectAddress(launchLobby, &launchPassword);
|
||||
if(launchConnect.empty())
|
||||
error("Error: could not find lobby with id '%s'\n", launchLobby.c_str());
|
||||
else
|
||||
info("Connecting to lobby '%s'...\n", launchLobby.c_str());
|
||||
}
|
||||
if(!launchConnect.empty()) {
|
||||
unsigned short port = 2048;
|
||||
auto portIndex = launchConnect.find_last_of(':');
|
||||
if(portIndex != std::string::npos)
|
||||
port = toNumber<unsigned>(launchConnect.substr(portIndex+1));
|
||||
|
||||
devices.network->connect(launchConnect.substr(0, portIndex), port, launchPassword, true);
|
||||
}
|
||||
|
||||
devices.render->reportErrors("Pre-init");
|
||||
|
||||
//Run the main loop
|
||||
while(game_state != GS_Quit) {
|
||||
//Render whichever state we're in
|
||||
switch(game_state) {
|
||||
case GS_Menu:
|
||||
tickMenu();
|
||||
break;
|
||||
case GS_Game:
|
||||
if(!game_running)
|
||||
initGame();
|
||||
tickGame();
|
||||
break;
|
||||
case GS_Monitor_Scripts:
|
||||
if(tickMonitor()) {
|
||||
finishPreload();
|
||||
destroyMod();
|
||||
if(createWindow)
|
||||
console.clear();
|
||||
else
|
||||
printf("\n\n--Reloading--\n\n\n");
|
||||
initMods(mods);
|
||||
}
|
||||
if(!createWindow) {
|
||||
threads::sleep(100);
|
||||
break;
|
||||
}
|
||||
case GS_Console_Wait:
|
||||
console.show();
|
||||
tickConsole();
|
||||
break;
|
||||
case GS_Load_Prep:
|
||||
extern bool loadPrep(const std::string& fname);
|
||||
if(!loadPrep(loadSaveName)) {
|
||||
loadSaveName.clear();
|
||||
if(game_running)
|
||||
game_state = GS_Game;
|
||||
else
|
||||
game_state = GS_Menu;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
destroyMod();
|
||||
destroyGlobal();
|
||||
|
||||
if(launchPatcher) {
|
||||
#ifdef _MSC_VER
|
||||
char buffer[1024];
|
||||
sprintf_s(buffer, "start \"Star Ruler 2 Patcher\" patcher.exe \"%s\"", getAbsolutePath(".").c_str());
|
||||
system(buffer);
|
||||
#else
|
||||
if(fork() == 0) {
|
||||
#ifdef __amd64__
|
||||
execlp("./bin/lin64/Patcher.bin", "./bin/lin64/Patcher.bin", (char*)nullptr);
|
||||
#else
|
||||
execlp("./bin/lin32/Patcher.bin", "./bin/lin32/Patcher.bin", (char*)nullptr);
|
||||
#endif
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
@@ -0,0 +1,11 @@
|
||||
#define NET_IDLE_SLEEP 5
|
||||
|
||||
#include "network.h"
|
||||
const int MS_PORT = 8892;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
net::LobbyServer srv(MS_PORT);
|
||||
srv.runThreads(4);
|
||||
while(srv.active)
|
||||
threads::sleep(100);
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
|
||||
namespace memory {
|
||||
|
||||
//A pre-allocated pool of uniformly sized objects
|
||||
//Allocated memory is only cleared when all objects in the pool are deleted
|
||||
//When all memory is used up, falls back on global operator new/delete
|
||||
template<class T, class L>
|
||||
class AllocOnlyPool {
|
||||
L lock;
|
||||
T* pStart, *pNext, *pEnd;
|
||||
unsigned allocated;
|
||||
|
||||
public:
|
||||
AllocOnlyPool(unsigned count) : allocated(0) {
|
||||
pStart = (T*)new unsigned char[count * sizeof(T)];
|
||||
pNext = pStart;
|
||||
pEnd = pStart + count;
|
||||
}
|
||||
|
||||
~AllocOnlyPool() {
|
||||
delete[] (unsigned char*)pStart;
|
||||
}
|
||||
|
||||
void* alloc() {
|
||||
lock.lock();
|
||||
|
||||
void* r;
|
||||
if(pNext != pEnd) {
|
||||
++allocated;
|
||||
r = pNext++;
|
||||
lock.release();
|
||||
}
|
||||
else {
|
||||
lock.release();
|
||||
r = ::operator new(sizeof(T));
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void dealloc(T* p) {
|
||||
if(p >= pStart && p < pEnd) {
|
||||
lock.lock();
|
||||
if(--allocated == 0)
|
||||
pNext = pStart;
|
||||
lock.release();
|
||||
}
|
||||
else {
|
||||
::operator delete(p);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//A pre-allocated region for any data types
|
||||
//Allocated memory is only cleared when all objects in the pool are deleted
|
||||
//When all memory is used up, falls back on global operator new/delete
|
||||
template<class Lock>
|
||||
class AllocOnlyRegion {
|
||||
unsigned char* pStart, *pNext, *pEnd;
|
||||
unsigned allocated;
|
||||
Lock lock;
|
||||
public:
|
||||
AllocOnlyRegion(unsigned bytes) : allocated(0) {
|
||||
pStart = new unsigned char[bytes];
|
||||
pNext = pStart;
|
||||
pEnd = pStart + bytes;
|
||||
}
|
||||
|
||||
~AllocOnlyRegion() {
|
||||
delete[] (unsigned char*)pStart;
|
||||
}
|
||||
|
||||
void* alloc(size_t size) {
|
||||
lock.lock();
|
||||
if(size < (size_t)(pEnd - pNext)) {
|
||||
++allocated;
|
||||
auto pCopy = pNext;
|
||||
pNext += size;
|
||||
lock.release();
|
||||
return pCopy;
|
||||
}
|
||||
else {
|
||||
lock.release();
|
||||
return ::operator new(size);
|
||||
}
|
||||
}
|
||||
|
||||
void dealloc(void* p) {
|
||||
if(p >= pStart && p < pEnd) {
|
||||
lock.lock();
|
||||
if(--allocated == 0)
|
||||
pNext = pStart;
|
||||
lock.release();
|
||||
}
|
||||
else {
|
||||
::operator delete(p);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
|
||||
namespace memory {
|
||||
|
||||
//A pre-allocated pool of uniformly sized objects
|
||||
template<class T>
|
||||
class MemoryPool {
|
||||
T* pPool, *pEnd;
|
||||
unsigned short* indices;
|
||||
unsigned nextIndex;
|
||||
|
||||
public:
|
||||
MemoryPool(unsigned count) {
|
||||
if(count > 65535)
|
||||
count = 65535;
|
||||
nextIndex = count-1;
|
||||
|
||||
pPool = (T*)new unsigned char[count * sizeof(T)];
|
||||
pEnd = pPool + count;
|
||||
|
||||
indices = new unsigned short[count];
|
||||
for(unsigned short i = 0; i < count; ++i)
|
||||
indices[i] = count-i;
|
||||
}
|
||||
|
||||
~MemoryPool() {
|
||||
delete[] (unsigned char*)pPool;
|
||||
delete[] indices;
|
||||
}
|
||||
|
||||
void* alloc() {
|
||||
if(nextIndex != 0xffffffff) {
|
||||
return pPool + indices[nextIndex--];
|
||||
}
|
||||
else {
|
||||
return ::operator new(sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
void dealloc(T* p) {
|
||||
if(p >= pPool && p < pEnd) {
|
||||
indices[++nextIndex] = p - pPool;
|
||||
}
|
||||
else {
|
||||
::operator delete(p);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,531 @@
|
||||
#include "compat/misc.h"
|
||||
#include "files.h"
|
||||
#include "mods/mod_manager.h"
|
||||
#include "main/logging.h"
|
||||
#include "str_util.h"
|
||||
#include "main/references.h"
|
||||
#include <string>
|
||||
|
||||
namespace mods {
|
||||
|
||||
static const unsigned CUR_COMPATIBILITY = 200;
|
||||
|
||||
Mod::Mod() : parent(0), isBase(false), version(1), listed(true), enabled(false), isNew(false), forced(false), compatibility(0), forCurrentVersion(false) {
|
||||
}
|
||||
|
||||
bool Mod::resolve(std::string& path) const {
|
||||
const Mod* mod = this;
|
||||
while(mod) {
|
||||
std::string modPath = path_join(mod->abspath, path);
|
||||
if(fileExists(modPath)) {
|
||||
path = modPath;
|
||||
return true;
|
||||
}
|
||||
mod = mod->parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Mod::resolve(const std::string& path, std::vector<std::string>& check) const {
|
||||
const Mod* mod = this;
|
||||
while(mod) {
|
||||
std::string modPath = path_join(mod->abspath, path);
|
||||
if(fileExists(modPath))
|
||||
check.push_back(modPath);
|
||||
mod = mod->parent;
|
||||
}
|
||||
}
|
||||
|
||||
bool Mod::hasOverride(const std::string& path, bool isDirectory) const {
|
||||
foreach(it, override_patterns) {
|
||||
if(match(path.c_str(), *it))
|
||||
return true;
|
||||
}
|
||||
|
||||
if(isDirectory)
|
||||
return hasOverride(path+"/");
|
||||
return false;
|
||||
}
|
||||
|
||||
void Mod::listFiles(const std::string& dir, std::map<std::string, std::string>& out,
|
||||
const char* filter, bool recurse, bool inherit) const {
|
||||
const Mod* mod = this;
|
||||
|
||||
CompiledPattern filterReqs;
|
||||
compile_pattern(filter, filterReqs);
|
||||
|
||||
std::function<void(const std::string&, const std::string&, const std::string&)> readDir
|
||||
= [&](const std::string& relpath, const std::string& abspath, const std::string& dirpath) {
|
||||
std::vector<std::string> filenames;
|
||||
listDirectory(abspath, filenames);
|
||||
|
||||
foreach(it, filenames) {
|
||||
std::string absfile = path_join(abspath, *it);
|
||||
std::string relfile = path_join(relpath, *it);
|
||||
std::string dirfile = path_join(dirpath, relfile);
|
||||
bool isDir = isDirectory(absfile);
|
||||
|
||||
//if(!dirpath.empty() && hasOverride(dirfile, isDir))
|
||||
// continue;
|
||||
|
||||
//Recurse into directories
|
||||
if(recurse && isDir) {
|
||||
readDir(relfile, absfile, dirpath);
|
||||
continue;
|
||||
}
|
||||
|
||||
//Check if it matches the pattern
|
||||
if(!match(it->c_str(), filterReqs))
|
||||
continue;
|
||||
|
||||
auto fnd = out.find(relfile);
|
||||
if(fnd == out.end())
|
||||
out[relfile] = absfile;
|
||||
}
|
||||
};
|
||||
|
||||
while(mod) {
|
||||
std::string abspath = path_join(mod->abspath, dir);
|
||||
if(isDirectory(abspath))
|
||||
readDir("", abspath, mod == this ? "" : dir);
|
||||
|
||||
if(mod == this && (!inherit || hasOverride(dir, true)))
|
||||
break;
|
||||
mod = mod->parent;
|
||||
}
|
||||
}
|
||||
|
||||
void Mod::resolveDirectory(const std::string& dir, std::vector<std::string>& out) const {
|
||||
const Mod* mod = this;
|
||||
while(mod) {
|
||||
std::string path = path_join(mod->abspath, dir);
|
||||
if(isDirectory(path))
|
||||
out.push_back(path);
|
||||
mod = mod->parent;
|
||||
}
|
||||
}
|
||||
|
||||
const Mod* Mod::getFallback(unsigned short v) const {
|
||||
if(v > version)
|
||||
return nullptr;
|
||||
if(v == version)
|
||||
return this;
|
||||
//Look for a version in [v,version)
|
||||
for(unsigned short i = v; i < version; ++i) {
|
||||
auto fb = fallbacks.find(i);
|
||||
if(fb != fallbacks.end())
|
||||
return devices.mods.getMod(fb->second);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
void Mod::loadFileList() {
|
||||
containedFiles.clear();
|
||||
|
||||
std::function<void(const std::string&, const std::string&)> readDir
|
||||
= [&](const std::string& relpath, const std::string& abspath) {
|
||||
std::vector<std::string> filenames;
|
||||
listDirectory(abspath, filenames);
|
||||
|
||||
foreach(it, filenames) {
|
||||
std::string absfile = path_join(abspath, *it);
|
||||
std::string relfile = path_join(relpath, *it);
|
||||
bool isDir = isDirectory(absfile);
|
||||
|
||||
if(isDir) {
|
||||
readDir(relfile, absfile);
|
||||
}
|
||||
else if(!relpath.empty()){
|
||||
containedFiles.insert(relfile);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
readDir("", abspath);
|
||||
}
|
||||
|
||||
void Mod::getConflicts(const Mod* other, std::vector<std::string>& conflicts) const {
|
||||
foreach(it, containedFiles) {
|
||||
if(other->containedFiles.find(*it) != other->containedFiles.end())
|
||||
conflicts.push_back(*it);
|
||||
}
|
||||
}
|
||||
|
||||
bool Mod::isCompatible(const Mod* other) const {
|
||||
if(isBase || other->isBase)
|
||||
return !isBase || !other->isBase;
|
||||
foreach(it, containedFiles) {
|
||||
if(other->containedFiles.find(*it) != other->containedFiles.end())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Mod::getProfile() const {
|
||||
std::string dir = getProfileRoot();
|
||||
|
||||
//Make sure the root profile dir is created
|
||||
if(!isDirectory(dir))
|
||||
makeDirectory(dir);
|
||||
|
||||
//Create any intermediate directories
|
||||
std::vector<std::string> dirs;
|
||||
|
||||
if(dirname.empty())
|
||||
dirs.push_back("base");
|
||||
else
|
||||
dirs.push_back(dirname);
|
||||
|
||||
foreach(it, dirs) {
|
||||
dir = path_join(dir, *it);
|
||||
|
||||
if(!isDirectory(dir))
|
||||
makeDirectory(dir);
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
std::string Mod::getProfile(const std::string& path) const {
|
||||
std::string dir = getProfileRoot();
|
||||
|
||||
//Make sure the root profile dir is created
|
||||
if(!isDirectory(dir))
|
||||
makeDirectory(dir);
|
||||
|
||||
//Create any intermediate directories
|
||||
std::vector<std::string> dirs;
|
||||
|
||||
if(dirname.empty())
|
||||
dirs.push_back("base");
|
||||
else
|
||||
dirs.push_back(dirname);
|
||||
|
||||
if(!path.empty())
|
||||
path_split(path, dirs);
|
||||
|
||||
foreach(it, dirs) {
|
||||
dir = path_join(dir, *it);
|
||||
|
||||
if(!isDirectory(dir))
|
||||
makeDirectory(dir);
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
Manager::Manager() {
|
||||
baseMod = new Mod();
|
||||
baseMod->ident = "base";
|
||||
baseMod->name = "base";
|
||||
baseMod->abspath = getAbsolutePath(".");
|
||||
baseMod->isBase = true;
|
||||
baseMod->listed = false;
|
||||
baseMod->version = 3;
|
||||
baseMod->fallbacks[0] = "r3444";
|
||||
baseMod->fallbacks[1] = "r4257";
|
||||
baseMod->fallbacks[2] = "r4676";
|
||||
baseMod->compatibility = 200;
|
||||
baseMod->forCurrentVersion = true;
|
||||
|
||||
modNames["base"] = baseMod;
|
||||
mods.push_back(baseMod);
|
||||
}
|
||||
|
||||
Manager::~Manager() {
|
||||
foreach(it, mods)
|
||||
delete *it;
|
||||
}
|
||||
|
||||
void Manager::registerMod(const std::string& folder, const std::string& ident) {
|
||||
std::string infopath = path_join(folder, "modinfo.txt");
|
||||
if(!fileExists(infopath))
|
||||
return;
|
||||
|
||||
Mod& mod = *new Mod();
|
||||
mod.ident = ident;
|
||||
mod.dirname = ident;
|
||||
mod.abspath = getAbsolutePath(folder);
|
||||
|
||||
std::string key, value;
|
||||
DataReader datafile(infopath);
|
||||
while(datafile++) {
|
||||
key = datafile.key;
|
||||
value = datafile.value;
|
||||
if(key == "Name") {
|
||||
mod.name = value;
|
||||
}
|
||||
else if(key == "Description") {
|
||||
mod.description = value;
|
||||
}
|
||||
else if(key == "Override") {
|
||||
mod.overrides.push_back(value);
|
||||
|
||||
CompiledPattern compiled;
|
||||
compile_pattern(value.c_str(), compiled);
|
||||
|
||||
mod.override_patterns.push_back(compiled);
|
||||
}
|
||||
else if(key == "Derives From") {
|
||||
if(value == "-")
|
||||
mod.parentname = "";
|
||||
else
|
||||
mod.parentname = value;
|
||||
}
|
||||
else if(key == "Base Mod") {
|
||||
mod.isBase = toBool(value);
|
||||
}
|
||||
else if(key == "Listed") {
|
||||
mod.listed = toBool(value);
|
||||
}
|
||||
else if(key == "Version") {
|
||||
mod.version = toNumber<unsigned short>(value);
|
||||
}
|
||||
else if(key == "Compatibility") {
|
||||
mod.compatibility = toNumber<unsigned>(value);
|
||||
mod.forCurrentVersion = mod.compatibility >= CUR_COMPATIBILITY;
|
||||
}
|
||||
else if(key == "Fallback") {
|
||||
std::string m, v;
|
||||
splitKeyValue(value, m, v, "=");
|
||||
mod.fallbacks[toNumber<unsigned short>(v)] = m;
|
||||
}
|
||||
}
|
||||
|
||||
if(!mod.name.empty()) {
|
||||
mods.push_back(&mod);
|
||||
if(mod.parentname.empty() && !mod.overrides.empty())
|
||||
warn("WARNING: Mod '%s' has overrides declared, but does not derive from any mod.", mod.name.c_str());
|
||||
while(modNames.find(mod.name) != modNames.end()) {
|
||||
error("Duplicate mod named '%s'", mod.name.c_str());
|
||||
mod.name += " (Copy)";
|
||||
}
|
||||
while(modNames.find(mod.ident) != modNames.end()) {
|
||||
error("Duplicate mod ident '%s'", mod.ident.c_str());
|
||||
mod.ident += " (Copy)";
|
||||
}
|
||||
modNames[mod.name] = &mod;
|
||||
modNames[mod.ident] = &mod;
|
||||
}
|
||||
else {
|
||||
delete &mod;
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::registerDirectory(const std::string& dirname) {
|
||||
std::vector<std::string> files;
|
||||
listDirectory(dirname, files);
|
||||
|
||||
foreach(it, files) {
|
||||
std::string abspath = path_join(dirname, *it);
|
||||
std::string infopath = path_join(abspath, "modinfo.txt");
|
||||
|
||||
if(isDirectory(abspath) && fileExists(infopath))
|
||||
registerMod(abspath, *it);
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::finalize() {
|
||||
foreach(it, mods) {
|
||||
auto* mod = *it;
|
||||
if(!mod->parentname.empty()) {
|
||||
auto fnd = modNames.find(mod->parentname);
|
||||
if(fnd != modNames.end()) {
|
||||
mod->parent = fnd->second;
|
||||
if(!mod->isBase && fnd->second->isBase)
|
||||
mod->isBase = true;
|
||||
}
|
||||
}
|
||||
if(mod->listed)
|
||||
mod->isNew = true;
|
||||
}
|
||||
|
||||
//Load enabled and disabled mod files
|
||||
std::string filename = path_join(getProfileRoot(), "mods.txt");
|
||||
DataReader datafile(filename);
|
||||
while(datafile++) {
|
||||
auto it = modNames.find(datafile.value);
|
||||
if(it != modNames.end()) {
|
||||
it->second->enabled = datafile.key == "Enabled" || datafile.key == "Forced";
|
||||
it->second->forced = datafile.key == "Forced";
|
||||
it->second->isNew = false;
|
||||
|
||||
if(it->second->enabled && it->second->compatibility < CUR_COMPATIBILITY) {
|
||||
if(!it->second->forced) {
|
||||
it->second->enabled = false;
|
||||
it->second->isNew = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Disable conflicted mods
|
||||
for(size_t i = 0, cnt = mods.size(); i < cnt; ++i) {
|
||||
auto* mod = mods[i];
|
||||
mod->loadFileList();
|
||||
if(!mod->enabled)
|
||||
continue;
|
||||
for(size_t j = 0; j < i; ++j) {
|
||||
auto* othermod = mods[j];
|
||||
if(!othermod->enabled)
|
||||
continue;
|
||||
if(!mod->isCompatible(othermod)) {
|
||||
error("ERROR: Mod '%s' is not compatible with previously enabled mod '%s'. Disabling.",
|
||||
othermod->name.c_str(), mod->name.c_str());
|
||||
othermod->enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::saveState() {
|
||||
std::string filename = path_join(getProfileRoot(), "mods.txt");
|
||||
std::ofstream file(filename);
|
||||
|
||||
for(auto it = mods.begin(), end = mods.end(); it != end; ++it) {
|
||||
auto* mod = *it;
|
||||
if(!mod->listed)
|
||||
continue;
|
||||
|
||||
mod->isNew = false;
|
||||
if(mod->enabled) {
|
||||
if(mod->forced && mod->compatibility < CUR_COMPATIBILITY)
|
||||
file << "Forced: ";
|
||||
else
|
||||
file << "Enabled: ";
|
||||
}
|
||||
else
|
||||
file << "Disabled: ";
|
||||
file << mod->ident << "\n";
|
||||
}
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
void Manager::clearMods() {
|
||||
activeMods.clear();
|
||||
activeMods.push_back(baseMod);
|
||||
currentMod = baseMod;
|
||||
}
|
||||
|
||||
bool Manager::enableMod(const std::string& name) {
|
||||
auto it = modNames.find(name);
|
||||
if(it == modNames.end()) {
|
||||
error("Could not find mod: %s", name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
Mod* mod = it->second;
|
||||
foreach(chk, activeMods) {
|
||||
if(*chk == mod)
|
||||
return true;
|
||||
if(mod->isBase && (*chk)->isBase) {
|
||||
if(*chk == baseMod) {
|
||||
activeMods[0] = mod;
|
||||
currentMod = mod;
|
||||
return true;
|
||||
}
|
||||
error("Cannot enable mod: %s. A base mod is already loaded.", name.c_str());
|
||||
return false;
|
||||
}
|
||||
if(!mod->isCompatible(*chk)) {
|
||||
error("Cannot enable mod: %s. Incompatible with mod %s.", name.c_str(), (*chk)->name.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
activeMods.push_back(mod);
|
||||
return true;
|
||||
}
|
||||
|
||||
const Mod* Manager::getMod(const std::string& name) const {
|
||||
auto it = modNames.find(name);
|
||||
if(it == modNames.end())
|
||||
return 0;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::string Manager::resolve(const std::string& path) const {
|
||||
if(path[0] == '~')
|
||||
return path.substr(1, path.size() - 1);
|
||||
std::string result = path;
|
||||
for(int i = (int)activeMods.size() - 1; i >= 0; --i) {
|
||||
if(activeMods[i]->resolve(result))
|
||||
return result;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
std::string Manager::resolve(const std::string& path, const std::string& indir) const {
|
||||
if(path[0] == '~') {
|
||||
if(path[1] == '/')
|
||||
return path.substr(2, path.size() - 2);
|
||||
else
|
||||
return path_join(indir, path.substr(1, path.size() - 1));
|
||||
}
|
||||
if(path[0] == '/')
|
||||
return resolve(path.substr(1, path.size() - 1));
|
||||
return resolve(path_join(indir, path));
|
||||
}
|
||||
|
||||
void Manager::resolve(const std::string& path, std::vector<std::string>& check) const {
|
||||
if(path[0] == '~') {
|
||||
check.push_back(path.substr(1, path.size() - 1));
|
||||
}
|
||||
else {
|
||||
for(int i = (int)activeMods.size() - 1; i >= 0; --i)
|
||||
activeMods[i]->resolve(path, check);
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::listFiles(const std::string& dir, std::map<std::string, std::string>& out,
|
||||
const char* filter, bool recurse) const {
|
||||
for(int i = (int)activeMods.size() - 1; i >= 0; --i)
|
||||
activeMods[i]->listFiles(dir, out, filter, recurse);
|
||||
}
|
||||
|
||||
void Manager::listFiles(const std::string& dir, const char* filter,
|
||||
std::function<void(const std::string&)> cb, bool recurse) const {
|
||||
std::map<std::string, std::string> result;
|
||||
listFiles(dir, result, filter, recurse);
|
||||
|
||||
foreach(it, result)
|
||||
cb(it->second);
|
||||
}
|
||||
|
||||
void Manager::resolveDirectory(const std::string& dir, std::vector<std::string>& out) const {
|
||||
currentMod->resolveDirectory(dir, out);
|
||||
}
|
||||
|
||||
std::string Manager::getProfile(const std::string& path) const {
|
||||
return currentMod->getProfile(path);
|
||||
}
|
||||
|
||||
std::string Manager::getProfile() const {
|
||||
return currentMod->getProfile();
|
||||
}
|
||||
|
||||
std::string Manager::getGlobalProfile(const std::string& path) const {
|
||||
std::string dir = getProfileRoot();
|
||||
|
||||
//Make sure the root profile dir is created
|
||||
if(!isDirectory(dir))
|
||||
makeDirectory(dir);
|
||||
|
||||
//Create any intermediate directories
|
||||
std::vector<std::string> dirs;
|
||||
|
||||
if(!path.empty())
|
||||
path_split(path, dirs);
|
||||
|
||||
foreach(it, dirs) {
|
||||
dir = path_join(dir, *it);
|
||||
|
||||
if(!isDirectory(dir))
|
||||
makeDirectory(dir);
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <functional>
|
||||
|
||||
namespace mods {
|
||||
|
||||
struct Mod {
|
||||
std::string ident;
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::string dirname;
|
||||
std::string abspath;
|
||||
|
||||
std::string parentname;
|
||||
Mod* parent;
|
||||
bool isBase;
|
||||
bool listed;
|
||||
bool enabled;
|
||||
bool isNew;
|
||||
bool forced;
|
||||
bool forCurrentVersion;
|
||||
|
||||
unsigned short version;
|
||||
unsigned compatibility;
|
||||
|
||||
std::unordered_map<unsigned short,std::string> fallbacks;
|
||||
std::vector<std::string> overrides;
|
||||
std::vector<std::vector<std::string>> override_patterns;
|
||||
std::unordered_set<std::string> containedFiles;
|
||||
|
||||
Mod();
|
||||
|
||||
std::string getProfile() const;
|
||||
std::string getProfile(const std::string& path) const;
|
||||
|
||||
bool hasOverride(const std::string& path, bool isDirectory = false) const;
|
||||
|
||||
bool resolve(std::string& path) const;
|
||||
|
||||
void resolve(const std::string& path, std::vector<std::string>& check) const;
|
||||
void listFiles(const std::string& dir, std::map<std::string, std::string>& out,
|
||||
const char* filter = "*", bool recurse = false, bool inherit = true) const;
|
||||
void resolveDirectory(const std::string& dir, std::vector<std::string>& out) const;
|
||||
|
||||
void loadFileList();
|
||||
bool isCompatible(const Mod* other) const;
|
||||
void getConflicts(const Mod* other, std::vector<std::string>& conflicts) const;
|
||||
|
||||
//Returns a mod to use for the requested version (may be this mod)
|
||||
// If the required mod is not present, returns null
|
||||
const Mod* getFallback(unsigned short version) const;
|
||||
};
|
||||
|
||||
class Manager {
|
||||
public:
|
||||
std::map<std::string, Mod*> modNames;
|
||||
std::vector<Mod*> mods, activeMods;
|
||||
Mod* baseMod;
|
||||
Mod* currentMod;
|
||||
|
||||
Manager();
|
||||
~Manager();
|
||||
|
||||
void registerMod(const std::string& folder, const std::string& ident);
|
||||
void registerDirectory(const std::string& dirname);
|
||||
void finalize();
|
||||
void saveState();
|
||||
|
||||
void clearMods();
|
||||
bool enableMod(const std::string& name);
|
||||
|
||||
const Mod* getMod(const std::string& name) const;
|
||||
|
||||
std::string resolve(const std::string& path) const;
|
||||
std::string resolve(const std::string& path, const std::string& indir) const;
|
||||
void resolve(const std::string& path, std::vector<std::string>& check) const;
|
||||
|
||||
//Lists all files in a directory, pairing each name to the full path in <out>
|
||||
void listFiles(const std::string& dir, std::map<std::string, std::string>& out,
|
||||
const char* filter = "*", bool recurse = false) const;
|
||||
|
||||
//Lists all files in a directory, calling <cb> with the full path of each
|
||||
void listFiles(const std::string& dir, const char* filter,
|
||||
std::function<void(const std::string&)> cb, bool recurse = false) const;
|
||||
|
||||
//Finds all directories that match the passed relative dir and outputs their absolute paths
|
||||
void resolveDirectory(const std::string& dir, std::vector<std::string>& out) const;
|
||||
|
||||
std::string getProfile() const;
|
||||
std::string getProfile(const std::string& path) const;
|
||||
std::string getGlobalProfile(const std::string& path) const;
|
||||
};
|
||||
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,248 @@
|
||||
#pragma once
|
||||
#include "network/player.h"
|
||||
#include "network/lobby.h"
|
||||
#include "network/message_types.h"
|
||||
#include "threads.h"
|
||||
#include "vec3.h"
|
||||
#include "quaternion.h"
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#ifndef NET_DELTA_INTERVAL
|
||||
//Periodic delta flushing, etc every N ms
|
||||
#define NET_DELTA_INTERVAL 125
|
||||
#endif
|
||||
|
||||
#ifndef NET_DELTA_PACKETHINT
|
||||
//If a delta packet is larger than this,
|
||||
//cut it off.
|
||||
#define NET_DELTA_PACKETHINT 1400
|
||||
#endif
|
||||
|
||||
#ifndef NET_VISION_HIDE_SIZE
|
||||
//Max size for a group of vision updates
|
||||
#define NET_VISION_HIDE_SIZE 400
|
||||
#endif
|
||||
|
||||
#ifndef NET_DETAILED_PERTICK
|
||||
//Average amount of random detailed updates per delta tick
|
||||
#define NET_DETAILED_PERTICK 16
|
||||
#endif
|
||||
|
||||
//Message types
|
||||
enum NetworkMessageType {
|
||||
MT_Event_Call = net::MT_Application,
|
||||
MT_Object_Component_Call,
|
||||
MT_Empire_Component_Call,
|
||||
MT_Start_Game,
|
||||
MT_End_Game,
|
||||
MT_Game_Ready,
|
||||
MT_Request_Galaxy,
|
||||
MT_Object_Data,
|
||||
MT_Create_Empire,
|
||||
MT_Script_Sync_Initial,
|
||||
MT_Script_Sync_Periodic,
|
||||
MT_Galaxy_Header,
|
||||
MT_Galaxy_Done,
|
||||
MT_Request_Object_Details,
|
||||
MT_Design_Data,
|
||||
MT_Design_Update,
|
||||
MT_Effector_Update,
|
||||
MT_Effector_Trigger,
|
||||
MT_Game_Speed,
|
||||
MT_Nickname,
|
||||
MT_Particle_System,
|
||||
MT_Player_ID,
|
||||
MT_Time_Sync,
|
||||
|
||||
MT_COUNT,
|
||||
MT_START = net::MT_Application,
|
||||
};
|
||||
|
||||
enum ObjectNetEvent {
|
||||
OE_Create,
|
||||
OE_Destroy,
|
||||
OE_Hide,
|
||||
OE_VisionDetails,
|
||||
OE_Delta,
|
||||
OE_Detailed,
|
||||
|
||||
OE_COUNT,
|
||||
OE_MAX = OE_COUNT-1
|
||||
};
|
||||
|
||||
namespace net {
|
||||
struct Message;
|
||||
class Client;
|
||||
class Server;
|
||||
class Connection;
|
||||
struct Address;
|
||||
struct Sequence;
|
||||
};
|
||||
|
||||
namespace scripts {
|
||||
class Manager;
|
||||
};
|
||||
|
||||
class Object;
|
||||
class Design;
|
||||
struct DesignClass;
|
||||
class Effector;
|
||||
class EffectorDef;
|
||||
struct EffectorTarget;
|
||||
|
||||
struct PlayerBatches {
|
||||
threads::Mutex objLock, projLock;
|
||||
net::Message objData, projs;
|
||||
bool hasObjDatas, hasProjs;
|
||||
|
||||
PlayerBatches() :
|
||||
hasObjDatas(false), hasProjs(false),
|
||||
objData(MT_Object_Data, net::MF_Managed), projs(MT_Effector_Trigger)
|
||||
{}
|
||||
};
|
||||
|
||||
//Stub class to prepare things for network interaction
|
||||
class NetworkManager {
|
||||
public:
|
||||
static int MP_VERSION;
|
||||
threads::ReadWriteMutex playerLock;
|
||||
Player currentPlayer;
|
||||
std::unordered_map<int, Player*> players;
|
||||
std::unordered_map<int, PlayerBatches*> batches;
|
||||
std::unordered_map<net::Connection*, Player*> connmap;
|
||||
int nextPlayerId;
|
||||
|
||||
bool isClient, isServer;
|
||||
net::Server* server;
|
||||
net::Client* client;
|
||||
net::Sequence* defaultSequence;
|
||||
net::LobbyHeartbeat* heartbeat;
|
||||
net::LobbyPunchthrough* punch;
|
||||
bool serverReady, clientReady;
|
||||
bool hasGalaxy, hasSyncedClients;
|
||||
bool connected;
|
||||
unsigned galaxiesInFlight;
|
||||
bool waitForClients;
|
||||
float galaxyProgress;
|
||||
float empireProgress;
|
||||
float objectProgress;
|
||||
std::string password;
|
||||
net::DisconnectReason disconnection;
|
||||
|
||||
net::LobbyQuery* query;
|
||||
threads::Mutex queryMtx;
|
||||
std::vector<net::Game> queryResult;
|
||||
|
||||
bool monitorBandwidth;
|
||||
double lastMonitorTick;
|
||||
threads::atomic_int incomingData[MT_COUNT - MT_START];
|
||||
threads::atomic_int outgoingData[MT_COUNT - MT_START];
|
||||
threads::atomic_int incomingPackets[MT_COUNT - MT_START];
|
||||
threads::atomic_int outgoingPackets[MT_COUNT - MT_START];
|
||||
int lastIncomingData[MT_COUNT - MT_START];
|
||||
int lastOutgoingData[MT_COUNT - MT_START];
|
||||
int lastIncomingPackets[MT_COUNT - MT_START];
|
||||
int lastOutgoingPackets[MT_COUNT - MT_START];
|
||||
int totalIncomingData[MT_COUNT - MT_START];
|
||||
int totalOutgoingData[MT_COUNT - MT_START];
|
||||
int totalIncomingPackets[MT_COUNT - MT_START];
|
||||
int totalOutgoingPackets[MT_COUNT - MT_START];
|
||||
int totalSeconds;
|
||||
int currentOutgoing;
|
||||
int currentIncoming;
|
||||
int queuedPackets;
|
||||
|
||||
//Millisecond difference between local time and server time
|
||||
int serverTimeOffset;
|
||||
|
||||
NetworkManager();
|
||||
|
||||
void resetNetState() {
|
||||
isClient = false;
|
||||
isServer = false;
|
||||
}
|
||||
|
||||
void prepNetState() {
|
||||
isClient = (client != nullptr);
|
||||
isServer = (server != nullptr);
|
||||
}
|
||||
|
||||
//Server management
|
||||
void host(const std::string& gamename, int port, unsigned maxPlayers = 0, bool isPublic = true, bool punchthrough = true, const std::string& password = "");
|
||||
void connect(const std::string& hostname, int port, const std::string& password = "", bool tryPunchthrough = false);
|
||||
void connect(const net::Address& addr, bool tryPunchthrough, bool attemptOnly, const std::string& password);
|
||||
void connect(const net::Address& addr, const std::string& password = "", bool attemptOnly = false);
|
||||
void connect(net::Game& game, bool disablePunchthrough = false, const std::string& password = "");
|
||||
void disconnect();
|
||||
void tick(double time);
|
||||
void queryServers();
|
||||
void kick(int playerId);
|
||||
void setPassword(const std::string& pwd);
|
||||
|
||||
//Bandwidth monitoring
|
||||
void monitorIn(net::Message& msg, unsigned count = 1);
|
||||
void monitorOut(net::Message& msg, unsigned count = 1);
|
||||
|
||||
void dumpIncomingMonitor();
|
||||
void dumpOutgoingMonitor();
|
||||
|
||||
void dumpIncomingTotals();
|
||||
void dumpOutgoingTotals();
|
||||
|
||||
//Player management
|
||||
Player* getPlayer(int id);
|
||||
Player* getPlayer(net::Connection& conn);
|
||||
|
||||
Player& getCurrentPlayer() {
|
||||
return currentPlayer;
|
||||
}
|
||||
|
||||
//Transmit from the client to the server
|
||||
void send(net::Message& msg);
|
||||
|
||||
//Transmit to clients from the server
|
||||
void send(Player* sendTo, net::Message& msg);
|
||||
void sendAll(net::Message& msg, bool requireGalaxy = false);
|
||||
void sendOther(Player* notTo, net::Message& msg, bool requireGalaxy = false);
|
||||
void sendEmpire(net::Message& msg, Empire* emp);
|
||||
void sendMasked(net::Message& msg, unsigned mask);
|
||||
void sendVisionMasked(net::Message& msg, unsigned mask);
|
||||
|
||||
//Messages to script managers
|
||||
struct ManagerMessage {
|
||||
scripts::Manager* manager;
|
||||
net::Message* message;
|
||||
Player* player;
|
||||
};
|
||||
threads::Mutex managerMtx;
|
||||
std::vector<ManagerMessage> managerQueue;
|
||||
double menuTimer;
|
||||
void managerNetworking(scripts::Manager* manager, double time);
|
||||
|
||||
//Specialized commands
|
||||
void setNickname(const std::string& nick);
|
||||
void signalClientReady();
|
||||
void requestGalaxy();
|
||||
void requestDetailed(Object* obj);
|
||||
void sendDesign(Empire* emp, DesignClass* cls, const Design* dsg);
|
||||
void sendDesignUpdate(Empire* emp, const Design* older, const Design* newer, DesignClass* cls = nullptr);
|
||||
void sendEffectorUpdate(const Effector* eff);
|
||||
void sendEffectorDestruction(const Effector* eff);
|
||||
void sendEffectorTrigger(const EffectorDef* eff, const Effector* efftr, Object* obj, const EffectorTarget& target, double atTime);
|
||||
void sendParticleSystem(const std::string& id, const vec3d& position, const vec3d& velocity, const quaterniond& rot, float scale, unsigned visionMask);
|
||||
void sendParticleSystem(const std::string& id, const vec3d& position, const vec3d& velocity, const quaterniond& rot, float scale, Object* parent);
|
||||
void setGameSpeed(double speed);
|
||||
bool isSerializing();
|
||||
|
||||
void startSignal();
|
||||
void signalServerReady();
|
||||
void sendGalaxy(Player* player);
|
||||
void endGame();
|
||||
|
||||
void sendObject(Object* obj);
|
||||
void destroyObject(Object* obj);
|
||||
void sendObjectVisionDelta(Object* obj, unsigned prevMask, unsigned newMask);
|
||||
void sendObjectDelta(Object* obj);
|
||||
void sendObjectDetails(Object* obj, int playerID = -1);
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include "network/address.h"
|
||||
|
||||
class Empire;
|
||||
namespace net {
|
||||
class Connection;
|
||||
struct Sequence;
|
||||
};
|
||||
|
||||
struct Player {
|
||||
int id;
|
||||
char nickname[32];
|
||||
Empire* emp;
|
||||
net::Address address;
|
||||
net::Connection* conn;
|
||||
net::Sequence* defaultSequence;
|
||||
bool hasGalaxy, wantsDeltas;
|
||||
bool changedEmpire;
|
||||
unsigned controlMask;
|
||||
unsigned viewMask;
|
||||
|
||||
Player()
|
||||
: id(-1), emp(0), conn(0), defaultSequence(0), hasGalaxy(false), wantsDeltas(false), changedEmpire(false), controlMask(0), viewMask(0) {
|
||||
nickname[0] = '\0';
|
||||
}
|
||||
|
||||
Player(int ID)
|
||||
: id(ID), emp(0), conn(0), defaultSequence(0), hasGalaxy(false), wantsDeltas(false), changedEmpire(false), controlMask(0), viewMask(0) {
|
||||
nickname[0] = '\0';
|
||||
}
|
||||
|
||||
bool controls(Empire* emp);
|
||||
bool views(Empire* emp);
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
#include "design/effector.h"
|
||||
#include "design/design.h"
|
||||
#include "util/basic_type.h"
|
||||
#include "util/hex_grid.h"
|
||||
|
||||
class SaveMessage;
|
||||
|
||||
enum HexFlags {
|
||||
HF_Active = 0x1,
|
||||
HF_Destroyed = 0x2,
|
||||
HF_NoHP = 0x4,
|
||||
HF_Gone = 0x8,
|
||||
HF_NoRepair = 0x10,
|
||||
};
|
||||
|
||||
enum DamageFlags {
|
||||
DF_DestroyedObject = 0x40000000,
|
||||
};
|
||||
|
||||
namespace net {
|
||||
struct Message;
|
||||
};
|
||||
|
||||
class CScriptAny;
|
||||
class Blueprint {
|
||||
public:
|
||||
struct HexStatus {
|
||||
unsigned char flags;
|
||||
unsigned char hp;
|
||||
};
|
||||
|
||||
struct SysStatus {
|
||||
unsigned short workingHexes;
|
||||
EffectStatus status;
|
||||
};
|
||||
|
||||
const Design* design;
|
||||
HexStatus* hexes;
|
||||
SysStatus* subsystems;
|
||||
BasicType* states;
|
||||
double* effectorStates;
|
||||
EffectorTarget* effectorTargets;
|
||||
double currentHP;
|
||||
double quadrantHP[4];
|
||||
float removedHP;
|
||||
double shipEffectiveness;
|
||||
unsigned statusID;
|
||||
unsigned short destroyedHexes;
|
||||
bool designChanged;
|
||||
bool hpDelta;
|
||||
bool holdFire;
|
||||
float hpFactor;
|
||||
vec2i repairingHex;
|
||||
CScriptAny** data;
|
||||
|
||||
HexStatus* getHexStatus(unsigned index);
|
||||
HexStatus* getHexStatus(unsigned x, unsigned y);
|
||||
SysStatus* getSysStatus(unsigned index);
|
||||
SysStatus* getSysStatus(unsigned x, unsigned y);
|
||||
CScriptAny* getHookData(unsigned index);
|
||||
|
||||
Blueprint();
|
||||
|
||||
void destroy(Object* obj);
|
||||
void preClear();
|
||||
~Blueprint();
|
||||
|
||||
void init(Object* obj);
|
||||
float think(Object* obj, double time);
|
||||
void ownerChange(Object* obj, Empire* prevEmpire, Empire* newEmpire);
|
||||
|
||||
bool hasTagActive(int index);
|
||||
double getTagEfficiency(int index, bool ignoreInactive = true);
|
||||
double getEfficiencySum(int variable, int tag = -1, bool ignoreInactive = true);
|
||||
double getEfficiencyFactor(int variable, int tag = -1, bool ignoreInactive = true);
|
||||
|
||||
bool doesAutoTarget(Object* obj, Object* target);
|
||||
bool canTarget(Object* obj, Object* target);
|
||||
|
||||
void target(Object* obj, Object* target, TargetFlags flags = TF_Target);
|
||||
void target(Object* obj, unsigned efftrIndex, Object* target, TargetFlags flags = TF_Target);
|
||||
void target(Object* obj, const Subsystem* sys, Object* target, TargetFlags flags = TF_Target);
|
||||
void clearTracking(Object* obj);
|
||||
Object* getCombatTarget();
|
||||
|
||||
vec3d getOptimalFacing(int sysVariable, int tag = -1, bool ignoreInactive = true);
|
||||
|
||||
void damage(Object* obj, DamageEvent& evt, const vec2u& hex);
|
||||
void damage_internal(Object* obj, DamageEvent& evt, const vec2u& hex);
|
||||
|
||||
void damage(Object* obj, DamageEvent& evt, const vec2u& hex, HexGridAdjacency dir, bool runGlobal);
|
||||
void damage(Object* obj, DamageEvent& evt, const vec2u& hex, bool runGlobal);
|
||||
bool globalDamage(Object* obj, DamageEvent& evt);
|
||||
|
||||
void damage(Object* obj, DamageEvent& evt, const vec2d& direction);
|
||||
void damage(Object* obj, DamageEvent& evt, double position, const vec2d& direction);
|
||||
void damage(Object* obj, DamageEvent& evt, const vec2u& position, const vec2d& endPoint);
|
||||
void create(Object* obj, const Design* design);
|
||||
void start(Object* obj, bool fromRetrofit = false);
|
||||
void retrofit(Object* obj, const Design* design);
|
||||
|
||||
double repair(Object* obj, double amount);
|
||||
double repair(Object* obj, const vec2u& hex, double amount);
|
||||
|
||||
void sendDetails(Object* obj, net::Message& msg);
|
||||
void recvDetails(Object* obj, net::Message& msg);
|
||||
|
||||
bool sendDelta(Object* obj, net::Message& msg);
|
||||
void recvDelta(Object* obj, net::Message& msg);
|
||||
|
||||
void save(Object* obj, SaveMessage& file);
|
||||
void load(Object* obj, SaveMessage& file);
|
||||
};
|
||||
@@ -0,0 +1,886 @@
|
||||
#include "obj/lock.h"
|
||||
#include "obj/universe.h"
|
||||
#include "compat/misc.h"
|
||||
#include "util/random.h"
|
||||
#include "str_util.h"
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
#include "network/network_manager.h"
|
||||
#include "memory/AllocOnlyPool.h"
|
||||
#include "processing.h"
|
||||
#include <deque>
|
||||
#include <assert.h>
|
||||
#include "empire.h"
|
||||
|
||||
#ifdef PROFILE_PROCESSING
|
||||
namespace processing {
|
||||
extern Threaded(ProcessingData*) threadProc;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef LOCK_SWITCH_CHANCE
|
||||
#define LOCK_SWITCH_CHANCE 0.1
|
||||
#endif
|
||||
#ifndef TARGET_UPDATE_INTERVAL
|
||||
#define TARGET_UPDATE_INTERVAL 0.2
|
||||
#endif
|
||||
#ifndef SCRIPT_GC_INTERVAL
|
||||
#define SCRIPT_GC_INTERVAL 0.05
|
||||
#endif
|
||||
|
||||
threads::atomic_int tickingLocks;
|
||||
threads::atomic_int remainingMessages, queuedChildren;
|
||||
std::vector<LockGroup*> lockGroups;
|
||||
|
||||
std::deque<LockGroup*> lockQueue;
|
||||
|
||||
#ifdef PROFILE_LOCKS
|
||||
threads::Mutex lockQueueLock("lockQueueLock");
|
||||
threads::Mutex lockGlobalLock("lockGlobalLock");
|
||||
threads::Mutex switchedLock("switchedLock");
|
||||
#else
|
||||
threads::Mutex lockQueueLock;
|
||||
threads::Mutex lockGlobalLock;
|
||||
threads::Mutex switchedLock;
|
||||
#endif
|
||||
|
||||
std::vector<Object*> switchedObjects;
|
||||
Threaded(LockGroup*) activeLockGroup = 0;
|
||||
Threaded(Object*) activeObject = 0;
|
||||
double nextTargetUpdateTime = 0;
|
||||
double nextScriptGCTime = 0;
|
||||
double prevTick_s = 0;
|
||||
|
||||
threads::Mutex delayedReleaseLock;
|
||||
std::vector<Object*> delayedReleaseObjects;
|
||||
|
||||
void delayObjectRelease(Object* obj) {
|
||||
threads::Lock lock(delayedReleaseLock);
|
||||
delayedReleaseObjects.push_back(obj);
|
||||
}
|
||||
|
||||
void performDelayedObjectReleases() {
|
||||
threads::Lock lock(delayedReleaseLock);
|
||||
for(auto it = delayedReleaseObjects.begin(), end = delayedReleaseObjects.end(); it != end; ++it)
|
||||
(*it)->drop();
|
||||
delayedReleaseObjects.clear();
|
||||
}
|
||||
|
||||
threads::atomic_int switches;
|
||||
double statTime = 0.0;
|
||||
bool switchStage = false;
|
||||
|
||||
memory::AllocOnlyRegion<threads::Mutex> objMessagePool(4096 * sizeof(void*));
|
||||
|
||||
void* ObjectMessage::operator new(size_t bytes) {
|
||||
return objMessagePool.alloc(bytes);
|
||||
}
|
||||
|
||||
void ObjectMessage::operator delete(void* p) {
|
||||
objMessagePool.dealloc(p);
|
||||
}
|
||||
|
||||
unsigned getLockCount() {
|
||||
return (unsigned)lockGroups.size();
|
||||
}
|
||||
|
||||
LockGroup* getLock(unsigned index) {
|
||||
return lockGroups[index];
|
||||
}
|
||||
|
||||
LockGroup* getActiveLockGroup() {
|
||||
return activeLockGroup;
|
||||
}
|
||||
|
||||
Object* getActiveObject() {
|
||||
return activeObject;
|
||||
}
|
||||
|
||||
void setActiveObject(Object* obj) {
|
||||
activeObject = obj;
|
||||
}
|
||||
|
||||
threads::Mutex clearLock;
|
||||
std::vector<Object*> queuedClearObjects;
|
||||
|
||||
void queueObjectClear(Object* obj) {
|
||||
threads::Lock lock(clearLock);
|
||||
obj->grab();
|
||||
queuedClearObjects.push_back(obj);
|
||||
}
|
||||
|
||||
void handleObjectClears() {
|
||||
if(queuedClearObjects.empty())
|
||||
return;
|
||||
|
||||
threads::Lock lock(clearLock);
|
||||
for(auto i = queuedClearObjects.begin(), end = queuedClearObjects.end(); i != end; ++i) {
|
||||
Object* obj = *i;
|
||||
obj->clearScripts();
|
||||
obj->drop();
|
||||
}
|
||||
queuedClearObjects.clear();
|
||||
}
|
||||
|
||||
bool printNextLockStats = false;
|
||||
void printLockStats() {
|
||||
printNextLockStats = true;
|
||||
}
|
||||
|
||||
LockGroup::LockGroup()
|
||||
: tickIndex(0) {
|
||||
#ifdef PROFILE_LOCKS
|
||||
mutex.name = "LockGroup "+toString(getLockCount());
|
||||
mutex.observed = true;
|
||||
|
||||
addMutex.name = mutex.name+" Add Mutex";
|
||||
addMutex.observed = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool LockGroup::hasLock() {
|
||||
return mutex.hasLock();
|
||||
}
|
||||
|
||||
void tickSwitchedObjects(double time) {
|
||||
switchStage = true;
|
||||
|
||||
foreach(it, switchedObjects) {
|
||||
Object* obj = *it;
|
||||
ObjectLock olock(obj);
|
||||
|
||||
if(obj->isValid()) {
|
||||
//Only tick if the object wants to be ticked
|
||||
if(time > obj->nextTick && !obj->getFlag(objStopTicking)) {
|
||||
activeObject = obj;
|
||||
double delay = obj->think(time - obj->lastTick);
|
||||
|
||||
obj->lastTick = time;
|
||||
obj->nextTick = time + delay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switchedObjects.clear();
|
||||
switchStage = false;
|
||||
activeObject = nullptr;
|
||||
}
|
||||
|
||||
double lastLockGlobalUpdate = 0.0;
|
||||
bool updateLockGlobals(double time) {
|
||||
if(lockGroups.empty() || Object::GALAXY_CREATION)
|
||||
return false;
|
||||
if(time < lastLockGlobalUpdate + 0.001)
|
||||
return false;
|
||||
|
||||
lockGlobalLock.lock();
|
||||
lockQueueLock.lock();
|
||||
|
||||
double prevUpdate = lastLockGlobalUpdate;
|
||||
lastLockGlobalUpdate = time;
|
||||
|
||||
if(lockQueue.size() + tickingLocks != 0) {
|
||||
lockGlobalLock.release();
|
||||
lockQueueLock.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
prevTick_s = time - prevUpdate;
|
||||
lockQueueLock.release();
|
||||
|
||||
#ifdef PROFILE_PROCESSING
|
||||
double startTime = devices.driver->getAccurateTime();
|
||||
processing::threadProc->globalCount += 1;
|
||||
processing::threadProc->switchedCount += switchedObjects.size();
|
||||
#endif
|
||||
|
||||
//TODO: This should probably be threaded and not
|
||||
//block the entire tick cycle.
|
||||
tickSwitchedObjects(time);
|
||||
|
||||
#ifdef PROFILE_PROCESSING
|
||||
double curTime = devices.driver->getAccurateTime();
|
||||
processing::threadProc->switchedTime += curTime - startTime;
|
||||
startTime = curTime;
|
||||
#endif
|
||||
|
||||
bool doGC = nextScriptGCTime < devices.driver->getFrameTime();
|
||||
if(doGC)
|
||||
devices.scripts.server->pauseScriptThreads();
|
||||
processing::pauseMessageHandling();
|
||||
|
||||
//Record stats
|
||||
if(statTime < time) {
|
||||
statTime = time + 1.0;
|
||||
|
||||
for(size_t i = 0, cnt = lockGroups.size(); i < cnt; ++i) {
|
||||
LockGroup* grp = lockGroups[i];
|
||||
if(printNextLockStats) {
|
||||
print("LockGroup %d: %d objects", i, grp->objects.size());
|
||||
print(" Bad Locks: %d / %d (%.0g%)",
|
||||
grp->badLocks, (grp->goodLocks + grp->badLocks),
|
||||
(double)grp->badLocks / (double)(grp->goodLocks
|
||||
+ grp->badLocks) * 100.0);
|
||||
print(" Gained %d objects, Lost %d objects.",
|
||||
grp->gainedObjects, grp->lostObjects);
|
||||
}
|
||||
|
||||
grp->gainedObjects = 0;
|
||||
grp->lostObjects = 0;
|
||||
grp->goodLocks = 0;
|
||||
grp->badLocks = 0;
|
||||
}
|
||||
|
||||
if(printNextLockStats) {
|
||||
print("Total Switches: %d\n", switches);
|
||||
printNextLockStats = false;
|
||||
}
|
||||
switches = 0;
|
||||
}
|
||||
|
||||
//Update universe children
|
||||
if(devices.universe)
|
||||
devices.universe->doQueued();
|
||||
|
||||
//Update target system periodically
|
||||
if(nextTargetUpdateTime < time) {
|
||||
#ifdef PROFILE_PROCESSING
|
||||
startTime = devices.driver->getAccurateTime();
|
||||
processing::threadProc->targetUpdates += 1;
|
||||
#endif
|
||||
|
||||
nextTargetUpdateTime = time + TARGET_UPDATE_INTERVAL;
|
||||
|
||||
foreach(it, devices.universe->children) {
|
||||
Object* obj = *it;
|
||||
if(obj->isValid())
|
||||
obj->updateTargets();
|
||||
}
|
||||
|
||||
unsigned childCount = (unsigned)devices.universe->children.size();
|
||||
for(unsigned i = 0, cnt = (childCount + 255) / 256; i < cnt; ++i)
|
||||
devices.universe->setRandomTarget(devices.universe->children[randomi(0, childCount-1)]->targets[randomi(0, OBJ_TARGETS-1)]);
|
||||
|
||||
#ifdef PROFILE_PROCESSING
|
||||
curTime = devices.driver->getAccurateTime();
|
||||
processing::threadProc->targUpdateTime += curTime - startTime;
|
||||
#endif
|
||||
}
|
||||
|
||||
//We need to process deletion of objects' script classes while the server is paused, or component calls could randomly crash
|
||||
handleObjectClears();
|
||||
|
||||
//Do server script garbage collection here,
|
||||
//so we don't need a lock around object tick calls
|
||||
if(doGC) {
|
||||
nextScriptGCTime = devices.driver->getFrameTime() + SCRIPT_GC_INTERVAL;
|
||||
#ifdef PROFILE_PROCESSING
|
||||
double gcStart = devices.driver->getAccurateTime();
|
||||
#endif
|
||||
int mode = devices.scripts.server->garbageCollect();
|
||||
#ifdef PROFILE_PROCESSING
|
||||
double gcEnd = devices.driver->getAccurateTime();
|
||||
if(gcEnd >= gcStart + 0.01)
|
||||
print("Server GC took %dms (mode %d)", (int)((gcEnd - gcStart) * 1000.0), mode);
|
||||
#endif
|
||||
|
||||
// Do delayed object releases here too
|
||||
performDelayedObjectReleases();
|
||||
}
|
||||
|
||||
processing::resumeMessageHandling();
|
||||
if(doGC)
|
||||
devices.scripts.server->resumeScriptThreads();
|
||||
|
||||
//Queue up all locks for ticking again
|
||||
lockQueueLock.lock();
|
||||
foreach(it, lockGroups)
|
||||
lockQueue.push_back(*it);
|
||||
lockQueueLock.release();
|
||||
lockGlobalLock.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
void tickRandomMessages(int limit) {
|
||||
if(lockGroups.empty())
|
||||
return;
|
||||
if(activeLockGroup)
|
||||
throw "Can't lock multiple lock groups.";
|
||||
|
||||
unsigned count = lockGroups.size();
|
||||
unsigned off = randomi(0, count-1);
|
||||
|
||||
for(unsigned i = 0; i < count; ++i) {
|
||||
LockGroup* lock = lockGroups[(i+off) % count];
|
||||
|
||||
if(!lock->messages.empty() && !lock->hasLock()) {
|
||||
devices.scripts.server->threadedCallMutex.readLock();
|
||||
if(lock->mutex.try_lock()) {
|
||||
activeLockGroup = lock;
|
||||
lock->processMessages(limit);
|
||||
activeLockGroup = 0;
|
||||
lock->mutex.release();
|
||||
}
|
||||
devices.scripts.server->threadedCallMutex.release();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void acquireRandomChildren() {
|
||||
if(lockGroups.empty())
|
||||
return;
|
||||
if(activeLockGroup)
|
||||
throw "Can't lock multiple lock groups.";
|
||||
|
||||
LockGroup* lock = lockGroups[randomi(0,(int)lockGroups.size()-1)];
|
||||
|
||||
if(!lock->addQueue.empty()) {
|
||||
devices.scripts.server->threadedCallMutex.readLock();
|
||||
lock->acquireChildren();
|
||||
devices.scripts.server->threadedCallMutex.release();
|
||||
}
|
||||
}
|
||||
|
||||
void tickLockMessages(LockGroup* lock, int limit) {
|
||||
if(activeLockGroup)
|
||||
throw "Can't lock multiple lock groups.";
|
||||
|
||||
if(!lock->messages.empty()) {
|
||||
devices.scripts.server->threadedCallMutex.readLock();
|
||||
if(lock->mutex.try_lock()) {
|
||||
activeLockGroup = lock;
|
||||
lock->processMessages(limit);
|
||||
activeLockGroup = 0;
|
||||
lock->mutex.release();
|
||||
}
|
||||
devices.scripts.server->threadedCallMutex.release();
|
||||
}
|
||||
}
|
||||
|
||||
//Find a random lock group that's still queued up for ticking and tick it, or
|
||||
//update global data and requeue if the queue is empty.
|
||||
bool tickRandomLock(double time, int limit) {
|
||||
if(lockQueue.empty()) {
|
||||
if(tickingLocks == 0)
|
||||
return updateLockGlobals(time);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
lockQueueLock.lock();
|
||||
LockGroup* lock = nullptr;
|
||||
if(!lockQueue.empty()) {
|
||||
for(unsigned i = 0, cnt = lockQueue.size(); i < cnt; ++i) {
|
||||
lock = lockQueue.front();
|
||||
lockQueue.pop_front();
|
||||
if(!lock->hasLock())
|
||||
break;
|
||||
lockQueue.push_back(lock);
|
||||
lock = nullptr;
|
||||
}
|
||||
if(lock)
|
||||
++tickingLocks;
|
||||
}
|
||||
lockQueueLock.release();
|
||||
|
||||
if(!lock)
|
||||
return false;
|
||||
|
||||
#ifdef TRACE_GC_LOCK
|
||||
devices.scripts.server->markGCImpossible();
|
||||
#endif
|
||||
|
||||
if(!lock->process(time, limit)) {
|
||||
lockQueueLock.lock();
|
||||
lockQueue.push_back(lock);
|
||||
lockQueueLock.release();
|
||||
}
|
||||
|
||||
#ifdef TRACE_GC_LOCK
|
||||
devices.scripts.server->markGCPossible();
|
||||
#endif
|
||||
|
||||
--tickingLocks;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LockGroup::addMessage(ObjectMessage* message) {
|
||||
remainingMessages++;
|
||||
messageLock.lock();
|
||||
messages.push_back(message);
|
||||
messageLock.release();
|
||||
}
|
||||
|
||||
void LockGroup::add(Object* obj) {
|
||||
obj->grab();
|
||||
|
||||
addMutex.lock();
|
||||
++queuedChildren;
|
||||
addQueue.push_back(obj);
|
||||
addMutex.release();
|
||||
}
|
||||
|
||||
void LockGroup::processMessages(int limit) {
|
||||
while(!messages.empty() && limit--) {
|
||||
messageLock.lock();
|
||||
if(messages.empty()) {
|
||||
messageLock.release();
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectMessage* msg = messages.front();
|
||||
messages.pop_front();
|
||||
messageLock.release();
|
||||
|
||||
if(msg->object->lockGroup == this) {
|
||||
//Only execute messages that are
|
||||
//actually for this group
|
||||
Object* prevObj = activeObject;
|
||||
activeObject = msg->object;
|
||||
msg->process();
|
||||
activeObject = prevObj;
|
||||
delete msg;
|
||||
remainingMessages--;
|
||||
}
|
||||
else {
|
||||
//Sometimes, an object will have switched
|
||||
//lock groups before we get to a message
|
||||
LockGroup* lockGroup = msg->object->lockGroup;
|
||||
if(lockGroup) {
|
||||
lockGroup->addMessage(msg);
|
||||
remainingMessages--;
|
||||
}
|
||||
else {
|
||||
//The object was trashed, we don't care
|
||||
//about this message anymore
|
||||
delete msg;
|
||||
remainingMessages--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LockGroup::acquireChildren() {
|
||||
if(addQueue.empty())
|
||||
return;
|
||||
|
||||
lockGroup(this);
|
||||
|
||||
addMutex.lock();
|
||||
int offset = 0;
|
||||
for(int i = 0, size = (int)addQueue.size(); i < size; ++i) {
|
||||
Object* obj = addQueue[i];
|
||||
|
||||
if(auto* messages = obj->fetchMessages()) {
|
||||
DeferredObjMessage* prev = nullptr;
|
||||
while(messages) {
|
||||
messages->prev = prev;
|
||||
prev = messages;
|
||||
messages = messages->next;
|
||||
}
|
||||
|
||||
messages = prev;
|
||||
while(messages) {
|
||||
auto* msg = messages;
|
||||
|
||||
Object* prevObj = activeObject;
|
||||
activeObject = msg->msg->object;
|
||||
msg->msg->process();
|
||||
activeObject = prevObj;
|
||||
|
||||
delete msg->msg;
|
||||
messages = msg->prev;
|
||||
delete msg;
|
||||
}
|
||||
}
|
||||
|
||||
//Remove objects we already have or that are not ours
|
||||
//any longer.
|
||||
if(obj->originalLock == this || obj->lockGroup != this) {
|
||||
++offset;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Wait for the other group to release it
|
||||
if(obj->originalLock != 0) {
|
||||
//Since we're skipping this tick, put it in the switched
|
||||
//objects queue to tick it.
|
||||
switchedLock.lock();
|
||||
switchedObjects.push_back(obj);
|
||||
switchedLock.release();
|
||||
|
||||
if(offset > 0)
|
||||
addQueue[i - offset] = obj;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Don't do anything until the object has started ticking
|
||||
if(obj->getFlag(objStopTicking)) {
|
||||
if(offset > 0)
|
||||
addQueue[i - offset] = obj;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Add object to list
|
||||
objects.push_back(obj);
|
||||
obj->originalLock = this;
|
||||
if(devices.network->isServer && devices.network->hasSyncedClients)
|
||||
devices.network->sendObject(obj);
|
||||
++offset;
|
||||
}
|
||||
|
||||
queuedChildren -= offset;
|
||||
addQueue.resize(addQueue.size() - offset);
|
||||
addMutex.release();
|
||||
|
||||
unlockGroup(this);
|
||||
}
|
||||
|
||||
bool LockGroup::process(double time, int limit) {
|
||||
#ifdef OBJECT_LOCK_NAGGING
|
||||
if(activeLockGroup && activeLockGroup != this)
|
||||
throw "Trying to process a LockGroup with an object locked.";
|
||||
#endif
|
||||
lockGroup(this);
|
||||
|
||||
if(tickIndex >= objects.size()) {
|
||||
//Add queued objects
|
||||
addMutex.lock();
|
||||
int offset = 0;
|
||||
for(int i = 0, size = (int)addQueue.size(); i < size; ++i) {
|
||||
Object* obj = addQueue[i];
|
||||
|
||||
if(auto* messages = obj->fetchMessages()) {
|
||||
DeferredObjMessage* prev = nullptr;
|
||||
while(messages) {
|
||||
messages->prev = prev;
|
||||
prev = messages;
|
||||
messages = messages->next;
|
||||
}
|
||||
|
||||
messages = prev;
|
||||
while(messages) {
|
||||
auto* msg = messages;
|
||||
|
||||
Object* prevObj = activeObject;
|
||||
activeObject = msg->msg->object;
|
||||
msg->msg->process();
|
||||
activeObject = prevObj;
|
||||
|
||||
delete msg->msg;
|
||||
messages = msg->prev;
|
||||
delete msg;
|
||||
}
|
||||
}
|
||||
|
||||
//Remove objects we already have or that are not ours
|
||||
//any longer.
|
||||
if(obj->originalLock == this || obj->lockGroup != this) {
|
||||
++offset;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Wait for the other group to release it
|
||||
if(obj->originalLock != 0) {
|
||||
//Since we're skipping this tick, put it in the switched
|
||||
//objects queue to tick it.
|
||||
switchedLock.lock();
|
||||
switchedObjects.push_back(obj);
|
||||
switchedLock.release();
|
||||
|
||||
if(offset > 0)
|
||||
addQueue[i - offset] = obj;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Don't do anything until the object has started ticking
|
||||
if(obj->getFlag(objStopTicking)) {
|
||||
if(offset > 0)
|
||||
addQueue[i - offset] = obj;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Add object to list
|
||||
objects.push_back(obj);
|
||||
obj->originalLock = this;
|
||||
if(devices.network->isServer && devices.network->hasSyncedClients)
|
||||
devices.network->sendObject(obj);
|
||||
++offset;
|
||||
}
|
||||
|
||||
queuedChildren -= offset;
|
||||
addQueue.resize(addQueue.size() - offset);
|
||||
addMutex.release();
|
||||
|
||||
//Reset ticking
|
||||
tickIndex = 0;
|
||||
}
|
||||
|
||||
#ifdef PROFILE_PROCESSING
|
||||
double procStart;
|
||||
int procType = -1;
|
||||
int procCount = 0;
|
||||
#endif
|
||||
|
||||
processMessages();
|
||||
|
||||
bool isNetServer = devices.network->isServer;
|
||||
|
||||
//Tick objects
|
||||
int i = 0;
|
||||
unsigned char rnd = (unsigned char)randomi();
|
||||
while(tickIndex < objects.size()) {
|
||||
//Caller can limit amount of ticked objects
|
||||
if(i >= limit)
|
||||
break;
|
||||
|
||||
//Break when a priority lock is detected
|
||||
if(priorityLocks.get() > 0)
|
||||
break;
|
||||
|
||||
//Tick object
|
||||
Object* obj = objects[tickIndex];
|
||||
|
||||
if(obj->lockGroup != this) {
|
||||
//Ignore locks that have switched already
|
||||
obj->originalLock = 0;
|
||||
objects[tickIndex] = objects.back();
|
||||
objects.pop_back();
|
||||
}
|
||||
else if(!obj->isValid()) {
|
||||
//Remove invalid objects from the list
|
||||
obj->originalLock = 0;
|
||||
obj->drop();
|
||||
objects[tickIndex] = objects.back();
|
||||
objects.pop_back();
|
||||
}
|
||||
else {
|
||||
//Only tick if the object wants to be ticked
|
||||
if((time > obj->nextTick || obj->getFlag(objWakeUp)) && !obj->getFlag(objStopTicking)) {
|
||||
#ifdef PROFILE_PROCESSING
|
||||
if(obj->type->id != procType) {
|
||||
double curTime = devices.driver->getAccurateTime();
|
||||
if(procType != -1) {
|
||||
processing::threadProc->measureType(
|
||||
procType,
|
||||
curTime - procStart,
|
||||
procCount);
|
||||
}
|
||||
procStart = curTime;
|
||||
procType = obj->type->id;
|
||||
procCount = 0;
|
||||
}
|
||||
++procCount;
|
||||
#endif
|
||||
if(obj->deferredMessages) {
|
||||
if(auto* messages = obj->fetchMessages()) {
|
||||
DeferredObjMessage* prev = nullptr;
|
||||
while(messages) {
|
||||
messages->prev = prev;
|
||||
prev = messages;
|
||||
messages = messages->next;
|
||||
}
|
||||
|
||||
messages = prev;
|
||||
while(messages) {
|
||||
auto* msg = messages;
|
||||
|
||||
Object* prevObj = activeObject;
|
||||
activeObject = msg->msg->object;
|
||||
msg->msg->process();
|
||||
activeObject = prevObj;
|
||||
|
||||
delete msg->msg;
|
||||
messages = msg->prev;
|
||||
delete msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activeObject = obj;
|
||||
double delay = obj->think(time - obj->lastTick);
|
||||
|
||||
rnd ^= (unsigned char)obj->id;
|
||||
delay *= 0.85f + ((float)rnd / 255.f) * 0.15f;
|
||||
|
||||
obj->lastTick = time;
|
||||
obj->nextTick = time + delay;
|
||||
|
||||
if(isNetServer)
|
||||
devices.network->sendObjectDelta(obj);
|
||||
|
||||
++i;
|
||||
|
||||
if(!messages.empty())
|
||||
processMessages();
|
||||
|
||||
//We need to sleep periodically to guarantee progress at high cpu utilization
|
||||
if(i % 1024 == 0) {
|
||||
unlockGroup(this);
|
||||
threads::sleep(1);
|
||||
lockGroup(this);
|
||||
}
|
||||
}
|
||||
|
||||
++tickIndex;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef PROFILE_PROCESSING
|
||||
if(procType != -1) {
|
||||
double curTime = devices.driver->getAccurateTime();
|
||||
processing::threadProc->measureType(
|
||||
procType,
|
||||
curTime - procStart,
|
||||
procCount);
|
||||
}
|
||||
#endif
|
||||
|
||||
unlockGroup(this);
|
||||
activeObject = nullptr;
|
||||
return tickIndex >= objects.size();
|
||||
}
|
||||
|
||||
LockGroup* getRandomLock() {
|
||||
assert(!lockGroups.empty());
|
||||
return lockGroups[randomi(0, (int)lockGroups.size() - 1)];
|
||||
}
|
||||
|
||||
void initLocks(unsigned lockCount) {
|
||||
assert(tickingLocks == 0);
|
||||
lockQueueLock.lock();
|
||||
for(unsigned i = 0; i < lockCount; ++i) {
|
||||
LockGroup* lock = new LockGroup();
|
||||
lock->id = i;
|
||||
lockGroups.push_back(lock);
|
||||
lockQueue.push_back(lock);
|
||||
}
|
||||
lockQueueLock.release();
|
||||
}
|
||||
|
||||
void destroyLocks() {
|
||||
foreach(it, lockGroups)
|
||||
delete *it;
|
||||
lockGroups.clear();
|
||||
lockQueue.clear();
|
||||
}
|
||||
|
||||
LockGroup* lockObject(Object* obj, bool priority) {
|
||||
LockGroup* other;
|
||||
if(!obj)
|
||||
return 0;
|
||||
|
||||
changedGroup:
|
||||
other = obj->lockGroup;
|
||||
if(!other)
|
||||
return 0;
|
||||
|
||||
lockGroup(other, priority);
|
||||
if(obj->lockGroup != other) {
|
||||
unlockGroup(other);
|
||||
goto changedGroup;
|
||||
}
|
||||
return other;
|
||||
|
||||
//TODO: Reimplement this for secondary locks
|
||||
//if(other == activeLockGroup) {
|
||||
//++other->goodLocks;
|
||||
//return 0;
|
||||
//}
|
||||
|
||||
//if(activeLockGroup)
|
||||
//++other->badLocks;
|
||||
//else
|
||||
//++other->goodLocks;
|
||||
|
||||
////Chance to switch locks
|
||||
//if(write && activeLockGroup) {
|
||||
//if(randomf() < LOCK_SWITCH_CHANCE) {
|
||||
//++switches;
|
||||
//++obj->lockGroup->lostObjects;
|
||||
//++activeLockGroup->gainedObjects;
|
||||
|
||||
//if(activeLockGroup != obj->originalLock)
|
||||
//activeLockGroup->add(obj);
|
||||
//obj->lockGroup = activeLockGroup;
|
||||
|
||||
////Queue a tick if this is the first switch
|
||||
//if(!switchStage && obj->originalLock == other) {
|
||||
//switchedLock.lock();
|
||||
//switchedObjects.push_back(obj);
|
||||
//switchedLock.release();
|
||||
//}
|
||||
|
||||
//other->mutex.release();
|
||||
//return 0;
|
||||
//}
|
||||
//}
|
||||
}
|
||||
|
||||
void lockError(const char* ch) {
|
||||
if(scripts::getActiveManager())
|
||||
scripts::throwException(ch);
|
||||
else
|
||||
throw ch;
|
||||
}
|
||||
|
||||
inline void doLock(LockGroup* group, bool priority) {
|
||||
if(priority)
|
||||
++group->priorityLocks;
|
||||
|
||||
group->mutex.lock();
|
||||
|
||||
if(priority)
|
||||
--group->priorityLocks;
|
||||
}
|
||||
|
||||
void lockGroup(LockGroup* group, bool priority) {
|
||||
//Check if we already have the group locked
|
||||
if(activeLockGroup) {
|
||||
if(group == activeLockGroup) {
|
||||
group->mutex.lock();
|
||||
}
|
||||
else {
|
||||
#ifdef OBJECT_LOCK_NAGGING
|
||||
lockError("Cannot lock object with another object already locked.");
|
||||
return;
|
||||
#else
|
||||
//Compatibility. This is a potential deadlock, but silently
|
||||
//do it anyway if not in nagging mode.
|
||||
doLock(group, priority);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else {
|
||||
activeLockGroup = group;
|
||||
doLock(group, priority);
|
||||
}
|
||||
}
|
||||
|
||||
inline void _unlockGroup(LockGroup* lock) {
|
||||
if(lock) {
|
||||
lock->mutex.release();
|
||||
|
||||
if(!lock->hasLock()) {
|
||||
if(lock == activeLockGroup)
|
||||
activeLockGroup = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void unlockObject(LockGroup* lock) {
|
||||
_unlockGroup(lock);
|
||||
}
|
||||
|
||||
void unlockGroup(LockGroup* lock) {
|
||||
_unlockGroup(lock);
|
||||
}
|
||||
|
||||
bool hasQueuedChildren() {
|
||||
return queuedChildren != 0;
|
||||
}
|
||||
|
||||
bool hasRemainingMessages() {
|
||||
return remainingMessages != 0;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
#include "threads.h"
|
||||
#include "obj/object.h"
|
||||
#include <set>
|
||||
#include <queue>
|
||||
|
||||
#define OBJECT_LOCK_NAGGING
|
||||
|
||||
struct ObjectMessage;
|
||||
|
||||
struct LockGroup {
|
||||
int id;
|
||||
threads::Mutex mutex;
|
||||
threads::atomic_int priorityLocks;
|
||||
std::vector<Object*> objects;
|
||||
unsigned tickIndex;
|
||||
|
||||
bool hasLock();
|
||||
bool hasWriteLock();
|
||||
|
||||
std::vector<Object*> addQueue;
|
||||
threads::Mutex addMutex;
|
||||
|
||||
std::deque<ObjectMessage*> messages;
|
||||
std::unordered_map<Object*, std::vector<ObjectMessage*>*> deferredMessages;
|
||||
threads::Mutex messageLock;
|
||||
|
||||
threads::atomic_int badLocks;
|
||||
threads::atomic_int goodLocks;
|
||||
threads::atomic_int lostObjects;
|
||||
threads::atomic_int gainedObjects;
|
||||
|
||||
LockGroup();
|
||||
|
||||
void addMessage(ObjectMessage* message);
|
||||
void add(Object* obj);
|
||||
bool process(double time, int limit = 1000);
|
||||
void acquireChildren();
|
||||
void processMessages(int limit = 100);
|
||||
};
|
||||
|
||||
struct ObjectMessage {
|
||||
Object* object;
|
||||
|
||||
ObjectMessage(Object* obj) : object(obj) {}
|
||||
virtual void process() = 0;
|
||||
virtual ~ObjectMessage() {}
|
||||
|
||||
void* operator new(size_t bytes);
|
||||
void operator delete(void* p);
|
||||
};
|
||||
|
||||
LockGroup* getRandomLock();
|
||||
void initLocks(unsigned lockCount);
|
||||
void destroyLocks();
|
||||
|
||||
unsigned getLockCount();
|
||||
LockGroup* getLock(unsigned index);
|
||||
void printLockStats();
|
||||
|
||||
void acquireRandomChildren();
|
||||
bool tickRandomLock(double time, int limit = 1000);
|
||||
void tickRandomMessages(int limit = 100);
|
||||
//Attempts to process the messages in a particular group, with no guarantee of any success
|
||||
void tickLockMessages(LockGroup* lock, int limit = 8);
|
||||
|
||||
//If a message can't be processed yet (the object has no lock group), it must be queued for later execution
|
||||
void queueDeferredMessage(ObjectMessage* msg);
|
||||
|
||||
LockGroup* getActiveLockGroup();
|
||||
Object* getActiveObject();
|
||||
void setActiveObject(Object* obj);
|
||||
void queueObjectClear(Object* obj);
|
||||
void delayObjectRelease(Object* obj);
|
||||
|
||||
LockGroup* lockObject(Object* obj, bool priority = false);
|
||||
void unlockObject(LockGroup* lock);
|
||||
|
||||
void lockGroup(LockGroup* group, bool priority = false);
|
||||
void unlockGroup(LockGroup* lock);
|
||||
|
||||
bool hasQueuedChildren();
|
||||
bool hasRemainingMessages();
|
||||
@@ -0,0 +1,263 @@
|
||||
#include "obj_group.h"
|
||||
#include "object.h"
|
||||
#include "physics/physics_world.h"
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
#include "util/save_file.h"
|
||||
#include <map>
|
||||
|
||||
threads::ReadWriteMutex groupIDsLock;
|
||||
threads::atomic_int nextGroupID(1);
|
||||
std::map<int,ObjectGroup*> groups;
|
||||
|
||||
void registerObjectGroup(ObjectGroup* group, int id = -1) {
|
||||
groupIDsLock.writeLock();
|
||||
|
||||
group->grab();
|
||||
|
||||
if(id == -1) {
|
||||
group->id = nextGroupID++;
|
||||
}
|
||||
else {
|
||||
group->id = id;
|
||||
nextGroupID = id+1;
|
||||
}
|
||||
|
||||
groups[group->id] = group;
|
||||
groupIDsLock.release();
|
||||
}
|
||||
|
||||
void unregisterObjectGroup(ObjectGroup* group) {
|
||||
groupIDsLock.writeLock();
|
||||
|
||||
groups.erase(group->id);
|
||||
group->drop();
|
||||
|
||||
groupIDsLock.release();
|
||||
}
|
||||
|
||||
ObjectGroup* ObjectGroup::byID(int id) {
|
||||
ObjectGroup* group = 0;
|
||||
|
||||
groupIDsLock.readLock();
|
||||
auto iter = groups.find(id);
|
||||
if(iter != groups.end())
|
||||
group = iter->second;
|
||||
|
||||
if(group)
|
||||
group->grab();
|
||||
groupIDsLock.release();
|
||||
return group;
|
||||
}
|
||||
|
||||
ObjectGroup::ObjectGroup(unsigned count, int id) : objectCount(count), origObjectCount(count), objects(new Object*[count]), physItem(new PhysicsItem), owner(0) {
|
||||
PhysicsGroup* physGroup = new PhysicsGroup;
|
||||
physGroup->itemCount = count;
|
||||
physGroup->items = new PhysicsItem[count];
|
||||
|
||||
physItem->type = PhysItemType(PIT_Group | PIT_Object);
|
||||
physItem->group = physGroup;
|
||||
|
||||
memset(objects, 0, sizeof(Object*) * count);
|
||||
registerObjectGroup(this, id);
|
||||
}
|
||||
|
||||
bool ObjectGroup::postLoad() {
|
||||
//Remove invalid objects
|
||||
unsigned off = 0;
|
||||
for(unsigned i = 0; i < objectCount; ++i) {
|
||||
Object* obj = objects[i];
|
||||
if(!obj->isValid() || !obj->isInitialized()) {
|
||||
physItem->group->items[i].object = 0;
|
||||
++off;
|
||||
}
|
||||
else {
|
||||
if(off != 0)
|
||||
objects[i-off] = obj;
|
||||
}
|
||||
obj->drop();
|
||||
}
|
||||
objectCount -= off;
|
||||
|
||||
//Check if the group should die
|
||||
if(objectCount == 0) {
|
||||
owner = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
//Check if the owner is valid
|
||||
if(!owner->isValid() || !owner->isInitialized())
|
||||
owner = objects[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
void ObjectGroup::postInit() {
|
||||
//Update physics item
|
||||
physItem->bound.reset(objects[0]->physItem->bound);
|
||||
for(unsigned i = 1; i < objectCount; ++i)
|
||||
physItem->bound.addBox(objects[i]->physItem->bound);
|
||||
|
||||
devices.physics->registerItem(*physItem);
|
||||
}
|
||||
|
||||
ObjectGroup::~ObjectGroup() {
|
||||
devices.physics->unregisterItem(*physItem);
|
||||
delete[] physItem->group->items;
|
||||
delete physItem;
|
||||
delete[] objects;
|
||||
}
|
||||
|
||||
unsigned ObjectGroup::getObjectCount() const {
|
||||
return objectCount;
|
||||
}
|
||||
|
||||
unsigned ObjectGroup::getMaxObjectCount() const {
|
||||
return origObjectCount;
|
||||
}
|
||||
|
||||
Object* ObjectGroup::getObject(unsigned index) {
|
||||
return objects[index];
|
||||
}
|
||||
|
||||
const Object* ObjectGroup::getObject(unsigned index) const {
|
||||
return objects[index];
|
||||
}
|
||||
|
||||
void ObjectGroup::setObject(unsigned index, Object* obj) {
|
||||
objects[index] = obj;
|
||||
if(index == 0 && owner == 0)
|
||||
owner = obj;
|
||||
if(obj)
|
||||
obj->grab();
|
||||
}
|
||||
|
||||
unsigned ObjectGroup::setNextObject(Object* obj) {
|
||||
for(unsigned i = 0; i < objectCount; ++i) {
|
||||
if(objects[i] != 0)
|
||||
continue;
|
||||
objects[i] = obj;
|
||||
if(i == 0 && owner == nullptr)
|
||||
owner = obj;
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Object* ObjectGroup::getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
void ObjectGroup::setOwner(Object* obj) {
|
||||
owner = obj;
|
||||
}
|
||||
|
||||
vec3d ObjectGroup::getCenter() const {
|
||||
return physItem->bound.getCenter();
|
||||
}
|
||||
|
||||
PhysicsItem* ObjectGroup::getPhysicsItem() {
|
||||
return physItem;
|
||||
}
|
||||
|
||||
PhysicsItem* ObjectGroup::getPhysicsItem(unsigned index) const {
|
||||
return &physItem->group->items[index];
|
||||
}
|
||||
|
||||
bool ObjectGroup::removeOwner() {
|
||||
Object* newOwner = 0;
|
||||
double nearest = 9.0e40;
|
||||
|
||||
//Move objects down to the lower indices, and find the nearest object to the previous owner to be the new owner
|
||||
unsigned j = 0;
|
||||
for(unsigned i = 0; i < objectCount; ++i) {
|
||||
Object*& obj = objects[i];
|
||||
|
||||
if(!obj) {
|
||||
++j;
|
||||
}
|
||||
else if(obj->isValid() && obj != owner) {
|
||||
double dist = owner->position.distanceToSQ(obj->position);
|
||||
if(dist < nearest) {
|
||||
nearest = dist;
|
||||
newOwner = obj;
|
||||
}
|
||||
|
||||
if(j != i) {
|
||||
objects[j] = obj;
|
||||
obj = 0;
|
||||
}
|
||||
++j;
|
||||
}
|
||||
}
|
||||
|
||||
objectCount = j;
|
||||
if(objectCount != 0) {
|
||||
if(newOwner)
|
||||
owner = newOwner;
|
||||
else
|
||||
owner = objects[0];
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
owner = 0;
|
||||
unregisterObjectGroup(this);
|
||||
drop();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectGroup::update() {
|
||||
AABBoxd box;
|
||||
box.reset(owner->physItem->bound);
|
||||
|
||||
unsigned j = 1;
|
||||
for(unsigned i = 1; i < objectCount; ++i) {
|
||||
if(!objects[i]) {
|
||||
++j;
|
||||
} else if(objects[i]->isValid()) {
|
||||
box.addBox(objects[i]->physItem->bound);
|
||||
if(j != i) {
|
||||
objects[j] = objects[i];
|
||||
objects[i] = 0;
|
||||
}
|
||||
++j;
|
||||
}
|
||||
}
|
||||
|
||||
objectCount = j;
|
||||
devices.physics->updateItem(*physItem, box);
|
||||
}
|
||||
|
||||
ObjectGroup::ObjectGroup(SaveFile& file) {
|
||||
file >> id >> objectCount >> formationFacing;
|
||||
objects = new Object*[objectCount];
|
||||
|
||||
for(unsigned i = 0; i < objectCount; ++i)
|
||||
file >> objects[i];
|
||||
|
||||
owner = objects[(unsigned short)file];
|
||||
|
||||
PhysicsGroup* physGroup = new PhysicsGroup;
|
||||
physGroup->itemCount = objectCount;
|
||||
physGroup->items = new PhysicsItem[objectCount];
|
||||
|
||||
physItem = new PhysicsItem;
|
||||
physItem->type = PhysItemType(PIT_Group | PIT_Object);
|
||||
physItem->group = physGroup;
|
||||
|
||||
registerObjectGroup(this);
|
||||
}
|
||||
|
||||
void ObjectGroup::save(SaveFile& file) {
|
||||
file << id;
|
||||
file << objectCount;
|
||||
file << formationFacing;
|
||||
|
||||
unsigned ownerIndex = 0;
|
||||
for(unsigned i = 0; i < objectCount; ++i) {
|
||||
file << objects[i];
|
||||
if(objects[i] == owner)
|
||||
ownerIndex = i;
|
||||
}
|
||||
file << (unsigned short)ownerIndex;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include "util/refcount.h"
|
||||
#include "quaternion.h"
|
||||
|
||||
class Object;
|
||||
class SaveFile;
|
||||
struct PhysicsItem;
|
||||
|
||||
class ObjectGroup : public AtomicRefCounted {
|
||||
PhysicsItem* physItem;
|
||||
unsigned objectCount, origObjectCount;
|
||||
Object** objects;
|
||||
Object* owner;
|
||||
public:
|
||||
int id;
|
||||
quaterniond formationFacing;
|
||||
|
||||
ObjectGroup(unsigned count, int id = -1);
|
||||
|
||||
bool postLoad();
|
||||
void postInit();
|
||||
ObjectGroup(SaveFile& file);
|
||||
void save(SaveFile& file);
|
||||
~ObjectGroup();
|
||||
|
||||
unsigned getObjectCount() const;
|
||||
unsigned getMaxObjectCount() const;
|
||||
Object* getObject(unsigned index);
|
||||
const Object* getObject(unsigned index) const;
|
||||
void setObject(unsigned index, Object* obj);
|
||||
unsigned setNextObject(Object* obj);
|
||||
Object* getOwner();
|
||||
void setOwner(Object* obj);
|
||||
vec3d getCenter() const;
|
||||
|
||||
PhysicsItem* getPhysicsItem();
|
||||
PhysicsItem* getPhysicsItem(unsigned index) const;
|
||||
|
||||
//Removes the owner; Returns true if the group is now empty
|
||||
bool removeOwner();
|
||||
void update();
|
||||
|
||||
//Note: grabs the group before returning it
|
||||
static ObjectGroup* byID(int id);
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,325 @@
|
||||
#pragma once
|
||||
#include "vec3.h"
|
||||
#include "vec2.h"
|
||||
#include "quaternion.h"
|
||||
#include "line3d.h"
|
||||
#include "util/refcount.h"
|
||||
#include "util/link_container.h"
|
||||
#include "threads.h"
|
||||
#include "general_states.h"
|
||||
#include "util/random.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#ifndef OBJ_TARGETS
|
||||
#define OBJ_TARGETS 3
|
||||
#endif
|
||||
|
||||
const unsigned ObjectTypeBitOffset = 26;
|
||||
const unsigned ObjectTypeMask = (unsigned)(0xFF << ObjectTypeBitOffset);
|
||||
const unsigned ObjectIDMask = 0xFFFFFFFF >> (32 - ObjectTypeBitOffset);
|
||||
extern unsigned ObjectTypeCount;
|
||||
|
||||
class Object;
|
||||
class ObjectGroup;
|
||||
struct PhysicsItem;
|
||||
struct ObjectMessage;
|
||||
|
||||
class asIScriptFunction;
|
||||
class asIScriptObject;
|
||||
class asITypeInfo;
|
||||
|
||||
class SaveFile;
|
||||
|
||||
enum ScriptObjectCallback {
|
||||
SOC_init,
|
||||
SOC_postInit,
|
||||
SOC_destroy,
|
||||
SOC_groupDestroyed,
|
||||
SOC_tick,
|
||||
SOC_ownerChange,
|
||||
SOC_damage,
|
||||
SOC_repair,
|
||||
SOC_syncInitial,
|
||||
SOC_syncDetailed,
|
||||
SOC_syncDelta,
|
||||
SOC_recvInitial,
|
||||
SOC_recvDetailed,
|
||||
SOC_recvDelta,
|
||||
SOC_save,
|
||||
SOC_load,
|
||||
SOC_postLoad,
|
||||
|
||||
SOC_COUNT
|
||||
};
|
||||
|
||||
struct ScriptObjectType {
|
||||
unsigned id;
|
||||
std::string name;
|
||||
std::string script;
|
||||
mutable threads::atomic_int nextID;
|
||||
|
||||
asITypeInfo* scriptType;
|
||||
asIScriptFunction* functions[SOC_COUNT];
|
||||
|
||||
const StateDefinition* states;
|
||||
std::vector<size_t> componentOffsets;
|
||||
std::vector<bool> optionalComponents;
|
||||
size_t blueprintOffset;
|
||||
|
||||
scripts::GenericType* refType;
|
||||
scripts::GenericType* handleType;
|
||||
|
||||
ScriptObjectType();
|
||||
void bind(const char* module, const char* decl);
|
||||
asIScriptObject* create() const;
|
||||
};
|
||||
|
||||
void prepScriptObjectTypes();
|
||||
void addObjectStateValueTypes();
|
||||
void setScriptObjectStates();
|
||||
void bindScriptObjectTypes();
|
||||
ScriptObjectType* getScriptObjectType(const std::string& name);
|
||||
|
||||
ScriptObjectType* getScriptObjectType(int index);
|
||||
unsigned getScriptObjectTypeCount();
|
||||
|
||||
//Retrieve the object referred to by the given id.
|
||||
// Grabs the object before returning it, so it needs
|
||||
// to be dropped afterwards
|
||||
Object* getObjectByID(unsigned id, bool create = false);
|
||||
ScriptObjectType* getObjectTypeFromID(unsigned id);
|
||||
void invalidateUninitializedObjects();
|
||||
|
||||
enum ObjectFlag : unsigned {
|
||||
//Object is not in the process of being deleted
|
||||
objValid = 0x1,
|
||||
//Object needs to go to sleep (going invalid)
|
||||
objStopTicking = 0x2,
|
||||
//Object has something to do and should ignore timeout delay
|
||||
objWakeUp = 0x4,
|
||||
//Object has been allocated as a reference-only object, and does not yet have data
|
||||
objUninitialized = 0x8,
|
||||
//Object has been flagged to send a data delta the next time it can
|
||||
objSendDelta = 0x10,
|
||||
//Object has been flagged as a focus object, this has various
|
||||
//effects for interpolation and multiplayer
|
||||
objFocus = 0x20,
|
||||
//Object is currently engaged in combat
|
||||
objEngaged = 0x40,
|
||||
//Object does not have physics
|
||||
objNoPhysics = 0x80,
|
||||
//Object is selected
|
||||
objSelected = 0x100,
|
||||
//Object is considered as in combat
|
||||
objCombat = 0x200,
|
||||
//Multiplayer clients should be able to get information even out of vision
|
||||
objMemorable = 0x400,
|
||||
//Whether it has been given a name
|
||||
objNamed = 0x800,
|
||||
//Whether the object should nudge movable objects (enforced by scripts)
|
||||
objNoCollide = 0x1000,
|
||||
//Whether the object can not receive damage by being hit by projectiles
|
||||
objNoDamage = 0x2000,
|
||||
|
||||
|
||||
objQueueDestroy = 0x80000000
|
||||
};
|
||||
|
||||
const unsigned objFlagSaveMask = ~(objSelected);
|
||||
|
||||
namespace net {
|
||||
struct Message;
|
||||
};
|
||||
|
||||
namespace scene {
|
||||
class Node;
|
||||
};
|
||||
|
||||
class Empire;
|
||||
struct LockGroup;
|
||||
class TimedEffect;
|
||||
class DamageEvent;
|
||||
|
||||
struct DeferredObjMessage {
|
||||
DeferredObjMessage* next, *prev;
|
||||
ObjectMessage* msg;
|
||||
};
|
||||
|
||||
class Object {
|
||||
public:
|
||||
static bool GALAXY_CREATION;
|
||||
|
||||
mutable threads::atomic_int references;
|
||||
heldPointer<Object> targets[OBJ_TARGETS];
|
||||
|
||||
//Object tree
|
||||
unsigned id;
|
||||
ObjectGroup* group;
|
||||
|
||||
//Locking
|
||||
LockGroup* lockGroup;
|
||||
LockGroup* originalLock;
|
||||
unsigned lockHint;
|
||||
|
||||
//Messages
|
||||
DeferredObjMessage* deferredMessages;
|
||||
void queueDeferredMessage(ObjectMessage* msg);
|
||||
DeferredObjMessage* fetchMessages();
|
||||
|
||||
//Ownership
|
||||
Empire* owner;
|
||||
|
||||
//Vision
|
||||
bool alwaysVisible;
|
||||
float sightRange;
|
||||
float seeableRange;
|
||||
unsigned char visionTimes[32];
|
||||
unsigned visibleMask, donatedVision, prevVisibleMask, sightedMask, sightDelay;
|
||||
|
||||
//State variables
|
||||
threads::atomic_int flags;
|
||||
|
||||
//Object spatial variables
|
||||
PhysicsItem* physItem;
|
||||
vec3d position, velocity, acceleration;
|
||||
quaterniond rotation;
|
||||
double radius;
|
||||
|
||||
//The last time this Object ran its think()
|
||||
// - the parent is responsible for updating this
|
||||
double lastTick;
|
||||
double nextTick;
|
||||
|
||||
//Effects
|
||||
std::vector<TimedEffect*> effects;
|
||||
void addTimedEffect(const TimedEffect& eff);
|
||||
|
||||
//Generic stats
|
||||
LinkMap stats;
|
||||
|
||||
//Graphics tree
|
||||
scene::Node* node;
|
||||
|
||||
std::string name;
|
||||
|
||||
asIScriptObject* script;
|
||||
|
||||
const ScriptObjectType* type;
|
||||
|
||||
void init();
|
||||
void postInit();
|
||||
void setOwner(Empire* newOwner);
|
||||
bool isLocked() const;
|
||||
|
||||
static Object* create(ScriptObjectType* type, LockGroup* lock = 0, int id = 0);
|
||||
void grab() const;
|
||||
void drop() const;
|
||||
|
||||
inline const ScriptObjectType* GetType() const { return type; }
|
||||
|
||||
void setFlag(ObjectFlag flag, bool val);
|
||||
bool getFlag(ObjectFlag flag) const;
|
||||
//Like set flag, but only succeeds if this call set the flag to that value
|
||||
bool setFlagSecure(ObjectFlag flag, bool val);
|
||||
|
||||
//Flag access wrappers
|
||||
bool isValid() const;
|
||||
bool isInitialized() const;
|
||||
void wake();
|
||||
void sleep(double seconds);
|
||||
|
||||
//Focus is an object property to indicate
|
||||
//priority syncing and calculations.
|
||||
//The focus flag decays after a while and needs to
|
||||
//be set repeatedly on accessed objects.
|
||||
bool isFocus() const;
|
||||
void focus();
|
||||
|
||||
bool isVisibleTo(Empire* emp) const;
|
||||
bool isKnownTo(Empire* emp) const;
|
||||
unsigned updateVision(Object* target, unsigned depth);
|
||||
|
||||
//Damage events
|
||||
void damage(DamageEvent& evt, double position, const vec2d& direction);
|
||||
void repair(double amount);
|
||||
|
||||
//Targeting
|
||||
void updateTargets();
|
||||
|
||||
template<class T>
|
||||
bool findTargets(T& cb, int depth) {
|
||||
for(unsigned i = 0; i < OBJ_TARGETS; ++i) {
|
||||
Object* targ = targets[i].ptr;
|
||||
if(targ == this)
|
||||
continue;
|
||||
if(cb.result(targ))
|
||||
return true;
|
||||
if(depth != 0) {
|
||||
if(targ->findTargets(cb, depth-1))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const unsigned char RANDOMIZE_TARGETS = 0;
|
||||
template<class T>
|
||||
bool findTargets(T& cb, int depth, unsigned char randomizer) {
|
||||
if(randomizer == RANDOMIZE_TARGETS)
|
||||
randomizer = (unsigned char)randomi();
|
||||
else
|
||||
randomizer ^= (unsigned char)id;
|
||||
unsigned index = randomizer % OBJ_TARGETS;
|
||||
for(unsigned i = 0; i < OBJ_TARGETS; ++i, index = (index+1) % OBJ_TARGETS) {
|
||||
Object* targ = targets[index].ptr;
|
||||
if(targ == this)
|
||||
continue;
|
||||
if(cb.result(targ))
|
||||
return true;
|
||||
if(depth != 0) {
|
||||
if(targ->findTargets(cb, depth-1, randomizer))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//Network syncing
|
||||
void sendInitial(net::Message& msg);
|
||||
void sendDetailed(net::Message& msg);
|
||||
bool sendDelta(net::Message& msg);
|
||||
|
||||
void recvDetailed(net::Message& msg, double fromTime);
|
||||
void recvDelta(net::Message& msg, double fromTime);
|
||||
|
||||
//Saving & loading
|
||||
void save(SaveFile& file);
|
||||
void load(SaveFile& file);
|
||||
void postLoad();
|
||||
|
||||
//Management
|
||||
Object(ScriptObjectType* Type, LockGroup* group = 0, int id = 0);
|
||||
~Object();
|
||||
|
||||
double think(double seconds);
|
||||
void flagDestroy() { setFlag(objQueueDestroy, true); setFlag(objWakeUp, true); }
|
||||
|
||||
friend class Universe;
|
||||
|
||||
void clearScripts();
|
||||
private:
|
||||
void destroy(bool fromUniverse = false);
|
||||
};
|
||||
|
||||
Object* recvObjectInitial(net::Message& msg, double fromTime);
|
||||
void clearObjects();
|
||||
|
||||
struct ObjectLock {
|
||||
LockGroup* group;
|
||||
bool released;
|
||||
|
||||
ObjectLock(Object* Obj, bool priority = false);
|
||||
void release();
|
||||
~ObjectLock();
|
||||
};
|
||||
@@ -0,0 +1,157 @@
|
||||
#include "object.h"
|
||||
#include "obj_group.h"
|
||||
#include "util/save_file.h"
|
||||
#include "empire.h"
|
||||
#include "physics/physics_world.h"
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
#include "network/message.h"
|
||||
#include "lock.h"
|
||||
|
||||
//TODO: Handle save files with different script type definitions
|
||||
|
||||
void Object::save(SaveFile& file) {
|
||||
file << id;
|
||||
|
||||
file << name;
|
||||
file << (flags & objFlagSaveMask);
|
||||
|
||||
file << alwaysVisible << sightRange << visibleMask << sightedMask;
|
||||
file << position << velocity << acceleration << rotation;
|
||||
file << radius;
|
||||
file << seeableRange;
|
||||
|
||||
file << lockHint;
|
||||
|
||||
file << (group ? group->id : int(0));
|
||||
file << (owner ? owner->id : INVALID_EMPIRE);
|
||||
|
||||
if(!isValid())
|
||||
return;
|
||||
|
||||
auto* func = type->functions[SOC_save];
|
||||
if(func) {
|
||||
SaveMessage msg(file);
|
||||
|
||||
scripts::Call cl = devices.scripts.server->call(func);
|
||||
cl.setObject(script);
|
||||
cl.push((void*)this);
|
||||
cl.push((void*)&msg);
|
||||
if(cl.call()) {
|
||||
char* pData; net::msize_t size;
|
||||
msg.getAsPacket(pData, size);
|
||||
|
||||
file << size;
|
||||
file.write(pData, size);
|
||||
}
|
||||
else {
|
||||
file << (net::msize_t)0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
file << (net::msize_t)0;
|
||||
}
|
||||
}
|
||||
|
||||
void Object::load(SaveFile& file) {
|
||||
if(!getFlag(objUninitialized))
|
||||
throw SaveFileError("Object already initialized");
|
||||
|
||||
file >> name;
|
||||
file >> flags;
|
||||
|
||||
file >> alwaysVisible >> sightRange >> visibleMask >> sightedMask;
|
||||
file >> position >> velocity >> acceleration >> rotation;
|
||||
file >> radius;
|
||||
if(file < SFV_0001) {
|
||||
double dummy;
|
||||
file >> dummy;
|
||||
}
|
||||
if(file >= SFV_0017)
|
||||
file >> seeableRange;
|
||||
|
||||
if(file >= SFV_0009) {
|
||||
file >> lockHint;
|
||||
if(lockHint > 0)
|
||||
lockGroup = getLock(lockHint % getLockCount());
|
||||
}
|
||||
|
||||
int groupID = file;
|
||||
if(groupID != 0 && isValid()) {
|
||||
group = ObjectGroup::byID(groupID);
|
||||
if(group) {
|
||||
for(unsigned i = 0; i < group->getObjectCount(); ++i) {
|
||||
if(group->getObject(i) == this) {
|
||||
physItem = group->getPhysicsItem(i);
|
||||
physItem->bound = AABBoxd::fromCircle(position, radius);
|
||||
physItem->gridLocation = 0;
|
||||
physItem->type = PIT_Object;
|
||||
physItem->object = this;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!physItem) {
|
||||
error("%s (#%d) missing in group", name.c_str(), id);
|
||||
group->drop();
|
||||
group = nullptr;
|
||||
}
|
||||
}
|
||||
else {
|
||||
error("%s (#%d) missing group", name.c_str(), id);
|
||||
}
|
||||
}
|
||||
|
||||
owner = Empire::getEmpireByID(file);
|
||||
|
||||
if(!isValid())
|
||||
return;
|
||||
|
||||
if(!physItem && !getFlag(objNoPhysics))
|
||||
physItem = devices.physics->registerItem(AABBoxd::fromCircle(position, radius), this);
|
||||
|
||||
void* mixinMem = this + 1;
|
||||
type->states->prepare(mixinMem);
|
||||
|
||||
script = type->create();
|
||||
|
||||
auto* loadFunc = type->functions[SOC_load];
|
||||
SaveMessage scriptTypeData(file);
|
||||
|
||||
net::msize_t size = file;
|
||||
if(size > 0) {
|
||||
char* buffer = (char*)malloc(size);
|
||||
file.read(buffer, size);
|
||||
scriptTypeData.setPacket(buffer, size);
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
//Either load the saved message, or default initialize
|
||||
if(loadFunc) {
|
||||
scripts::Call cl = devices.scripts.server->call(loadFunc);
|
||||
cl.setObject(script);
|
||||
cl.push((void*)this);
|
||||
cl.push((void*)&scriptTypeData);
|
||||
cl.call();
|
||||
}
|
||||
else {
|
||||
scripts::Call cl = devices.scripts.server->call(type->functions[SOC_init]);
|
||||
if(cl.valid()) {
|
||||
cl.setObject(script);
|
||||
cl.push((void*)this);
|
||||
cl.call();
|
||||
}
|
||||
}
|
||||
|
||||
lockGroup->add(this);
|
||||
setFlag(objUninitialized, false);
|
||||
}
|
||||
|
||||
void Object::postLoad() {
|
||||
auto* func = type->functions[SOC_postLoad];
|
||||
if(func && script) {
|
||||
scripts::Call cl = devices.scripts.server->call(func);
|
||||
cl.setObject(script);
|
||||
cl.push((void*)this);
|
||||
cl.call();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
#include "obj/universe.h"
|
||||
#include "empire.h"
|
||||
#include "compat/misc.h"
|
||||
#include "util/random.h"
|
||||
#include "main/references.h"
|
||||
#include "network/network_manager.h"
|
||||
#include "physics/physics_world.h"
|
||||
#include <assert.h>
|
||||
#include <unordered_set>
|
||||
|
||||
void Universe::doQueued() {
|
||||
threads::Lock qlock(queueLock);
|
||||
threads::WriteLock lock(childLock);
|
||||
|
||||
//Remove queued objects
|
||||
unsigned offset = 0;
|
||||
if(!removeQueue.empty()) {
|
||||
for(unsigned i = 0, cnt = (unsigned)children.size(); i < cnt; ++i) {
|
||||
Object* child = children[i];
|
||||
if(removeQueue.find(child) != removeQueue.end()) {
|
||||
++offset;
|
||||
removeTargetsTo(child); //TODO: better
|
||||
child->drop();
|
||||
}
|
||||
else if(offset != 0) {
|
||||
children[i - offset] = children[i];
|
||||
}
|
||||
}
|
||||
if(offset != 0)
|
||||
children.resize(children.size() - (size_t)offset);
|
||||
removeQueue.clear();
|
||||
}
|
||||
|
||||
//Add queued objects
|
||||
foreach(it, addQueue)
|
||||
children.push_back(*it);
|
||||
addQueue.clear();
|
||||
}
|
||||
|
||||
void Universe::addChild(Object* obj) {
|
||||
threads::Lock qlock(queueLock);
|
||||
|
||||
auto it = removeQueue.find(obj);
|
||||
if(it != removeQueue.end())
|
||||
removeQueue.erase(obj);
|
||||
else
|
||||
addQueue.insert(obj);
|
||||
obj->grab();
|
||||
}
|
||||
|
||||
void Universe::removeChild(Object* obj) {
|
||||
threads::Lock qlock(queueLock);
|
||||
auto it = addQueue.find(obj);
|
||||
if(it != addQueue.end()) {
|
||||
addQueue.erase(obj);
|
||||
for(unsigned i = 0; i < OBJ_TARGETS; ++i)
|
||||
obj->targets[i] = nullptr;
|
||||
}
|
||||
else
|
||||
removeQueue.insert(obj);
|
||||
}
|
||||
|
||||
void Universe::destroyAll() {
|
||||
foreach(child, children) {
|
||||
(*child)->destroy(true);
|
||||
(*child)->drop();
|
||||
}
|
||||
children.clear();
|
||||
}
|
||||
|
||||
Object* Universe::getRandomTarget(unsigned randVal) {
|
||||
//No child lock needed, guaranteed through lock ticking
|
||||
if(children.empty())
|
||||
return 0;
|
||||
if(randVal == 0)
|
||||
randVal = randomi();
|
||||
return children[randVal % children.size()];
|
||||
}
|
||||
|
||||
void Universe::setRandomTarget(heldPointer<Object>& targ, unsigned randVal) {
|
||||
//No child lock needed, guaranteed through lock ticking
|
||||
if(children.empty())
|
||||
return;
|
||||
|
||||
if(randVal == 0)
|
||||
randVal = randomi();
|
||||
|
||||
Object* child = children[randVal % children.size()];
|
||||
|
||||
if(child->isValid()) {
|
||||
auto& other = child->targets[randVal % OBJ_TARGETS];
|
||||
if(!other->getFlag(objNoPhysics))
|
||||
other.swap(targ);
|
||||
}
|
||||
}
|
||||
|
||||
void Universe::removeTargetsTo(Object* obj) {
|
||||
//No child lock needed, guaranteed through lock ticking
|
||||
unsigned remaining = OBJ_TARGETS;
|
||||
unsigned i = 0;
|
||||
unsigned cnt = (unsigned)children.size();
|
||||
|
||||
for(unsigned t = 0; t < OBJ_TARGETS; ++t)
|
||||
if(obj->targets[t] == obj)
|
||||
--remaining;
|
||||
|
||||
if(remaining == 0)
|
||||
goto clearTargets;
|
||||
|
||||
for(; i < cnt; ++i) {
|
||||
Object* child = children[i];
|
||||
|
||||
for(unsigned j = 0; j < OBJ_TARGETS; ++j) {
|
||||
if(child->targets[j] == obj && child != obj) {
|
||||
for(unsigned t = 0; t < OBJ_TARGETS; ++t) {
|
||||
if(obj->targets[t] != obj) {
|
||||
child->targets[j].swap(obj->targets[t]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(children[i]->targets[j] != obj);
|
||||
--remaining;
|
||||
if(remaining == 0)
|
||||
goto clearTargets;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearTargets:;
|
||||
for(unsigned i = 0; i < OBJ_TARGETS; ++i)
|
||||
obj->targets[i] = 0;
|
||||
}
|
||||
|
||||
const double coneSlope = 0.02;
|
||||
|
||||
Object* Universe::getClosestOnLine(const line3dd& line) const {
|
||||
threads::ReadLock lock(childLock);
|
||||
|
||||
Empire* empire = Empire::getPlayerEmpire();
|
||||
auto lineDir = line.getDirection();
|
||||
double closest_d = 9e32;
|
||||
double closest_p = 1.0;
|
||||
Object* closest_o = 0;
|
||||
|
||||
//TODO: This should handle the cone more accurately, possibly in multiple steps?
|
||||
//Bound should hold the line, and a considerable radius around it to handle the cone
|
||||
AABBoxd bound(line.start);
|
||||
bound.addBox( AABBoxd::fromCircle(line.end, 1500.0) );
|
||||
|
||||
devices.physics->findInBox(line, [&](const PhysicsItem& item) {
|
||||
Object* obj = item.object;
|
||||
if(!obj || !obj->isVisibleTo(empire))
|
||||
return;
|
||||
|
||||
double distOnLine = (obj->position - line.start).dot(lineDir);
|
||||
if(distOnLine <= 0)
|
||||
return;
|
||||
|
||||
auto p = line.start + (lineDir * distOnLine);
|
||||
double p_d = p.distanceTo(obj->position);
|
||||
double c_d = obj->radius + distOnLine * coneSlope;
|
||||
if(p_d >= c_d)
|
||||
return;
|
||||
|
||||
double pct = p_d / c_d;
|
||||
|
||||
auto dist = p.getLength();
|
||||
if(dist < closest_d && pct < closest_p) {
|
||||
closest_o = obj;
|
||||
closest_d = dist;
|
||||
closest_p = pct;
|
||||
}
|
||||
});
|
||||
|
||||
return closest_o;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
#include "obj/object.h"
|
||||
#include "util/refcount.h"
|
||||
#include "threads.h"
|
||||
#include <unordered_set>
|
||||
|
||||
class Universe : public AtomicRefCounted {
|
||||
public:
|
||||
mutable threads::Mutex queueLock;
|
||||
std::unordered_set<Object*> addQueue;
|
||||
std::unordered_set<Object*> removeQueue;
|
||||
|
||||
mutable threads::ReadWriteMutex childLock;
|
||||
std::vector<Object*> children;
|
||||
|
||||
void doQueued();
|
||||
void addChild(Object* obj);
|
||||
void removeChild(Object* obj);
|
||||
void destroyAll();
|
||||
|
||||
Object* getRandomTarget(unsigned randVal = 0);
|
||||
void setRandomTarget(heldPointer<Object>& targ, unsigned randVal = 0);
|
||||
void removeTargetsTo(Object* obj);
|
||||
|
||||
Object* getClosestOnLine(const line3dd& line) const;
|
||||
};
|
||||
@@ -0,0 +1,218 @@
|
||||
#pragma once
|
||||
#include "os/key_consts.h"
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
namespace os {
|
||||
|
||||
enum WindowMode {
|
||||
WM_Fullscreen,
|
||||
WM_Window,
|
||||
};
|
||||
|
||||
enum KeyAction {
|
||||
KA_Pressed = 1,
|
||||
KA_Released = 2,
|
||||
KA_Repeated = 1+4,
|
||||
};
|
||||
|
||||
struct WindowData {
|
||||
int width, height;
|
||||
int redbits, greenbits, bluebits, alphabits;
|
||||
int depthbits, stencilbits;
|
||||
int aa_samples;
|
||||
int refreshRate;
|
||||
bool resizable;
|
||||
int verticalSync;
|
||||
WindowMode mode;
|
||||
std::string targetMonitor;
|
||||
bool overrideMonitor;
|
||||
|
||||
WindowData()
|
||||
: width(1024), height(768),
|
||||
redbits(8), greenbits(8), bluebits(8),
|
||||
alphabits(8), depthbits(8), stencilbits(8),
|
||||
aa_samples(4), refreshRate(0), resizable(false),
|
||||
verticalSync(1), mode(WM_Window), overrideMonitor(false) {}
|
||||
};
|
||||
|
||||
enum OSCallback {
|
||||
OSC_WindowResize,
|
||||
OSC_WindowClose,
|
||||
OSC_Key,
|
||||
OSC_CharKey,
|
||||
OSC_MouseMove,
|
||||
OSC_MouseButton,
|
||||
OSC_MouseWheel,
|
||||
|
||||
OSC_COUNT
|
||||
};
|
||||
|
||||
template<class Arg0,class Arg1>
|
||||
struct DriverCallbacks_2 {
|
||||
typedef std::function<bool(Arg0,Arg1)> cb;
|
||||
std::list<cb> callbacks;
|
||||
|
||||
//Adds the callback to the list, earlier if priority
|
||||
void add(cb func, bool priority = false) {
|
||||
if(priority)
|
||||
callbacks.push_front(func);
|
||||
else
|
||||
callbacks.push_back(func);
|
||||
}
|
||||
|
||||
//Removes a callback previously added to the list
|
||||
void remove(cb func) {
|
||||
callbacks.remove_if([func](cb f) {
|
||||
return func.template target<bool(Arg0,Arg1)>() == f.template target<bool(Arg0,Arg1)>();
|
||||
});
|
||||
}
|
||||
//Calls each callback in order, stopping if true is returned
|
||||
void operator()(Arg0 arg0, Arg1 arg1) {
|
||||
for(auto i = callbacks.begin(), end = callbacks.end(); i != end; ++i)
|
||||
if((*i)(arg0,arg1))
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
template<class Arg0>
|
||||
struct DriverCallbacks_1 {
|
||||
typedef std::function<bool(Arg0)> cb;
|
||||
std::list<cb> callbacks;
|
||||
|
||||
//Adds the callback to the list, earlier if priority
|
||||
void add(cb func, bool priority = false) {
|
||||
if(priority)
|
||||
callbacks.push_front(func);
|
||||
else
|
||||
callbacks.push_back(func);
|
||||
}
|
||||
|
||||
//Removes a callback previously added to the list
|
||||
void remove(cb func) {
|
||||
callbacks.remove_if([func](cb f) {
|
||||
return func.template target<bool(Arg0)>() == f.template target<bool(Arg0)>();
|
||||
});
|
||||
}
|
||||
//Calls each callback in order, stopping if true is returned
|
||||
void operator()(Arg0 arg0) {
|
||||
for(auto i = callbacks.begin(), end = callbacks.end(); i != end; ++i)
|
||||
if((*i)(arg0))
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
struct DriverCallbacks {
|
||||
typedef std::function<bool()> cb;
|
||||
std::list<cb> callbacks;
|
||||
|
||||
//Adds the callback to the list, earlier if priority
|
||||
void add(cb func, bool priority = false) {
|
||||
if(priority)
|
||||
callbacks.push_front(func);
|
||||
else
|
||||
callbacks.push_back(func);
|
||||
}
|
||||
|
||||
//Removes a callback previously added to the list
|
||||
void remove(cb func) {
|
||||
callbacks.remove_if([func](cb f) {
|
||||
return func.template target<bool()>() == f.template target<bool()>();
|
||||
});
|
||||
}
|
||||
|
||||
//Calls each callback in order, stopping if true is returned
|
||||
void operator()() {
|
||||
for(auto i = callbacks.begin(), end = callbacks.end(); i != end; ++i)
|
||||
if((*i)())
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
class OSDriver {
|
||||
public:
|
||||
struct VideoMode {
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned refresh;
|
||||
};
|
||||
|
||||
DriverCallbacks onWindowClose;
|
||||
DriverCallbacks_1<unsigned> onCharTyped;
|
||||
DriverCallbacks_2<double,double> onScroll;
|
||||
DriverCallbacks_2<int,int> onResize, onMouseButton, onMouseMoved, onKeyEvent;
|
||||
|
||||
bool shiftKey, altKey, ctrlKey;
|
||||
int win_width, win_height;
|
||||
|
||||
int mouse_x, mouse_y;
|
||||
int leftButton, rightButton, middleButton;
|
||||
|
||||
virtual bool systemRandom(unsigned char* buffer, unsigned bytes) = 0;
|
||||
|
||||
virtual void setVerticalSync(int waitFrames) = 0;
|
||||
|
||||
virtual void swapBuffers(double minWait_s = 0) = 0;
|
||||
//NOTE: If you need to keep the program awake but can't render, use this instead of swapBuffers
|
||||
// Skips the elapsed time with minimal impact on timers
|
||||
virtual void handleEvents(unsigned minWait_ms = 100) = 0;
|
||||
|
||||
virtual void createWindow(WindowData& data) = 0;
|
||||
virtual void setWindowTitle(const char* str) = 0;
|
||||
virtual void setWindowSize(int width, int height) = 0;
|
||||
virtual void closeWindow() = 0;
|
||||
virtual bool isWindowFocused() = 0;
|
||||
virtual bool isWindowMinimized() = 0;
|
||||
virtual bool isMouseOver() = 0;
|
||||
virtual void flashWindow() = 0;
|
||||
virtual void getVideoModes(std::vector<VideoMode>& output) = 0;
|
||||
virtual void getMonitorNames(std::vector<std::string>& output) = 0;
|
||||
|
||||
virtual void getDesktopSize(unsigned& width, unsigned& height) = 0;
|
||||
|
||||
virtual void resetTimer() = 0;
|
||||
//Returns time since last reset in ms
|
||||
virtual int getTime() const = 0;
|
||||
//Returns time since last reset in seconds
|
||||
virtual double getAccurateTime() const = 0;
|
||||
|
||||
//Returns the current system time for the frame
|
||||
virtual double getFrameTime() const = 0;
|
||||
|
||||
//Resets the game timer (implied by resetTimer())
|
||||
virtual void resetGameTime(double time) = 0;
|
||||
//Returns the current game time
|
||||
virtual double getGameTime() const = 0;
|
||||
//Sets the game speed as a factor of real time
|
||||
virtual void setGameSpeed(double factor) = 0;
|
||||
//Returns the current game speed
|
||||
virtual double getGameSpeed() const = 0;
|
||||
|
||||
virtual unsigned getProcessorCount() = 0;
|
||||
|
||||
virtual void sleep(int milliseconds) = 0;
|
||||
|
||||
virtual unsigned getDoubleClickTime() const = 0;
|
||||
virtual void getMousePos(int& x, int& y) = 0;
|
||||
virtual void getLastMousePos(int& x, int& y) = 0;
|
||||
virtual void setMousePos(int x, int y) = 0;
|
||||
virtual void setCursorVisible(bool visible) = 0;
|
||||
virtual void setCursorLocked(bool locked) = 0;
|
||||
//Sets whether the cursor should currently lock if set to
|
||||
virtual void setCursorShouldLock(bool locked) = 0;
|
||||
|
||||
virtual void setClipboard(const std::string& text) = 0;
|
||||
virtual std::string getClipboard() = 0;
|
||||
|
||||
virtual int getCharForKey(int key) = 0;
|
||||
virtual int getKeyForChar(unsigned char chr) = 0;
|
||||
|
||||
OSDriver() : shiftKey(false), altKey(false), ctrlKey(false),
|
||||
win_width(0), win_height(0), mouse_x(0), mouse_y(0),
|
||||
leftButton(false), rightButton(false), middleButton(false) {}
|
||||
virtual ~OSDriver() {}
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,569 @@
|
||||
#include "os/glfw_driver.h"
|
||||
#include "compat/misc.h"
|
||||
#include "compat/gl.h"
|
||||
#include "main/game_platform.h"
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
#include "threads.h"
|
||||
#include "vec2.h"
|
||||
#include <list>
|
||||
#include <math.h>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
|
||||
#ifndef WIN_MODE
|
||||
#include <sys/time.h>
|
||||
#include <random>
|
||||
#else
|
||||
|
||||
//Undef duplicate macros from glfw
|
||||
#undef APIENTRY
|
||||
#undef WINGDIAPI
|
||||
|
||||
#include <Windows.h>
|
||||
#include <WinCrypt.h>
|
||||
#include "os/resource.h"
|
||||
#endif
|
||||
|
||||
//We use std::max instead
|
||||
#undef max
|
||||
|
||||
extern bool game_running;
|
||||
|
||||
namespace os {
|
||||
|
||||
void redirectWindowClose(GLFWwindow*);
|
||||
void redirectWindowResize(GLFWwindow*,int, int);
|
||||
void redirectSpecialKey(GLFWwindow*,int key, int scan, int action, int mods);
|
||||
void redirectCharKey(GLFWwindow*,unsigned);
|
||||
void redirectMouseButton(GLFWwindow*,int button, int action, int mods);
|
||||
void redirectMouseMove(GLFWwindow*,double x, double y);
|
||||
void redirectMouseWheel(GLFWwindow*,double x, double y);
|
||||
void trackMouseOver(GLFWwindow*,int over);
|
||||
void glfwError(int, const char*);
|
||||
|
||||
class GLFWDriver;
|
||||
GLFWDriver* driver = 0;
|
||||
|
||||
class GLFWDriver : public OSDriver {
|
||||
union Callback {
|
||||
std::function<bool(void)>* f_v;
|
||||
std::function<bool(int)>* f_i;
|
||||
std::function<bool(int,int)>* f_ii;
|
||||
|
||||
Callback(decltype(f_v) p_f_v) : f_v(p_f_v) {}
|
||||
Callback(decltype(f_i) p_f_i) : f_i(p_f_i) {}
|
||||
Callback(decltype(f_ii) p_f_ii) : f_ii(p_f_ii) {}
|
||||
};
|
||||
|
||||
std::list<Callback> callbacks[OSC_COUNT];
|
||||
GLFWwindow* window;
|
||||
|
||||
public:
|
||||
#ifndef WIN_MODE
|
||||
timeval start_time;
|
||||
#else
|
||||
ULARGE_INTEGER start_time;
|
||||
|
||||
LARGE_INTEGER start_time_hq, start_time_hq_freq;
|
||||
#endif
|
||||
|
||||
bool mouseOver;
|
||||
bool canLock, shouldLock;
|
||||
|
||||
double frameTime, gameTime, gameSpeed;
|
||||
|
||||
GLFWDriver() : window(0), mouseOver(true), canLock(false), shouldLock(false) {
|
||||
glfwSetErrorCallback(glfwError);
|
||||
glfwInit();
|
||||
resetTimer();
|
||||
}
|
||||
|
||||
~GLFWDriver() {
|
||||
driver = 0;
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
bool systemRandom(unsigned char* buffer, unsigned bytes) {
|
||||
#ifdef WIN_MODE
|
||||
HCRYPTPROV provider;
|
||||
|
||||
//Attempt to acquire the context if it exists, then make it if it does not
|
||||
BOOL ctx = CryptAcquireContext(&provider, "SR2SysRand", NULL, PROV_RSA_AES, NULL);
|
||||
if(!ctx)
|
||||
ctx = CryptAcquireContext(&provider, "SR2SysRand", NULL, PROV_RSA_AES, CRYPT_NEWKEYSET);
|
||||
|
||||
if(ctx) {
|
||||
BOOL success = CryptGenRandom(provider, bytes, buffer);
|
||||
CryptReleaseContext(provider, NULL);
|
||||
|
||||
return success != 0;
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
std::random_device rd;
|
||||
for(unsigned i = 0; i < bytes; ++i)
|
||||
buffer[i] = (unsigned char)rd();
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void setVerticalSync(int waitFrames) {
|
||||
glfwSwapInterval(waitFrames);
|
||||
}
|
||||
|
||||
void swapBuffers(double minWait_s) {
|
||||
glfwSwapBuffers(window);
|
||||
glfwPollEvents();
|
||||
if(devices.cloud)
|
||||
devices.cloud->update();
|
||||
|
||||
double waitTill = frameTime + minWait_s;
|
||||
double curTime = getAccurateTime();
|
||||
|
||||
while(curTime < waitTill) {
|
||||
glfwPollEvents();
|
||||
if(devices.cloud)
|
||||
devices.cloud->update();
|
||||
threads::sleep(0);
|
||||
curTime = getAccurateTime();
|
||||
}
|
||||
|
||||
if(gameSpeed > 0 && game_running) {
|
||||
double delta = curTime - frameTime;
|
||||
if(delta > 0.5)
|
||||
delta = 0.5;
|
||||
gameTime += delta * gameSpeed;
|
||||
}
|
||||
|
||||
frameTime = curTime;
|
||||
}
|
||||
|
||||
void handleEvents(unsigned minWait_ms) {
|
||||
glfwPollEvents();
|
||||
frameTime = getAccurateTime();
|
||||
threads::sleep(minWait_ms);
|
||||
}
|
||||
|
||||
void getDesktopSize(unsigned& width, unsigned& height) {
|
||||
auto* primary = glfwGetPrimaryMonitor();
|
||||
if(primary == nullptr) {
|
||||
width = 1280;
|
||||
height = 720;
|
||||
return;
|
||||
}
|
||||
auto& desktop = *glfwGetVideoMode(primary);
|
||||
width = desktop.width;
|
||||
height = desktop.height;
|
||||
}
|
||||
|
||||
void createWindow(WindowData& data) {
|
||||
glfwWindowHint(GLFW_SAMPLES, data.aa_samples);
|
||||
glfwWindowHint(GLFW_REFRESH_RATE, data.refreshRate);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
|
||||
|
||||
GLFWmonitor* monitor = nullptr;
|
||||
if(data.mode == WM_Fullscreen) {
|
||||
monitor = glfwGetPrimaryMonitor();
|
||||
|
||||
if(!data.targetMonitor.empty()) {
|
||||
int count = 0;
|
||||
GLFWmonitor** monitors = glfwGetMonitors(&count);
|
||||
for(int i = 0; i < count; ++i) {
|
||||
if(data.targetMonitor == glfwGetMonitorName(monitors[i])) {
|
||||
monitor = monitors[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(monitor && !data.overrideMonitor) {
|
||||
int count = 0;
|
||||
const GLFWvidmode* modes = glfwGetVideoModes(monitor, &count);
|
||||
bool match = false;
|
||||
for(int i = 0; i < count; ++i) {
|
||||
auto& mode = modes[i];
|
||||
if(mode.width == data.width && mode.height == data.height && (data.refreshRate == 0 || data.refreshRate == mode.refreshRate)) {
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!match) {
|
||||
monitor = nullptr;
|
||||
data.mode = WM_Window;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(data.mode != WM_Fullscreen && (data.width == 0 || data.height == 0)) {
|
||||
GLFWmonitor* primary = glfwGetPrimaryMonitor();
|
||||
const GLFWvidmode* mode = glfwGetVideoMode(primary);
|
||||
|
||||
data.width = mode->width;
|
||||
data.height = mode->height;
|
||||
}
|
||||
|
||||
window = glfwCreateWindow(data.width, data.height, "Star Ruler 2", monitor, nullptr);
|
||||
|
||||
if(window == 0)
|
||||
window = glfwCreateWindow(1024,768, "Star Ruler 2 - Error Creating Window", nullptr, nullptr);
|
||||
|
||||
if(window == 0) {
|
||||
error("Could not create window.");
|
||||
return;
|
||||
}
|
||||
|
||||
glfwMakeContextCurrent(window);
|
||||
|
||||
glfwGetWindowSize(window, &win_width, &win_height);
|
||||
glfwSwapInterval( data.verticalSync );
|
||||
|
||||
if(data.mode == WM_Fullscreen)
|
||||
setCursorVisible(true);
|
||||
|
||||
glfwSetWindowCloseCallback(window, redirectWindowClose);
|
||||
glfwSetWindowSizeCallback(window, redirectWindowResize);
|
||||
glfwSetKeyCallback(window, redirectSpecialKey);
|
||||
glfwSetCharCallback(window, redirectCharKey);
|
||||
glfwSetMouseButtonCallback(window, redirectMouseButton);
|
||||
glfwSetScrollCallback(window, redirectMouseWheel);
|
||||
glfwSetCursorPosCallback(window, redirectMouseMove);
|
||||
glfwSetCursorEnterCallback(window, trackMouseOver);
|
||||
|
||||
#ifdef WIN_MODE
|
||||
HICON hSmallIcon = (HICON) LoadImage ( 0, "sr2.ico", IMAGE_ICON, 32, 32, LR_LOADFROMFILE | LR_DEFAULTCOLOR );
|
||||
SendMessage ( GetActiveWindow(), WM_SETICON, ICON_SMALL, (long)hSmallIcon );
|
||||
#endif
|
||||
}
|
||||
|
||||
void getVideoModes(std::vector<OSDriver::VideoMode>& output) {
|
||||
int count = 0;
|
||||
const GLFWvidmode* modes;
|
||||
auto* primary = glfwGetPrimaryMonitor();
|
||||
if(primary)
|
||||
modes = glfwGetVideoModes(primary, &count);
|
||||
output.reserve(count);
|
||||
output.resize(0);
|
||||
|
||||
std::set<uint64_t> sizes;
|
||||
|
||||
for(int i = 0; i < count; ++i) {
|
||||
VideoMode m;
|
||||
auto& mode = modes[i];
|
||||
m.width = mode.width;
|
||||
m.height = mode.height;
|
||||
m.refresh = mode.refreshRate;
|
||||
|
||||
uint64_t size = (uint64_t)m.width << 16 | (uint64_t)m.height | ((uint64_t)mode.refreshRate << 32);
|
||||
|
||||
if(sizes.find(size) == sizes.end()) {
|
||||
sizes.insert(size);
|
||||
output.push_back(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void getMonitorNames(std::vector<std::string>& output) {
|
||||
int count = 0;
|
||||
GLFWmonitor** monitors = glfwGetMonitors(&count);
|
||||
output.resize(count);
|
||||
for(int i = 0; i < count; ++i)
|
||||
output[i] = glfwGetMonitorName(monitors[i]);
|
||||
}
|
||||
|
||||
bool isWindowFocused() override {
|
||||
return glfwGetWindowAttrib(window, GLFW_FOCUSED) != 0;
|
||||
}
|
||||
|
||||
bool isWindowMinimized() override {
|
||||
return glfwGetWindowAttrib(window, GLFW_ICONIFIED) != 0;
|
||||
}
|
||||
|
||||
void flashWindow() override {
|
||||
glfwFlashWindow(window);
|
||||
}
|
||||
|
||||
bool isMouseOver() {
|
||||
return mouseOver;
|
||||
}
|
||||
|
||||
void setClipboard(const std::string& text) {
|
||||
glfwSetClipboardString(window, text.c_str());
|
||||
}
|
||||
|
||||
std::string getClipboard() {
|
||||
const char* str = glfwGetClipboardString(window);
|
||||
if(str)
|
||||
return std::string(str);
|
||||
return std::string();
|
||||
}
|
||||
|
||||
int getCharForKey(int key) {
|
||||
return glfwGetCharForKey(key);
|
||||
}
|
||||
|
||||
int getKeyForChar(unsigned char chr) {
|
||||
return glfwGetKeyForChar(chr);
|
||||
}
|
||||
|
||||
unsigned getDoubleClickTime() const {
|
||||
#ifdef _MSC_VER
|
||||
return GetDoubleClickTime();
|
||||
#else
|
||||
//TODO: Get user's setting on linux
|
||||
return 200;
|
||||
#endif
|
||||
}
|
||||
|
||||
void getLastMousePos(int& x, int& y) {
|
||||
x = mouse_x;
|
||||
y = mouse_y;
|
||||
}
|
||||
|
||||
void getMousePos(int& x, int& y) {
|
||||
double dx, dy;
|
||||
glfwGetCursorPos(window, &dx,&dy);
|
||||
x = (int)floor(dx);
|
||||
y = (int)floor(dy);
|
||||
}
|
||||
|
||||
void setMousePos(int x, int y) {
|
||||
glfwSetCursorPos(window, x,y);
|
||||
}
|
||||
|
||||
void setCursorVisible(bool visible) {
|
||||
glfwSetInputMode(window, GLFW_CURSOR, visible ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_HIDDEN);
|
||||
}
|
||||
|
||||
void setCursorLocked(bool locked) {
|
||||
if(canLock == locked)
|
||||
return;
|
||||
canLock = locked;
|
||||
if(shouldLock) {
|
||||
if(locked)
|
||||
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_CAPTURED);
|
||||
else
|
||||
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_FREE);
|
||||
}
|
||||
}
|
||||
|
||||
void setCursorShouldLock(bool locked) {
|
||||
if(shouldLock == locked)
|
||||
return;
|
||||
shouldLock = locked;
|
||||
if(canLock) {
|
||||
if(locked)
|
||||
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_CAPTURED);
|
||||
else
|
||||
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_FREE);
|
||||
}
|
||||
}
|
||||
|
||||
void sleep(int milliseconds) {
|
||||
threads::sleep(milliseconds);
|
||||
}
|
||||
|
||||
void resetTimer() {
|
||||
#ifndef WIN_MODE
|
||||
gettimeofday(&start_time, 0);
|
||||
#else
|
||||
FILETIME cur_time;
|
||||
GetSystemTimeAsFileTime(&cur_time);
|
||||
start_time.HighPart = cur_time.dwHighDateTime;
|
||||
start_time.LowPart = cur_time.dwLowDateTime;
|
||||
|
||||
QueryPerformanceFrequency(&start_time_hq_freq);
|
||||
QueryPerformanceCounter(&start_time_hq);
|
||||
#endif
|
||||
frameTime = 0.0;
|
||||
resetGameTime(0);
|
||||
}
|
||||
|
||||
int getTime() const {
|
||||
#ifndef WIN_MODE
|
||||
timeval cur_time;
|
||||
gettimeofday(&cur_time, 0);
|
||||
|
||||
return (int)(
|
||||
1000 * (cur_time.tv_sec - start_time.tv_sec)
|
||||
+ (cur_time.tv_usec - start_time.tv_usec) / 1000);
|
||||
#else
|
||||
|
||||
FILETIME cur_ftime;
|
||||
GetSystemTimeAsFileTime(&cur_ftime);
|
||||
ULARGE_INTEGER cur_time;
|
||||
cur_time.HighPart = cur_ftime.dwHighDateTime;
|
||||
cur_time.LowPart = cur_ftime.dwLowDateTime;
|
||||
|
||||
const double ticksPerSecond = 10000000.0;
|
||||
auto timeInSeconds = (double)(cur_time.QuadPart - start_time.QuadPart) / ticksPerSecond;
|
||||
|
||||
return int(timeInSeconds * 1000.0);
|
||||
#endif
|
||||
}
|
||||
|
||||
double getAccurateTime() const {
|
||||
#ifndef WIN_MODE
|
||||
timeval cur_time;
|
||||
gettimeofday(&cur_time, 0);
|
||||
|
||||
return (double)(cur_time.tv_sec - start_time.tv_sec)
|
||||
+ (double)(cur_time.tv_usec - start_time.tv_usec)/1000000.0;
|
||||
#else
|
||||
LARGE_INTEGER cur_time, cur_freq;
|
||||
QueryPerformanceCounter(&cur_time);
|
||||
QueryPerformanceFrequency(&cur_freq);
|
||||
|
||||
return (double)cur_time.QuadPart / (double)cur_freq.QuadPart - (double)start_time_hq.QuadPart / (double)start_time_hq_freq.QuadPart;
|
||||
#endif
|
||||
}
|
||||
|
||||
void resetGameTime(double time) {
|
||||
//Game time starts slightly ahead of render time
|
||||
gameTime = time;
|
||||
gameSpeed = 1;
|
||||
}
|
||||
|
||||
double getGameTime() const {
|
||||
return gameTime;
|
||||
}
|
||||
|
||||
double getFrameTime() const {
|
||||
return frameTime;
|
||||
}
|
||||
|
||||
double getGameSpeed() const {
|
||||
return gameSpeed;
|
||||
}
|
||||
|
||||
void setGameSpeed(double speed) {
|
||||
gameSpeed = speed;
|
||||
}
|
||||
|
||||
unsigned getProcessorCount() {
|
||||
return std::max(threads::getNumberOfProcessors(),1u);
|
||||
}
|
||||
|
||||
void setWindowTitle(const char* str) {
|
||||
glfwSetWindowTitle(window, str);
|
||||
}
|
||||
|
||||
void setWindowSize(int width, int height){
|
||||
glfwSetWindowSize(window, width, height);
|
||||
}
|
||||
|
||||
void closeWindow() {
|
||||
glfwDestroyWindow(window);
|
||||
}
|
||||
};
|
||||
|
||||
void redirectWindowClose(GLFWwindow*) {
|
||||
driver->onWindowClose();
|
||||
}
|
||||
|
||||
void redirectWindowResize(GLFWwindow*, int w, int h) {
|
||||
driver->onResize(w,h);
|
||||
driver->win_width = w;
|
||||
driver->win_height = h;
|
||||
glViewport(0, 0, w, h);
|
||||
}
|
||||
|
||||
void redirectSpecialKey(GLFWwindow*, int key, int scan, int action, int mods) {
|
||||
driver->shiftKey = (mods & GLFW_MOD_SHIFT) != 0;
|
||||
driver->ctrlKey = (mods & GLFW_MOD_CONTROL) != 0;
|
||||
driver->altKey = (mods & GLFW_MOD_ALT) != 0;
|
||||
|
||||
bool pressed;
|
||||
int keyaction;
|
||||
switch(action) {
|
||||
case GLFW_PRESS:
|
||||
pressed = true;
|
||||
keyaction = KA_Pressed;
|
||||
break;
|
||||
case GLFW_RELEASE:
|
||||
pressed = false;
|
||||
keyaction = KA_Released;
|
||||
break;
|
||||
case GLFW_REPEAT:
|
||||
pressed = true;
|
||||
keyaction = KA_Repeated;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
switch(key) {
|
||||
case GLFW_KEY_LEFT_SHIFT:
|
||||
case GLFW_KEY_RIGHT_SHIFT:
|
||||
driver->shiftKey = pressed;
|
||||
break;
|
||||
|
||||
case GLFW_KEY_LEFT_CONTROL:
|
||||
case GLFW_KEY_RIGHT_CONTROL:
|
||||
driver->ctrlKey = pressed;
|
||||
break;
|
||||
|
||||
case GLFW_KEY_LEFT_ALT:
|
||||
case GLFW_KEY_RIGHT_ALT:
|
||||
driver->altKey = pressed;
|
||||
break;
|
||||
}
|
||||
|
||||
driver->onKeyEvent(key, keyaction);
|
||||
}
|
||||
|
||||
void redirectCharKey(GLFWwindow*, unsigned key) {
|
||||
driver->onCharTyped(key);
|
||||
}
|
||||
|
||||
void redirectMouseButton(GLFWwindow*, int button, int action, int mods) {
|
||||
bool pressed = action == GLFW_PRESS;
|
||||
|
||||
driver->shiftKey = (mods & GLFW_MOD_SHIFT) != 0;
|
||||
driver->ctrlKey = (mods & GLFW_MOD_CONTROL) != 0;
|
||||
driver->altKey = (mods & GLFW_MOD_ALT) != 0;
|
||||
|
||||
switch(button) {
|
||||
case GLFW_MOUSE_BUTTON_LEFT:
|
||||
driver->leftButton = pressed;
|
||||
break;
|
||||
|
||||
case GLFW_MOUSE_BUTTON_RIGHT:
|
||||
driver->rightButton = pressed;
|
||||
break;
|
||||
|
||||
case GLFW_MOUSE_BUTTON_MIDDLE:
|
||||
driver->middleButton = pressed;
|
||||
break;
|
||||
}
|
||||
|
||||
driver->onMouseButton(button, pressed);
|
||||
}
|
||||
|
||||
void redirectMouseMove(GLFWwindow*, double x, double y) {
|
||||
driver->onMouseMoved((int)x, (int)y);
|
||||
driver->mouse_x = (int)x;
|
||||
driver->mouse_y = (int)y;
|
||||
}
|
||||
|
||||
void redirectMouseWheel(GLFWwindow*, double x, double y) {
|
||||
driver->onScroll(x, y);
|
||||
}
|
||||
|
||||
void trackMouseOver(GLFWwindow*,int over) {
|
||||
driver->mouseOver = over == GL_TRUE;
|
||||
}
|
||||
|
||||
void glfwError(int code, const char* msg) {
|
||||
error("GLFW Error %d: %s", code, msg);
|
||||
}
|
||||
|
||||
OSDriver* getGLFWDriver() {
|
||||
if(!driver)
|
||||
driver = new GLFWDriver();
|
||||
return driver;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
#include "os/driver.h"
|
||||
|
||||
namespace os {
|
||||
OSDriver* getGLFWDriver();
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
namespace os {
|
||||
|
||||
enum SpecialKey {
|
||||
KEY_A = 65,
|
||||
KEY_ESC = 256,
|
||||
KEY_ENTER,
|
||||
KEY_TAB,
|
||||
KEY_BACKSPACE,
|
||||
KEY_INSERT,
|
||||
KEY_DEL,
|
||||
KEY_RIGHT,
|
||||
KEY_LEFT,
|
||||
KEY_DOWN,
|
||||
KEY_UP,
|
||||
KEY_PAGEUP,
|
||||
KEY_PAGEDOWN,
|
||||
KEY_HOME,
|
||||
KEY_END,
|
||||
KEY_CAPS_LOCK = 280,
|
||||
KEY_SCROLL_LOCK,
|
||||
KEY_NUM_LOCK,
|
||||
KEY_PRINT_SCREEN,
|
||||
KEY_PAUSE,
|
||||
KEY_F1 = 290,
|
||||
KEY_F2,
|
||||
KEY_F3,
|
||||
KEY_F4,
|
||||
KEY_F5,
|
||||
KEY_F6,
|
||||
KEY_F7,
|
||||
KEY_F8,
|
||||
KEY_F9,
|
||||
KEY_F10,
|
||||
KEY_F11,
|
||||
KEY_F12,
|
||||
KEY_F13,
|
||||
KEY_F14,
|
||||
KEY_F15,
|
||||
KEY_F16,
|
||||
KEY_F17,
|
||||
KEY_F18,
|
||||
KEY_F19,
|
||||
KEY_F20,
|
||||
KEY_F21,
|
||||
KEY_F22,
|
||||
KEY_F23,
|
||||
KEY_F24,
|
||||
KEY_F25,
|
||||
KEY_KP_0 = 320,
|
||||
KEY_KP_1,
|
||||
KEY_KP_2,
|
||||
KEY_KP_3,
|
||||
KEY_KP_4,
|
||||
KEY_KP_5,
|
||||
KEY_KP_6,
|
||||
KEY_KP_7,
|
||||
KEY_KP_8,
|
||||
KEY_KP_9,
|
||||
KEY_KP_DECIMAL,
|
||||
KEY_KP_DIVIDE,
|
||||
KEY_KP_MULTIPLY,
|
||||
KEY_KP_SUBTRACT,
|
||||
KEY_KP_ADD,
|
||||
KEY_KP_ENTER,
|
||||
KEY_KP_EQUAL,
|
||||
KEY_LSHIFT = 340,
|
||||
KEY_LCTRL,
|
||||
KEY_LALT,
|
||||
KEY_LSUPER,
|
||||
KEY_RSHIFT,
|
||||
KEY_RCTRL,
|
||||
KEY_RALT,
|
||||
KEY_RSUPER,
|
||||
KEY_MENU,
|
||||
|
||||
KEY_JOY_A,
|
||||
KEY_JOY_B,
|
||||
KEY_JOY_X,
|
||||
KEY_JOY_Y,
|
||||
KEY_JOY_UP,
|
||||
KEY_JOY_LEFT,
|
||||
KEY_JOY_DOWN,
|
||||
KEY_JOY_RIGHT,
|
||||
KEY_JOY_LB,
|
||||
KEY_JOY_LT,
|
||||
KEY_JOY_L3,
|
||||
KEY_JOY_RB,
|
||||
KEY_JOY_RT,
|
||||
KEY_JOY_R3,
|
||||
KEY_JOY_SELECT,
|
||||
KEY_JOY_START
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by Star Ruler 2.rc
|
||||
|
||||
#define IDI_ICON1 101
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 101
|
||||
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||
#define _APS_NEXT_CONTROL_VALUE 1001
|
||||
#define _APS_NEXT_SYMED_VALUE 101
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,504 @@
|
||||
#include "physics_world.h"
|
||||
#include <vec2.h>
|
||||
#include <math.h>
|
||||
#include "threads.h"
|
||||
#include "obj/object.h"
|
||||
#include "empire.h"
|
||||
#include "network/message.h"
|
||||
#include "util/save_file.h"
|
||||
#include <assert.h>
|
||||
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
|
||||
#include "compat/intrin.h"
|
||||
|
||||
#define SPLIT_COUNT 16
|
||||
#define REBALANCE_RATIO 4
|
||||
#define MAX_DEPTH 8
|
||||
|
||||
PhysicsWorld::PhysicsWorld(double GridSize, double GridFuzz, unsigned GridCount)
|
||||
: gridSize(GridSize), halfGridSize(GridSize * 0.5), gridFuzz(GridFuzz), gridCount(GridCount)
|
||||
{
|
||||
while(gridCount > 200) {
|
||||
gridCount = (gridCount + 1)/2;
|
||||
gridSize *= 2.0;
|
||||
halfGridSize *= 2.0;
|
||||
}
|
||||
|
||||
outside.bound.reset(AABBoxd::fromCircle(vec3d(),1.0e12));
|
||||
outside.fuzzBound = outside.bound;
|
||||
outside.itemCount = 0;
|
||||
|
||||
grid = new PhysicsGrid[gridCount*gridCount];
|
||||
for(unsigned x = 0; x < gridCount; ++x) {
|
||||
for(unsigned y = 0; y < gridCount; ++y) {
|
||||
auto& zone = grid[x*gridCount + y];
|
||||
zone.itemCount = 0;
|
||||
|
||||
for(unsigned i = 0; i < 32; ++i)
|
||||
zone.groups[i].fuzz = gridFuzz;
|
||||
|
||||
vec2d pos = vec2d(vec2i(x,y) - vec2i(gridCount / 2)) * gridSize;
|
||||
|
||||
zone.bound.reset(vec3d(pos.x,-100000.0,pos.y));
|
||||
zone.bound.addPoint(vec3d(pos.x+gridSize,100000.0,pos.y+gridSize));
|
||||
|
||||
zone.fuzzBound.reset(zone.bound.minimum - vec3d(gridFuzz));
|
||||
zone.fuzzBound.addPoint(zone.bound.maximum + vec3d(gridFuzz));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PhysicsWorld::~PhysicsWorld() {
|
||||
delete[] grid;
|
||||
}
|
||||
|
||||
//Gets the grid index for x
|
||||
unsigned PhysicsWorld::getBox(double x) const {
|
||||
int index = int(floor(x / gridSize)) + int(gridCount / 2);
|
||||
if(index <= 0)
|
||||
return 0;
|
||||
if(index >= (int)gridCount-1)
|
||||
return gridCount-1;
|
||||
return index;
|
||||
}
|
||||
|
||||
//Gets the smallest grid index that could contain x
|
||||
unsigned PhysicsWorld::getLowerBound(double x) const {
|
||||
double dIndex = floor(x / gridSize);
|
||||
double pct = (x / gridSize) - dIndex;
|
||||
|
||||
int index = int(dIndex) + int(gridCount / 2);
|
||||
if(pct < gridFuzz / gridSize)
|
||||
index -= 1;
|
||||
|
||||
if(index <= 0)
|
||||
return 0;
|
||||
if(index >= (int)gridCount-1)
|
||||
return gridCount-1;
|
||||
return index;
|
||||
}
|
||||
|
||||
//Gets the largest grid index that could contain x
|
||||
unsigned PhysicsWorld::getUpperBound(double x) const {
|
||||
double dIndex = floor(x / gridSize);
|
||||
double pct = (x / gridSize) - dIndex;
|
||||
|
||||
int index = int(dIndex) + int(gridCount / 2);
|
||||
if(pct > 1.0 - (gridFuzz / gridSize))
|
||||
index += 1;
|
||||
|
||||
if(index <= 0)
|
||||
return 0;
|
||||
if(index >= (int)gridCount-1)
|
||||
return gridCount-1;
|
||||
return index;
|
||||
}
|
||||
|
||||
void PhysBisect::findInBox(const AABBoxd& box, const std::function<void(const PhysicsItem&)>& callback, unsigned ownerMask) const {
|
||||
if(!items.empty()) {
|
||||
auto* pItems = &items.front();
|
||||
for(auto* end = pItems + items.size(), *next = pItems + 1; pItems != end; pItems = next, ++next) {
|
||||
auto& item = **pItems;
|
||||
//Prefetch all 3 cache lines that the next item's bound occupies
|
||||
if(next != end) {
|
||||
auto* pNextItem = *next;
|
||||
PREFETCH((const char*)pNextItem + offsetof(PhysicsItem,bound));
|
||||
PREFETCH((const char*)pNextItem + offsetof(PhysicsItem,bound) + (2 * sizeof(double)));
|
||||
PREFETCH((const char*)pNextItem + offsetof(PhysicsItem,bound) + (4 * sizeof(double)));
|
||||
}
|
||||
if(box.overlaps(item.bound))
|
||||
callback(item);
|
||||
}
|
||||
}
|
||||
|
||||
if(a) {
|
||||
int dir = classify(box);
|
||||
|
||||
if(dir != 1)
|
||||
a->findInBox(box, callback, ownerMask);
|
||||
if(dir != -1)
|
||||
b->findInBox(box, callback, ownerMask);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysBisect::fuse() {
|
||||
if(!a)
|
||||
return;
|
||||
|
||||
a->fuse(items);
|
||||
b->fuse(items);
|
||||
a = b = 0;
|
||||
childrenA = childrenB = 0;
|
||||
}
|
||||
|
||||
void PhysBisect::fuse(std::vector<PhysicsItem*>& into) {
|
||||
if(a) {
|
||||
a->fuse(into);
|
||||
b->fuse(into);
|
||||
}
|
||||
|
||||
into.insert(into.end(), items.begin(), items.end());
|
||||
delete this;
|
||||
}
|
||||
|
||||
void PhysBisect::split() {
|
||||
if(depth == MAX_DEPTH)
|
||||
return;
|
||||
a = new PhysBisect();
|
||||
b = new PhysBisect();
|
||||
|
||||
a->fuzz = b->fuzz = fuzz * 0.5;
|
||||
a->depth = b->depth = depth + 1;
|
||||
|
||||
a->parent = b->parent = this;
|
||||
a->xSplit = b->xSplit = !xSplit;
|
||||
|
||||
if(xSplit) {
|
||||
//Estimate a median value to split around
|
||||
double values[10];
|
||||
if(items.size() <= 10) {
|
||||
for(unsigned i = 0, cnt = (unsigned)items.size(); i < cnt; ++i) {
|
||||
auto* item = items[i];
|
||||
values[i] = (item->bound.minimum.x + item->bound.maximum.x) * 0.5;
|
||||
}
|
||||
std::sort(values, values + items.size());
|
||||
dimSplit = values[items.size() / 2];
|
||||
}
|
||||
else {
|
||||
unsigned step = (unsigned)items.size() / 10;
|
||||
for(unsigned i = 0; i < 10; ++i) {
|
||||
auto* item = items[i * step];
|
||||
values[i] = (item->bound.minimum.x + item->bound.maximum.x) * 0.5;
|
||||
}
|
||||
std::sort(values, values + 10);
|
||||
dimSplit = values[5];
|
||||
}
|
||||
|
||||
for(int i = (int)items.size() - 1; i >= 0; --i) {
|
||||
PhysicsItem* item = items[i];
|
||||
int location = classifyContainer(item->bound);
|
||||
if(location == -1) {
|
||||
a->items.push_back(item);
|
||||
items.erase(items.begin() + i);
|
||||
childrenA++;
|
||||
}
|
||||
else if(location == 1) {
|
||||
b->items.push_back(item);
|
||||
items.erase(items.begin() + i);
|
||||
childrenB++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
double values[10];
|
||||
if(items.size() <= 10) {
|
||||
for(unsigned i = 0, cnt = (unsigned)items.size(); i < cnt; ++i) {
|
||||
auto* item = items[i];
|
||||
values[i] = (item->bound.minimum.z + item->bound.maximum.z) * 0.5;
|
||||
}
|
||||
std::sort(values, values + items.size());
|
||||
dimSplit = values[items.size() / 2];
|
||||
}
|
||||
else {
|
||||
unsigned step = (unsigned)items.size() / 10;
|
||||
for(unsigned i = 0; i < 10; ++i) {
|
||||
auto* item = items[i * step];
|
||||
values[i] = (item->bound.minimum.z + item->bound.maximum.z) * 0.5;
|
||||
}
|
||||
std::sort(values, values + 10);
|
||||
dimSplit = values[5];
|
||||
}
|
||||
|
||||
for(int i = (int)items.size() - 1; i >= 0; --i) {
|
||||
PhysicsItem* item = items[i];
|
||||
int location = classifyContainer(item->bound);
|
||||
if(location == -1) {
|
||||
a->items.push_back(item);
|
||||
items.erase(items.begin() + i);
|
||||
childrenA++;
|
||||
}
|
||||
else if(location == 1) {
|
||||
b->items.push_back(item);
|
||||
items.erase(items.begin() + i);
|
||||
childrenB++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(a->items.size() > SPLIT_COUNT)
|
||||
a->split();
|
||||
if(b->items.size() > SPLIT_COUNT)
|
||||
b->split();
|
||||
}
|
||||
|
||||
void PhysicsWorld::findInBoxInZone(const PhysicsGrid& zone, const AABBoxd& box,
|
||||
const std::function<void(const PhysicsItem&)>& callback, unsigned ownerMask) {
|
||||
threads::ReadLock lock(zone.mutex);
|
||||
//if(ownerMask == ~0u) {
|
||||
//findInBoxInZone(zone, box, callback);
|
||||
// zone.findInBox(box, callback, ~0);
|
||||
// return;
|
||||
//}
|
||||
|
||||
for(unsigned i = 0; i < 32; ++i)
|
||||
if(ownerMask & (1 << i))
|
||||
zone.groups[i].findInBox(box, callback, 0);
|
||||
}
|
||||
|
||||
void PhysicsWorld::findInBox(const AABBoxd& box, const std::function<void(const PhysicsItem&)>& callback, unsigned ownerMask) {
|
||||
//double start = devices.driver->getAccurateTime();
|
||||
unsigned fromX = getLowerBound(box.minimum.x), toX = getUpperBound(box.maximum.x);
|
||||
unsigned fromY = getLowerBound(box.minimum.z), toY = getUpperBound(box.maximum.z);
|
||||
|
||||
for(unsigned x = fromX; x <= toX; ++x) {
|
||||
for(unsigned y = fromY; y <= toY; ++y) {
|
||||
const PhysicsGrid& zone = grid[x * gridCount + y];
|
||||
if(!zone.empty() && box.overlaps(zone.fuzzBound))
|
||||
findInBoxInZone(zone, box, callback, ownerMask);
|
||||
}
|
||||
}
|
||||
|
||||
if(!outside.empty())
|
||||
findInBoxInZone(outside, box, callback, ownerMask);
|
||||
//double end = devices.driver->getAccurateTime();
|
||||
//error("%d\n", (int)((end - start) * 1e6));
|
||||
}
|
||||
|
||||
bool PhysicsGrid::empty() const {
|
||||
return itemCount == 0;
|
||||
}
|
||||
|
||||
void PhysicsGrid::removeItem(PhysicsItem* item) {
|
||||
mutex.writeLock();
|
||||
itemCount -= 1;
|
||||
PhysBisect* bisect = &groups[item->maskID];
|
||||
while(bisect->a) {
|
||||
int location = bisect->classifyContainer(item->bound);
|
||||
if(location == -1) {
|
||||
bisect->childrenA--;
|
||||
bisect = bisect->a;
|
||||
}
|
||||
else if(location == 1) {
|
||||
bisect->childrenB--;
|
||||
bisect = bisect->b;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
bool itemRemoved = false;
|
||||
|
||||
for(auto i = bisect->items.begin(), end = bisect->items.end(); i != end; ++i) {
|
||||
if((*i) == item) {
|
||||
itemRemoved = true;
|
||||
bisect->items.erase(i);
|
||||
|
||||
while(bisect->items.empty() && bisect->parent && bisect->a == 0) {
|
||||
auto parent = bisect->parent;
|
||||
parent->fuse();
|
||||
if(parent->items.size() > SPLIT_COUNT)
|
||||
parent->split();
|
||||
bisect = parent;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mutex.release();
|
||||
|
||||
if(!itemRemoved)
|
||||
throw "Unable to locate physics item";
|
||||
}
|
||||
|
||||
void PhysicsGrid::addItem(PhysicsItem* item) {
|
||||
mutex.writeLock();
|
||||
itemCount += 1;
|
||||
PhysBisect* bisect = &groups[item->maskID];
|
||||
while(bisect->a) {
|
||||
int location = bisect->classifyContainer(item->bound);
|
||||
if(location == -1) {
|
||||
bisect->childrenA++;
|
||||
bisect = bisect->a;
|
||||
}
|
||||
else if(location == 1) {
|
||||
bisect->childrenB++;
|
||||
bisect = bisect->b;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
bisect->items.push_back(item);
|
||||
if(bisect->a) {
|
||||
PhysBisect* refuse = 0;
|
||||
while(bisect->childrenA > REBALANCE_RATIO * bisect->childrenB || bisect->childrenB > REBALANCE_RATIO * bisect->childrenA) {
|
||||
refuse = bisect;
|
||||
bisect = bisect->parent;
|
||||
if(bisect == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if(refuse) {
|
||||
refuse->fuse();
|
||||
if(refuse->items.size() > SPLIT_COUNT)
|
||||
refuse->split();
|
||||
}
|
||||
}
|
||||
else if(bisect->items.size() > SPLIT_COUNT) {
|
||||
bisect->split();
|
||||
}
|
||||
mutex.release();
|
||||
}
|
||||
|
||||
void PhysicsGrid::updateItem(PhysicsItem* item, const AABBoxd& newBound) {
|
||||
unsigned maskID = 1;
|
||||
if(item->type == PIT_Object)
|
||||
maskID = (item->object->owner ? item->object->owner->id : 1);
|
||||
if(maskID >= 32)
|
||||
maskID = 0;
|
||||
|
||||
if(maskID == item->maskID) {
|
||||
PhysBisect* bisect = &groups[item->maskID];
|
||||
|
||||
bool sameLocation = true;
|
||||
|
||||
mutex.readLock();
|
||||
while(bisect->a) {
|
||||
int location = bisect->classifyContainer(item->bound);
|
||||
int newLoc = bisect->classifyContainer(newBound);
|
||||
if(location != newLoc) {
|
||||
sameLocation = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if(location == -1)
|
||||
bisect = bisect->a;
|
||||
else if(location == 1)
|
||||
bisect = bisect->b;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if(sameLocation) {
|
||||
item->bound = newBound;
|
||||
mutex.release();
|
||||
return;
|
||||
}
|
||||
|
||||
mutex.release();
|
||||
}
|
||||
|
||||
//Container has changed
|
||||
mutex.writeLock();
|
||||
|
||||
removeItem(item);
|
||||
item->bound = newBound;
|
||||
item->maskID = maskID;
|
||||
addItem(item);
|
||||
|
||||
mutex.release();
|
||||
}
|
||||
|
||||
//TODO: Handle things too big for the grid, or outside the grid
|
||||
void PhysicsWorld::updateItem(PhysicsItem& item, const AABBoxd& newBox) {
|
||||
if(newBox.isWithin(item.gridLocation->fuzzBound)) {
|
||||
//TODO: Handle transition back from 'outside' if it is now able to be positioned in the grid
|
||||
item.gridLocation->updateItem(&item, newBox);
|
||||
}
|
||||
else {
|
||||
item.gridLocation->removeItem(&item);
|
||||
|
||||
vec3d center = newBox.getCenter();
|
||||
unsigned x = getBox(center.x), y = getBox(center.z);
|
||||
item.bound = newBox;
|
||||
|
||||
PhysicsGrid* newGrid = &grid[x*gridCount + y];
|
||||
if(!newBox.isWithin(newGrid->fuzzBound))
|
||||
newGrid = &outside;
|
||||
item.gridLocation = newGrid;
|
||||
newGrid->addItem(&item);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsWorld::registerItem(PhysicsItem& item) {
|
||||
assert((item.type & PIT_Object) == 0 || item.object);
|
||||
|
||||
vec3d center = item.bound.getCenter();
|
||||
unsigned x = getBox(center.x), y = getBox(center.z);
|
||||
|
||||
PhysicsGrid* newGrid = &grid[x*gridCount + y];
|
||||
if(!item.bound.isWithin(newGrid->fuzzBound))
|
||||
newGrid = &outside;
|
||||
|
||||
item.gridLocation = newGrid;
|
||||
newGrid->addItem(&item);
|
||||
}
|
||||
|
||||
PhysicsItem* PhysicsWorld::registerItem(const AABBoxd& bounds, Object* obj) {
|
||||
PhysicsItem* item = new PhysicsItem;
|
||||
item->bound = bounds;
|
||||
item->object = obj;
|
||||
item->type = PIT_Object;
|
||||
|
||||
unsigned maskID = (obj->owner ? obj->owner->id : 1);
|
||||
if(maskID >= 32)
|
||||
maskID = 0;
|
||||
item->maskID = maskID;
|
||||
|
||||
registerItem(*item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
PhysicsItem* PhysicsWorld::registerItem(const AABBoxd& bounds, scene::Node* node) {
|
||||
PhysicsItem* item = new PhysicsItem;
|
||||
item->bound = bounds;
|
||||
item->node = node;
|
||||
item->type = PIT_Node;
|
||||
item->maskID = 0;
|
||||
|
||||
registerItem(*item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void PhysicsWorld::unregisterItem(PhysicsItem& item) {
|
||||
item.gridLocation->removeItem(&item);
|
||||
}
|
||||
|
||||
PhysicsWorld* PhysicsWorld::fromMessage(net::Message& msg) {
|
||||
double gridSize, gridFuzz;
|
||||
unsigned gridCount;
|
||||
|
||||
msg >> gridSize;
|
||||
msg >> gridFuzz;
|
||||
msg >> gridCount;
|
||||
|
||||
return new PhysicsWorld(gridSize, gridFuzz, gridCount);
|
||||
}
|
||||
|
||||
PhysicsWorld* PhysicsWorld::fromSave(SaveFile& file) {
|
||||
double gridSize, gridFuzz;
|
||||
unsigned gridCount;
|
||||
|
||||
file >> gridSize;
|
||||
file >> gridFuzz;
|
||||
file >> gridCount;
|
||||
|
||||
return new PhysicsWorld(gridSize, gridFuzz, gridCount);
|
||||
}
|
||||
|
||||
void PhysicsWorld::writeSetup(net::Message& msg) {
|
||||
msg << gridSize;
|
||||
msg << gridFuzz;
|
||||
msg << gridCount;
|
||||
}
|
||||
|
||||
void PhysicsWorld::writeSetup(SaveFile& file) {
|
||||
file << gridSize;
|
||||
file << gridFuzz;
|
||||
file << gridCount;
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
#pragma once
|
||||
#include "aabbox.h"
|
||||
#include "util/refcount.h"
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
enum PhysItemType {
|
||||
PIT_Object = 0x01,
|
||||
PIT_Node = 0x02,
|
||||
//A group is both the type of PIT_Group, and the type of objects in the group
|
||||
PIT_Group = 0x80,
|
||||
};
|
||||
|
||||
class Object;
|
||||
struct PhysicsGrid;
|
||||
struct PhysicsItem;
|
||||
struct PhysicsGroup;
|
||||
class SaveFile;
|
||||
|
||||
namespace scene {
|
||||
class Node;
|
||||
};
|
||||
|
||||
namespace net {
|
||||
struct Message;
|
||||
};
|
||||
|
||||
struct PhysicsItem {
|
||||
AABBoxd bound;
|
||||
union {
|
||||
Object* object;
|
||||
scene::Node* node;
|
||||
PhysicsGroup* group;
|
||||
};
|
||||
unsigned maskID;
|
||||
PhysItemType type;
|
||||
PhysicsGrid* gridLocation;
|
||||
};
|
||||
|
||||
struct PhysicsGroup {
|
||||
unsigned itemCount;
|
||||
PhysicsItem* items;
|
||||
};
|
||||
|
||||
struct PhysBisect {
|
||||
std::vector<PhysicsItem*> items;
|
||||
PhysBisect *a, *b, *parent;
|
||||
unsigned childrenA, childrenB;
|
||||
bool xSplit;
|
||||
double dimSplit, fuzz;
|
||||
unsigned depth;
|
||||
|
||||
PhysBisect() : a(0), b(0), parent(0), depth(0), childrenA(0), childrenB(0), xSplit(true) {}
|
||||
|
||||
inline int classify(const AABBoxd& box) const {
|
||||
if(xSplit) {
|
||||
if(box.maximum.x < dimSplit - fuzz)
|
||||
return -1;
|
||||
if(box.minimum.x > dimSplit + fuzz)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
if(box.maximum.z < dimSplit - fuzz)
|
||||
return -1;
|
||||
if(box.minimum.z > dimSplit + fuzz)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int classifyContainer(const AABBoxd& box) const {
|
||||
if(xSplit) {
|
||||
double center = (box.minimum.x + box.maximum.x) * 0.5;
|
||||
if(center >= dimSplit && box.minimum.x > dimSplit - fuzz)
|
||||
return 1;
|
||||
else if(center < dimSplit && box.maximum.x < dimSplit + fuzz)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
double center = (box.minimum.z + box.maximum.z) * 0.5;
|
||||
if(center >= dimSplit && box.minimum.z > dimSplit - fuzz)
|
||||
return 1;
|
||||
else if(center < dimSplit && box.maximum.z < dimSplit + fuzz)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void findInBox(const AABBoxd& box, const std::function<void(const PhysicsItem&)>& callback, unsigned ownerMask) const;
|
||||
|
||||
void split();
|
||||
void fuse();
|
||||
void fuse(std::vector<PhysicsItem*>& into);
|
||||
};
|
||||
|
||||
struct PhysicsGrid {
|
||||
PhysBisect groups[32];
|
||||
mutable threads::ReadWriteMutex mutex;
|
||||
AABBoxd bound, fuzzBound;
|
||||
unsigned itemCount;
|
||||
|
||||
void removeItem(PhysicsItem* item);
|
||||
void addItem(PhysicsItem* item);
|
||||
void updateItem(PhysicsItem* item, const AABBoxd& newBound);
|
||||
bool empty() const;
|
||||
};
|
||||
|
||||
class PhysicsWorld : public AtomicRefCounted {
|
||||
//Physical space that items exist withing
|
||||
PhysicsGrid* grid;
|
||||
//Catch-all for items that do not exist within the grid, or can't fit inside the grid
|
||||
PhysicsGrid outside;
|
||||
|
||||
//Access mutex
|
||||
threads::ReadWriteMutex mutex;
|
||||
|
||||
//Size of a grid square
|
||||
double gridSize, halfGridSize;
|
||||
//Grid fuzz margin
|
||||
double gridFuzz;
|
||||
//Number of grid squares on a side
|
||||
unsigned gridCount;
|
||||
|
||||
unsigned getBox(double x) const;
|
||||
unsigned getLowerBound(double x) const;
|
||||
unsigned getUpperBound(double x) const;
|
||||
|
||||
void findInBoxInZone(const PhysicsGrid& zone, const AABBoxd& box, const std::function<void(const PhysicsItem&)>& callback, unsigned ownerMask);
|
||||
public:
|
||||
PhysicsWorld(double GridSize = 3000.0, double GridFuzz = 350.0, unsigned GridCount = 60);
|
||||
~PhysicsWorld();
|
||||
|
||||
//Calls <callback> for each of the PhysicsItems within the box
|
||||
//Optionally filters by owner of the item (Mask 1 permits unowned objects)
|
||||
void findInBox(const AABBoxd& box, const std::function<void(const PhysicsItem&)>& callback, unsigned ownerMask = 0xffffffff);
|
||||
|
||||
void updateItem(PhysicsItem& item, const AABBoxd& newBox);
|
||||
PhysicsItem* registerItem(const AABBoxd& bounds, Object* obj);
|
||||
PhysicsItem* registerItem(const AABBoxd& bounds, scene::Node* node);
|
||||
void registerItem(PhysicsItem& item);
|
||||
void unregisterItem(PhysicsItem& item);
|
||||
|
||||
void writeSetup(net::Message& msg);
|
||||
void writeSetup(SaveFile& file);
|
||||
static PhysicsWorld* fromMessage(net::Message& msg);
|
||||
static PhysicsWorld* fromSave(SaveFile& file);
|
||||
};
|
||||
@@ -0,0 +1,366 @@
|
||||
#include "processing.h"
|
||||
#include "main/references.h"
|
||||
#include "threads.h"
|
||||
#include "main/initialization.h"
|
||||
#include "main/logging.h"
|
||||
#include "util/random.h"
|
||||
#include "scripts/context_cache.h"
|
||||
#include "obj/lock.h"
|
||||
#include "design/projectiles.h"
|
||||
#include "empire.h"
|
||||
#include <deque>
|
||||
|
||||
extern threads::atomic_int remainingEmpireMessages;
|
||||
|
||||
namespace processing {
|
||||
|
||||
threads::threadreturn threadcall process(void* data);
|
||||
threads::threadreturn threadcall messageProcess(void* data);
|
||||
threads::threadreturn threadcall empireMessageProcess(void* data);
|
||||
|
||||
int threadCount = 0;
|
||||
bool stopProcessing = false, pauseProcessing = false, pauseMessageHandlers = false;
|
||||
threads::Signal threadsActive, msgHandlersActive;
|
||||
|
||||
threads::Mutex actionLock;
|
||||
std::deque<Action*> process_queue;
|
||||
|
||||
std::vector<Action*> isolation_queue;
|
||||
|
||||
#ifdef PROFILE_PROCESSING
|
||||
Threaded(ProcessingData*) threadProc;
|
||||
#endif
|
||||
|
||||
unsigned idleObjectTickCount = 25, activeObjectTickCount = 300;
|
||||
|
||||
void queueAction(Action* action) {
|
||||
actionLock.lock();
|
||||
process_queue.push_back(action);
|
||||
actionLock.release();
|
||||
}
|
||||
|
||||
void queueIsolationAction(Action* action) {
|
||||
actionLock.lock();
|
||||
isolation_queue.push_back(action);
|
||||
actionLock.release();
|
||||
}
|
||||
|
||||
void runIsolation() {
|
||||
if(isolation_queue.empty())
|
||||
return;
|
||||
processing::pause();
|
||||
actionLock.lock();
|
||||
foreach(it, isolation_queue) {
|
||||
(*it)->run();
|
||||
delete *it;
|
||||
}
|
||||
isolation_queue.clear();
|
||||
actionLock.release();
|
||||
processing::resume();
|
||||
}
|
||||
|
||||
Action* popAction() {
|
||||
if(process_queue.empty())
|
||||
return 0;
|
||||
actionLock.lock();
|
||||
Action* action = 0;
|
||||
if(!process_queue.empty()) {
|
||||
action = process_queue.back();
|
||||
process_queue.pop_back();
|
||||
}
|
||||
actionLock.release();
|
||||
return action;
|
||||
}
|
||||
|
||||
void start(unsigned threads) {
|
||||
#ifdef PROFILE_PROCESSING
|
||||
if(!threadProc)
|
||||
threadProc = new ProcessingData();
|
||||
#endif
|
||||
|
||||
stopProcessing = false;
|
||||
threadCount = threads;
|
||||
threadsActive.signal(threads);
|
||||
msgHandlersActive.signal(threads);
|
||||
for(unsigned i = 0; i < threads; ++i) {
|
||||
#ifdef PROFILE_PROCESSING
|
||||
threads::createThread(process, new ProcessingData());
|
||||
#else
|
||||
threads::createThread(process, nullptr);
|
||||
#endif
|
||||
threads::createThread(messageProcess, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void end() {
|
||||
stopProcessing = true;
|
||||
threadsActive.wait(0);
|
||||
msgHandlersActive.wait(0);
|
||||
}
|
||||
|
||||
bool isRunning() {
|
||||
return !threadsActive.check(0);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
if(devices.scripts.cache_server)
|
||||
devices.scripts.cache_server->clearScriptThreads();
|
||||
if(devices.scripts.cache_shadow)
|
||||
devices.scripts.cache_shadow->clearScriptThreads();
|
||||
if(devices.scripts.client)
|
||||
devices.scripts.client->clearScriptThreads();
|
||||
foreach(it, process_queue)
|
||||
delete *it;
|
||||
process_queue.clear();
|
||||
}
|
||||
|
||||
void pause() {
|
||||
pauseProjectiles();
|
||||
if(devices.scripts.cache_server)
|
||||
devices.scripts.cache_server->pauseScriptThreads();
|
||||
if(devices.scripts.cache_shadow)
|
||||
devices.scripts.cache_shadow->pauseScriptThreads();
|
||||
if(devices.scripts.client)
|
||||
devices.scripts.client->pauseScriptThreads();
|
||||
pauseProcessing = true;
|
||||
threadsActive.wait(0);
|
||||
while(hasRemainingMessages())
|
||||
threads::idle();
|
||||
pauseMessageHandlers = true;
|
||||
while(hasRemainingMessages())
|
||||
threads::idle();
|
||||
msgHandlersActive.wait(0);
|
||||
}
|
||||
|
||||
void resume() {
|
||||
pauseProcessing = false;
|
||||
pauseMessageHandlers = false;
|
||||
if(stopProcessing)
|
||||
return;
|
||||
threadsActive.waitNot(0);
|
||||
msgHandlersActive.waitNot(0);
|
||||
if(devices.scripts.cache_server)
|
||||
devices.scripts.cache_server->resumeScriptThreads();
|
||||
if(devices.scripts.cache_shadow)
|
||||
devices.scripts.cache_shadow->resumeScriptThreads();
|
||||
if(devices.scripts.client)
|
||||
devices.scripts.client->resumeScriptThreads();
|
||||
resumeProjectiles();
|
||||
}
|
||||
|
||||
void pauseMessageHandling() {
|
||||
pauseMessageHandlers = true;
|
||||
while(hasRemainingMessages())
|
||||
threads::idle();
|
||||
msgHandlersActive.wait(0);
|
||||
}
|
||||
|
||||
void resumeMessageHandling() {
|
||||
pauseMessageHandlers = false;
|
||||
}
|
||||
|
||||
#ifdef PROFILE_PROCESSING
|
||||
threads::atomic_int printProcProfile;
|
||||
|
||||
void printProcessingProfile() {
|
||||
printProcProfile = threadCount + 1;
|
||||
}
|
||||
|
||||
ProcessingData::ProcessingData() {
|
||||
syncTimer = 0;
|
||||
clear();
|
||||
}
|
||||
|
||||
void ProcessingData::clear() {
|
||||
for(unsigned i = 0, cnt = objTime.size(); i < cnt; ++i) {
|
||||
objTime[i] = 0;
|
||||
objCount[i] = 0;
|
||||
}
|
||||
|
||||
globalCount = 0;
|
||||
switchedTime = 0;
|
||||
targUpdateTime = 0;
|
||||
switchedCount = 0;
|
||||
targetUpdates = 0;
|
||||
}
|
||||
|
||||
void ProcessingData::measureType(int type, double time, int amount) {
|
||||
if(objTime.size() <= type) {
|
||||
objTime.resize(type+1, 0);
|
||||
objCount.resize(type+1, 0);
|
||||
}
|
||||
|
||||
objTime[type] += time;
|
||||
objCount[type] += amount;
|
||||
}
|
||||
|
||||
void profileProcessing() {
|
||||
double curTime = devices.driver->getAccurateTime();
|
||||
if(curTime >= threadProc->syncTimer) {
|
||||
if(printProcProfile > 0) {
|
||||
|
||||
print("Thread %d Processing Profile:", threads::getThreadID());
|
||||
for(unsigned i = 0, cnt = threadProc->objTime.size(); i < cnt; ++i) {
|
||||
if(threadProc->objCount[i] == 0)
|
||||
continue;
|
||||
print(" %s: %d objects in %.4gms (~%.3gms per)",
|
||||
getScriptObjectType(i)->name.c_str(),
|
||||
threadProc->objCount[i],
|
||||
threadProc->objTime[i] * 1000.0,
|
||||
(threadProc->objTime[i] / threadProc->objCount[i]) * 1000.0);
|
||||
}
|
||||
if(threadProc->globalCount != 0) {
|
||||
print(" Global processing: %d ticks", threadProc->globalCount);
|
||||
if(threadProc->switchedCount != 0)
|
||||
print(" Switched objects: %d objects in %.4gms (~%.3gms per)",
|
||||
threadProc->switchedCount, threadProc->switchedTime * 1000.0,
|
||||
(threadProc->switchedTime / threadProc->switchedCount) * 1000.0);
|
||||
if(threadProc->targetUpdates != 0)
|
||||
print(" Target Update: %d updates in %.4gms (~%.3gms per)",
|
||||
threadProc->targetUpdates, threadProc->targUpdateTime * 1000.0,
|
||||
threadProc->targUpdateTime * 1000.0 / threadProc->targetUpdates);
|
||||
}
|
||||
|
||||
--printProcProfile;
|
||||
}
|
||||
|
||||
threadProc->clear();
|
||||
threadProc->syncTimer = curTime + 1.0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void run(bool onlyActions) {
|
||||
Action* cur_action = popAction();
|
||||
if(cur_action) {
|
||||
if(cur_action->run())
|
||||
delete cur_action;
|
||||
else
|
||||
queueAction(cur_action);
|
||||
}
|
||||
else if(!onlyActions) {
|
||||
double time = devices.driver->getGameTime();
|
||||
tickRandomLock(time, idleObjectTickCount);
|
||||
}
|
||||
|
||||
#ifdef PROFILE_PROCESSING
|
||||
profileProcessing();
|
||||
#endif
|
||||
}
|
||||
|
||||
threads::threadreturn threadcall messageProcess(void* data) {
|
||||
enterSection(NS_MessageThread);
|
||||
initNewThread();
|
||||
|
||||
while(!stopProcessing) {
|
||||
if(hasRemainingMessages()) {
|
||||
tickRandomMessages(5);
|
||||
threads::sleep(0);
|
||||
}
|
||||
else {
|
||||
if(pauseMessageHandlers) {
|
||||
msgHandlersActive.signalDown();
|
||||
while(pauseMessageHandlers && !hasRemainingMessages())
|
||||
threads::sleep(1);
|
||||
msgHandlersActive.signalUp();
|
||||
}
|
||||
else {
|
||||
threads::idle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanupThread();
|
||||
msgHandlersActive.signalDown();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void startEmpireThread(Empire* emp) {
|
||||
msgHandlersActive.signalUp();
|
||||
threads::createThread(empireMessageProcess, emp);
|
||||
}
|
||||
|
||||
threads::atomic_int nextEmpTickIndex;
|
||||
threads::threadreturn threadcall empireMessageProcess(void* data) {
|
||||
enterSection(NS_MessageThread);
|
||||
Empire* emp = (Empire*)data;
|
||||
initNewThread();
|
||||
|
||||
while(!stopProcessing) {
|
||||
if(hasRemainingMessages()) {
|
||||
if(!emp->messages.empty()) {
|
||||
emp->processMessages();
|
||||
}
|
||||
else {
|
||||
threads::sleep(1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(pauseMessageHandlers) {
|
||||
msgHandlersActive.signalDown();
|
||||
while(pauseMessageHandlers && !hasRemainingMessages())
|
||||
threads::sleep(1);
|
||||
msgHandlersActive.signalUp();
|
||||
}
|
||||
else {
|
||||
threads::idle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanupThread();
|
||||
msgHandlersActive.signalDown();
|
||||
return 0;
|
||||
}
|
||||
|
||||
threads::threadreturn threadcall process(void* data) {
|
||||
enterSection(NS_Processing);
|
||||
#ifdef PROFILE_PROCESSING
|
||||
threadProc = (ProcessingData*)data;
|
||||
#endif
|
||||
initNewThread();
|
||||
|
||||
while(!stopProcessing) {
|
||||
if(pauseProcessing) {
|
||||
while(hasRemainingMessages() || hasQueuedChildren()) {
|
||||
acquireRandomChildren();
|
||||
tickRandomMessages();
|
||||
threads::idle();
|
||||
}
|
||||
threadsActive.signalDown();
|
||||
while(pauseProcessing)
|
||||
threads::sleep(1);
|
||||
threadsActive.signalUp();
|
||||
}
|
||||
|
||||
Action* cur_action = popAction();
|
||||
if(cur_action) {
|
||||
if(cur_action->run()) {
|
||||
delete cur_action;
|
||||
threads::sleep(0);
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
queueAction(cur_action);
|
||||
}
|
||||
}
|
||||
|
||||
double time = devices.driver->getGameTime();
|
||||
|
||||
tickRandomMessages();
|
||||
tickRandomLock(time, activeObjectTickCount);
|
||||
|
||||
threads::idle();
|
||||
|
||||
#ifdef PROFILE_PROCESSING
|
||||
profileProcessing();
|
||||
#endif
|
||||
}
|
||||
|
||||
cleanupThread();
|
||||
threadsActive.signalDown();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
|
||||
class Empire;
|
||||
|
||||
namespace processing {
|
||||
|
||||
//#define PROFILE_PROCESSING
|
||||
#ifdef PROFILE_PROCESSING
|
||||
struct ProcessingData {
|
||||
std::vector<double> objTime;
|
||||
std::vector<int> objCount;
|
||||
double syncTimer;
|
||||
|
||||
int globalCount;
|
||||
double switchedTime;
|
||||
int switchedCount;
|
||||
double targUpdateTime;
|
||||
int targetUpdates;
|
||||
|
||||
void measureType(int type, double time, int amount);
|
||||
|
||||
ProcessingData();
|
||||
void clear();
|
||||
};
|
||||
|
||||
void printProcessingProfile();
|
||||
#endif
|
||||
|
||||
class Action {
|
||||
public:
|
||||
virtual bool run() = 0;
|
||||
virtual ~Action() {}
|
||||
};
|
||||
|
||||
void queueAction(Action* action);
|
||||
void queueIsolationAction(Action* action);
|
||||
void run(bool onlyActions = false);
|
||||
void runIsolation();
|
||||
void pauseMessageHandling();
|
||||
void resumeMessageHandling();
|
||||
|
||||
void startEmpireThread(Empire* emp);
|
||||
void start(unsigned threads);
|
||||
void end();
|
||||
void clear();
|
||||
bool isRunning();
|
||||
|
||||
void pause();
|
||||
void resume();
|
||||
|
||||
};
|
||||
@@ -0,0 +1,588 @@
|
||||
#include "profile/keybinds.h"
|
||||
#include "main/logging.h"
|
||||
#include "os/key_consts.h"
|
||||
#include "str_util.h"
|
||||
#include "main/references.h"
|
||||
#include "main/input_handling.h"
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace profile {
|
||||
|
||||
void BindGroup::clear() {
|
||||
clearPressedKeys();
|
||||
foreach(bind, descriptors)
|
||||
delete bind->bind;
|
||||
|
||||
descriptors.clear();
|
||||
descriptorIDs.clear();
|
||||
binds.clear();
|
||||
}
|
||||
|
||||
BindGroup::~BindGroup() {
|
||||
clear();
|
||||
}
|
||||
|
||||
Keybinds::Keybinds() {
|
||||
global.name = "Global";
|
||||
groups.push_back(&global);
|
||||
groupNames["Global"] = &global;
|
||||
}
|
||||
|
||||
void Keybinds::clear() {
|
||||
clearPressedKeys();
|
||||
global.clear();
|
||||
foreach(it, groups) {
|
||||
if(*it != &global)
|
||||
delete *it;
|
||||
}
|
||||
groups.clear();
|
||||
groupNames.clear();
|
||||
groups.push_back(&global);
|
||||
groupNames["Global"] = &global;
|
||||
}
|
||||
|
||||
void Keybinds::loadBinds(const std::string& filename) {
|
||||
DataReader datafile(filename);
|
||||
BindGroup* grp = &global;
|
||||
while(datafile++) {
|
||||
if(datafile.key == "Group") {
|
||||
auto it = groupNames.find(datafile.value);
|
||||
if(it != groupNames.end()) {
|
||||
grp = it->second;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
int key = getKey(datafile.key);
|
||||
if(datafile.value == "-") {
|
||||
grp->clearBind(key);
|
||||
}
|
||||
else {
|
||||
auto it = grp->descriptorIDs.find(datafile.value);
|
||||
if(it != grp->descriptorIDs.end())
|
||||
grp->setBind(key, it->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Keybinds::saveBinds(const std::string& filename) {
|
||||
std::ofstream file(filename);
|
||||
foreach(it, groups) {
|
||||
BindGroup* grp = *it;
|
||||
std::unordered_set<int> writtenKeys;
|
||||
|
||||
file << "Group: " << grp->name << "\n";
|
||||
|
||||
//Set all the binds we have
|
||||
foreach(it, grp->binds) {
|
||||
BindDescriptor& desc = grp->descriptors[it->second];
|
||||
std::string line = getKeyName(it->first);
|
||||
line += ": ";
|
||||
line += desc.name;
|
||||
line += "\n";
|
||||
|
||||
file << line;
|
||||
writtenKeys.insert(it->first);
|
||||
}
|
||||
|
||||
//Clear out all default binds we no longer have
|
||||
foreach(it, grp->descriptors) {
|
||||
BindDescriptor& desc = *it;
|
||||
|
||||
foreach(k, desc.defaults) {
|
||||
int key = *k;
|
||||
if(writtenKeys.find(key) == writtenKeys.end()) {
|
||||
file << "\t" << getKeyName(key) << ": -\n";
|
||||
writtenKeys.insert(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file << "\n";
|
||||
}
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
BindGroup* Keybinds::getGroup(const std::string& name) {
|
||||
auto it = groupNames.find(name);
|
||||
if(it == groupNames.end())
|
||||
return 0;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void Keybinds::loadDescriptors(const std::string& filename) {
|
||||
BindGroup* grp = &global;
|
||||
DataReader datafile(filename);
|
||||
while(datafile++) {
|
||||
if(datafile.key == "Group") {
|
||||
if(datafile.value == "Global") {
|
||||
grp = &global;
|
||||
}
|
||||
else {
|
||||
grp = new BindGroup();
|
||||
grp->name = datafile.value;
|
||||
groups.push_back(grp);
|
||||
groupNames[grp->name] = grp;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(grp->descriptorIDs.find(datafile.key) != grp->descriptorIDs.end())
|
||||
continue;
|
||||
|
||||
BindDescriptor bind;
|
||||
bind.name = datafile.key;
|
||||
bind.bind = 0;
|
||||
|
||||
if(datafile.value != "-" && !datafile.value.empty()) {
|
||||
std::vector<std::string> keys;
|
||||
split(datafile.value, keys, ',');
|
||||
|
||||
foreach(key, keys) {
|
||||
if(int num = getKey(*key))
|
||||
bind.defaults.push_back(num);
|
||||
}
|
||||
}
|
||||
|
||||
grp->addDescriptor(bind);
|
||||
}
|
||||
}
|
||||
|
||||
void Keybinds::setDefaultBinds() {
|
||||
foreach(it, groups)
|
||||
(*it)->setDefaultBinds();
|
||||
}
|
||||
|
||||
void BindGroup::addDescriptor(BindDescriptor desc) {
|
||||
desc.id = (int)descriptors.size();
|
||||
descriptors.push_back(desc);
|
||||
descriptorIDs[desc.name] = desc.id;
|
||||
}
|
||||
|
||||
void BindGroup::addBind(const std::string& name, Keybind* bind) {
|
||||
auto it = descriptorIDs.find(name);
|
||||
if(it == descriptorIDs.end()) {
|
||||
error(std::string("Error (addBind): Could not find keybind with name: ")+name);
|
||||
return;
|
||||
}
|
||||
|
||||
BindDescriptor& desc = descriptors[it->second];
|
||||
clearPressedKeys();
|
||||
delete desc.bind;
|
||||
desc.bind = bind;
|
||||
}
|
||||
|
||||
void BindGroup::addBind(unsigned id, Keybind* bind) {
|
||||
if(id >= (unsigned)descriptors.size()) {
|
||||
error(std::string("Error (addBind): Descriptor id out of range: ")+toString(id));
|
||||
return;
|
||||
}
|
||||
|
||||
BindDescriptor& desc = descriptors[id];
|
||||
clearPressedKeys();
|
||||
delete desc.bind;
|
||||
desc.bind = bind;
|
||||
}
|
||||
|
||||
void BindGroup::setBind(int key, const std::string& name) {
|
||||
auto it = descriptorIDs.find(name);
|
||||
if(it == descriptorIDs.end()) {
|
||||
error(std::string("Error (setBind): Could not find keybind with name: ")+name);
|
||||
return;
|
||||
}
|
||||
|
||||
setBind(key, it->second);
|
||||
}
|
||||
|
||||
void BindGroup::setBind(int key, unsigned id) {
|
||||
if(id >= descriptors.size()) {
|
||||
error(std::string("Error (setBind): Descriptor id out of range: ")+toString(id));
|
||||
return;
|
||||
}
|
||||
|
||||
clearBind(key);
|
||||
|
||||
binds[key] = id;
|
||||
descriptors[id].current.push_back(key);
|
||||
}
|
||||
|
||||
void BindGroup::clearBind(int key) {
|
||||
auto it = binds.find(key);
|
||||
if(it != binds.end()) {
|
||||
if(it->second < (int)descriptors.size()) {
|
||||
foreach(kb, descriptors[it->second].current) {
|
||||
if(*kb == key) {
|
||||
descriptors[it->second].current.erase(kb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
binds.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void BindGroup::setDefaultBinds() {
|
||||
for(unsigned i = 0; i < descriptors.size(); ++i) {
|
||||
foreach(key, descriptors[i].defaults)
|
||||
setBind(*key, i);
|
||||
}
|
||||
}
|
||||
|
||||
BindDescriptor* BindGroup::getDescriptor(const std::string& name) {
|
||||
auto it = descriptorIDs.find(name);
|
||||
if(it == descriptorIDs.end())
|
||||
return 0;
|
||||
return &descriptors[it->second];
|
||||
}
|
||||
|
||||
Keybind* BindGroup::getBind(int key) {
|
||||
auto it = binds.find(key);
|
||||
if(it == binds.end()) {
|
||||
//SPECIAL: Try it without shift
|
||||
if(key & Mod_Shift) {
|
||||
key &= ~Mod_Shift;
|
||||
it = binds.find(key);
|
||||
if(it == binds.end())
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return descriptors[it->second].bind;
|
||||
}
|
||||
|
||||
int BindGroup::getBindID(int key) {
|
||||
auto it = binds.find(key);
|
||||
if(it == binds.end()) {
|
||||
//SPECIAL: Try it without shift
|
||||
if(key & Mod_Shift) {
|
||||
key &= ~Mod_Shift;
|
||||
it = binds.find(key);
|
||||
if(it == binds.end())
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return descriptors[it->second].id;
|
||||
}
|
||||
|
||||
unsigned BindGroup::getDefaultCount(unsigned id) {
|
||||
if(id >= descriptors.size()) {
|
||||
error("Error (getDefaultCount): Descriptor id out of range: %d", id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (unsigned)descriptors[id].defaults.size();
|
||||
}
|
||||
|
||||
int BindGroup::getDefaultKey(unsigned id, unsigned index) {
|
||||
if(id >= descriptors.size()) {
|
||||
error("Error (getDefaultKey): Descriptor id out of range: %d", id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(index >= descriptors[id].defaults.size()) {
|
||||
error("Error (getDefaultKey): Key index out of range: %d", index);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return descriptors[id].defaults[index];
|
||||
}
|
||||
|
||||
unsigned BindGroup::getBindCount(unsigned id) {
|
||||
if(id >= descriptors.size()) {
|
||||
error("Error (getBindCount): Descriptor id out of range: %d", id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (unsigned)descriptors[id].current.size();
|
||||
}
|
||||
|
||||
int BindGroup::getBindKey(unsigned id, unsigned index) {
|
||||
if(id >= descriptors.size()) {
|
||||
error("Error (getBindKey): Descriptor id out of range: %d", id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(index >= descriptors[id].current.size()) {
|
||||
error("Error (getBindKey): Key index out of range: %d", index);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return descriptors[id].current[index];
|
||||
}
|
||||
|
||||
void BindGroup::clearBinds(unsigned id) {
|
||||
if(id >= descriptors.size()) {
|
||||
error("Error (clearBinds): Descriptor id out of range: %d", id);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(it, descriptors[id].current)
|
||||
binds.erase(*it);
|
||||
descriptors[id].current.clear();
|
||||
}
|
||||
|
||||
void BindGroup::setDefaultBinds(unsigned id) {
|
||||
if(id >= descriptors.size()) {
|
||||
error("Error (setDefaultBinds): Descriptor id out of range: %d", id);
|
||||
return;
|
||||
}
|
||||
|
||||
clearBinds(id);
|
||||
foreach(it, descriptors[id].defaults)
|
||||
setBind(*it, id);
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, int> keyNames;
|
||||
std::unordered_map<int, std::string> keyValues;
|
||||
#define keyName(name, value)\
|
||||
keyNames[name] = value;\
|
||||
keyValues[value] = name;
|
||||
bool init_keyNames = true;
|
||||
|
||||
inline void initKeyNames() {
|
||||
if(init_keyNames) {
|
||||
init_keyNames = false;
|
||||
|
||||
keyName("esc", os::KEY_ESC);
|
||||
keyName("f1", os::KEY_F1);
|
||||
keyName("f2", os::KEY_F2);
|
||||
keyName("f3", os::KEY_F3);
|
||||
keyName("f4", os::KEY_F4);
|
||||
keyName("f5", os::KEY_F5);
|
||||
keyName("f6", os::KEY_F6);
|
||||
keyName("f7", os::KEY_F7);
|
||||
keyName("f8", os::KEY_F8);
|
||||
keyName("f9", os::KEY_F9);
|
||||
keyName("f10", os::KEY_F10);
|
||||
keyName("f11", os::KEY_F11);
|
||||
keyName("f12", os::KEY_F12);
|
||||
keyName("f13", os::KEY_F13);
|
||||
keyName("f14", os::KEY_F14);
|
||||
keyName("f15", os::KEY_F15);
|
||||
keyName("f16", os::KEY_F16);
|
||||
keyName("f17", os::KEY_F17);
|
||||
keyName("f18", os::KEY_F18);
|
||||
keyName("f19", os::KEY_F19);
|
||||
keyName("f20", os::KEY_F20);
|
||||
keyName("f21", os::KEY_F21);
|
||||
keyName("f22", os::KEY_F22);
|
||||
keyName("f23", os::KEY_F23);
|
||||
keyName("f24", os::KEY_F24);
|
||||
keyName("f25", os::KEY_F25);
|
||||
keyName("up", os::KEY_UP);
|
||||
keyName("down", os::KEY_DOWN);
|
||||
keyName("left", os::KEY_LEFT);
|
||||
keyName("right", os::KEY_RIGHT);
|
||||
keyName("lshift", os::KEY_LSHIFT);
|
||||
keyName("rshift", os::KEY_RSHIFT);
|
||||
keyName("lctrl", os::KEY_LCTRL);
|
||||
keyName("rctrl", os::KEY_RCTRL);
|
||||
keyName("lalt", os::KEY_LALT);
|
||||
keyName("ralt", os::KEY_RALT);
|
||||
keyName("tab", os::KEY_TAB);
|
||||
keyName("enter", os::KEY_ENTER);
|
||||
keyName("backspace", os::KEY_BACKSPACE);
|
||||
keyName("space", ' ');
|
||||
keyName("insert", os::KEY_INSERT);
|
||||
keyName("del", os::KEY_DEL);
|
||||
keyName("pageup", os::KEY_PAGEUP);
|
||||
keyName("pagedown", os::KEY_PAGEDOWN);
|
||||
keyName("home", os::KEY_HOME);
|
||||
keyName("end", os::KEY_END);
|
||||
keyName("kp_0", os::KEY_KP_0);
|
||||
keyName("kp_1", os::KEY_KP_1);
|
||||
keyName("kp_2", os::KEY_KP_2);
|
||||
keyName("kp_3", os::KEY_KP_3);
|
||||
keyName("kp_4", os::KEY_KP_4);
|
||||
keyName("kp_5", os::KEY_KP_5);
|
||||
keyName("kp_6", os::KEY_KP_6);
|
||||
keyName("kp_7", os::KEY_KP_7);
|
||||
keyName("kp_8", os::KEY_KP_8);
|
||||
keyName("kp_9", os::KEY_KP_9);
|
||||
keyName("kp_divide", os::KEY_KP_DIVIDE);
|
||||
keyName("kp_multiply", os::KEY_KP_MULTIPLY);
|
||||
keyName("kp_subtract", os::KEY_KP_SUBTRACT);
|
||||
keyName("kp_add", os::KEY_KP_ADD);
|
||||
keyName("kp_decimal", os::KEY_KP_DECIMAL);
|
||||
keyName("kp_equal", os::KEY_KP_EQUAL);
|
||||
keyName("kp_enter", os::KEY_KP_ENTER);
|
||||
keyName("num_lock", os::KEY_NUM_LOCK);
|
||||
keyName("caps_lock", os::KEY_CAPS_LOCK);
|
||||
keyName("scroll_lock", os::KEY_SCROLL_LOCK);
|
||||
keyName("pause", os::KEY_PAUSE);
|
||||
keyName("lsuper", os::KEY_LSUPER);
|
||||
keyName("rsuper", os::KEY_RSUPER);
|
||||
keyName("menu", os::KEY_MENU);
|
||||
}
|
||||
}
|
||||
|
||||
int getKey(std::string name) {
|
||||
initKeyNames();
|
||||
int key = 0;
|
||||
name = trim(name);
|
||||
toLowercase(name);
|
||||
while(!name.empty()) {
|
||||
if(name.compare(0, 5, "ctrl+") == 0) {
|
||||
name = name.substr(5, name.size() - 5);
|
||||
key |= Mod_Ctrl;
|
||||
continue;
|
||||
}
|
||||
if(name.compare(0, 4, "alt+") == 0) {
|
||||
name = name.substr(4, name.size() - 4);
|
||||
key |= Mod_Alt;
|
||||
continue;
|
||||
}
|
||||
if(name.compare(0, 6, "shift+") == 0) {
|
||||
name = name.substr(6, name.size() - 6);
|
||||
key |= Mod_Shift;
|
||||
continue;
|
||||
}
|
||||
if(name[0] == '#') {
|
||||
key |= toNumber<int>(name.substr(1, name.size() - 1));
|
||||
break;
|
||||
}
|
||||
|
||||
auto it = keyNames.find(name);
|
||||
if(it != keyNames.end()) {
|
||||
key |= (int)it->second;
|
||||
break;
|
||||
}
|
||||
|
||||
u8it ch(name);
|
||||
key |= ch++;
|
||||
break;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
int getKeyFromDisplayName(std::string name) {
|
||||
initKeyNames();
|
||||
|
||||
int key = 0;
|
||||
name = trim(name);
|
||||
toLowercase(name);
|
||||
while(!name.empty()) {
|
||||
if(name.compare(0, 5, "ctrl+") == 0) {
|
||||
name = name.substr(5, name.size() - 5);
|
||||
key |= Mod_Ctrl;
|
||||
continue;
|
||||
}
|
||||
if(name.compare(0, 4, "alt+") == 0) {
|
||||
name = name.substr(4, name.size() - 4);
|
||||
key |= Mod_Alt;
|
||||
continue;
|
||||
}
|
||||
if(name.compare(0, 6, "shift+") == 0) {
|
||||
name = name.substr(6, name.size() - 6);
|
||||
key |= Mod_Shift;
|
||||
continue;
|
||||
}
|
||||
if(name[0] == '#') {
|
||||
key |= toNumber<int>(name.substr(1, name.size() - 1));
|
||||
break;
|
||||
}
|
||||
|
||||
auto it = keyNames.find(name);
|
||||
if(it != keyNames.end()) {
|
||||
key |= (int)it->second;
|
||||
break;
|
||||
}
|
||||
|
||||
u8it ch(name);
|
||||
int chr = ch++;
|
||||
|
||||
int lookup = devices.driver->getKeyForChar(chr);
|
||||
if(lookup >= 'A' && lookup <= 'Z')
|
||||
lookup -= 'A'-'a';
|
||||
if(lookup == -1)
|
||||
key |= chr;
|
||||
else
|
||||
key |= lookup;
|
||||
break;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
std::string getKeyName(int key) {
|
||||
std::string name;
|
||||
|
||||
if(key & Mod_Ctrl) {
|
||||
name += "ctrl+";
|
||||
key &= ~Mod_Ctrl;
|
||||
}
|
||||
|
||||
if(key & Mod_Alt) {
|
||||
name += "alt+";
|
||||
key &= ~Mod_Alt;
|
||||
}
|
||||
|
||||
if(key & Mod_Shift) {
|
||||
name += "shift+";
|
||||
key &= ~Mod_Shift;
|
||||
}
|
||||
|
||||
auto it = keyValues.find(key);
|
||||
if(it != keyValues.end()) {
|
||||
name += it->second;
|
||||
return name;
|
||||
}
|
||||
|
||||
u8append(name, key);
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string getKeyDisplayName(int key) {
|
||||
std::string name;
|
||||
|
||||
if(key & Mod_Ctrl) {
|
||||
name += "ctrl+";
|
||||
key &= ~Mod_Ctrl;
|
||||
}
|
||||
|
||||
if(key & Mod_Alt) {
|
||||
name += "alt+";
|
||||
key &= ~Mod_Alt;
|
||||
}
|
||||
|
||||
if(key & Mod_Shift) {
|
||||
name += "shift+";
|
||||
key &= ~Mod_Shift;
|
||||
}
|
||||
|
||||
auto it = keyValues.find(key);
|
||||
if(it != keyValues.end()) {
|
||||
name += it->second;
|
||||
return name;
|
||||
}
|
||||
|
||||
int chr = devices.driver->getCharForKey(key);
|
||||
if(chr == -1)
|
||||
u8append(name, key);
|
||||
else
|
||||
u8append(name, chr);
|
||||
return name;
|
||||
}
|
||||
|
||||
int getModifiedKey(int key, bool ctrlKey, bool altKey, bool shiftKey) {
|
||||
int mod_key = key;
|
||||
if(mod_key >= 'A' && mod_key <= 'Z')
|
||||
mod_key += 'a'-'A';
|
||||
if(ctrlKey)
|
||||
mod_key |= profile::Mod_Ctrl;
|
||||
if(altKey)
|
||||
mod_key |= profile::Mod_Alt;
|
||||
if(shiftKey)
|
||||
mod_key |= profile::Mod_Shift;
|
||||
return mod_key;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
#include "compat/misc.h"
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace profile {
|
||||
|
||||
const int Mod_Ctrl = 0x1 << 30;
|
||||
const int Mod_Alt = 0x1 << 29;
|
||||
const int Mod_Shift = 0x1 << 28;
|
||||
|
||||
struct Keybind {
|
||||
virtual void call(bool pressed) = 0;
|
||||
virtual ~Keybind() {};
|
||||
};
|
||||
|
||||
struct BindDescriptor {
|
||||
int id;
|
||||
std::string name;
|
||||
Keybind* bind;
|
||||
std::vector<int> defaults;
|
||||
std::vector<int> current;
|
||||
};
|
||||
|
||||
struct BindGroup {
|
||||
std::string name;
|
||||
std::vector<BindDescriptor> descriptors;
|
||||
umap<std::string, int> descriptorIDs;
|
||||
umap<int, int> binds;
|
||||
|
||||
void addDescriptor(BindDescriptor desc);
|
||||
void addBind(const std::string& name, Keybind* bind);
|
||||
void addBind(unsigned id, Keybind* bind);
|
||||
void setBind(int key, const std::string& name);
|
||||
void setBind(int key, unsigned id);
|
||||
void clearBind(int key);
|
||||
void setDefaultBinds();
|
||||
|
||||
unsigned getDefaultCount(unsigned id);
|
||||
int getDefaultKey(unsigned id, unsigned index);
|
||||
|
||||
unsigned getBindCount(unsigned id);
|
||||
int getBindKey(unsigned id, unsigned index);
|
||||
|
||||
void setDefaultBinds(unsigned id);
|
||||
void clearBinds(unsigned id);
|
||||
|
||||
BindDescriptor* getDescriptor(const std::string& name);
|
||||
Keybind* getBind(int key);
|
||||
int getBindID(int key);
|
||||
|
||||
void save();
|
||||
|
||||
void clear();
|
||||
~BindGroup();
|
||||
};
|
||||
|
||||
class Keybinds {
|
||||
public:
|
||||
BindGroup global;
|
||||
std::vector<BindGroup*> groups;
|
||||
umap<std::string, BindGroup*> groupNames;
|
||||
|
||||
Keybinds();
|
||||
void clear();
|
||||
|
||||
BindGroup* getGroup(const std::string& name);
|
||||
void loadDescriptors(const std::string& filename);
|
||||
void setDefaultBinds();
|
||||
|
||||
void loadBinds(const std::string& filename);
|
||||
void saveBinds(const std::string& filename);
|
||||
};
|
||||
|
||||
int getModifiedKey(int key, bool ctrlKey, bool altKey, bool shiftKey);
|
||||
|
||||
//Physical key name
|
||||
std::string getKeyName(int key);
|
||||
int getKey(std::string name);
|
||||
|
||||
//Logical key name
|
||||
std::string getKeyDisplayName(int key);
|
||||
int getKeyFromDisplayName(std::string name);
|
||||
|
||||
};
|
||||
@@ -0,0 +1,127 @@
|
||||
#include "str_util.h"
|
||||
#include "compat/misc.h"
|
||||
#include "profile/settings.h"
|
||||
|
||||
namespace profile {
|
||||
|
||||
SettingCategory::SettingCategory() {
|
||||
}
|
||||
|
||||
SettingCategory::SettingCategory(const std::string& Name) : name(Name) {
|
||||
}
|
||||
|
||||
SettingCategory::~SettingCategory() {
|
||||
}
|
||||
|
||||
Settings::~Settings() {
|
||||
foreach(it, categories)
|
||||
delete *it;
|
||||
}
|
||||
|
||||
void Settings::clear() {
|
||||
foreach(it, categories)
|
||||
delete *it;
|
||||
categories.clear();
|
||||
settings.clear();
|
||||
}
|
||||
|
||||
NamedGeneric* Settings::getSetting(const std::string& name) {
|
||||
auto it = settings.find(name);
|
||||
if(it == settings.end())
|
||||
return 0;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void Settings::loadDescriptors(const std::string& filename) {
|
||||
DataReader datafile(filename);
|
||||
SettingCategory* cat = 0;
|
||||
NamedGeneric* set = 0;
|
||||
while(datafile++) {
|
||||
if(datafile.key == "Category") {
|
||||
cat = new SettingCategory();
|
||||
categories.push_back(cat);
|
||||
cat->name = datafile.value;
|
||||
}
|
||||
else if(cat) {
|
||||
auto makeSetting = [&](GenericType typevalue) {
|
||||
set = new NamedGeneric();
|
||||
set->name = datafile.value;
|
||||
set->type = typevalue;
|
||||
cat->settings.push_back(set);
|
||||
settings[set->name] = set;
|
||||
};
|
||||
|
||||
if(datafile.key == "Bool") {
|
||||
makeSetting(GT_Bool);
|
||||
}
|
||||
else if(datafile.key == "Integer") {
|
||||
makeSetting(GT_Integer);
|
||||
}
|
||||
else if(datafile.key == "Double") {
|
||||
makeSetting(GT_Double);
|
||||
}
|
||||
else if(datafile.key == "Enum") {
|
||||
makeSetting(GT_Enum);
|
||||
set->values = new std::vector<std::string>();
|
||||
}
|
||||
else if(datafile.key == "String") {
|
||||
makeSetting(GT_String);
|
||||
set->str = new std::string();
|
||||
}
|
||||
else if(set) {
|
||||
if(datafile.key == "Default") {
|
||||
set->fromString(datafile.value);
|
||||
}
|
||||
else if(datafile.key == "Option") {
|
||||
if(set->type == GT_Enum)
|
||||
set->values->push_back(datafile.value);
|
||||
}
|
||||
else if(datafile.key == "Min") {
|
||||
if(set->type == GT_Integer)
|
||||
set->num_min = toNumber<int>(datafile.value);
|
||||
else if(set->type == GT_Double)
|
||||
set->flt_min = toNumber<double>(datafile.value);
|
||||
}
|
||||
else if(datafile.key == "Max") {
|
||||
if(set->type == GT_Integer)
|
||||
set->num_max = toNumber<int>(datafile.value);
|
||||
else if(set->type == GT_Double)
|
||||
set->flt_max = toNumber<double>(datafile.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::addCategory(SettingCategory* cat) {
|
||||
categories.push_back(cat);
|
||||
foreach(it, cat->settings)
|
||||
settings[(*it)->name] = (*it);
|
||||
}
|
||||
|
||||
void Settings::loadSettings(const std::string& filename) {
|
||||
DataReader datafile(filename);
|
||||
while(datafile++) {
|
||||
NamedGeneric* set = getSetting(datafile.key);
|
||||
if(set)
|
||||
set->fromString(datafile.value);
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::saveSettings(const std::string& filename) {
|
||||
std::ofstream file(filename);
|
||||
|
||||
for(auto iCat = categories.begin(), catEnd = categories.end(); iCat != catEnd; ++iCat) {
|
||||
auto* cat = *iCat;
|
||||
file << cat->name << "\n";
|
||||
|
||||
for(auto i = cat->settings.begin(), end = cat->settings.end(); i != end; ++i) {
|
||||
auto* set = *i;
|
||||
file << "\t" << set->name << ": " << set->toString() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include "util/generic.h"
|
||||
#include "compat/misc.h"
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace profile {
|
||||
|
||||
struct SettingCategory {
|
||||
std::string name;
|
||||
std::vector<NamedGeneric*> settings;
|
||||
|
||||
SettingCategory(const std::string& name);
|
||||
SettingCategory();
|
||||
~SettingCategory();
|
||||
};
|
||||
|
||||
class Settings {
|
||||
public:
|
||||
std::vector<SettingCategory*> categories;
|
||||
umap<std::string, NamedGeneric*> settings;
|
||||
|
||||
NamedGeneric* getSetting(const std::string& name);
|
||||
~Settings();
|
||||
|
||||
void clear();
|
||||
void loadDescriptors(const std::string& filename);
|
||||
void addCategory(SettingCategory* cat);
|
||||
|
||||
void loadSettings(const std::string& filename);
|
||||
void saveSettings(const std::string& filename);
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,261 @@
|
||||
#include "bmf_loader.h"
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <string.h>
|
||||
|
||||
//BMF Specification (version 0):
|
||||
//uint32 == "BMF "
|
||||
//uint32: version number (0 for current version)
|
||||
//
|
||||
//uint32: vertex count
|
||||
//Repeat <vertex count>:
|
||||
// float x,y,z: vertex[n] coordinates
|
||||
//
|
||||
//uint32: normal count
|
||||
//Repeat <normal count>:
|
||||
// float x,y,z: normal[n] values
|
||||
//
|
||||
//uint32: uv count
|
||||
//Repeat <uv count>:
|
||||
// float u,v: uv[n] coordinates
|
||||
//
|
||||
//uint32: face count
|
||||
//Repeat <face count>:
|
||||
// If <vertex count> <= 0xffff
|
||||
// uint16 v1,v2,v3: face[n] vertex indices
|
||||
// Else
|
||||
// uint32 v1,v2,v3: face[n] vertex indices
|
||||
//
|
||||
// If <normal count> > 0
|
||||
// If <normal count> <= 0xffff
|
||||
// uint16 n1,n2,n3: face[n] normal indices
|
||||
// Else
|
||||
// uint32 n1,n2,n3: face[n] normal indices
|
||||
//
|
||||
// If <uv count> > 0
|
||||
// If <uv count> <= 0xffff
|
||||
// uint16 u1,u2,u3: face[n] uv indices
|
||||
// Else
|
||||
// uint32 u1,u2,u3: face[n] uv indices
|
||||
|
||||
//First 4 characters of any Binary Mesh File
|
||||
const char* bmfHead = "BMF ";
|
||||
|
||||
namespace render {
|
||||
|
||||
struct UV {
|
||||
float u, v;
|
||||
};
|
||||
|
||||
struct VertexIndex {
|
||||
unsigned a, b, c;
|
||||
|
||||
VertexIndex() : a(0), b(0), c(0) {}
|
||||
VertexIndex(unsigned A, unsigned B, unsigned C) : a(A), b(B), c(C) {}
|
||||
|
||||
bool operator<(const VertexIndex& other) const {
|
||||
return memcmp(this, &other, sizeof(unsigned) * 3) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void loadBinaryMesh(const char* filename, Mesh& mesh) {
|
||||
static_assert(sizeof(vec3f) == 12, "vec3f must be the size of 3 floats");
|
||||
static_assert(sizeof(UV) == 8, "UV must be the size of 2 floats");
|
||||
|
||||
std::vector<vec3f> vertices;
|
||||
std::vector<vec3f> normals;
|
||||
std::vector<UV> uvs;
|
||||
|
||||
std::ifstream file(filename, std::ios_base::binary | std::ios_base::in);
|
||||
if(!file.is_open())
|
||||
return;
|
||||
|
||||
char buff[4];
|
||||
file.read(buff, 4);
|
||||
if(file.fail() || strncmp(bmfHead, buff, 4) != 0)
|
||||
return;
|
||||
|
||||
unsigned version;
|
||||
file.read((char*)&version, sizeof(version));
|
||||
if(file.fail() || version != 0)
|
||||
return;
|
||||
|
||||
//Vertices
|
||||
unsigned count;
|
||||
file.read((char*)&count, sizeof(count));
|
||||
if(file.fail() || count == 0)
|
||||
return;
|
||||
|
||||
vertices.resize(count);
|
||||
file.read((char*)&vertices.front(), sizeof(vec3f) * count);
|
||||
|
||||
//Normals
|
||||
file.read((char*)&count, sizeof(count));
|
||||
if(file.fail())
|
||||
return;
|
||||
|
||||
normals.resize(count);
|
||||
file.read((char*)&normals.front(), sizeof(vec3f) * count);
|
||||
|
||||
//UVs
|
||||
file.read((char*)&count, sizeof(count));
|
||||
if(file.fail())
|
||||
return;
|
||||
|
||||
uvs.resize(count);
|
||||
file.read((char*)&uvs.front(), sizeof(UV) * count);
|
||||
|
||||
//Faces
|
||||
file.read((char*)&count, sizeof(count));
|
||||
if(file.fail())
|
||||
return;
|
||||
|
||||
std::map<VertexIndex,unsigned> vertexMap;
|
||||
|
||||
mesh.faces.reserve(count);
|
||||
mesh.vertices.reserve(count);
|
||||
for(unsigned i = 0; i < count; ++i) {
|
||||
Mesh::Face face;
|
||||
Vertex vertex[3];
|
||||
VertexIndex vertIndices[3];
|
||||
|
||||
//Load vertex position indices
|
||||
for(unsigned j = 0; j < 3; ++j) {
|
||||
unsigned index;
|
||||
|
||||
if(vertices.size() <= 0xffff) {
|
||||
unsigned short v;
|
||||
file.read((char*)&v, sizeof(v));
|
||||
index = v;
|
||||
}
|
||||
else {
|
||||
file.read((char*)&index, sizeof(index));
|
||||
}
|
||||
|
||||
if(index >= vertices.size())
|
||||
index = 0;
|
||||
|
||||
vertex[j].position = vertices[index];
|
||||
vertIndices[j].a = index;
|
||||
}
|
||||
|
||||
//Load vertex normal indices
|
||||
if(!normals.empty()) {
|
||||
for(unsigned j = 0; j < 3; ++j) {
|
||||
unsigned index;
|
||||
|
||||
if(normals.size() <= 0xffff) {
|
||||
unsigned short v;
|
||||
file.read((char*)&v, sizeof(v));
|
||||
index = v;
|
||||
}
|
||||
else {
|
||||
file.read((char*)&index, sizeof(index));
|
||||
}
|
||||
|
||||
if(index >= normals.size())
|
||||
index = 0;
|
||||
|
||||
vertex[j].normal = normals[index];
|
||||
vertIndices[j].b = index;
|
||||
}
|
||||
}
|
||||
|
||||
//Load vertex uv indices
|
||||
if(!uvs.empty()) {
|
||||
for(unsigned j = 0; j < 3; ++j) {
|
||||
unsigned index;
|
||||
|
||||
if(uvs.size() <= 0xffff) {
|
||||
unsigned short v;
|
||||
file.read((char*)&v, sizeof(v));
|
||||
index = v;
|
||||
}
|
||||
else {
|
||||
file.read((char*)&index, sizeof(index));
|
||||
}
|
||||
|
||||
if(index >= uvs.size())
|
||||
index = 0;
|
||||
|
||||
vertex[j].u = uvs[index].u;
|
||||
vertex[j].v = uvs[index].v;
|
||||
vertIndices[j].c = index;
|
||||
}
|
||||
}
|
||||
|
||||
if(file.fail())
|
||||
return;
|
||||
|
||||
//Automatically fuse identical vertices and store results in mesh
|
||||
unsigned indices[3];
|
||||
for(unsigned j = 0; j < 3; ++j) {
|
||||
auto previous = vertexMap.find(vertIndices[j]);
|
||||
if(previous != vertexMap.end()) {
|
||||
indices[j] = previous->second;
|
||||
}
|
||||
else {
|
||||
indices[j] = (unsigned)mesh.vertices.size();
|
||||
mesh.vertices.push_back(vertex[j]);
|
||||
vertexMap[vertIndices[j]] = indices[j];
|
||||
}
|
||||
}
|
||||
|
||||
face.a = indices[0];
|
||||
face.b = indices[1];
|
||||
face.c = indices[2];
|
||||
mesh.faces.push_back(face);
|
||||
}
|
||||
}
|
||||
|
||||
bool saveBinaryMesh(const char* filename, Mesh& mesh) {
|
||||
std::ofstream file(filename, std::ios_base::binary | std::ios_base::out);
|
||||
if(!file.is_open())
|
||||
return false;
|
||||
|
||||
file.write(bmfHead, 4);
|
||||
unsigned version = 0;
|
||||
file.write((char*)&version, sizeof(version));
|
||||
|
||||
//TODO: Writes duplicate data unnecessarily
|
||||
|
||||
unsigned count;
|
||||
|
||||
count = (unsigned)mesh.vertices.size();
|
||||
file.write((char*)&count, sizeof(count));
|
||||
for(unsigned i = 0; i < count; ++i)
|
||||
file.write((char*)&mesh.vertices[i].position, sizeof(vec3f));
|
||||
|
||||
count = (unsigned)mesh.vertices.size();
|
||||
file.write((char*)&count, sizeof(count));
|
||||
for(unsigned i = 0; i < count; ++i)
|
||||
file.write((char*)&mesh.vertices[i].normal, sizeof(vec3f));
|
||||
|
||||
count = (unsigned)mesh.vertices.size();
|
||||
file.write((char*)&count, sizeof(count));
|
||||
for(unsigned i = 0; i < count; ++i) {
|
||||
file.write((char*)&mesh.vertices[i].u, sizeof(float));
|
||||
file.write((char*)&mesh.vertices[i].v, sizeof(float));
|
||||
}
|
||||
|
||||
bool shortIndices = mesh.vertices.size() <= 0xffff;
|
||||
|
||||
count = (unsigned)mesh.faces.size();
|
||||
file.write((char*)&count, sizeof(count));
|
||||
for(unsigned i = 0; i < count; ++i) {
|
||||
Mesh::Face face = mesh.faces[i];
|
||||
if(shortIndices) {
|
||||
unsigned short data[] = {face.a, face.b, face.c, face.a, face.b, face.c, face.a, face.b, face.c};
|
||||
file.write((char*)data, sizeof(data));
|
||||
}
|
||||
else {
|
||||
unsigned data[] = {face.a, face.b, face.c, face.a, face.b, face.c, face.a, face.b, face.c};
|
||||
file.write((char*)data, sizeof(data));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include "mesh.h"
|
||||
|
||||
namespace render {
|
||||
|
||||
void loadBinaryMesh(const char* filename, Mesh& mesh);
|
||||
bool saveBinaryMesh(const char* filename, Mesh& mesh);
|
||||
|
||||
};
|
||||
@@ -0,0 +1,409 @@
|
||||
#include "render/camera.h"
|
||||
#include "constants.h"
|
||||
#include <stdio.h>
|
||||
|
||||
namespace render {
|
||||
|
||||
const double pctPerSecond = 0.9999;
|
||||
|
||||
Camera::Camera()
|
||||
: radius(300), qd_yaw(0), qd_pitch(0), qd_roll(0), qd_zoom(1), qd_zoom_min_distance(0), positionBound(vec3d(-1e7), vec3d(1e7)), maxDist(1e7),
|
||||
qd_abs_yaw(0), qd_abs_pitch(0), zNear(1), zFar(1000), fov(0.8), aspect(1), objectCamera(false), linearZoom(false), lockedRotation(true)
|
||||
{
|
||||
}
|
||||
|
||||
void Camera::yaw(double radians, bool snap) {
|
||||
if(snap)
|
||||
rotation = rotation * quaterniond::fromAxisAngle(vec3d::up(), radians);
|
||||
else
|
||||
qd_yaw += radians;
|
||||
}
|
||||
|
||||
void Camera::pitch(double radians, bool snap) {
|
||||
if(snap) {
|
||||
rotation = rotation * quaterniond::fromAxisAngle(vec3d::right(), radians);
|
||||
rotation.normalize();
|
||||
}
|
||||
else {
|
||||
qd_pitch += radians;
|
||||
}
|
||||
}
|
||||
|
||||
void Camera::abs_yaw(double radians, bool snap) {
|
||||
if(snap) {
|
||||
rotation = rotation * quaterniond::fromAxisAngle(rotation.inverted() * vec3d::up(), radians);
|
||||
rotation.normalize();
|
||||
}
|
||||
else {
|
||||
qd_abs_yaw += radians;
|
||||
}
|
||||
}
|
||||
|
||||
void Camera::abs_yaw_to(double radians, bool snap) {
|
||||
if(snap) {
|
||||
double amount = getYaw() - radians;
|
||||
rotation = rotation * quaterniond::fromAxisAngle(rotation.inverted() * vec3d::up(), amount);
|
||||
}
|
||||
else
|
||||
qd_abs_yaw = getYaw() - radians;
|
||||
}
|
||||
|
||||
void Camera::abs_pitch(double radians, bool snap) {
|
||||
if(snap) {
|
||||
rotation = rotation * quaterniond::fromAxisAngle(rotation.inverted() * vec3d::right(), radians);
|
||||
rotation.normalize();
|
||||
}
|
||||
else {
|
||||
qd_abs_pitch += radians;
|
||||
}
|
||||
}
|
||||
|
||||
void Camera::abs_pitch_to(double radians, bool snap) {
|
||||
if(snap) {
|
||||
double amount = getPitch() - radians;
|
||||
rotation = rotation * quaterniond::fromAxisAngle(rotation.inverted() * vec3d::right(), amount);
|
||||
}
|
||||
else {
|
||||
qd_abs_pitch = getPitch() - radians;
|
||||
}
|
||||
}
|
||||
|
||||
void Camera::roll(double radians, bool snap) {
|
||||
if(snap)
|
||||
rotation = rotation * quaterniond::fromAxisAngle(vec3d::front(), radians);
|
||||
else
|
||||
qd_roll += radians;
|
||||
}
|
||||
|
||||
void Camera::zoom(double factor) {
|
||||
qd_zoom *= factor;
|
||||
qd_zoom_point = vec3d();
|
||||
qd_zoom_min_distance = 0;
|
||||
}
|
||||
|
||||
void Camera::zoomTo(double factor, const vec3d& towards, double minDistance) {
|
||||
qd_zoom *= factor;
|
||||
qd_zoom_point = towards;
|
||||
qd_zoom_line = vec3d();
|
||||
qd_zoom_min_distance = minDistance;
|
||||
}
|
||||
|
||||
void Camera::zoomAlong(double factor, const vec3d& line) {
|
||||
qd_zoom *= factor;
|
||||
qd_zoom_line = line;
|
||||
qd_zoom_point = vec3d();
|
||||
qd_zoom_min_distance = 0;
|
||||
}
|
||||
|
||||
void Camera::setRadius(double amount) {
|
||||
radius = amount;
|
||||
if(radius > maxDist)
|
||||
radius = maxDist;
|
||||
if(radius < 0)
|
||||
radius = 1.0;
|
||||
}
|
||||
|
||||
double Camera::getRadius() {
|
||||
return radius;
|
||||
}
|
||||
|
||||
void Camera::move_world(const vec3d& motion) {
|
||||
qd_world_motion += motion * radius;
|
||||
}
|
||||
|
||||
void Camera::move_world_abs(const vec3d& motion) {
|
||||
qd_world_motion += motion;
|
||||
}
|
||||
|
||||
void Camera::move_cam(const vec3d& motion) {
|
||||
qd_cam_motion += motion * radius;
|
||||
}
|
||||
|
||||
void Camera::move_cam_abs(const vec3d& motion) {
|
||||
qd_cam_motion += motion;
|
||||
}
|
||||
|
||||
void Camera::move_abs(const vec3d& motion) {
|
||||
qd_abs_motion += motion;
|
||||
}
|
||||
|
||||
void Camera::setPositionBound(const vec3d& minimum, const vec3d& maximum) {
|
||||
positionBound = AABBoxd(minimum, maximum);
|
||||
}
|
||||
|
||||
void Camera::setMaxDistance(double dist) {
|
||||
maxDist = dist;
|
||||
}
|
||||
|
||||
void Camera::setRenderConstraints(double ZNear, double ZFar, double FOV, double Aspect, double w, double h) {
|
||||
zNear = ZNear;
|
||||
zFar = ZFar;
|
||||
fov = FOV * (twopi / 360.0);
|
||||
aspect = Aspect;
|
||||
pxWidth = w;
|
||||
pxHeight = h;
|
||||
}
|
||||
|
||||
bool Camera::inverted() {
|
||||
return getUp().y <= 0;
|
||||
}
|
||||
|
||||
vec3d Camera::getPosition() const {
|
||||
if(objectCamera)
|
||||
return center;
|
||||
else
|
||||
return center + (rotation * vec3d::front(-radius));
|
||||
}
|
||||
|
||||
vec3d Camera::getMovedPosition(vec3d pos, double pct) const {
|
||||
//Camera motion
|
||||
if(qd_cam_motion.getLength() > 0.00001) {
|
||||
vec3d cam_x = rotation * vec3d::right(); cam_x.normalize();
|
||||
vec3d cam_y = rotation * vec3d::up(); cam_y.normalize();
|
||||
vec3d cam_z = rotation * vec3d::front(); cam_z.normalize();
|
||||
|
||||
pos += (cam_x * qd_cam_motion.x * pct) + (cam_y * qd_cam_motion.y * pct) + (cam_z * qd_cam_motion.z * pct);
|
||||
}
|
||||
|
||||
//World motion
|
||||
if(qd_world_motion.getLength() > 0.00001) {
|
||||
vec3d world_x = rotation * vec3d::right(); world_x.y = 0; world_x.normalize();
|
||||
vec3d world_y = rotation * vec3d::up(); world_y.x = 0; world_y.z = 0; world_y.normalize();
|
||||
vec3d world_z = rotation * vec3d::front(); world_z.y = 0; world_z.normalize();
|
||||
|
||||
pos += (world_x * qd_world_motion.x * pct) + (world_y * qd_world_motion.y * pct) + (world_z * qd_world_motion.z * pct);
|
||||
}
|
||||
|
||||
//Absolute motion
|
||||
if(qd_abs_motion.getLength() > 0.00001)
|
||||
pos += qd_abs_motion * pct;
|
||||
|
||||
pos = pos.elementMax(positionBound.minimum).elementMin(positionBound.maximum);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
vec3d Camera::getFinalPosition() const {
|
||||
return getMovedPosition(getPosition(), 1.0);
|
||||
}
|
||||
|
||||
vec3d Camera::getFacing() const {
|
||||
return rotation * vec3d::front();
|
||||
}
|
||||
|
||||
vec3d Camera::getRight() const {
|
||||
return rotation * vec3d::right();
|
||||
}
|
||||
|
||||
vec3d Camera::getUp() const {
|
||||
return (rotation * vec3d::up()).normalized();
|
||||
}
|
||||
|
||||
quaterniond Camera::getRotation() const {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
vec3d Camera::getLookAt() const {
|
||||
return center;
|
||||
}
|
||||
|
||||
vec3d Camera::getFinalLookAt() const {
|
||||
return getMovedPosition(getLookAt(), 1.0);
|
||||
}
|
||||
|
||||
double Camera::getDistance() const {
|
||||
return radius;
|
||||
}
|
||||
|
||||
double Camera::getYaw() const {
|
||||
vec3d rotated = rotation * vec3d::front();
|
||||
return atan2(rotated.z, rotated.x);
|
||||
}
|
||||
|
||||
double Camera::getPitch() const {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double Camera::getRoll() const {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
vec2i Camera::screenPos(const vec3d& point) const {
|
||||
//Transform to camera space
|
||||
vec3d transformed = rotation.inverted() * (point - getPosition());
|
||||
|
||||
//Transform to OpenGL's space
|
||||
vec3d converted = vec3d(vec3d::right().dot(transformed), vec3d::up().dot(transformed), -vec3d::front().dot(transformed));
|
||||
|
||||
Matrix projection = Matrix::projection(fov / (twopi / 360.0), aspect, zNear, zFar);
|
||||
|
||||
//Apply projection and return final result (Left-Right and Top-Bottom are x[-1,1] and y[-1,1])
|
||||
vec4d pos = projection * vec4d(converted.x, converted.y, converted.z, 1.0);
|
||||
pos.x /= -pos.w; pos.y /= -pos.w;
|
||||
|
||||
return vec2i((int)(pxWidth * (pos.x + 1.0) * 0.5), (int)(pxHeight * (pos.y + 1.0) * 0.5));
|
||||
}
|
||||
|
||||
double Camera::screenAngle(const vec3d& toPoint) const {
|
||||
quaterniond inv = rotation.inverted();
|
||||
vec3d cam = inv * center;
|
||||
vec3d pos = inv * toPoint;
|
||||
|
||||
vec2d flatOffset(cam.x - pos.x, pos.y - cam.y);
|
||||
return flatOffset.radians() / pi;
|
||||
}
|
||||
|
||||
line3dd Camera::screenToRay(double x, double y) const {
|
||||
double tan_fov = tan(fov/2.0);
|
||||
|
||||
vec3d view_dir =
|
||||
vec3d::front(1.0)
|
||||
+ vec3d::right(tan_fov * 2.0 * (0.5-x) * aspect)
|
||||
+ vec3d::up(tan_fov * 2.0 * (0.5-y));
|
||||
|
||||
view_dir = rotation * view_dir.normalized();
|
||||
|
||||
vec3d start = view_dir * zNear, end = view_dir * zFar;
|
||||
|
||||
vec3d camPos = getPosition();
|
||||
start += camPos; end += camPos;
|
||||
|
||||
return line3dd(start, end);
|
||||
}
|
||||
|
||||
void Camera::setLockedRotation(bool locked) {
|
||||
lockedRotation = locked;
|
||||
}
|
||||
|
||||
void Camera::animatePercentage(double pct) {
|
||||
{ //Rotation
|
||||
auto prevRot = rotation;
|
||||
auto rotLock = [&prevRot,this]() {
|
||||
if(lockedRotation && (rotation * vec3d::up()).y < 0.001)
|
||||
rotation = prevRot;
|
||||
else
|
||||
prevRot = rotation;
|
||||
};
|
||||
|
||||
if(fabs(qd_abs_pitch) > 0.00001)
|
||||
rotation = rotation * quaterniond::fromAxisAngle(rotation.inverted() * vec3d::right(), pct * qd_abs_pitch);
|
||||
qd_abs_pitch *= 1.0 - pct;
|
||||
|
||||
rotLock();
|
||||
|
||||
if(fabs(qd_pitch) > 0.00001)
|
||||
rotation = rotation * quaterniond::fromAxisAngle(vec3d::right(), pct * qd_pitch);
|
||||
qd_pitch *= 1.0 - pct;
|
||||
|
||||
rotLock();
|
||||
|
||||
if(fabs(qd_roll) > 0.00001)
|
||||
rotation = rotation * quaterniond::fromAxisAngle(vec3d::front(), pct * qd_roll);
|
||||
qd_roll *= 1.0 - pct;
|
||||
|
||||
rotLock();
|
||||
|
||||
//Quaternion rotations
|
||||
if(fabs(qd_yaw) > 0.00001)
|
||||
rotation = rotation * quaterniond::fromAxisAngle(vec3d::up(), pct * qd_yaw);
|
||||
qd_yaw *= 1.0 - pct;
|
||||
|
||||
//Absolute rotations around world axes
|
||||
if(fabs(qd_abs_yaw) > 0.00001)
|
||||
rotation = rotation * quaterniond::fromAxisAngle(rotation.inverted() * vec3d::up(), pct * qd_abs_yaw);
|
||||
qd_abs_yaw *= 1.0 - pct;
|
||||
|
||||
rotation.normalize();
|
||||
}
|
||||
|
||||
center = getMovedPosition(center, pct);
|
||||
|
||||
//Move the center of the camera
|
||||
qd_cam_motion *= 1.0 - pct;
|
||||
qd_world_motion *= 1.0 - pct;
|
||||
qd_abs_motion *= 1.0 - pct;
|
||||
|
||||
//Zoom level
|
||||
if(fabs(qd_zoom-1) > 0.00001) {
|
||||
if(linearZoom) {
|
||||
double dest = (qd_zoom * radius);
|
||||
double zoomDist = dest - radius;
|
||||
radius += pct * zoomDist;
|
||||
qd_zoom = dest / radius;
|
||||
}
|
||||
else {
|
||||
double doZoom = pow(qd_zoom, pct);
|
||||
qd_zoom /= doZoom;
|
||||
radius *= doZoom;
|
||||
|
||||
if(!qd_zoom_line.zero()) {
|
||||
//Screw you and your entire 3D vector family
|
||||
}
|
||||
else if(!qd_zoom_point.zero()) {
|
||||
center = qd_zoom_point.interpolate(center, doZoom);
|
||||
}
|
||||
}
|
||||
|
||||
if(radius > maxDist)
|
||||
radius = maxDist;
|
||||
if(radius < qd_zoom_min_distance)
|
||||
radius = qd_zoom_min_distance;
|
||||
}
|
||||
else {
|
||||
qd_zoom = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void Camera::animate(double seconds) {
|
||||
//Interpolate the camera's current state to its destination state
|
||||
if(seconds <= 0)
|
||||
return;
|
||||
|
||||
double pct = 1.0 - pow(1.0 - pctPerSecond,seconds);
|
||||
animatePercentage(pct);
|
||||
}
|
||||
|
||||
void Camera::snap() {
|
||||
animatePercentage(1.0);
|
||||
}
|
||||
|
||||
void Camera::snapTranslation() {
|
||||
center = getMovedPosition(center, 1.0);
|
||||
|
||||
//Move the center of the camera
|
||||
qd_cam_motion *= 0.0;
|
||||
qd_world_motion *= 0.0;
|
||||
qd_abs_motion *= 0.0;
|
||||
}
|
||||
|
||||
void Camera::toLookAt(vec3d& cameraPosition, vec3d& cameraLookAt, vec3d& cameraUp) const {
|
||||
cameraPosition = getPosition();
|
||||
cameraLookAt = cameraPosition + getFacing();
|
||||
cameraUp = getUp();
|
||||
}
|
||||
|
||||
void Camera::setObjectCamera(bool ObjectCamera) {
|
||||
objectCamera = ObjectCamera;
|
||||
}
|
||||
|
||||
void Camera::setLinearZoom(bool linear) {
|
||||
linearZoom = linear;
|
||||
}
|
||||
|
||||
void Camera::resetRotation() {
|
||||
//TODO: Make this calculate the required yaw/pitch/roll to
|
||||
//reset back to defaults, so that this can be animated instead
|
||||
//of always needing to snap.
|
||||
rotation = quaterniond();
|
||||
qd_yaw = 0;
|
||||
qd_pitch = 0;
|
||||
qd_roll = 0;
|
||||
qd_zoom = 1;
|
||||
}
|
||||
|
||||
void Camera::resetZoom() {
|
||||
qd_zoom = 1;
|
||||
radius = 300;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "quaternion.h"
|
||||
#include "vec2.h"
|
||||
#include "line3d.h"
|
||||
#include "util/refcount.h"
|
||||
#include "aabbox.h"
|
||||
|
||||
namespace render {
|
||||
|
||||
class Camera : public AtomicRefCounted {
|
||||
private:
|
||||
quaterniond rotation;
|
||||
vec3d center;
|
||||
double radius;
|
||||
|
||||
AABBoxd positionBound;
|
||||
double maxDist;
|
||||
|
||||
vec3d qd_zoom_point;
|
||||
vec3d qd_zoom_line;
|
||||
|
||||
vec3d qd_world_motion;
|
||||
vec3d qd_cam_motion;
|
||||
vec3d qd_abs_motion;
|
||||
double qd_yaw, qd_pitch, qd_roll, qd_zoom;
|
||||
double qd_abs_yaw, qd_abs_pitch;
|
||||
double qd_zoom_min_distance;
|
||||
|
||||
double zNear, zFar, fov, aspect, pxWidth, pxHeight;
|
||||
|
||||
bool objectCamera;
|
||||
bool linearZoom;
|
||||
bool lockedRotation;
|
||||
|
||||
public:
|
||||
void yaw(double radians, bool snap = false);
|
||||
void pitch(double radians, bool snap = false);
|
||||
void abs_yaw(double radians, bool snap = false);
|
||||
void abs_pitch(double radians, bool snap = false);
|
||||
void roll(double radians, bool snap = false);
|
||||
void zoom(double factor);
|
||||
void snap();
|
||||
void snapTranslation();
|
||||
|
||||
void setRadius(double radius);
|
||||
double getRadius();
|
||||
|
||||
void abs_yaw_to(double yaw, bool snap = false);
|
||||
void abs_pitch_to(double pitch, bool snap = false);
|
||||
|
||||
void move_cam(const vec3d& motion);
|
||||
void move_cam_abs(const vec3d& motion);
|
||||
void move_world(const vec3d& motion);
|
||||
void move_world_abs(const vec3d& motion);
|
||||
void move_abs(const vec3d& motion);
|
||||
|
||||
void zoomTo(double factor, const vec3d& towards, double minDistance = 0);
|
||||
void zoomAlong(double factor, const vec3d& line);
|
||||
|
||||
void animatePercentage(double pct);
|
||||
void animate(double seconds);
|
||||
|
||||
void setLinearZoom(bool linear);
|
||||
void setLockedRotation(bool locked);
|
||||
|
||||
void setPositionBound(const vec3d& minimum, const vec3d& maximum);
|
||||
void setMaxDistance(double dist);
|
||||
|
||||
//Returns the angle in physical screen space
|
||||
//that a particular point is directed in from the
|
||||
//center of the screen
|
||||
double screenAngle(const vec3d& toPoint) const;
|
||||
vec2i screenPos(const vec3d& point) const;
|
||||
|
||||
//Returns a ray starting at the near Z plane, ending at the
|
||||
//far Z plane, with each endpoint being at <x,y> on the screen in
|
||||
//normalized coordinates [0,1]
|
||||
line3dd screenToRay(double x, double y) const;
|
||||
|
||||
vec3d getMovedPosition(vec3d pos, double pct) const;
|
||||
vec3d getFinalPosition() const;
|
||||
vec3d getPosition() const;
|
||||
vec3d getFacing() const;
|
||||
vec3d getUp() const;
|
||||
vec3d getRight() const;
|
||||
quaterniond getRotation() const;
|
||||
|
||||
vec3d getLookAt() const;
|
||||
vec3d getFinalLookAt() const;
|
||||
double getDistance() const;
|
||||
|
||||
double getYaw() const;
|
||||
double getPitch() const;
|
||||
double getRoll() const;
|
||||
|
||||
//Returns whether the camera is currently upside down
|
||||
bool inverted();
|
||||
|
||||
void setObjectCamera(bool ObjectCamera);
|
||||
void toLookAt(vec3d& cameraPosition, vec3d& cameraLookAt, vec3d& cameraUp) const;
|
||||
void setRenderConstraints(double ZNear, double ZFar, double FOV_degrees, double Aspect, double w, double h);
|
||||
void resetRotation();
|
||||
void resetZoom();
|
||||
|
||||
Camera();
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,164 @@
|
||||
#pragma once
|
||||
#include "scene/node.h"
|
||||
#include "render/render_state.h"
|
||||
#include "render/render_mesh.h"
|
||||
#include "render/texture.h"
|
||||
#include "matrix.h"
|
||||
#include "mesh.h"
|
||||
#include "rect.h"
|
||||
#include "image.h"
|
||||
#include "color.h"
|
||||
#include "vec2.h"
|
||||
#include "line3d.h"
|
||||
|
||||
struct frustum;
|
||||
|
||||
namespace scene {
|
||||
class Node;
|
||||
};
|
||||
|
||||
namespace render {
|
||||
|
||||
enum PrimitiveType {
|
||||
PT_Lines,
|
||||
PT_LineStrip,
|
||||
PT_Triangles,
|
||||
PT_Quads,
|
||||
};
|
||||
|
||||
class Camera;
|
||||
|
||||
class RenderDriver {
|
||||
public:
|
||||
RenderState activeRenderState;
|
||||
scene::Node rootNode;
|
||||
vec3d cam_pos, cam_facing, cam_up;
|
||||
|
||||
virtual bool init() = 0;
|
||||
virtual void reportErrors(const char* context = nullptr) const = 0;
|
||||
|
||||
virtual const RenderState* getLastRenderState() const = 0;
|
||||
|
||||
virtual void setSkybox(const RenderState* mat) = 0;
|
||||
virtual void setSkyboxMesh(const RenderMesh* mesh) = 0;
|
||||
virtual void setScreenSize(int w, int h) = 0;
|
||||
virtual void setFOV(double fov) = 0;
|
||||
virtual void setNearFarPlanes(double near, double far) = 0;
|
||||
|
||||
virtual const frustum& getViewFrustum() const = 0;
|
||||
virtual void setCameraData(Camera& camera) = 0;
|
||||
|
||||
virtual void setDefaultRenderState() = 0;
|
||||
virtual void set2DRenderState() = 0;
|
||||
virtual void switchToRenderState(const RenderState&) = 0;
|
||||
|
||||
virtual void setTransformation(const Matrix&) = 0;
|
||||
virtual void setTransformationAbs(const Matrix&) = 0;
|
||||
virtual void setTransformationIdentity() = 0;
|
||||
virtual void setBBTransform(vec3d pos, double width, double rot) = 0;
|
||||
virtual void resetTransformation() = 0;
|
||||
|
||||
virtual void getInverseView(float* mat3) const = 0;
|
||||
|
||||
virtual void getBillboardVecs(vec3d& upLeft, vec3d& upRight, double rotation = 0) const = 0;
|
||||
virtual void getBillboardVecs(const vec3d& from, vec3d& upLeft, vec3d& upRight, double rotation = 0) const = 0;
|
||||
|
||||
virtual void clearRenderPrepared() = 0;
|
||||
virtual bool isRenderPrepared() = 0;
|
||||
virtual void prepareRender3D(Camera& camera, const recti* clip = 0) = 0;
|
||||
virtual void prepareRender2D() = 0;
|
||||
virtual void renderWorld() = 0;
|
||||
|
||||
virtual RenderMesh* createMesh(const Mesh& mesh) = 0;
|
||||
virtual Shader* createShader() = 0;
|
||||
virtual ShaderProgram* createShaderProgram(const char* vertex_shader, const char* fragment_shader) = 0;
|
||||
virtual Texture* createTexture(Image& image, bool mipmap = true, bool cachePixels = false) = 0;
|
||||
static Texture* createTexture();
|
||||
static Texture* createCubemap();
|
||||
|
||||
virtual Texture* createRenderTarget(const vec2i& size) = 0;
|
||||
virtual void setRenderTarget(Texture* texture, bool intermediate = false) = 0;
|
||||
|
||||
virtual Image* getScreen(int x, int y, int w, int h) = 0;
|
||||
|
||||
virtual void drawFPSGraph(const recti& location) = 0;
|
||||
|
||||
virtual void drawBillboard(vec3d center, double width) = 0;
|
||||
|
||||
virtual void drawLine(line3dd line, Color start, Color end) = 0;
|
||||
|
||||
virtual void drawQuad(
|
||||
const RenderState* mat,
|
||||
const vec2<float>* vertices,
|
||||
const vec2<float>* textureCoords = 0,
|
||||
const Color* color = 0
|
||||
) = 0;
|
||||
|
||||
virtual void drawQuad(
|
||||
const vec2<float>* vertices,
|
||||
const vec2<float>* textureCoords,
|
||||
const Color* colors
|
||||
) = 0;
|
||||
|
||||
virtual void drawQuad(
|
||||
const vec3d* vertices,
|
||||
const vec2<float>* textureCoords,
|
||||
const Color* colors
|
||||
) = 0;
|
||||
|
||||
virtual void drawRectangle(
|
||||
const recti& rectangle,
|
||||
const RenderState* mat,
|
||||
Color color,
|
||||
const recti* clip = 0
|
||||
) = 0;
|
||||
|
||||
virtual void drawRectangle(
|
||||
recti rectangle,
|
||||
const RenderState* mat = 0,
|
||||
const recti* sourceRect = 0,
|
||||
const Color* color = 0,
|
||||
const recti* clip = 0
|
||||
) = 0;
|
||||
|
||||
virtual void drawRectangle(
|
||||
recti rectangle,
|
||||
const RenderState* mat,
|
||||
const recti* sourceRect,
|
||||
const Color* color,
|
||||
const recti* clip,
|
||||
double rotation
|
||||
) = 0;
|
||||
|
||||
virtual void drawBillboard(
|
||||
vec3d center,
|
||||
double width,
|
||||
const RenderState& mat,
|
||||
double rotation,
|
||||
Color* color = 0
|
||||
) = 0;
|
||||
|
||||
virtual void drawBillboard(
|
||||
vec3d center,
|
||||
double width,
|
||||
const RenderState& mat,
|
||||
const recti& source,
|
||||
Color* color = 0
|
||||
) = 0;
|
||||
|
||||
virtual void drawBillboard(
|
||||
vec3d center,
|
||||
double width,
|
||||
const RenderState& mat,
|
||||
const recti& source,
|
||||
double rotation,
|
||||
Color color = Color()
|
||||
) = 0;
|
||||
|
||||
virtual void pushScreenClip(const recti& box) = 0;
|
||||
virtual void popScreenClip() = 0;
|
||||
|
||||
virtual ~RenderDriver() {}
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "render/texture.h"
|
||||
#include "render/render_state.h"
|
||||
#include "render/driver.h"
|
||||
#include "color.h"
|
||||
#include "image.h"
|
||||
#include <vector>
|
||||
|
||||
namespace render {
|
||||
|
||||
typedef int fontChar;
|
||||
|
||||
class Font {
|
||||
//Prevent copying the font
|
||||
Font(const Font& other) {}
|
||||
void operator=(const Font& other) {}
|
||||
protected:
|
||||
Font() : bold(0), italic(0) {}
|
||||
public:
|
||||
Font* bold;
|
||||
Font* italic;
|
||||
|
||||
virtual vec2i getDimension(const char* text) const {
|
||||
return vec2i();
|
||||
};
|
||||
|
||||
virtual vec2i getDimension(int c, int lastC) const {
|
||||
return vec2i();
|
||||
}
|
||||
|
||||
virtual unsigned getBaseline() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual unsigned getLineHeight() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual void draw(render::RenderDriver* driver, const char* text,
|
||||
int x, int y, const Color* color = 0, const recti* clip = 0) const {
|
||||
};
|
||||
|
||||
virtual vec2i drawChar(render::RenderDriver* driver, int c, int lastC,
|
||||
int X, int Y, const Color* color = 0, const recti* clip = 0) const {
|
||||
return vec2i();
|
||||
}
|
||||
|
||||
virtual ~Font() {
|
||||
};
|
||||
|
||||
static Font* createDummyFont();
|
||||
};
|
||||
|
||||
bool clipQuad(rectf pos, rectf source, const rectf* clip, vec2f* verts, vec2f* texcoords);
|
||||
|
||||
Font* loadFontFNT(render::RenderDriver* driver, const char* filename);
|
||||
|
||||
Font* loadFontFT2(render::RenderDriver& driver, const char* filename,
|
||||
std::vector<std::pair<int,int>>& pages, int size);
|
||||
|
||||
};
|
||||
@@ -0,0 +1,368 @@
|
||||
#include "render/font.h"
|
||||
#include "render/driver.h"
|
||||
#include <stdio.h>
|
||||
#include "str_util.h"
|
||||
#include "vec2.h"
|
||||
#include "main/initialization.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace resource {
|
||||
extern render::Texture* queueImage(const std::string& abs_file, int priority, bool mipmap, bool cachePixels, bool cubemap = false);
|
||||
};
|
||||
|
||||
namespace render {
|
||||
|
||||
class FontFNT : public Font {
|
||||
public:
|
||||
std::vector<render::RenderState*> textures;
|
||||
|
||||
struct Glyph {
|
||||
unsigned short x : 16, y : 16;
|
||||
bool draw : 1;
|
||||
unsigned char page : 3;
|
||||
unsigned char w : 8, h : 8;
|
||||
char wOff : 8, hOff : 8;
|
||||
unsigned char xAdv : 8;
|
||||
};
|
||||
|
||||
struct GlyphEntry {
|
||||
fontChar letter;
|
||||
Glyph glyph;
|
||||
|
||||
bool operator<(const GlyphEntry& other) const { return letter < other.letter; }
|
||||
GlyphEntry(fontChar Letter) : letter(Letter) {}
|
||||
GlyphEntry(fontChar Letter, Glyph& Glyph) : letter(Letter), glyph(Glyph) {}
|
||||
};
|
||||
|
||||
struct KernEntry {
|
||||
union {
|
||||
unsigned v;
|
||||
struct { fontChar left : 16, right : 16; };
|
||||
};
|
||||
int offset;
|
||||
|
||||
bool operator<(const KernEntry& other) const { return v < other.v; }
|
||||
KernEntry(fontChar Left, fontChar Right) : left(Left), right(Right) {}
|
||||
};
|
||||
|
||||
Glyph* lowChars;
|
||||
std::vector<GlyphEntry> hiChars;
|
||||
std::vector<KernEntry> kerning;
|
||||
|
||||
unsigned glyphHeight, textureHeight, textureWidth;
|
||||
|
||||
unsigned getBaseline() const {
|
||||
return glyphHeight;
|
||||
}
|
||||
|
||||
unsigned getLineHeight() const {
|
||||
return glyphHeight;
|
||||
}
|
||||
|
||||
void draw(render::RenderDriver* driver, const char* text, int X, int Y, const Color* color, const recti* clip = 0) const {
|
||||
int x = X, y = Y;
|
||||
|
||||
Color colors[4];
|
||||
if(color)
|
||||
colors[0] = colors[1] = colors[2] = colors[3] = *color;
|
||||
|
||||
float xFactor = 1.f / float(textureWidth), yFactor = 1.f / float(textureHeight);
|
||||
vec2f font_verts[4], font_tc[4];
|
||||
|
||||
rectf fclip;
|
||||
if(clip)
|
||||
fclip = rectf(*clip);
|
||||
|
||||
unsigned lastTexture = (unsigned)textures.size();
|
||||
|
||||
fontChar lastC = 0;
|
||||
u8it it(text);
|
||||
|
||||
while(fontChar c = it++) {
|
||||
if(c == L'\n') {
|
||||
y += glyphHeight;
|
||||
x = X;
|
||||
lastC = 0;
|
||||
}
|
||||
else {
|
||||
if(lastC) {
|
||||
auto k = std::lower_bound(kerning.begin(), kerning.end(), KernEntry(lastC, c));
|
||||
if(k != kerning.end() && k->left == lastC && k->right == c)
|
||||
x += k->offset;
|
||||
}
|
||||
lastC = c;
|
||||
|
||||
Glyph glyph;
|
||||
|
||||
if(c <= 0xff)
|
||||
glyph = lowChars[c];
|
||||
else {
|
||||
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
||||
if(g != hiChars.end() && g->letter == c) {
|
||||
glyph = g->glyph;
|
||||
}
|
||||
else {
|
||||
glyph = lowChars['?'];
|
||||
}
|
||||
}
|
||||
|
||||
if(glyph.draw) {
|
||||
rectf pos = rectf::area(vec2f((float)(x + glyph.wOff), (float)(y + glyph.hOff)), vec2f(glyph.w, glyph.h));
|
||||
rectf source = rectf::area(vec2f((float)glyph.x * xFactor, (float)glyph.y * yFactor),
|
||||
vec2f((float)glyph.w * xFactor, (float)glyph.h * yFactor));
|
||||
|
||||
if(clipQuad(pos, source, clip ? &fclip : 0, font_verts, font_tc)) {
|
||||
if(glyph.page != lastTexture) {
|
||||
auto& mat = *textures[glyph.page];
|
||||
driver->switchToRenderState(mat);
|
||||
lastTexture = glyph.page;
|
||||
}
|
||||
|
||||
driver->drawQuad(font_verts,font_tc,color ? colors : 0);
|
||||
}
|
||||
}
|
||||
|
||||
x += glyph.xAdv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vec2i drawChar(render::RenderDriver* driver, int c, int lastC,
|
||||
int X, int Y, const Color* color, const recti* clip = 0) const {
|
||||
int x = X, y = Y;
|
||||
Color colors[4];
|
||||
if(color)
|
||||
colors[0] = colors[1] = colors[2] = colors[3] = *color;
|
||||
|
||||
float xFactor = 1.f / float(textureWidth), yFactor = 1.f / float(textureHeight);
|
||||
vec2f font_verts[4], font_tc[4];
|
||||
|
||||
rectf fclip;
|
||||
if(clip)
|
||||
fclip = rectf(*clip);
|
||||
|
||||
if(lastC) {
|
||||
auto k = std::lower_bound(kerning.begin(), kerning.end(), KernEntry(lastC, c));
|
||||
if(k != kerning.end() && k->left == lastC && k->right == c)
|
||||
x += k->offset;
|
||||
}
|
||||
|
||||
Glyph glyph;
|
||||
|
||||
if(c <= 0xff)
|
||||
glyph = lowChars[c];
|
||||
else {
|
||||
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
||||
if(g != hiChars.end() && g->letter == c) {
|
||||
glyph = g->glyph;
|
||||
}
|
||||
else {
|
||||
glyph = lowChars['?'];
|
||||
}
|
||||
}
|
||||
|
||||
if(glyph.draw) {
|
||||
rectf pos = rectf::area(vec2f((float)(x + glyph.wOff), (float)(y + glyph.hOff)), vec2f(glyph.w, glyph.h));
|
||||
rectf source = rectf::area(vec2f((float)glyph.x * xFactor, (float)glyph.y * yFactor),
|
||||
vec2f((float)glyph.w * xFactor, (float)glyph.h * yFactor));
|
||||
|
||||
if(clipQuad(pos, source, clip ? &fclip : 0, font_verts, font_tc)) {
|
||||
auto& mat = *textures[glyph.page];
|
||||
driver->switchToRenderState(mat);
|
||||
|
||||
driver->drawQuad(font_verts,font_tc,color ? colors : 0);
|
||||
}
|
||||
}
|
||||
|
||||
x += glyph.xAdv;
|
||||
return vec2i(x - X, y - Y);
|
||||
}
|
||||
|
||||
vec2i getDimension(const char* text) const {
|
||||
vec2i size(0, glyphHeight);
|
||||
|
||||
fontChar lastC = 0;
|
||||
u8it it(text);
|
||||
|
||||
while(fontChar c = it++) {
|
||||
if(lastC) {
|
||||
auto k = std::lower_bound(kerning.begin(), kerning.end(), KernEntry(lastC, c));
|
||||
if(k != kerning.end() && k->left == lastC && k->right == c)
|
||||
size.x += k->offset;
|
||||
}
|
||||
lastC = c;
|
||||
|
||||
const Glyph* glyph = 0;
|
||||
|
||||
if(c <= 0xff) {
|
||||
glyph = &lowChars[c];
|
||||
}
|
||||
else {
|
||||
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
||||
if(g != hiChars.end() && g->letter == c)
|
||||
glyph = &g->glyph;
|
||||
else
|
||||
glyph = &lowChars['?'];
|
||||
}
|
||||
|
||||
if(glyph)
|
||||
size.x += glyph->xAdv;
|
||||
++text;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
vec2i getDimension(int c, int lastC) const {
|
||||
vec2i size(0, glyphHeight);
|
||||
|
||||
if(lastC) {
|
||||
auto k = std::lower_bound(kerning.begin(), kerning.end(), KernEntry(lastC, c));
|
||||
if(k != kerning.end() && k->left == lastC && k->right == c)
|
||||
size.x += k->offset;
|
||||
}
|
||||
|
||||
const Glyph* glyph = 0;
|
||||
|
||||
if(c <= 0xff) {
|
||||
glyph = &lowChars[c];
|
||||
}
|
||||
else {
|
||||
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
||||
if(g != hiChars.end() && g->letter == c)
|
||||
glyph = &g->glyph;
|
||||
else
|
||||
glyph = &lowChars['?'];
|
||||
}
|
||||
|
||||
if(glyph)
|
||||
size.x += glyph->xAdv;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
FontFNT()
|
||||
: lowChars(new Glyph[256]), glyphHeight(0), textureHeight(1), textureWidth(1)
|
||||
{
|
||||
memset(lowChars, 0, sizeof(Glyph) * 256);
|
||||
}
|
||||
|
||||
~FontFNT()
|
||||
{
|
||||
delete[] lowChars;
|
||||
foreach(tex, textures)
|
||||
delete *tex;
|
||||
}
|
||||
};
|
||||
|
||||
Font* loadFontFNT(render::RenderDriver* driver, const char* filename) {
|
||||
auto file = fopen(filename, "r");
|
||||
if(file == 0)
|
||||
return 0;
|
||||
|
||||
auto slash = strrchr(filename, '/');
|
||||
if(auto bSlash = strrchr(filename, '\\'))
|
||||
if(bSlash > slash)
|
||||
slash = bSlash;
|
||||
|
||||
std::string folder(filename, slash ? slash + 1 - filename : 0);
|
||||
|
||||
//Skip first line
|
||||
do {
|
||||
int c = fgetc(file);
|
||||
if(c == L'\n' || c == L'\r' || c == EOF)
|
||||
break;
|
||||
} while(true);
|
||||
|
||||
if(feof(file)) {
|
||||
fclose(file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
FontFNT* font = new FontFNT;
|
||||
|
||||
if(false) {
|
||||
failedLoad:
|
||||
delete font;
|
||||
fclose(file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pages, lineCheck;
|
||||
|
||||
if(fscanf(file,
|
||||
" common lineHeight=%i base=%*i scaleW=%i scaleH=%i pages=%i packed=%*i alphaChnl=%*i redChnl=%*i greenChnl=%*i blueChnl=%i ",
|
||||
&font->glyphHeight, &font->textureWidth, &font->textureHeight, &pages, &lineCheck) < 5
|
||||
|| pages == 0 || pages > 7 || font->glyphHeight <= 0)
|
||||
goto failedLoad;
|
||||
|
||||
int page; char pageName[256];
|
||||
while(pages--) {
|
||||
int args = fscanf(file,"page id=%i file=%255s ", &page, pageName);
|
||||
if(args < 2 || (unsigned int)page != font->textures.size())
|
||||
goto failedLoad;
|
||||
|
||||
std::string pageFile = folder + trim(pageName,"\"");
|
||||
|
||||
auto material = new render::RenderState();
|
||||
material->depthTest = render::DT_NoDepthTest;
|
||||
material->lighting = false;
|
||||
material->culling = render::FC_None;
|
||||
material->baseMat = render::MAT_Alpha;
|
||||
if(load_resources)
|
||||
material->textures[0] = resource::queueImage(pageFile, 20, true, false);
|
||||
|
||||
font->textures.push_back(material);
|
||||
}
|
||||
|
||||
int glyphs;
|
||||
if(fscanf(file, "chars count=%i ", &glyphs) != 1)
|
||||
goto failedLoad;
|
||||
|
||||
while(glyphs--) {
|
||||
int c, x, y, w, h, wOff, hOff, xAdv, page, channel;
|
||||
if(fscanf(file, "char id=%i x=%i y=%i width=%i height=%i xoffset=%i yoffset=%i xadvance=%i page=%i chnl=%i ",
|
||||
&c, &x, &y, &w, &h, &wOff, &hOff, &xAdv, &page, &channel) != 10
|
||||
|| page < 0 || page >= (int)font->textures.size())
|
||||
goto failedLoad;
|
||||
FontFNT::Glyph glyph;
|
||||
glyph.draw = c != 32;
|
||||
glyph.x = x;
|
||||
glyph.y = y;
|
||||
glyph.w = w;
|
||||
glyph.h = h;
|
||||
glyph.wOff = wOff;
|
||||
glyph.hOff = hOff;
|
||||
glyph.xAdv = xAdv;
|
||||
glyph.page = page;
|
||||
|
||||
if(c <= 0xff)
|
||||
font->lowChars[c] = glyph;
|
||||
else
|
||||
font->hiChars.push_back(FontFNT::GlyphEntry(c,glyph));
|
||||
}
|
||||
|
||||
//Load kerning data
|
||||
int kerns;
|
||||
if(fscanf(file, "kernings count=%i ", &kerns) == 1) {
|
||||
while(kerns--) {
|
||||
int left, right, offset;
|
||||
if(fscanf(file, "kerning first=%i second=%i amount=%i ", &left, &right, &offset) == 3) {
|
||||
FontFNT::KernEntry kerning((fontChar)left, (fontChar)right);
|
||||
kerning.offset = offset;
|
||||
font->kerning.push_back(kerning);
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(font->hiChars.begin(), font->hiChars.end());
|
||||
std::sort(font->kerning.begin(), font->kerning.end());
|
||||
return font;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,487 @@
|
||||
#include "render/font.h"
|
||||
#include "render/driver.h"
|
||||
#include "render/vertexBuffer.h"
|
||||
#include "main/initialization.h"
|
||||
#include <stdio.h>
|
||||
#include "str_util.h"
|
||||
#include "vec2.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <ft2build.h>
|
||||
#else
|
||||
#include <freetype2/ft2build.h>
|
||||
#endif
|
||||
|
||||
#include FT_FREETYPE_H
|
||||
#include FT_SIZES_H
|
||||
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
|
||||
namespace resource {
|
||||
extern render::Texture* queueImage(Image* img, int priority, bool mipmap, bool cachePixels);
|
||||
};
|
||||
|
||||
namespace render {
|
||||
|
||||
Font* Font::createDummyFont() {
|
||||
return new Font();
|
||||
}
|
||||
|
||||
bool clipQuad(rectf pos, rectf source, const rectf* clip, vec2f* verts, vec2f* texcoords) {
|
||||
if(clip) {
|
||||
if(!clip->overlaps(pos))
|
||||
return false;
|
||||
|
||||
if(!clip->isRectInside(pos)) {
|
||||
//TODThere seem to be some oddities with the texture coordinates if a clip occurs
|
||||
rectf clipped = clip->clipAgainst(pos);
|
||||
source = source.clipProportional(pos, clipped);
|
||||
pos = clipped;
|
||||
}
|
||||
}
|
||||
|
||||
verts[0] = vec2f(pos.topLeft.x, pos.topLeft.y);
|
||||
verts[1] = vec2f(pos.botRight.x, pos.topLeft.y);
|
||||
verts[3] = vec2f(pos.topLeft.x, pos.botRight.y);
|
||||
verts[2] = vec2f(pos.botRight.x, pos.botRight.y);
|
||||
|
||||
texcoords[0] = vec2f(source.topLeft.x, source.topLeft.y);
|
||||
texcoords[1] = vec2f(source.botRight.x, source.topLeft.y);
|
||||
texcoords[3] = vec2f(source.topLeft.x, source.botRight.y);
|
||||
texcoords[2] = vec2f(source.botRight.x, source.botRight.y);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class FontFT2 : public Font {
|
||||
public:
|
||||
std::vector<render::RenderState*> textures;
|
||||
|
||||
struct Glyph {
|
||||
unsigned short x : 16, y : 16;
|
||||
bool draw : 1;
|
||||
unsigned char page : 7;
|
||||
unsigned char w : 8, h : 8;
|
||||
char wOff : 8, hOff : 8;
|
||||
float xAdv;
|
||||
int glyph_index;
|
||||
};
|
||||
|
||||
struct GlyphEntry {
|
||||
fontChar letter;
|
||||
Glyph glyph;
|
||||
|
||||
bool operator<(const GlyphEntry& other) const { return letter < other.letter; }
|
||||
GlyphEntry(fontChar Letter) : letter(Letter) {}
|
||||
GlyphEntry(fontChar Letter, Glyph& Glyph) : letter(Letter), glyph(Glyph) {}
|
||||
};
|
||||
|
||||
Glyph* lowChars;
|
||||
FT_Face* face;
|
||||
FT_Size size;
|
||||
std::vector<GlyphEntry> hiChars;
|
||||
|
||||
double x_scale, y_scale;
|
||||
unsigned glyphHeight, baseLine, textureHeight, textureWidth;
|
||||
|
||||
void draw(render::RenderDriver* driver, const char* text, int X, int Y, const Color* color, const recti* clip = 0) const {
|
||||
float x = (float)X, y = (float)Y;
|
||||
Color col = color ? *color : Color();
|
||||
|
||||
float xFactor = 1.f / float(textureWidth), yFactor = 1.f / float(textureHeight);
|
||||
vec2f font_verts[4], font_tc[4];
|
||||
|
||||
rectf fclip;
|
||||
if(clip)
|
||||
fclip = rectf(*clip);
|
||||
|
||||
FT_Activate_Size(size);
|
||||
|
||||
unsigned lastTexture = (unsigned)textures.size();
|
||||
VertexBufferTCV* buffer = 0;
|
||||
|
||||
u8it it(text);
|
||||
fontChar lastC = 0;
|
||||
|
||||
while(fontChar c = it++) {
|
||||
if(c == L'\n') {
|
||||
y += glyphHeight;
|
||||
x = (float)X;
|
||||
lastC = 0;
|
||||
}
|
||||
else if(c == L'\t') {
|
||||
x += 32.f - fmod(x - X, 32.f);
|
||||
lastC = L' ';
|
||||
}
|
||||
else {
|
||||
Glyph glyph;
|
||||
|
||||
if(c <= 0xff)
|
||||
glyph = lowChars[c];
|
||||
else {
|
||||
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
||||
if(g != hiChars.end() && g->letter == c) {
|
||||
glyph = g->glyph;
|
||||
}
|
||||
else {
|
||||
glyph = lowChars['?'];
|
||||
}
|
||||
}
|
||||
|
||||
//Don't kern anything involving digits
|
||||
if(c >= '0' && c <= '9') {
|
||||
lastC = 0;
|
||||
}
|
||||
else {
|
||||
if(lastC) {
|
||||
FT_Vector kerning;
|
||||
FT_Get_Kerning(*face, lastC, glyph.glyph_index, 0/*FT_KERNING_UNFITTED*/, &kerning);
|
||||
|
||||
if(kerning.x != 0)
|
||||
x += ((float)kerning.x) / 64.f;
|
||||
}
|
||||
|
||||
lastC = glyph.glyph_index;
|
||||
}
|
||||
|
||||
if(glyph.draw) {
|
||||
rectf pos = rectf::area(vec2f(x + (float)glyph.wOff, y + (float)glyph.hOff), vec2f(glyph.w, glyph.h));
|
||||
rectf source = rectf::area(vec2f((float)glyph.x * xFactor, (float)glyph.y * yFactor),
|
||||
vec2f((float)glyph.w * xFactor, (float)glyph.h * yFactor));
|
||||
|
||||
if(clipQuad(pos, source, clip ? &fclip : 0, font_verts, font_tc)) {
|
||||
if(glyph.page != lastTexture) {
|
||||
auto& mat = *textures[glyph.page];
|
||||
buffer = VertexBufferTCV::fetch(&mat);
|
||||
lastTexture = glyph.page;
|
||||
}
|
||||
|
||||
auto* verts = buffer->request(1, PT_Quads);
|
||||
|
||||
for(unsigned i = 0; i < 4; ++i) {
|
||||
auto& v = verts[i];
|
||||
v.uv = font_tc[i];
|
||||
v.col = col;
|
||||
v.pos.set(font_verts[i].x, font_verts[i].y, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
x += glyph.xAdv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vec2i drawChar(render::RenderDriver* driver, int c, int lastC,
|
||||
int X, int Y, const Color* color, const recti* clip = 0) const {
|
||||
float x = (float)X, y = (float)Y;
|
||||
Color colors[4];
|
||||
if(color)
|
||||
colors[0] = colors[1] = colors[2] = colors[3] = *color;
|
||||
|
||||
rectf fclip;
|
||||
if(clip)
|
||||
fclip = rectf(*clip);
|
||||
|
||||
float xFactor = 1.f / float(textureWidth), yFactor = 1.f / float(textureHeight);
|
||||
vec2f font_verts[4], font_tc[4];
|
||||
|
||||
FT_Activate_Size(size);
|
||||
|
||||
Glyph glyph;
|
||||
|
||||
if(c <= 0xff)
|
||||
glyph = lowChars[c];
|
||||
else {
|
||||
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
||||
if(g != hiChars.end() && g->letter == c) {
|
||||
glyph = g->glyph;
|
||||
}
|
||||
else {
|
||||
glyph = lowChars['?'];
|
||||
}
|
||||
}
|
||||
|
||||
//Don't kern anything involving digits
|
||||
if(c >= '0' && c <= '9') {
|
||||
lastC = 0;
|
||||
}
|
||||
else {
|
||||
if(lastC) {
|
||||
FT_Vector kerning;
|
||||
FT_Get_Kerning(*face, lastC, glyph.glyph_index, 0, &kerning);
|
||||
|
||||
if(kerning.x != 0)
|
||||
x += ((float)kerning.x) / 64.f;
|
||||
}
|
||||
|
||||
lastC = glyph.glyph_index;
|
||||
}
|
||||
|
||||
if(glyph.draw) {
|
||||
rectf pos = rectf::area(vec2f(x + glyph.wOff, y + glyph.hOff), vec2f(glyph.w, glyph.h));
|
||||
rectf source = rectf::area(vec2f((float)glyph.x * xFactor, (float)glyph.y * yFactor),
|
||||
vec2f((float)glyph.w * xFactor, (float)glyph.h * yFactor));
|
||||
|
||||
if(clipQuad(pos, source, clip ? &fclip : 0, font_verts, font_tc))
|
||||
driver->drawQuad(textures[glyph.page], font_verts, font_tc, color ? colors : 0);
|
||||
}
|
||||
|
||||
x += glyph.xAdv;
|
||||
return vec2i((int)x - X, (int)y - Y);
|
||||
}
|
||||
|
||||
vec2i getDimension(const char* text) const {
|
||||
vec2i dim(0, glyphHeight);
|
||||
|
||||
FT_Activate_Size(size);
|
||||
|
||||
fontChar lastC = 0;
|
||||
u8it it(text);
|
||||
unsigned maxWidth = 0;
|
||||
|
||||
while(fontChar c = it++) {
|
||||
const Glyph* glyph = 0;
|
||||
|
||||
if(c == L'\n') {
|
||||
if(dim.x > (int)maxWidth)
|
||||
maxWidth = dim.x;
|
||||
dim.x = 0;
|
||||
dim.y += glyphHeight;
|
||||
}
|
||||
else if(c <= 0xff) {
|
||||
glyph = &lowChars[c];
|
||||
}
|
||||
else {
|
||||
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
||||
if(g != hiChars.end() && g->letter == c)
|
||||
glyph = &g->glyph;
|
||||
else
|
||||
glyph = &lowChars['?'];
|
||||
}
|
||||
|
||||
if(glyph) {
|
||||
dim.x += (int)glyph->xAdv;
|
||||
|
||||
//Don't kern anything involving digits
|
||||
if(c < '0' || c > '9') {
|
||||
if(lastC) {
|
||||
FT_Vector kerning;
|
||||
FT_Get_Kerning(*face, lastC, glyph->glyph_index, 0, &kerning);
|
||||
|
||||
if(kerning.x != 0)
|
||||
dim.x += (unsigned)(kerning.x >> 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(dim.x < (int)maxWidth)
|
||||
dim.x = maxWidth;
|
||||
return dim;
|
||||
}
|
||||
|
||||
vec2i getDimension(int c, int lastC) const {
|
||||
vec2i dim(0, glyphHeight);
|
||||
|
||||
FT_Activate_Size(size);
|
||||
|
||||
const Glyph* glyph = 0;
|
||||
|
||||
if(c <= 0xff) {
|
||||
glyph = &lowChars[c];
|
||||
}
|
||||
else {
|
||||
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
||||
if(g != hiChars.end() && g->letter == c)
|
||||
glyph = &g->glyph;
|
||||
else
|
||||
glyph = &lowChars['?'];
|
||||
}
|
||||
|
||||
if(glyph) {
|
||||
dim.x += (int)glyph->xAdv;
|
||||
|
||||
//Don't kern anything involving digits
|
||||
if(c < '0' || c > '9') {
|
||||
if(lastC) {
|
||||
FT_Vector kerning;
|
||||
FT_Get_Kerning(*face, lastC, glyph->glyph_index, 0, &kerning);
|
||||
|
||||
if(kerning.x != 0)
|
||||
dim.x += (unsigned)(kerning.x >> 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dim;
|
||||
}
|
||||
|
||||
unsigned getBaseline() const {
|
||||
return baseLine;
|
||||
}
|
||||
|
||||
unsigned getLineHeight() const {
|
||||
return glyphHeight;
|
||||
}
|
||||
|
||||
FontFT2()
|
||||
: lowChars(new Glyph[256]), glyphHeight(0), baseLine(0), textureHeight(1), textureWidth(1)
|
||||
{
|
||||
memset(lowChars, 0, sizeof(Glyph) * 256);
|
||||
}
|
||||
|
||||
~FontFT2()
|
||||
{
|
||||
FT_Done_Size(size);
|
||||
delete[] lowChars;
|
||||
for(auto tex = textures.begin(), end = textures.end(); tex != end; ++tex)
|
||||
delete *tex;
|
||||
}
|
||||
};
|
||||
|
||||
FT_Library* library = 0;
|
||||
std::map<std::string, FT_Face*> faces;
|
||||
const int PAGE_SIZE = 512;
|
||||
|
||||
Font* loadFontFT2(render::RenderDriver& driver, const char* filename,
|
||||
std::vector<std::pair<int,int>>& pages, int size) {
|
||||
FontFT2* font = new FontFT2();
|
||||
font->textureWidth = PAGE_SIZE;
|
||||
font->textureHeight = PAGE_SIZE;
|
||||
|
||||
std::vector<Image*> images;
|
||||
int cur_page = 0;
|
||||
|
||||
Image* img = new Image(PAGE_SIZE, PAGE_SIZE, FMT_Alpha, 0xff);
|
||||
images.push_back(img);
|
||||
|
||||
int x = 0, y = 0;
|
||||
|
||||
//Create the library if none exists
|
||||
if(!library) {
|
||||
library = new FT_Library();
|
||||
FT_Init_FreeType(library);
|
||||
}
|
||||
|
||||
//Read in the font face if necessary
|
||||
FT_Face* face = 0;
|
||||
|
||||
auto it = faces.find(filename);
|
||||
if(it != faces.end()) {
|
||||
face = it->second;
|
||||
}
|
||||
else {
|
||||
face = new FT_Face();
|
||||
if (int err = FT_New_Face(*library, filename, 0, face)) {
|
||||
fprintf(stderr, "Error loading font %s (code %d).\n", filename, err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
faces[filename] = face;
|
||||
}
|
||||
|
||||
FT_New_Size(*face, &font->size);
|
||||
FT_Activate_Size(font->size);
|
||||
|
||||
FT_Set_Char_Size(*face, 0, size << 6, 96, 96);
|
||||
|
||||
font->x_scale = (double)((*face)->size->metrics.x_scale / 65536.0);
|
||||
font->y_scale = (double)((*face)->size->metrics.y_scale / 65536.0);
|
||||
font->glyphHeight = (unsigned)((*face)->size->metrics.height >> 6);
|
||||
font->face = face;
|
||||
|
||||
font->baseLine = font->glyphHeight + (unsigned)((*face)->size->metrics.descender >> 6);
|
||||
int lineHeight = 0;
|
||||
|
||||
for(auto page = pages.begin(), end = pages.end(); page != end; ++page) {
|
||||
int from = page->first;
|
||||
int to = page->second;
|
||||
|
||||
for(int ch = from; ch < to; ++ch) {
|
||||
int glyph_ind = FT_Get_Char_Index(*face, ch);
|
||||
|
||||
FontFT2::Glyph glyph;
|
||||
glyph.page = cur_page;
|
||||
|
||||
if(glyph_ind != 0) {
|
||||
FT_Load_Glyph(*face, glyph_ind, FT_LOAD_RENDER);
|
||||
|
||||
auto glp = (*face)->glyph;
|
||||
auto bmp = glp->bitmap;
|
||||
|
||||
//Collect data about glyph
|
||||
glyph.draw = true;
|
||||
glyph.glyph_index = glyph_ind;
|
||||
glyph.w = glp->bitmap.width;
|
||||
glyph.h = glp->bitmap.rows;
|
||||
glyph.wOff = glp->bitmap_left;
|
||||
glyph.hOff = font->baseLine - glp->bitmap_top;
|
||||
glyph.xAdv = ((float)glp->metrics.horiAdvance) / 64.f;
|
||||
|
||||
glyph.x = x;
|
||||
glyph.y = y;
|
||||
|
||||
if(glyph.h > lineHeight)
|
||||
lineHeight = glyph.h;
|
||||
|
||||
//Find a fitting spot on the image
|
||||
x += (unsigned)glyph.w + 2;
|
||||
if(x > PAGE_SIZE) {
|
||||
x = (unsigned)glyph.w + 2;
|
||||
glyph.x = 0;
|
||||
|
||||
y += lineHeight + 1;
|
||||
glyph.y += lineHeight + 1;
|
||||
lineHeight = glyph.h;
|
||||
|
||||
if(y + font->glyphHeight + 1 > (unsigned)PAGE_SIZE) {
|
||||
img = new Image(PAGE_SIZE, PAGE_SIZE, FMT_Alpha, 0xff);
|
||||
images.push_back(img);
|
||||
|
||||
y = 0;
|
||||
glyph.y = 0;
|
||||
|
||||
++cur_page;
|
||||
++glyph.page;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Put the glyph on the actual image
|
||||
unsigned char* buffer = bmp.buffer;
|
||||
for(int row = 0; row < bmp.rows; ++row, buffer += bmp.width)
|
||||
memcpy(&img->get_alpha(glyph.x, glyph.y + row), buffer, bmp.width);
|
||||
}
|
||||
else {
|
||||
glyph.draw = false;
|
||||
glyph.xAdv = 0;
|
||||
}
|
||||
|
||||
if(ch <= 0xff)
|
||||
font->lowChars[ch] = glyph;
|
||||
else
|
||||
font->hiChars.push_back(FontFT2::GlyphEntry(ch, glyph));
|
||||
}
|
||||
}
|
||||
|
||||
//Create the textures
|
||||
for(auto it = images.begin(), end = images.end(); it != end; ++it) {
|
||||
auto material = new render::RenderState();
|
||||
material->depthTest = render::DT_NoDepthTest;
|
||||
material->lighting = false;
|
||||
material->culling = render::FC_None;
|
||||
material->baseMat = MAT_Font;
|
||||
material->depthWrite = false;
|
||||
if(load_resources)
|
||||
material->textures[0] = resource::queueImage(*it, 20, true, false);
|
||||
|
||||
font->textures.push_back(material);
|
||||
}
|
||||
|
||||
std::sort(font->hiChars.begin(), font->hiChars.end());
|
||||
return font;
|
||||
}
|
||||
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
#include "render/driver.h"
|
||||
|
||||
namespace render {
|
||||
RenderDriver* createGLDriver();
|
||||
};
|
||||
@@ -0,0 +1,137 @@
|
||||
#include "gl_framebuffer.h"
|
||||
#include "main/logging.h"
|
||||
#include "main/references.h"
|
||||
|
||||
namespace render {
|
||||
|
||||
class DummyTexture : public Texture {
|
||||
GLuint glName;
|
||||
public:
|
||||
DummyTexture(GLuint name) : glName(name) {
|
||||
type = TT_2D;
|
||||
hasMipMaps = false;
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
virtual bool isPixelActive(vec2i px) const { return false; }
|
||||
|
||||
virtual void loadStart(Image& image, bool mipmap = true, bool cachePixels = false, unsigned lod = 0) {}
|
||||
virtual void loadPartial(Image& image, const recti& pixels, bool cachePixels = false, unsigned lod = 0) {}
|
||||
virtual void loadFinish(bool mipmap, unsigned lod = 0) {}
|
||||
|
||||
virtual void load(Image& image, bool mipmap = true, bool cachePixels = false) {}
|
||||
virtual void save(Image& image) const {}
|
||||
virtual void bind() { glBindTexture(GL_TEXTURE_2D, glName); }
|
||||
virtual unsigned getID() const { return glName; }
|
||||
|
||||
virtual unsigned getTextureBytes() const { return 0; }
|
||||
};
|
||||
|
||||
glFrameBuffer::glFrameBuffer(const vec2i& Size) {
|
||||
devices.render->reportErrors();
|
||||
|
||||
type = TT_2D;
|
||||
loaded = true;
|
||||
|
||||
size = Size;
|
||||
hasMipMaps = false;
|
||||
glGenFramebuffers(1, &buffer);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, buffer);
|
||||
prevRenderState.filterMin = TF_Nearest;
|
||||
prevRenderState.filterMag = TF_Nearest;
|
||||
|
||||
textures = new GLuint[2];
|
||||
glGenTextures(2, textures);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, textures[0]);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Size.width, Size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, textures[1]);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, Size.width, Size.height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
devices.render->reportErrors("setting framebuffer parameters");
|
||||
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[0], 0);
|
||||
|
||||
devices.render->reportErrors("creating framebuffer color attachment");
|
||||
|
||||
renderBuffers = new GLuint[1];
|
||||
//glGenRenderbuffers(1, renderBuffers);
|
||||
//glBindRenderbuffer(GL_RENDERBUFFER, renderBuffers[0]);
|
||||
//glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, Size.width, Size.height);
|
||||
//glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
|
||||
//glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderBuffers[0]);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, textures[1], 0);
|
||||
|
||||
depthTexture = new DummyTexture(textures[1]);
|
||||
|
||||
devices.render->reportErrors("creating framebuffer depth attachment");
|
||||
|
||||
auto state = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
if(state != GL_FRAMEBUFFER_COMPLETE)
|
||||
error("Incomplete framebuffer: %d", state);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
glFrameBuffer::~glFrameBuffer() {
|
||||
glDeleteFramebuffers(1, &buffer);
|
||||
glDeleteTextures(2, textures); delete[] textures;
|
||||
//glDeleteRenderbuffers(1, renderBuffers);
|
||||
delete[] renderBuffers;
|
||||
delete depthTexture;
|
||||
}
|
||||
|
||||
void glFrameBuffer::bind() {
|
||||
glBindTexture(GL_TEXTURE_2D, textures[0]);
|
||||
}
|
||||
|
||||
unsigned glFrameBuffer::getID() const {
|
||||
return textures[0];
|
||||
}
|
||||
|
||||
void glFrameBuffer::setAsTarget() {
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, buffer);
|
||||
glViewport(0,0,size.width, size.height);
|
||||
}
|
||||
|
||||
void glFrameBuffer::save(Image& image) const {
|
||||
image.resize(size.width, size.height, FMT_RGB);
|
||||
glBindTexture(GL_TEXTURE_2D, textures[0]);
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, image.rgb);
|
||||
}
|
||||
|
||||
void glFrameBuffer::load(Image& img, bool mipmap, bool cachePixels) {
|
||||
throw "Cannot load framebuffer from image.";
|
||||
}
|
||||
|
||||
void glFrameBuffer::loadStart(Image& image, bool mipmap, bool cachePixels, unsigned lod) {
|
||||
throw "Cannot load framebuffer from image.";
|
||||
}
|
||||
|
||||
void glFrameBuffer::loadPartial(Image& image, const recti& pixels, bool cachePixels, unsigned lod) {
|
||||
throw "Cannot load framebuffer from image.";
|
||||
}
|
||||
|
||||
void glFrameBuffer::loadFinish(bool mipmap, unsigned lod) {
|
||||
throw "Cannot load framebuffer from image.";
|
||||
}
|
||||
|
||||
bool glFrameBuffer::isPixelActive(vec2i px) const {
|
||||
throw "Cannot get framebuffer pixels.";
|
||||
}
|
||||
|
||||
unsigned glFrameBuffer::getTextureBytes() const {
|
||||
//4 bytes for RGBA, 3 bytes for 24 bit z buffer
|
||||
return size.width * size.height * 7;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
#include "texture.h"
|
||||
#include "compat/gl.h"
|
||||
|
||||
namespace render {
|
||||
|
||||
class glFrameBuffer : public Texture {
|
||||
GLuint buffer;
|
||||
GLuint* textures;
|
||||
GLuint* renderBuffers;
|
||||
public:
|
||||
glFrameBuffer(const vec2i& Size);
|
||||
~glFrameBuffer();
|
||||
|
||||
Texture* depthTexture;
|
||||
|
||||
bool isPixelActive(vec2i px) const;
|
||||
void bind();
|
||||
unsigned getID() const;
|
||||
void load(Image& img, bool mipmap = true, bool cachePixels = false);
|
||||
void loadStart(Image& image, bool mipmap = true, bool cachePixels = false, unsigned lod = 0);
|
||||
void loadPartial(Image& image, const recti& pixels, bool cachePixels = false, unsigned lod = 0);
|
||||
void loadFinish(bool mipmap, unsigned lod = 0);
|
||||
void save(Image& img) const;
|
||||
unsigned getTextureBytes() const;
|
||||
void setAsTarget();
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,238 @@
|
||||
#include "compat/gl.h"
|
||||
#include "vertex.h"
|
||||
#include "vec3.h"
|
||||
#include "mesh.h"
|
||||
#include "render/render_mesh.h"
|
||||
#include "render/gl_mesh.h"
|
||||
#include "render/driver.h"
|
||||
#include "main/references.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
namespace render {
|
||||
|
||||
bool vertexArraysAvailable = false, vertArraysChecked = false;
|
||||
|
||||
bool useVertexArrays() {
|
||||
if(vertArraysChecked)
|
||||
return vertexArraysAvailable;
|
||||
|
||||
vertexArraysAvailable = GLEW_ARB_vertex_array_object;
|
||||
vertArraysChecked = true;
|
||||
return vertexArraysAvailable;
|
||||
}
|
||||
|
||||
const RenderMesh* lastRenderedMesh = 0;
|
||||
|
||||
class GLMesh : public RenderMesh {
|
||||
bool valid;
|
||||
public:
|
||||
GLuint vertex_buffer;
|
||||
GLuint element_buffer;
|
||||
GLuint tangent_buffer;
|
||||
GLuint color_buffer;
|
||||
GLuint uv2_buffer;
|
||||
|
||||
GLuint vertex_array;
|
||||
|
||||
unsigned int vertices;
|
||||
unsigned int faces;
|
||||
|
||||
AABBoxf bbox;
|
||||
Mesh mesh;
|
||||
|
||||
double lod_distance;
|
||||
const RenderMesh* lod;
|
||||
|
||||
GLMesh() : valid(false), lod_distance(1), lod(0), tangent_buffer(0), vertex_array(0), color_buffer(0), uv2_buffer(0) {
|
||||
}
|
||||
|
||||
GLMesh(const Mesh& mesh) : valid(false), lod_distance(1), lod(0), tangent_buffer(0), vertex_array(0), color_buffer(0), uv2_buffer(0) {
|
||||
resetToMesh(mesh);
|
||||
}
|
||||
|
||||
~GLMesh() {
|
||||
clear();
|
||||
}
|
||||
|
||||
const Mesh& getMesh() const {
|
||||
return mesh;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
if(!valid)
|
||||
return;
|
||||
|
||||
glDeleteVertexArrays(1, &vertex_array);
|
||||
glDeleteBuffers(1, &vertex_buffer);
|
||||
glDeleteBuffers(1, &element_buffer);
|
||||
if(tangent_buffer != 0) {
|
||||
glDeleteBuffers(1, &tangent_buffer);
|
||||
tangent_buffer = 0;
|
||||
}
|
||||
if(color_buffer != 0) {
|
||||
glDeleteBuffers(1, &color_buffer);
|
||||
color_buffer = 0;
|
||||
}
|
||||
if(uv2_buffer != 0) {
|
||||
glDeleteBuffers(1, &uv2_buffer);
|
||||
uv2_buffer = 0;
|
||||
}
|
||||
|
||||
valid = false;
|
||||
}
|
||||
|
||||
void setupBuffers() const {
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_COLOR_ARRAY);
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex,position));
|
||||
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex,u));
|
||||
|
||||
glEnableVertexAttribArray(2);
|
||||
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex,normal));
|
||||
|
||||
if(tangent_buffer) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, tangent_buffer);
|
||||
glEnableVertexAttribArray(3);
|
||||
glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(vec4f), nullptr);
|
||||
}
|
||||
else {
|
||||
glDisableVertexAttribArray(3);
|
||||
}
|
||||
|
||||
if(color_buffer) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, color_buffer);
|
||||
glEnableVertexAttribArray(4);
|
||||
glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(Colorf), nullptr);
|
||||
}
|
||||
else {
|
||||
glDisableVertexAttribArray(4);
|
||||
}
|
||||
|
||||
if(uv2_buffer) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, uv2_buffer);
|
||||
glEnableVertexAttribArray(5);
|
||||
glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, sizeof(vec4f), nullptr);
|
||||
}
|
||||
else {
|
||||
glDisableVertexAttribArray(5);
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
|
||||
}
|
||||
|
||||
void resetToMesh(const Mesh& mesh) {
|
||||
this->mesh = mesh;
|
||||
clear();
|
||||
|
||||
if(mesh.vertices.empty() || mesh.faces.empty())
|
||||
return;
|
||||
|
||||
devices.render->reportErrors();
|
||||
|
||||
// Generate vertex buffer
|
||||
vertices = (unsigned)mesh.vertices.size();
|
||||
glGenBuffers(1, &vertex_buffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, vertices * sizeof(Vertex),
|
||||
&mesh.vertices[0], GL_STATIC_DRAW);
|
||||
|
||||
if(!mesh.tangents.empty()) {
|
||||
glGenBuffers(1, &tangent_buffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, tangent_buffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, mesh.tangents.size() * sizeof(vec4f), mesh.tangents.data(), GL_STATIC_DRAW);
|
||||
}
|
||||
|
||||
if(!mesh.colors.empty()) {
|
||||
glGenBuffers(1, &color_buffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, color_buffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, mesh.colors.size() * sizeof(Colorf), mesh.colors.data(), GL_STATIC_DRAW);
|
||||
}
|
||||
|
||||
if(!mesh.uvs2.empty()) {
|
||||
glGenBuffers(1, &uv2_buffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, uv2_buffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, mesh.uvs2.size() * sizeof(vec4f), mesh.uvs2.data(), GL_STATIC_DRAW);
|
||||
}
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
// Generate element buffer
|
||||
faces = (unsigned)mesh.faces.size();
|
||||
glGenBuffers(1, &element_buffer);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, faces * sizeof(Mesh::Face),
|
||||
&mesh.faces[0], GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
|
||||
devices.render->reportErrors("Filling mesh buffers");
|
||||
|
||||
if(useVertexArrays()) {
|
||||
glGenVertexArrays(1, &vertex_array);
|
||||
glBindVertexArray(vertex_array);
|
||||
setupBuffers();
|
||||
glBindVertexArray(0);
|
||||
|
||||
devices.render->reportErrors("Setting up mesh vertex array");
|
||||
}
|
||||
|
||||
//Generate Bounding Box
|
||||
for(unsigned i = 0; i < vertices; ++i)
|
||||
bbox.addPoint(mesh.vertices[i].position);
|
||||
|
||||
valid = true;
|
||||
}
|
||||
|
||||
const RenderMesh* selectLOD(double distance) const {
|
||||
if(lod && distance > lod_distance)
|
||||
return lod->selectLOD(distance);
|
||||
return this;
|
||||
}
|
||||
|
||||
void setLOD(double distance, const RenderMesh* mesh) {
|
||||
lod_distance = distance;
|
||||
lod = mesh;
|
||||
}
|
||||
|
||||
const AABBoxf& getBoundingBox() const {
|
||||
return bbox;
|
||||
}
|
||||
|
||||
unsigned getMeshBytes() const {
|
||||
if(!valid)
|
||||
return 0;
|
||||
|
||||
return (vertices * sizeof(float) * 8) + (faces * 3 * sizeof(short));
|
||||
}
|
||||
|
||||
void render() const {
|
||||
if(lastRenderedMesh != this) {
|
||||
//We only need to check valid here, because we can't be the rendered mesh if we aren't valid
|
||||
if(!valid)
|
||||
return;
|
||||
lastRenderedMesh = this;
|
||||
if(vertex_array)
|
||||
glBindVertexArray(vertex_array);
|
||||
else
|
||||
setupBuffers();
|
||||
}
|
||||
|
||||
glDrawElements(
|
||||
GL_TRIANGLES, //Mode
|
||||
3 * faces, //Total amount of vertices
|
||||
GL_UNSIGNED_SHORT, //Index type
|
||||
(void*)0 //Offset
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
RenderMesh* createGLMesh(const Mesh& mesh) {
|
||||
return new GLMesh(mesh);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include "render/render_mesh.h"
|
||||
#include "mesh.h"
|
||||
|
||||
namespace render {
|
||||
RenderMesh* createGLMesh(const Mesh& mesh);
|
||||
};
|
||||
@@ -0,0 +1,925 @@
|
||||
#include "render/shader.h"
|
||||
#include "render/gl_shader.h"
|
||||
#include "compat/gl.h"
|
||||
#include "compat/misc.h"
|
||||
#include "str_util.h"
|
||||
#include "files.h"
|
||||
#include "main/logging.h"
|
||||
#include "main/references.h"
|
||||
|
||||
extern unsigned frameNumber;
|
||||
|
||||
namespace render {
|
||||
|
||||
void preProcessShader(std::string& shader) {
|
||||
std::string output;
|
||||
size_t start = 0;
|
||||
while(start < shader.size()) {
|
||||
size_t pos = shader.find('#', start);
|
||||
if(pos == std::string::npos)
|
||||
break;
|
||||
|
||||
if(pos != start)
|
||||
output += shader.substr(start, pos-start);
|
||||
|
||||
if(shader.size() - pos > 3 && shader[pos+1] == '{' && shader[pos+2] == '{') {
|
||||
size_t endpos = shader.find("}}", pos, 2);
|
||||
if(endpos == std::string::npos)
|
||||
break;
|
||||
|
||||
std::string value = shader.substr(pos+3, endpos-pos-3);
|
||||
std::string replace;
|
||||
|
||||
if(value == "level:extreme") {
|
||||
auto* sl = devices.settings.engine.getSetting("iShaderLevel");
|
||||
if(sl)
|
||||
output += (sl->getInteger() >= 4) ? "true" : "false";
|
||||
}
|
||||
else if(value == "level:high") {
|
||||
auto* sl = devices.settings.engine.getSetting("iShaderLevel");
|
||||
if(sl)
|
||||
output += (sl->getInteger() >= 3) ? "true" : "false";
|
||||
}
|
||||
else if(value == "level:medium") {
|
||||
auto* sl = devices.settings.engine.getSetting("iShaderLevel");
|
||||
if(sl)
|
||||
output += (sl->getInteger() >= 2) ? "true" : "false";
|
||||
}
|
||||
else if(value == "fallback") {
|
||||
auto* fallback = devices.settings.engine.getSetting("bShaderFallback");
|
||||
if(fallback)
|
||||
output += (fallback->getBool()) ? "true" : "false";
|
||||
}
|
||||
else {
|
||||
auto* setting = devices.settings.engine.getSetting(value.c_str());
|
||||
if(!setting)
|
||||
setting = devices.settings.mod.getSetting(value.c_str());
|
||||
|
||||
if(setting) {
|
||||
switch(setting->type) {
|
||||
case GT_Bool:
|
||||
output += (setting->getBool()) ? "true" : "false";
|
||||
break;
|
||||
case GT_Integer:
|
||||
case GT_Enum:
|
||||
output += toString<int>(setting->getInteger());
|
||||
break;
|
||||
case GT_Double:
|
||||
output += toString<double>(setting->getDouble());
|
||||
break;
|
||||
case GT_String:
|
||||
output += *setting->getString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
error("ERROR: Could not find shader variable %s.", value.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
start = endpos + 2;
|
||||
}
|
||||
else if(shader.size() - pos > 11 && shader.compare(pos, 10, "#include \"") == 0) {
|
||||
size_t endpos = shader.find("\"", pos+10, 1);
|
||||
if(endpos == std::string::npos)
|
||||
break;
|
||||
|
||||
std::string value = shader.substr(pos+10, endpos-pos-10);
|
||||
std::string fname = devices.mods.resolve(value);
|
||||
if(!fileExists(fname)) {
|
||||
error("ERROR COMPILING SHADER: Could not find include file %s. (Resolved to %s)", value.c_str(), fname.c_str());
|
||||
}
|
||||
else {
|
||||
std::string includeContents = getFileContents(fname);
|
||||
preProcessShader(includeContents);
|
||||
output += includeContents;
|
||||
}
|
||||
|
||||
start = endpos + 1;
|
||||
}
|
||||
else {
|
||||
output += "#";
|
||||
start = pos + 1;
|
||||
}
|
||||
}
|
||||
if(start < shader.size())
|
||||
output += shader.substr(start, shader.size()-start);
|
||||
shader = output;
|
||||
}
|
||||
|
||||
class GLShader;
|
||||
class GLShaderProgram : public ShaderProgram {
|
||||
public:
|
||||
GLuint vertex_shader, fragment_shader, program;
|
||||
std::string vertex_file, fragment_file;
|
||||
|
||||
std::unordered_map<GLuint, unsigned> cached_uniforms;
|
||||
std::vector<float> mem_buffer;
|
||||
|
||||
const GLShader* lastShader;
|
||||
|
||||
GLShaderProgram(const std::string& vFile, const std::string& fFile)
|
||||
: vertex_file(vFile), fragment_file(fFile), program(0), lastShader(0)
|
||||
{
|
||||
}
|
||||
|
||||
~GLShaderProgram() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
if(program != 0) {
|
||||
glDeleteProgram(program);
|
||||
program = 0;
|
||||
}
|
||||
|
||||
mem_buffer.clear();
|
||||
cached_uniforms.clear();
|
||||
}
|
||||
|
||||
unsigned cacheUniform(GLuint position, size_t size) {
|
||||
auto it = cached_uniforms.find(position);
|
||||
if(it != cached_uniforms.end())
|
||||
return it->second;
|
||||
|
||||
unsigned pos = (unsigned)mem_buffer.size();
|
||||
cached_uniforms[position] = pos;
|
||||
|
||||
for(size_t i = 0; i < size; ++i)
|
||||
mem_buffer.push_back(0);
|
||||
return pos;
|
||||
}
|
||||
|
||||
GLuint compileShader(GLenum type, const std::string& fname, const std::string& source) {
|
||||
GLuint shader;
|
||||
|
||||
//Compile
|
||||
const GLchar* str_ptr = (const GLchar*)source.c_str();
|
||||
GLint str_len = (GLint)source.size();
|
||||
shader = glCreateShader(type);
|
||||
glShaderSource(shader, 1, &str_ptr, &str_len);
|
||||
glCompileShader(shader);
|
||||
|
||||
//Check compile value
|
||||
GLint shader_ok;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &shader_ok);
|
||||
|
||||
if(!shader_ok) {
|
||||
error("Failed to compile shader: %s", fname.c_str());
|
||||
|
||||
GLint log_length;
|
||||
char* log;
|
||||
|
||||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
|
||||
log = (char*)malloc(log_length);
|
||||
|
||||
glGetShaderInfoLog(shader, log_length, NULL, log);
|
||||
error("%s", log);
|
||||
|
||||
free(log);
|
||||
|
||||
glDeleteShader(shader);
|
||||
return 0;
|
||||
}
|
||||
else if(getLogLevel() == LL_Info) {
|
||||
//Print warnings in verbose
|
||||
GLint log_length;
|
||||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
|
||||
|
||||
//We're always provided at least an empty string, just ignore very small logs
|
||||
if(log_length > 8) {
|
||||
char* log;
|
||||
log = (char*)malloc(log_length);
|
||||
|
||||
glGetShaderInfoLog(shader, log_length, NULL, log);
|
||||
error("Shader '%s' has warnings:\n%s", fname.c_str(), log);
|
||||
|
||||
free(log);
|
||||
}
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
int compile() override {
|
||||
reset();
|
||||
//Compile vertex shader
|
||||
{
|
||||
auto contents = getFileContents(vertex_file);
|
||||
preProcessShader(contents);
|
||||
if(contents.empty()) {
|
||||
error("Could not open vertex shader, or the file was empty");
|
||||
return 1;
|
||||
}
|
||||
|
||||
vertex_shader = compileShader(GL_VERTEX_SHADER, vertex_file, contents);
|
||||
if(!vertex_shader)
|
||||
return 1;
|
||||
}
|
||||
|
||||
//Compile fragment shader
|
||||
{
|
||||
auto contents = getFileContents(fragment_file);
|
||||
preProcessShader(contents);
|
||||
fragment_shader = compileShader(GL_FRAGMENT_SHADER, fragment_file, contents);
|
||||
if(!fragment_shader) {
|
||||
if(contents.empty())
|
||||
error("Could not open fragment shader, or the file was empty");
|
||||
glDeleteShader(vertex_shader);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
//Link entire program
|
||||
program = glCreateProgram();
|
||||
|
||||
glBindAttribLocation(program, 0, "in_vertex");
|
||||
glBindAttribLocation(program, 2, "in_normal");
|
||||
glBindAttribLocation(program, 1, "in_uv");
|
||||
glBindAttribLocation(program, 3, "in_tangent");
|
||||
glBindAttribLocation(program, 4, "in_color");
|
||||
glBindAttribLocation(program, 5, "in_uv2");
|
||||
|
||||
glAttachShader(program, vertex_shader);
|
||||
glAttachShader(program, fragment_shader);
|
||||
glLinkProgram(program);
|
||||
|
||||
glDeleteShader(vertex_shader);
|
||||
glDeleteShader(fragment_shader);
|
||||
|
||||
//Make sure it linked correctly
|
||||
GLint status;
|
||||
glGetProgramiv(program,GL_LINK_STATUS,&status);
|
||||
if(status == GL_FALSE) {
|
||||
error("Linking failed:");
|
||||
GLint log_length;
|
||||
char* log;
|
||||
|
||||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
|
||||
log = (char*)malloc(log_length);
|
||||
|
||||
glGetProgramInfoLog(program, log_length, NULL, log);
|
||||
error("%s", log);
|
||||
|
||||
free(log);
|
||||
|
||||
glDeleteProgram(program);
|
||||
program = 0;
|
||||
return 3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
class GLShader : public Shader {
|
||||
public:
|
||||
mutable unsigned lastBoundFrame;
|
||||
|
||||
std::vector<std::string> uniform_names;
|
||||
std::vector<GLuint> uniform_locations;
|
||||
std::vector<unsigned> uniform_vars;
|
||||
std::vector<unsigned> uniform_cache;
|
||||
|
||||
GLShader()
|
||||
{
|
||||
program = 0;
|
||||
constant = true;
|
||||
dynamicFloats = 0;
|
||||
lastBoundFrame = 0xffffffff;
|
||||
}
|
||||
|
||||
~GLShader() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
uniform_locations.clear();
|
||||
uniform_cache.clear();
|
||||
uniform_vars.clear();
|
||||
}
|
||||
|
||||
virtual int compile() {
|
||||
reset();
|
||||
|
||||
if(!program) {
|
||||
error("No associated program");
|
||||
return 4;
|
||||
}
|
||||
|
||||
auto glProgram = (GLShaderProgram*)program;
|
||||
if(glProgram->program == 0)
|
||||
return 4;
|
||||
|
||||
bool missingVars = false;
|
||||
for(unsigned i = 0; i < uniform_names.size(); ++i) {
|
||||
GLuint location = glGetUniformLocation(glProgram->program, uniform_names[i].c_str());
|
||||
|
||||
if(location == (GLuint)-1) {
|
||||
//NOTE: This is no longer considered an error, because dynamic shader levels
|
||||
//are going to be causing uniforms to be missing all the time.
|
||||
//
|
||||
//error("Unable to find uniform '%s'", uniform_names[i].c_str());
|
||||
//missingVars = true;
|
||||
}
|
||||
else {
|
||||
uniform_locations.push_back(location);
|
||||
uniform_vars.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned i = 0; i < uniform_locations.size(); ++i) {
|
||||
auto& v = vars[uniform_vars[i]];
|
||||
size_t size = 0;
|
||||
switch(v.type) {
|
||||
case VT_int: case VT_float: size = 1; break;
|
||||
case VT_int2: case VT_float2: size = 2; break;
|
||||
case VT_int3: case VT_float3: size = 3; break;
|
||||
case VT_int4: case VT_float4: size = 4; break;
|
||||
case VT_mat3: size = 9; break;
|
||||
}
|
||||
|
||||
size *= v.count;
|
||||
uniform_cache.push_back(glProgram->cacheUniform(uniform_locations[i], size));
|
||||
}
|
||||
|
||||
if(missingVars)
|
||||
return 4;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual void addVariable(const std::string& name, const Variable& var) {
|
||||
uniform_names.push_back(name);
|
||||
Shader::addVariable(name, var);
|
||||
}
|
||||
|
||||
virtual void bind(float* dynamic) const {
|
||||
if(!program) {
|
||||
glUseProgram(0);
|
||||
return;
|
||||
}
|
||||
|
||||
auto glProgram = (GLShaderProgram*)program;
|
||||
if(auto programID = glProgram->program) {
|
||||
glUseProgram(programID);
|
||||
}
|
||||
else {
|
||||
glUseProgram(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool bindConst = lastBoundFrame != frameNumber || glProgram->lastShader != this;
|
||||
lastBoundFrame = frameNumber;
|
||||
glProgram->lastShader = this;
|
||||
|
||||
if(dynamic) {
|
||||
float* floats = dynamic;
|
||||
|
||||
for(size_t i = 0, cnt = uniform_locations.size(); i < cnt; ++i) {
|
||||
const Shader::Variable& var = vars[uniform_vars[i]];
|
||||
if(!bindConst && var.constant)
|
||||
continue;
|
||||
|
||||
GLuint uniform = uniform_locations[i];
|
||||
void* uniformMem = (void*)&glProgram->mem_buffer[uniform_cache[i]];
|
||||
|
||||
switch(var.type) {
|
||||
case VT_invalid:
|
||||
break;
|
||||
|
||||
case VT_int:
|
||||
if(var.constant) {
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints, var.count, var._args);
|
||||
|
||||
if(memcmp(uniformMem, var._ints, var.count * 4) != 0) {
|
||||
glUniform1iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 4);
|
||||
}
|
||||
}
|
||||
else if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 4) != 0) {
|
||||
glUniform1iv(uniform, var.count, (int*)floats);
|
||||
memcpy(uniformMem, floats, var.count * 4);
|
||||
}
|
||||
floats += var.count;
|
||||
}
|
||||
break;
|
||||
case VT_int2:
|
||||
if(var.constant) {
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints, var.count, var._args);
|
||||
|
||||
if(memcmp(uniformMem, var._ints, var.count * 8) != 0) {
|
||||
glUniform2iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 8);
|
||||
}
|
||||
}
|
||||
else if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 8) != 0) {
|
||||
glUniform2iv(uniform, var.count, (int*)floats);
|
||||
memcpy(uniformMem, floats, var.count * 8);
|
||||
}
|
||||
floats += var.count * 2;
|
||||
}
|
||||
break;
|
||||
case VT_int3:
|
||||
if(var.constant) {
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints, var.count, var._args);
|
||||
|
||||
if(memcmp(uniformMem, var._ints, var.count * 12) != 0) {
|
||||
glUniform3iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 12);
|
||||
}
|
||||
}
|
||||
else if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 12) != 0) {
|
||||
glUniform3iv(uniform, var.count, (int*)floats);
|
||||
memcpy(uniformMem, floats, var.count * 12);
|
||||
}
|
||||
floats += var.count * 3;
|
||||
}
|
||||
break;
|
||||
case VT_int4:
|
||||
if(var.constant) {
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints, var.count, var._args);
|
||||
|
||||
if(memcmp(uniformMem, var._ints, var.count * 16) != 0) {
|
||||
glUniform4iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 16);
|
||||
}
|
||||
}
|
||||
else if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 16) != 0) {
|
||||
glUniform4iv(uniform, var.count, (int*)floats);
|
||||
memcpy(uniformMem, floats, var.count * 16);
|
||||
}
|
||||
floats += var.count * 4;
|
||||
}
|
||||
break;
|
||||
|
||||
case VT_float:
|
||||
if(var.constant) {
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats, var.count, var._args);
|
||||
|
||||
if(memcmp(uniformMem, var._floats, var.count * 4) != 0) {
|
||||
glUniform1fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 4);
|
||||
}
|
||||
}
|
||||
else if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 4) != 0) {
|
||||
glUniform1fv(uniform, var.count, floats);
|
||||
memcpy(uniformMem, floats, var.count * 4);
|
||||
}
|
||||
floats += var.count;
|
||||
}
|
||||
break;
|
||||
case VT_float2:
|
||||
if(var.constant) {
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats, var.count, var._args);
|
||||
|
||||
if(memcmp(uniformMem, var._floats, var.count * 8) != 0) {
|
||||
glUniform2fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 8);
|
||||
}
|
||||
}
|
||||
else if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 8) != 0) {
|
||||
glUniform2fv(uniform, var.count, floats);
|
||||
memcpy(uniformMem, floats, var.count * 8);
|
||||
}
|
||||
floats += var.count * 2;
|
||||
}
|
||||
break;
|
||||
case VT_float3:
|
||||
if(var.constant) {
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats, var.count, var._args);
|
||||
|
||||
if(memcmp(uniformMem, var._floats, var.count * 12) != 0) {
|
||||
glUniform3fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 12);
|
||||
}
|
||||
}
|
||||
else if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 12) != 0) {
|
||||
glUniform3fv(uniform, var.count, floats);
|
||||
memcpy(uniformMem, floats, var.count * 12);
|
||||
}
|
||||
floats += var.count * 3;
|
||||
}
|
||||
break;
|
||||
case VT_float4:
|
||||
if(var.constant) {
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats, var.count, var._args);
|
||||
|
||||
if(memcmp(uniformMem, var._floats, var.count * 16) != 0) {
|
||||
glUniform4fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 16);
|
||||
}
|
||||
}
|
||||
else if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 16) != 0) {
|
||||
glUniform4fv(uniform, var.count, floats);
|
||||
memcpy(uniformMem, floats, var.count * 16);
|
||||
}
|
||||
floats += var.count * 4;
|
||||
}
|
||||
break;
|
||||
case VT_mat3:
|
||||
if(var.constant) {
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats, var.count, var._args);
|
||||
|
||||
if(memcmp(uniformMem, var._floats, var.count * 36) != 0) {
|
||||
glUniformMatrix3fv(uniform, var.count, false, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 36);
|
||||
}
|
||||
}
|
||||
else if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 36) != 0) {
|
||||
glUniformMatrix3fv(uniform, var.count, false, floats);
|
||||
memcpy(uniformMem, floats, var.count * 36);
|
||||
}
|
||||
floats += var.count * 9;
|
||||
}
|
||||
NO_DEFAULT
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for(size_t i = 0, cnt = uniform_locations.size(); i < cnt; ++i) {
|
||||
const Shader::Variable& var = vars[uniform_vars[i]];
|
||||
if(!bindConst && var.constant)
|
||||
continue;
|
||||
|
||||
GLuint uniform = uniform_locations[i];
|
||||
void* uniformMem = (void*)&glProgram->mem_buffer[uniform_cache[i]];
|
||||
|
||||
switch(var.type) {
|
||||
case VT_invalid:
|
||||
break;
|
||||
|
||||
case VT_int:
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._ints, var.count * 4) != 0) {
|
||||
glUniform1iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 4);
|
||||
}
|
||||
break;
|
||||
case VT_int2:
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._ints, var.count * 8) != 0) {
|
||||
glUniform2iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 8);
|
||||
}
|
||||
break;
|
||||
case VT_int3:
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._ints, var.count * 12) != 0) {
|
||||
glUniform3iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 12);
|
||||
}
|
||||
break;
|
||||
case VT_int4:
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._ints, var.count * 16) != 0) {
|
||||
glUniform4iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 16);
|
||||
}
|
||||
break;
|
||||
|
||||
case VT_float:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 4) != 0) {
|
||||
glUniform1fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 4);
|
||||
}
|
||||
break;
|
||||
case VT_float2:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 8) != 0) {
|
||||
glUniform2fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 8);
|
||||
}
|
||||
break;
|
||||
case VT_float3:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 12) != 0) {
|
||||
glUniform3fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 12);
|
||||
}
|
||||
break;
|
||||
case VT_float4:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 16) != 0) {
|
||||
glUniform4fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 16);
|
||||
}
|
||||
break;
|
||||
|
||||
case VT_mat3:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 36) != 0) {
|
||||
glUniformMatrix3fv(uniform, var.count, false, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 36);
|
||||
}
|
||||
break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateDynamicVars() const {
|
||||
for(size_t i = 0, cnt = uniform_locations.size(); i < cnt; ++i) {
|
||||
const Shader::Variable& var = vars[uniform_vars[i]];
|
||||
if(var.constant)
|
||||
continue;
|
||||
GLuint uniform = uniform_locations[i];
|
||||
void* uniformMem = (void*)&((GLShaderProgram*)program)->mem_buffer[uniform_cache[i]];
|
||||
|
||||
switch(var.type) {
|
||||
case VT_invalid:
|
||||
break;
|
||||
|
||||
case VT_int:
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._ints, var.count * 4) != 0) {
|
||||
glUniform1iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 4);
|
||||
}
|
||||
break;
|
||||
case VT_int2:
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._ints, var.count * 8) != 0) {
|
||||
glUniform2iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 8);
|
||||
}
|
||||
break;
|
||||
case VT_int3:
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._ints, var.count * 12) != 0) {
|
||||
glUniform3iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 12);
|
||||
}
|
||||
break;
|
||||
case VT_int4:
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._ints, var.count * 16) != 0) {
|
||||
glUniform4iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 16);
|
||||
}
|
||||
break;
|
||||
|
||||
case VT_float:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 4) != 0) {
|
||||
glUniform1fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 4);
|
||||
}
|
||||
break;
|
||||
case VT_float2:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 8) != 0) {
|
||||
glUniform2fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 8);
|
||||
}
|
||||
break;
|
||||
case VT_float3:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 12) != 0) {
|
||||
glUniform3fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 12);
|
||||
}
|
||||
break;
|
||||
case VT_float4:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 16) != 0) {
|
||||
glUniform4fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 16);
|
||||
}
|
||||
break;
|
||||
|
||||
case VT_mat3:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 36) != 0) {
|
||||
glUniformMatrix3fv(uniform, var.count, false, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 36);
|
||||
}
|
||||
break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void saveDynamicVars(float* buffer) const {
|
||||
float* floats = buffer;
|
||||
|
||||
for(size_t i = 0, cnt = uniform_locations.size(); i < cnt; ++i) {
|
||||
const Shader::Variable& var = vars[uniform_vars[i]];
|
||||
if(var.constant)
|
||||
continue;
|
||||
|
||||
switch(var.type) {
|
||||
case VT_invalid:
|
||||
break;
|
||||
|
||||
case VT_int:
|
||||
if(var._intcall) {
|
||||
var._intcall((int*)floats,var.count,var._args);
|
||||
floats += var.count;
|
||||
}
|
||||
break;
|
||||
case VT_int2:
|
||||
if(var._intcall) {
|
||||
var._intcall((int*)floats,var.count,var._args);
|
||||
floats += var.count * 2;
|
||||
}
|
||||
break;
|
||||
case VT_int3:
|
||||
if(var._intcall) {
|
||||
var._intcall((int*)floats,var.count,var._args);
|
||||
floats += var.count * 3;
|
||||
}
|
||||
break;
|
||||
case VT_int4:
|
||||
if(var._intcall) {
|
||||
var._intcall((int*)floats,var.count,var._args);
|
||||
floats += var.count * 4;
|
||||
}
|
||||
break;
|
||||
|
||||
case VT_float:
|
||||
if(var._floatcall) {
|
||||
var._floatcall(floats,var.count,var._args);
|
||||
floats += var.count;
|
||||
}
|
||||
break;
|
||||
case VT_float2:
|
||||
if(var._floatcall) {
|
||||
var._floatcall(floats,var.count,var._args);
|
||||
floats += var.count * 2;
|
||||
}
|
||||
break;
|
||||
case VT_float3:
|
||||
if(var._floatcall) {
|
||||
var._floatcall(floats,var.count,var._args);
|
||||
floats += var.count * 3;
|
||||
}
|
||||
break;
|
||||
case VT_float4:
|
||||
if(var._floatcall) {
|
||||
var._floatcall(floats,var.count,var._args);
|
||||
floats += var.count * 4;
|
||||
}
|
||||
break;
|
||||
|
||||
case VT_mat3:
|
||||
if(var._floatcall) {
|
||||
var._floatcall(floats,var.constant,var._args);
|
||||
floats += var.constant * 9;
|
||||
}
|
||||
NO_DEFAULT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadDynamicVars(float* buffer) const {
|
||||
float* floats = buffer;
|
||||
|
||||
for(size_t i = 0, cnt = uniform_locations.size(); i < cnt; ++i) {
|
||||
const Shader::Variable& var = vars[uniform_vars[i]];
|
||||
if(var.constant)
|
||||
continue;
|
||||
|
||||
GLuint uniform = uniform_locations[i];
|
||||
void* uniformMem = (void*)&((GLShaderProgram*)program)->mem_buffer[uniform_cache[i]];
|
||||
|
||||
switch(var.type) {
|
||||
case VT_invalid:
|
||||
break;
|
||||
|
||||
case VT_int:
|
||||
if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 4) != 0) {
|
||||
glUniform1iv(uniform, var.count, (int*)floats);
|
||||
memcpy(uniformMem, floats, var.count * 4);
|
||||
}
|
||||
floats += var.count;
|
||||
}
|
||||
break;
|
||||
case VT_int2:
|
||||
if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 8) != 0) {
|
||||
glUniform2iv(uniform, var.count, (int*)floats);
|
||||
memcpy(uniformMem, floats, var.count * 8);
|
||||
}
|
||||
floats += var.count * 2;
|
||||
}
|
||||
break;
|
||||
case VT_int3:
|
||||
if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 12) != 0) {
|
||||
glUniform3iv(uniform, var.count, (int*)floats);
|
||||
memcpy(uniformMem, floats, var.count * 12);
|
||||
}
|
||||
floats += var.count * 3;
|
||||
}
|
||||
break;
|
||||
case VT_int4:
|
||||
if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 16) != 0) {
|
||||
glUniform4iv(uniform, var.count, (int*)floats);
|
||||
memcpy(uniformMem, floats, var.count * 16);
|
||||
}
|
||||
floats += var.count * 4;
|
||||
}
|
||||
break;
|
||||
|
||||
case VT_float:
|
||||
if(var._floatcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 4) != 0) {
|
||||
glUniform1fv(uniform, var.count, floats);
|
||||
memcpy(uniformMem, floats, var.count * 4);
|
||||
}
|
||||
floats += var.count;
|
||||
}
|
||||
break;
|
||||
case VT_float2:
|
||||
if(var._floatcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 8) != 0) {
|
||||
glUniform2fv(uniform, var.count, floats);
|
||||
memcpy(uniformMem, floats, var.count * 8);
|
||||
}
|
||||
floats += var.count * 2;
|
||||
}
|
||||
break;
|
||||
case VT_float3:
|
||||
if(var._floatcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 12) != 0) {
|
||||
glUniform3fv(uniform, var.count, floats);
|
||||
memcpy(uniformMem, floats, var.count * 12);
|
||||
}
|
||||
floats += var.count * 3;
|
||||
}
|
||||
break;
|
||||
case VT_float4:
|
||||
if(var._floatcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 16) != 0) {
|
||||
glUniform4fv(uniform, var.count, floats);
|
||||
memcpy(uniformMem, floats, var.count * 16);
|
||||
}
|
||||
floats += var.count * 4;
|
||||
}
|
||||
break;
|
||||
|
||||
case VT_mat3:
|
||||
if(var._floatcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 36) != 0) {
|
||||
glUniformMatrix3fv(uniform, var.count, false, floats);
|
||||
memcpy(uniformMem, floats, var.count * 36);
|
||||
}
|
||||
floats += var.count * 9;
|
||||
}
|
||||
break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Shader* createGLShader() {
|
||||
return new GLShader();
|
||||
}
|
||||
|
||||
ShaderProgram* createGLShaderProgram(const char* vertex_shader, const char* fragment_shader) {
|
||||
return new GLShaderProgram(vertex_shader, fragment_shader);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include "render/shader.h"
|
||||
|
||||
namespace render {
|
||||
Shader* createGLShader();
|
||||
ShaderProgram* createGLShaderProgram(const char* vertex_shader, const char* fragment_shader);
|
||||
};
|
||||
@@ -0,0 +1,423 @@
|
||||
#include "render/gl_texture.h"
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
#include <assert.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace render {
|
||||
extern bool isIntelCard;
|
||||
|
||||
void GLTexture::bind() {
|
||||
glBindTexture(GL_TEXTURE_2D, glName);
|
||||
}
|
||||
|
||||
unsigned GLTexture::getID() const {
|
||||
return glName;
|
||||
}
|
||||
|
||||
GLTexture::GLTexture()
|
||||
: glName(0), pixelDepth(1)
|
||||
{
|
||||
type = TT_2D;
|
||||
loaded = false;
|
||||
}
|
||||
|
||||
GLTexture::GLTexture(Image& image, bool mipmap, bool cachePixels)
|
||||
: glName(0), pixelDepth(1)
|
||||
{
|
||||
type = TT_2D;
|
||||
loaded = false;
|
||||
load(image, mipmap, cachePixels);
|
||||
}
|
||||
|
||||
void createTexture(Image& image, bool mipmap) {
|
||||
GLenum format;
|
||||
|
||||
unsigned levels = 1;
|
||||
if(mipmap) {
|
||||
unsigned smallDim = std::min(image.width, image.height);
|
||||
while(smallDim > 2) {
|
||||
smallDim /= 2;
|
||||
levels += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(GLEW_ARB_texture_storage) {
|
||||
switch(image.format) {
|
||||
case FMT_Grey:
|
||||
format = GL_LUMINANCE8;
|
||||
//{
|
||||
// GLint swizzleMask[] = {GL_RED, GL_RED, GL_RED, GL_ONE};
|
||||
// glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask);
|
||||
//}
|
||||
break;
|
||||
case FMT_Alpha:
|
||||
format = GL_ALPHA8;
|
||||
//{
|
||||
// GLint swizzleMask[] = {GL_ONE, GL_ONE, GL_ONE, GL_RED};
|
||||
// glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask);
|
||||
//}
|
||||
break;
|
||||
case FMT_RGB:
|
||||
format = GL_RGB8; break;
|
||||
case FMT_RGBA:
|
||||
format = GL_RGBA8; break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
glTexStorage2D(GL_TEXTURE_2D, levels, format, image.width, image.height);
|
||||
|
||||
if(!isIntelCard)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, levels-1);
|
||||
}
|
||||
else {
|
||||
//Sized lum/alpha are from texture storage arb
|
||||
switch(image.format) {
|
||||
case FMT_Grey:
|
||||
format = GL_LUMINANCE; break;
|
||||
case FMT_Alpha:
|
||||
format = GL_ALPHA; break;
|
||||
case FMT_RGB:
|
||||
format = GL_RGB; break;
|
||||
case FMT_RGBA:
|
||||
format = GL_RGBA; break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
|
||||
unsigned w = image.width, h = image.height;
|
||||
for(unsigned i = 0; i < levels; ++i) {
|
||||
glTexImage2D(GL_TEXTURE_2D, i, format, w, h, 0, format, GL_UNSIGNED_BYTE, nullptr);
|
||||
w /= 2;
|
||||
h /= 2;
|
||||
}
|
||||
|
||||
if(!isIntelCard)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, levels-1);
|
||||
}
|
||||
devices.render->reportErrors("creating texture");
|
||||
}
|
||||
|
||||
void loadTexture(Image& image, const recti& pixels, unsigned lod = 0) {
|
||||
devices.render->reportErrors();
|
||||
|
||||
GLenum format;
|
||||
switch(image.format) {
|
||||
case FMT_Grey:
|
||||
format = GL_LUMINANCE; break;
|
||||
case FMT_Alpha:
|
||||
format = GL_ALPHA; break;
|
||||
case FMT_RGB:
|
||||
format = GL_RGB; break;
|
||||
case FMT_RGBA:
|
||||
format = GL_RGBA; break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
|
||||
assert((unsigned)(pixels.topLeft.x + pixels.getWidth()) <= image.width);
|
||||
assert((unsigned)(pixels.topLeft.y + pixels.getHeight()) <= image.height);
|
||||
|
||||
if(pixels.getSize() == vec2i(image.width, image.height)) {
|
||||
void* ptrPixels;
|
||||
switch(image.format) {
|
||||
case FMT_Grey:
|
||||
ptrPixels = (void*)image.grey; break;
|
||||
case FMT_Alpha:
|
||||
ptrPixels = (void*)image.grey; break;
|
||||
case FMT_RGB:
|
||||
ptrPixels = (void*)image.rgb; break;
|
||||
case FMT_RGBA:
|
||||
ptrPixels = (void*)image.rgba; break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
|
||||
glTexSubImage2D(GL_TEXTURE_2D, lod, pixels.topLeft.x, pixels.topLeft.y, pixels.getWidth(), pixels.getHeight(), format, GL_UNSIGNED_BYTE, ptrPixels);
|
||||
}
|
||||
else if((unsigned)pixels.getWidth() == image.width) {
|
||||
assert(pixels.topLeft.x == 0);
|
||||
|
||||
void* ptrPixels;
|
||||
switch(image.format) {
|
||||
case FMT_Grey:
|
||||
ptrPixels = (void*)&image.get_grey(0, pixels.topLeft.y); break;
|
||||
case FMT_Alpha:
|
||||
ptrPixels = (void*)&image.get_alpha(0, pixels.topLeft.y); break;
|
||||
case FMT_RGB:
|
||||
ptrPixels = (void*)&image.get_rgb(0, pixels.topLeft.y); break;
|
||||
case FMT_RGBA:
|
||||
ptrPixels = (void*)&image.get_rgba(0, pixels.topLeft.y); break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
|
||||
glTexSubImage2D(GL_TEXTURE_2D, lod, pixels.topLeft.x, pixels.topLeft.y, pixels.getWidth(), pixels.getHeight(), format, GL_UNSIGNED_BYTE, ptrPixels);
|
||||
}
|
||||
else {
|
||||
//Must load line by line
|
||||
for(unsigned y = pixels.topLeft.y, endY = pixels.botRight.y; y < endY; ++y) {
|
||||
void* ptrPixels;
|
||||
switch(image.format) {
|
||||
case FMT_Grey:
|
||||
ptrPixels = (void*)&image.get_grey(pixels.topLeft.x, y); break;
|
||||
case FMT_Alpha:
|
||||
ptrPixels = (void*)&image.get_alpha(pixels.topLeft.x, y); break;
|
||||
case FMT_RGB:
|
||||
ptrPixels = (void*)&image.get_rgb(pixels.topLeft.x, y); break;
|
||||
case FMT_RGBA:
|
||||
ptrPixels = (void*)&image.get_rgba(pixels.topLeft.x, y); break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
|
||||
glTexSubImage2D(GL_TEXTURE_2D, lod, pixels.topLeft.x, y, pixels.getWidth(), 1, format, GL_UNSIGNED_BYTE, ptrPixels);
|
||||
}
|
||||
}
|
||||
devices.render->reportErrors("filling sub image");
|
||||
}
|
||||
|
||||
void GLTexture::loadStart(Image& image, bool mipmap, bool cachePixels, unsigned lod) {
|
||||
devices.render->reportErrors();
|
||||
loaded = true;
|
||||
|
||||
if(lod == 0) {
|
||||
//Destroy old texture
|
||||
if(glName != 0) {
|
||||
glDeleteTextures(1, &glName);
|
||||
glName = 0;
|
||||
}
|
||||
|
||||
//Generate a new texture
|
||||
glGenTextures(1,&glName);
|
||||
|
||||
//Store texture size
|
||||
size = vec2i(image.width, image.height);
|
||||
|
||||
pixelDepth = ColorDepths[image.format];
|
||||
|
||||
if(cachePixels)
|
||||
activePixels.resize(size.x * size.y);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, glName);
|
||||
if(lod == 0)
|
||||
createTexture(image, mipmap);
|
||||
}
|
||||
|
||||
void GLTexture::loadPartial(Image& image, const recti& pixels, bool cachePixels, unsigned lod) {
|
||||
glBindTexture(GL_TEXTURE_2D, glName);
|
||||
loadTexture(image, pixels, lod);
|
||||
|
||||
if(cachePixels && lod == 0) {
|
||||
for(int y = pixels.topLeft.y; y < pixels.getHeight(); ++y) {
|
||||
for(int x = pixels.topLeft.x; x < pixels.getWidth(); ++x) {
|
||||
unsigned char a = image.get(x, y).a;
|
||||
activePixels[y * size.width + x] = a != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GLTexture::loadFinish(bool mipmap, unsigned lod) {
|
||||
devices.render->reportErrors();
|
||||
glBindTexture(GL_TEXTURE_2D, glName);
|
||||
|
||||
hasMipMaps = mipmap;
|
||||
if(mipmap) {
|
||||
//Set linear mipmap filter
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
|
||||
if(lod == 0) {
|
||||
if(size.width * size.height > (1024 * 1024))
|
||||
glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
|
||||
else
|
||||
glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
|
||||
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
}
|
||||
}
|
||||
else if(lod == 0) {
|
||||
//Set linear filter
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
}
|
||||
else {
|
||||
//We're defining mipmap data now, switch back to mipmaping
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, lod);
|
||||
}
|
||||
|
||||
devices.render->reportErrors("finalizing texture");
|
||||
}
|
||||
|
||||
unsigned GLTexture::getTextureBytes() const {
|
||||
unsigned bytes = size.width * size.height * pixelDepth;
|
||||
if(hasMipMaps)
|
||||
bytes += bytes / 3;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void GLTexture::load(Image& image, bool mipmap, bool cachePixels) {
|
||||
loadStart(image, mipmap, cachePixels);
|
||||
loadPartial(image, recti(0,0, image.width, image.height), cachePixels);
|
||||
loadFinish(mipmap);
|
||||
}
|
||||
|
||||
void GLTexture::save(Image& image) const {
|
||||
image.resize(size.width, size.height, FMT_RGBA);
|
||||
glBindTexture(GL_TEXTURE_2D, glName);
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.rgba);
|
||||
}
|
||||
|
||||
bool GLTexture::isPixelActive(vec2i px) const {
|
||||
unsigned index = px.y * size.width + px.x;
|
||||
if(activePixels.empty())
|
||||
return true;
|
||||
if(index >= activePixels.size())
|
||||
return false;
|
||||
return activePixels[index];
|
||||
}
|
||||
|
||||
GLTexture::~GLTexture() {
|
||||
glDeleteTextures(1, &glName);
|
||||
}
|
||||
|
||||
bool GLCubeMap::isPixelActive(vec2i px) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLCubeMap::load(Image& image, bool mipmap, bool cachePixels) {
|
||||
devices.render->reportErrors();
|
||||
loaded = false;
|
||||
|
||||
vec2u tile = vec2u(image.width / 4, image.height / 3);
|
||||
if(tile.width != tile.height || tile.width == 0) {
|
||||
error("ERROR loading cubemap: Cubemap must have a 4:3 aspect ratio.");
|
||||
return;
|
||||
}
|
||||
|
||||
//Destroy old texture
|
||||
if(glName != 0) {
|
||||
glDeleteTextures(1, &glName);
|
||||
glName = 0;
|
||||
}
|
||||
|
||||
//Generate a new texture
|
||||
glGenTextures(1,&glName);
|
||||
|
||||
//Store texture size
|
||||
size = vec2i(image.width, image.height);
|
||||
loaded = true;
|
||||
|
||||
pixelDepth = ColorDepths[image.format];
|
||||
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, glName);
|
||||
|
||||
unsigned char* data = image.grey;
|
||||
|
||||
GLenum format;
|
||||
switch(image.format) {
|
||||
case FMT_Grey:
|
||||
format = GL_LUMINANCE; break;
|
||||
case FMT_Alpha:
|
||||
format = GL_ALPHA; break;
|
||||
case FMT_RGB:
|
||||
format = GL_RGB; break;
|
||||
case FMT_RGBA:
|
||||
format = GL_RGBA; break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
|
||||
auto getTile = [&](unsigned x, unsigned y) -> Image* {
|
||||
vec2u top = vec2u(tile.width * x, tile.height * y);
|
||||
return image.crop(rect<unsigned>(top, top + tile));
|
||||
};
|
||||
|
||||
auto* img = getTile(1,1);
|
||||
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, format, tile.width, tile.height, 0, format, GL_UNSIGNED_BYTE, img->grey);
|
||||
delete img;
|
||||
|
||||
img = getTile(2,1);
|
||||
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, format, tile.width, tile.height, 0, format, GL_UNSIGNED_BYTE, img->grey);
|
||||
delete img;
|
||||
|
||||
img = getTile(1,2);
|
||||
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, format, tile.width, tile.height, 0, format, GL_UNSIGNED_BYTE, img->grey);
|
||||
delete img;
|
||||
|
||||
img = getTile(3,1);
|
||||
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, format, tile.width, tile.height, 0, format, GL_UNSIGNED_BYTE, img->grey);
|
||||
delete img;
|
||||
|
||||
img = getTile(0,1);
|
||||
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, format, tile.width, tile.height, 0, format, GL_UNSIGNED_BYTE, img->grey);
|
||||
delete img;
|
||||
|
||||
img = getTile(1,0);
|
||||
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, format, tile.width, tile.height, 0, format, GL_UNSIGNED_BYTE, img->grey);
|
||||
delete img;
|
||||
|
||||
devices.render->reportErrors("loading cubemap images");
|
||||
|
||||
hasMipMaps = mipmap;
|
||||
if(mipmap) {
|
||||
//Set linear mipmap filter
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
|
||||
if(size.width * size.height > (1024 * 1024))
|
||||
glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
|
||||
else
|
||||
glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
|
||||
|
||||
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
|
||||
}
|
||||
else {
|
||||
//Set linear filter
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
}
|
||||
|
||||
devices.render->reportErrors("finalizing texture");
|
||||
}
|
||||
|
||||
unsigned GLCubeMap::getTextureBytes() const {
|
||||
unsigned bytes = size.width * size.height * pixelDepth * 6;
|
||||
if(hasMipMaps)
|
||||
bytes += bytes / 3;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void GLCubeMap::save(Image& image) const {}
|
||||
|
||||
void GLCubeMap::bind() {
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, glName);
|
||||
}
|
||||
|
||||
unsigned GLCubeMap::getID() const {
|
||||
return glName;
|
||||
}
|
||||
|
||||
GLCubeMap::GLCubeMap(Image& image, bool mipmap) : glName(0), pixelDepth(1) {
|
||||
loaded = false;
|
||||
type = TT_Cubemap;
|
||||
load(image, mipmap);
|
||||
}
|
||||
|
||||
GLCubeMap::GLCubeMap() : glName(0), pixelDepth(0) {
|
||||
loaded = false;
|
||||
type = TT_Cubemap;
|
||||
}
|
||||
|
||||
GLCubeMap::~GLCubeMap() {
|
||||
glDeleteTextures(1, &glName);
|
||||
}
|
||||
|
||||
void GLCubeMap::loadStart(Image& image, bool mipmap, bool cachePixels, unsigned lod) {
|
||||
load(image, mipmap);
|
||||
}
|
||||
|
||||
void GLCubeMap::loadPartial(Image& image, const recti& pixels, bool cachePixels, unsigned lod) {
|
||||
}
|
||||
|
||||
void GLCubeMap::loadFinish(bool mipmap, unsigned lod) {
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
#include "compat/gl.h"
|
||||
#include "render/texture.h"
|
||||
#include "image.h"
|
||||
#include "rect.h"
|
||||
#include <vector>
|
||||
|
||||
namespace render {
|
||||
class GLTexture : public Texture {
|
||||
GLuint glName;
|
||||
unsigned pixelDepth;
|
||||
std::vector<bool> activePixels;
|
||||
public:
|
||||
bool isPixelActive(vec2i px) const;
|
||||
void load(Image& image, bool mipmap = true, bool cachePixels = false);
|
||||
|
||||
void loadStart(Image& image, bool mipmap = true, bool cachePixels = false, unsigned lod = 0);
|
||||
void loadPartial(Image& image, const recti& pixels, bool cachePixels = false, unsigned lod = 0);
|
||||
void loadFinish(bool mipmap, unsigned lod = 0);
|
||||
unsigned getTextureBytes() const;
|
||||
|
||||
void save(Image& image) const;
|
||||
void bind();
|
||||
unsigned getID() const;
|
||||
|
||||
GLTexture(Image& image, bool mipmap = true, bool cachePixels = false);
|
||||
GLTexture();
|
||||
|
||||
~GLTexture();
|
||||
};
|
||||
|
||||
class GLCubeMap : public Texture {
|
||||
GLuint glName;
|
||||
unsigned pixelDepth;
|
||||
public:
|
||||
bool isPixelActive(vec2i px) const;
|
||||
void load(Image& image, bool mipmap = true, bool cachePixels = false);
|
||||
void loadStart(Image& image, bool mipmap = true, bool cachePixels = false, unsigned lod = 0);
|
||||
void loadPartial(Image& image, const recti& pixels, bool cachePixels = false, unsigned lod = 0);
|
||||
void loadFinish(bool mipmap, unsigned lod = 0);
|
||||
|
||||
unsigned getTextureBytes() const;
|
||||
|
||||
void save(Image& image) const;
|
||||
void bind();
|
||||
unsigned getID() const;
|
||||
|
||||
GLCubeMap(Image& image, bool mipmap = true);
|
||||
GLCubeMap();
|
||||
|
||||
~GLCubeMap();
|
||||
};
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user