Open source Star Ruler 2 source code!

This commit is contained in:
Lucas de Vries
2018-07-17 14:15:37 +02:00
commit cc307720ff
4342 changed files with 2365070 additions and 0 deletions
+97
View File
@@ -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;
}*/
}
+3
View File
@@ -0,0 +1,3 @@
class asIScriptEngine;
void printBindings(asIScriptEngine* engine, const char* filename);
File diff suppressed because it is too large Load Diff
+53
View File
@@ -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();
};
+3
View File
@@ -0,0 +1,3 @@
#pragma once
#include <GL/glew.h>
#include "GLFW/glfw3.h"
+7
View File
@@ -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
+39
View File
@@ -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; };
+33
View File
@@ -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
+164
View File
@@ -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);
};
+378
View File
@@ -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);
}
}
}
+149
View File
@@ -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
+261
View File
@@ -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();
+305
View File
@@ -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();
+15
View File
@@ -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);
+912
View File
@@ -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];
}
+125
View File
@@ -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);
+749
View File
@@ -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;
}
+85
View File
@@ -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
+398
View File
@@ -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);
};
+114
View File
@@ -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
+193
View File
@@ -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);
+74
View File
@@ -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);
}
+11
View File
@@ -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);
+831
View File
@@ -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();
}
+140
View File
@@ -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;
+480
View File
@@ -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));
}
};
};
+133
View File
@@ -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();
};
};
+891
View File
@@ -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
}
}
+848
View File
@@ -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();
}
+78
View File
@@ -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;
+106
View File
@@ -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
+66
View File
@@ -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);
+328
View File
@@ -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);
}
}
+13
View File
@@ -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();
+175
View File
@@ -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];
}
+54
View File
@@ -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();
+25
View File
@@ -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);
}
};
+12
View File
@@ -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); }
};
+10
View File
@@ -0,0 +1,10 @@
#include "main/references.h"
references devices;
references::references() : physics(nullptr), nodePhysics(nullptr), cloud(nullptr) {
}
namespace audio {
bool disableSFX = false;
};
+67
View File
@@ -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;
+329
View File
@@ -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
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include <string>
bool saveGame(const std::string& file);
bool loadGame(const std::string& file);
+862
View 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;
}
+48
View File
@@ -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();
+27
View File
@@ -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"
+11
View File
@@ -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);
}
+103
View File
@@ -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);
}
}
};
};
+51
View File
@@ -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);
}
}
};
};
+531
View File
@@ -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;
}
};
+97
View File
@@ -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
+248
View File
@@ -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);
};
+34
View File
@@ -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
+114
View File
@@ -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);
};
+886
View 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;
}
+83
View File
@@ -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();
+263
View File
@@ -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;
}
+45
View File
@@ -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
+325
View File
@@ -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();
};
+157
View File
@@ -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();
}
}
+176
View File
@@ -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;
}
+26
View File
@@ -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;
};
+218
View File
@@ -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() {}
};
};
+569
View File
@@ -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;
}
};
+6
View File
@@ -0,0 +1,6 @@
#pragma once
#include "os/driver.h"
namespace os {
OSDriver* getGLFWDriver();
};
+96
View File
@@ -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
};
};
+16
View File
@@ -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
+504
View File
@@ -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;
}
+149
View File
@@ -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);
};
+366
View 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;
}
};
+52
View File
@@ -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();
};
+588
View File
@@ -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;
}
};
+88
View File
@@ -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);
};
+127
View File
@@ -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();
}
};
+36
View File
@@ -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);
};
};
+261
View File
@@ -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;
}
};
+9
View File
@@ -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);
};
+409
View File
@@ -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;
}
};
+109
View File
@@ -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();
};
};
+164
View File
@@ -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() {}
};
};
+62
View File
@@ -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);
};
+368
View File
@@ -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;
}
};
+487
View File
@@ -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
+6
View File
@@ -0,0 +1,6 @@
#pragma once
#include "render/driver.h"
namespace render {
RenderDriver* createGLDriver();
};
+137
View File
@@ -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;
}
};
+28
View File
@@ -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();
};
};
+238
View File
@@ -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);
}
};
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "render/render_mesh.h"
#include "mesh.h"
namespace render {
RenderMesh* createGLMesh(const Mesh& mesh);
};
+925
View File
@@ -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);
}
};
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "render/shader.h"
namespace render {
Shader* createGLShader();
ShaderProgram* createGLShaderProgram(const char* vertex_shader, const char* fragment_shader);
};
+423
View File
@@ -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) {
}
};
+53
View File
@@ -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