Open source Star Ruler 2 source code!
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
#include "design/effector.h"
|
||||
#include "design/design.h"
|
||||
#include "util/basic_type.h"
|
||||
#include "util/hex_grid.h"
|
||||
|
||||
class SaveMessage;
|
||||
|
||||
enum HexFlags {
|
||||
HF_Active = 0x1,
|
||||
HF_Destroyed = 0x2,
|
||||
HF_NoHP = 0x4,
|
||||
HF_Gone = 0x8,
|
||||
HF_NoRepair = 0x10,
|
||||
};
|
||||
|
||||
enum DamageFlags {
|
||||
DF_DestroyedObject = 0x40000000,
|
||||
};
|
||||
|
||||
namespace net {
|
||||
struct Message;
|
||||
};
|
||||
|
||||
class CScriptAny;
|
||||
class Blueprint {
|
||||
public:
|
||||
struct HexStatus {
|
||||
unsigned char flags;
|
||||
unsigned char hp;
|
||||
};
|
||||
|
||||
struct SysStatus {
|
||||
unsigned short workingHexes;
|
||||
EffectStatus status;
|
||||
};
|
||||
|
||||
const Design* design;
|
||||
HexStatus* hexes;
|
||||
SysStatus* subsystems;
|
||||
BasicType* states;
|
||||
double* effectorStates;
|
||||
EffectorTarget* effectorTargets;
|
||||
double currentHP;
|
||||
double quadrantHP[4];
|
||||
float removedHP;
|
||||
double shipEffectiveness;
|
||||
unsigned statusID;
|
||||
unsigned short destroyedHexes;
|
||||
bool designChanged;
|
||||
bool hpDelta;
|
||||
bool holdFire;
|
||||
float hpFactor;
|
||||
vec2i repairingHex;
|
||||
CScriptAny** data;
|
||||
|
||||
HexStatus* getHexStatus(unsigned index);
|
||||
HexStatus* getHexStatus(unsigned x, unsigned y);
|
||||
SysStatus* getSysStatus(unsigned index);
|
||||
SysStatus* getSysStatus(unsigned x, unsigned y);
|
||||
CScriptAny* getHookData(unsigned index);
|
||||
|
||||
Blueprint();
|
||||
|
||||
void destroy(Object* obj);
|
||||
void preClear();
|
||||
~Blueprint();
|
||||
|
||||
void init(Object* obj);
|
||||
float think(Object* obj, double time);
|
||||
void ownerChange(Object* obj, Empire* prevEmpire, Empire* newEmpire);
|
||||
|
||||
bool hasTagActive(int index);
|
||||
double getTagEfficiency(int index, bool ignoreInactive = true);
|
||||
double getEfficiencySum(int variable, int tag = -1, bool ignoreInactive = true);
|
||||
double getEfficiencyFactor(int variable, int tag = -1, bool ignoreInactive = true);
|
||||
|
||||
bool doesAutoTarget(Object* obj, Object* target);
|
||||
bool canTarget(Object* obj, Object* target);
|
||||
|
||||
void target(Object* obj, Object* target, TargetFlags flags = TF_Target);
|
||||
void target(Object* obj, unsigned efftrIndex, Object* target, TargetFlags flags = TF_Target);
|
||||
void target(Object* obj, const Subsystem* sys, Object* target, TargetFlags flags = TF_Target);
|
||||
void clearTracking(Object* obj);
|
||||
Object* getCombatTarget();
|
||||
|
||||
vec3d getOptimalFacing(int sysVariable, int tag = -1, bool ignoreInactive = true);
|
||||
|
||||
void damage(Object* obj, DamageEvent& evt, const vec2u& hex);
|
||||
void damage_internal(Object* obj, DamageEvent& evt, const vec2u& hex);
|
||||
|
||||
void damage(Object* obj, DamageEvent& evt, const vec2u& hex, HexGridAdjacency dir, bool runGlobal);
|
||||
void damage(Object* obj, DamageEvent& evt, const vec2u& hex, bool runGlobal);
|
||||
bool globalDamage(Object* obj, DamageEvent& evt);
|
||||
|
||||
void damage(Object* obj, DamageEvent& evt, const vec2d& direction);
|
||||
void damage(Object* obj, DamageEvent& evt, double position, const vec2d& direction);
|
||||
void damage(Object* obj, DamageEvent& evt, const vec2u& position, const vec2d& endPoint);
|
||||
void create(Object* obj, const Design* design);
|
||||
void start(Object* obj, bool fromRetrofit = false);
|
||||
void retrofit(Object* obj, const Design* design);
|
||||
|
||||
double repair(Object* obj, double amount);
|
||||
double repair(Object* obj, const vec2u& hex, double amount);
|
||||
|
||||
void sendDetails(Object* obj, net::Message& msg);
|
||||
void recvDetails(Object* obj, net::Message& msg);
|
||||
|
||||
bool sendDelta(Object* obj, net::Message& msg);
|
||||
void recvDelta(Object* obj, net::Message& msg);
|
||||
|
||||
void save(Object* obj, SaveMessage& file);
|
||||
void load(Object* obj, SaveMessage& file);
|
||||
};
|
||||
@@ -0,0 +1,886 @@
|
||||
#include "obj/lock.h"
|
||||
#include "obj/universe.h"
|
||||
#include "compat/misc.h"
|
||||
#include "util/random.h"
|
||||
#include "str_util.h"
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
#include "network/network_manager.h"
|
||||
#include "memory/AllocOnlyPool.h"
|
||||
#include "processing.h"
|
||||
#include <deque>
|
||||
#include <assert.h>
|
||||
#include "empire.h"
|
||||
|
||||
#ifdef PROFILE_PROCESSING
|
||||
namespace processing {
|
||||
extern Threaded(ProcessingData*) threadProc;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef LOCK_SWITCH_CHANCE
|
||||
#define LOCK_SWITCH_CHANCE 0.1
|
||||
#endif
|
||||
#ifndef TARGET_UPDATE_INTERVAL
|
||||
#define TARGET_UPDATE_INTERVAL 0.2
|
||||
#endif
|
||||
#ifndef SCRIPT_GC_INTERVAL
|
||||
#define SCRIPT_GC_INTERVAL 0.05
|
||||
#endif
|
||||
|
||||
threads::atomic_int tickingLocks;
|
||||
threads::atomic_int remainingMessages, queuedChildren;
|
||||
std::vector<LockGroup*> lockGroups;
|
||||
|
||||
std::deque<LockGroup*> lockQueue;
|
||||
|
||||
#ifdef PROFILE_LOCKS
|
||||
threads::Mutex lockQueueLock("lockQueueLock");
|
||||
threads::Mutex lockGlobalLock("lockGlobalLock");
|
||||
threads::Mutex switchedLock("switchedLock");
|
||||
#else
|
||||
threads::Mutex lockQueueLock;
|
||||
threads::Mutex lockGlobalLock;
|
||||
threads::Mutex switchedLock;
|
||||
#endif
|
||||
|
||||
std::vector<Object*> switchedObjects;
|
||||
Threaded(LockGroup*) activeLockGroup = 0;
|
||||
Threaded(Object*) activeObject = 0;
|
||||
double nextTargetUpdateTime = 0;
|
||||
double nextScriptGCTime = 0;
|
||||
double prevTick_s = 0;
|
||||
|
||||
threads::Mutex delayedReleaseLock;
|
||||
std::vector<Object*> delayedReleaseObjects;
|
||||
|
||||
void delayObjectRelease(Object* obj) {
|
||||
threads::Lock lock(delayedReleaseLock);
|
||||
delayedReleaseObjects.push_back(obj);
|
||||
}
|
||||
|
||||
void performDelayedObjectReleases() {
|
||||
threads::Lock lock(delayedReleaseLock);
|
||||
for(auto it = delayedReleaseObjects.begin(), end = delayedReleaseObjects.end(); it != end; ++it)
|
||||
(*it)->drop();
|
||||
delayedReleaseObjects.clear();
|
||||
}
|
||||
|
||||
threads::atomic_int switches;
|
||||
double statTime = 0.0;
|
||||
bool switchStage = false;
|
||||
|
||||
memory::AllocOnlyRegion<threads::Mutex> objMessagePool(4096 * sizeof(void*));
|
||||
|
||||
void* ObjectMessage::operator new(size_t bytes) {
|
||||
return objMessagePool.alloc(bytes);
|
||||
}
|
||||
|
||||
void ObjectMessage::operator delete(void* p) {
|
||||
objMessagePool.dealloc(p);
|
||||
}
|
||||
|
||||
unsigned getLockCount() {
|
||||
return (unsigned)lockGroups.size();
|
||||
}
|
||||
|
||||
LockGroup* getLock(unsigned index) {
|
||||
return lockGroups[index];
|
||||
}
|
||||
|
||||
LockGroup* getActiveLockGroup() {
|
||||
return activeLockGroup;
|
||||
}
|
||||
|
||||
Object* getActiveObject() {
|
||||
return activeObject;
|
||||
}
|
||||
|
||||
void setActiveObject(Object* obj) {
|
||||
activeObject = obj;
|
||||
}
|
||||
|
||||
threads::Mutex clearLock;
|
||||
std::vector<Object*> queuedClearObjects;
|
||||
|
||||
void queueObjectClear(Object* obj) {
|
||||
threads::Lock lock(clearLock);
|
||||
obj->grab();
|
||||
queuedClearObjects.push_back(obj);
|
||||
}
|
||||
|
||||
void handleObjectClears() {
|
||||
if(queuedClearObjects.empty())
|
||||
return;
|
||||
|
||||
threads::Lock lock(clearLock);
|
||||
for(auto i = queuedClearObjects.begin(), end = queuedClearObjects.end(); i != end; ++i) {
|
||||
Object* obj = *i;
|
||||
obj->clearScripts();
|
||||
obj->drop();
|
||||
}
|
||||
queuedClearObjects.clear();
|
||||
}
|
||||
|
||||
bool printNextLockStats = false;
|
||||
void printLockStats() {
|
||||
printNextLockStats = true;
|
||||
}
|
||||
|
||||
LockGroup::LockGroup()
|
||||
: tickIndex(0) {
|
||||
#ifdef PROFILE_LOCKS
|
||||
mutex.name = "LockGroup "+toString(getLockCount());
|
||||
mutex.observed = true;
|
||||
|
||||
addMutex.name = mutex.name+" Add Mutex";
|
||||
addMutex.observed = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool LockGroup::hasLock() {
|
||||
return mutex.hasLock();
|
||||
}
|
||||
|
||||
void tickSwitchedObjects(double time) {
|
||||
switchStage = true;
|
||||
|
||||
foreach(it, switchedObjects) {
|
||||
Object* obj = *it;
|
||||
ObjectLock olock(obj);
|
||||
|
||||
if(obj->isValid()) {
|
||||
//Only tick if the object wants to be ticked
|
||||
if(time > obj->nextTick && !obj->getFlag(objStopTicking)) {
|
||||
activeObject = obj;
|
||||
double delay = obj->think(time - obj->lastTick);
|
||||
|
||||
obj->lastTick = time;
|
||||
obj->nextTick = time + delay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switchedObjects.clear();
|
||||
switchStage = false;
|
||||
activeObject = nullptr;
|
||||
}
|
||||
|
||||
double lastLockGlobalUpdate = 0.0;
|
||||
bool updateLockGlobals(double time) {
|
||||
if(lockGroups.empty() || Object::GALAXY_CREATION)
|
||||
return false;
|
||||
if(time < lastLockGlobalUpdate + 0.001)
|
||||
return false;
|
||||
|
||||
lockGlobalLock.lock();
|
||||
lockQueueLock.lock();
|
||||
|
||||
double prevUpdate = lastLockGlobalUpdate;
|
||||
lastLockGlobalUpdate = time;
|
||||
|
||||
if(lockQueue.size() + tickingLocks != 0) {
|
||||
lockGlobalLock.release();
|
||||
lockQueueLock.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
prevTick_s = time - prevUpdate;
|
||||
lockQueueLock.release();
|
||||
|
||||
#ifdef PROFILE_PROCESSING
|
||||
double startTime = devices.driver->getAccurateTime();
|
||||
processing::threadProc->globalCount += 1;
|
||||
processing::threadProc->switchedCount += switchedObjects.size();
|
||||
#endif
|
||||
|
||||
//TODO: This should probably be threaded and not
|
||||
//block the entire tick cycle.
|
||||
tickSwitchedObjects(time);
|
||||
|
||||
#ifdef PROFILE_PROCESSING
|
||||
double curTime = devices.driver->getAccurateTime();
|
||||
processing::threadProc->switchedTime += curTime - startTime;
|
||||
startTime = curTime;
|
||||
#endif
|
||||
|
||||
bool doGC = nextScriptGCTime < devices.driver->getFrameTime();
|
||||
if(doGC)
|
||||
devices.scripts.server->pauseScriptThreads();
|
||||
processing::pauseMessageHandling();
|
||||
|
||||
//Record stats
|
||||
if(statTime < time) {
|
||||
statTime = time + 1.0;
|
||||
|
||||
for(size_t i = 0, cnt = lockGroups.size(); i < cnt; ++i) {
|
||||
LockGroup* grp = lockGroups[i];
|
||||
if(printNextLockStats) {
|
||||
print("LockGroup %d: %d objects", i, grp->objects.size());
|
||||
print(" Bad Locks: %d / %d (%.0g%)",
|
||||
grp->badLocks, (grp->goodLocks + grp->badLocks),
|
||||
(double)grp->badLocks / (double)(grp->goodLocks
|
||||
+ grp->badLocks) * 100.0);
|
||||
print(" Gained %d objects, Lost %d objects.",
|
||||
grp->gainedObjects, grp->lostObjects);
|
||||
}
|
||||
|
||||
grp->gainedObjects = 0;
|
||||
grp->lostObjects = 0;
|
||||
grp->goodLocks = 0;
|
||||
grp->badLocks = 0;
|
||||
}
|
||||
|
||||
if(printNextLockStats) {
|
||||
print("Total Switches: %d\n", switches);
|
||||
printNextLockStats = false;
|
||||
}
|
||||
switches = 0;
|
||||
}
|
||||
|
||||
//Update universe children
|
||||
if(devices.universe)
|
||||
devices.universe->doQueued();
|
||||
|
||||
//Update target system periodically
|
||||
if(nextTargetUpdateTime < time) {
|
||||
#ifdef PROFILE_PROCESSING
|
||||
startTime = devices.driver->getAccurateTime();
|
||||
processing::threadProc->targetUpdates += 1;
|
||||
#endif
|
||||
|
||||
nextTargetUpdateTime = time + TARGET_UPDATE_INTERVAL;
|
||||
|
||||
foreach(it, devices.universe->children) {
|
||||
Object* obj = *it;
|
||||
if(obj->isValid())
|
||||
obj->updateTargets();
|
||||
}
|
||||
|
||||
unsigned childCount = (unsigned)devices.universe->children.size();
|
||||
for(unsigned i = 0, cnt = (childCount + 255) / 256; i < cnt; ++i)
|
||||
devices.universe->setRandomTarget(devices.universe->children[randomi(0, childCount-1)]->targets[randomi(0, OBJ_TARGETS-1)]);
|
||||
|
||||
#ifdef PROFILE_PROCESSING
|
||||
curTime = devices.driver->getAccurateTime();
|
||||
processing::threadProc->targUpdateTime += curTime - startTime;
|
||||
#endif
|
||||
}
|
||||
|
||||
//We need to process deletion of objects' script classes while the server is paused, or component calls could randomly crash
|
||||
handleObjectClears();
|
||||
|
||||
//Do server script garbage collection here,
|
||||
//so we don't need a lock around object tick calls
|
||||
if(doGC) {
|
||||
nextScriptGCTime = devices.driver->getFrameTime() + SCRIPT_GC_INTERVAL;
|
||||
#ifdef PROFILE_PROCESSING
|
||||
double gcStart = devices.driver->getAccurateTime();
|
||||
#endif
|
||||
int mode = devices.scripts.server->garbageCollect();
|
||||
#ifdef PROFILE_PROCESSING
|
||||
double gcEnd = devices.driver->getAccurateTime();
|
||||
if(gcEnd >= gcStart + 0.01)
|
||||
print("Server GC took %dms (mode %d)", (int)((gcEnd - gcStart) * 1000.0), mode);
|
||||
#endif
|
||||
|
||||
// Do delayed object releases here too
|
||||
performDelayedObjectReleases();
|
||||
}
|
||||
|
||||
processing::resumeMessageHandling();
|
||||
if(doGC)
|
||||
devices.scripts.server->resumeScriptThreads();
|
||||
|
||||
//Queue up all locks for ticking again
|
||||
lockQueueLock.lock();
|
||||
foreach(it, lockGroups)
|
||||
lockQueue.push_back(*it);
|
||||
lockQueueLock.release();
|
||||
lockGlobalLock.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
void tickRandomMessages(int limit) {
|
||||
if(lockGroups.empty())
|
||||
return;
|
||||
if(activeLockGroup)
|
||||
throw "Can't lock multiple lock groups.";
|
||||
|
||||
unsigned count = lockGroups.size();
|
||||
unsigned off = randomi(0, count-1);
|
||||
|
||||
for(unsigned i = 0; i < count; ++i) {
|
||||
LockGroup* lock = lockGroups[(i+off) % count];
|
||||
|
||||
if(!lock->messages.empty() && !lock->hasLock()) {
|
||||
devices.scripts.server->threadedCallMutex.readLock();
|
||||
if(lock->mutex.try_lock()) {
|
||||
activeLockGroup = lock;
|
||||
lock->processMessages(limit);
|
||||
activeLockGroup = 0;
|
||||
lock->mutex.release();
|
||||
}
|
||||
devices.scripts.server->threadedCallMutex.release();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void acquireRandomChildren() {
|
||||
if(lockGroups.empty())
|
||||
return;
|
||||
if(activeLockGroup)
|
||||
throw "Can't lock multiple lock groups.";
|
||||
|
||||
LockGroup* lock = lockGroups[randomi(0,(int)lockGroups.size()-1)];
|
||||
|
||||
if(!lock->addQueue.empty()) {
|
||||
devices.scripts.server->threadedCallMutex.readLock();
|
||||
lock->acquireChildren();
|
||||
devices.scripts.server->threadedCallMutex.release();
|
||||
}
|
||||
}
|
||||
|
||||
void tickLockMessages(LockGroup* lock, int limit) {
|
||||
if(activeLockGroup)
|
||||
throw "Can't lock multiple lock groups.";
|
||||
|
||||
if(!lock->messages.empty()) {
|
||||
devices.scripts.server->threadedCallMutex.readLock();
|
||||
if(lock->mutex.try_lock()) {
|
||||
activeLockGroup = lock;
|
||||
lock->processMessages(limit);
|
||||
activeLockGroup = 0;
|
||||
lock->mutex.release();
|
||||
}
|
||||
devices.scripts.server->threadedCallMutex.release();
|
||||
}
|
||||
}
|
||||
|
||||
//Find a random lock group that's still queued up for ticking and tick it, or
|
||||
//update global data and requeue if the queue is empty.
|
||||
bool tickRandomLock(double time, int limit) {
|
||||
if(lockQueue.empty()) {
|
||||
if(tickingLocks == 0)
|
||||
return updateLockGlobals(time);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
lockQueueLock.lock();
|
||||
LockGroup* lock = nullptr;
|
||||
if(!lockQueue.empty()) {
|
||||
for(unsigned i = 0, cnt = lockQueue.size(); i < cnt; ++i) {
|
||||
lock = lockQueue.front();
|
||||
lockQueue.pop_front();
|
||||
if(!lock->hasLock())
|
||||
break;
|
||||
lockQueue.push_back(lock);
|
||||
lock = nullptr;
|
||||
}
|
||||
if(lock)
|
||||
++tickingLocks;
|
||||
}
|
||||
lockQueueLock.release();
|
||||
|
||||
if(!lock)
|
||||
return false;
|
||||
|
||||
#ifdef TRACE_GC_LOCK
|
||||
devices.scripts.server->markGCImpossible();
|
||||
#endif
|
||||
|
||||
if(!lock->process(time, limit)) {
|
||||
lockQueueLock.lock();
|
||||
lockQueue.push_back(lock);
|
||||
lockQueueLock.release();
|
||||
}
|
||||
|
||||
#ifdef TRACE_GC_LOCK
|
||||
devices.scripts.server->markGCPossible();
|
||||
#endif
|
||||
|
||||
--tickingLocks;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LockGroup::addMessage(ObjectMessage* message) {
|
||||
remainingMessages++;
|
||||
messageLock.lock();
|
||||
messages.push_back(message);
|
||||
messageLock.release();
|
||||
}
|
||||
|
||||
void LockGroup::add(Object* obj) {
|
||||
obj->grab();
|
||||
|
||||
addMutex.lock();
|
||||
++queuedChildren;
|
||||
addQueue.push_back(obj);
|
||||
addMutex.release();
|
||||
}
|
||||
|
||||
void LockGroup::processMessages(int limit) {
|
||||
while(!messages.empty() && limit--) {
|
||||
messageLock.lock();
|
||||
if(messages.empty()) {
|
||||
messageLock.release();
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectMessage* msg = messages.front();
|
||||
messages.pop_front();
|
||||
messageLock.release();
|
||||
|
||||
if(msg->object->lockGroup == this) {
|
||||
//Only execute messages that are
|
||||
//actually for this group
|
||||
Object* prevObj = activeObject;
|
||||
activeObject = msg->object;
|
||||
msg->process();
|
||||
activeObject = prevObj;
|
||||
delete msg;
|
||||
remainingMessages--;
|
||||
}
|
||||
else {
|
||||
//Sometimes, an object will have switched
|
||||
//lock groups before we get to a message
|
||||
LockGroup* lockGroup = msg->object->lockGroup;
|
||||
if(lockGroup) {
|
||||
lockGroup->addMessage(msg);
|
||||
remainingMessages--;
|
||||
}
|
||||
else {
|
||||
//The object was trashed, we don't care
|
||||
//about this message anymore
|
||||
delete msg;
|
||||
remainingMessages--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LockGroup::acquireChildren() {
|
||||
if(addQueue.empty())
|
||||
return;
|
||||
|
||||
lockGroup(this);
|
||||
|
||||
addMutex.lock();
|
||||
int offset = 0;
|
||||
for(int i = 0, size = (int)addQueue.size(); i < size; ++i) {
|
||||
Object* obj = addQueue[i];
|
||||
|
||||
if(auto* messages = obj->fetchMessages()) {
|
||||
DeferredObjMessage* prev = nullptr;
|
||||
while(messages) {
|
||||
messages->prev = prev;
|
||||
prev = messages;
|
||||
messages = messages->next;
|
||||
}
|
||||
|
||||
messages = prev;
|
||||
while(messages) {
|
||||
auto* msg = messages;
|
||||
|
||||
Object* prevObj = activeObject;
|
||||
activeObject = msg->msg->object;
|
||||
msg->msg->process();
|
||||
activeObject = prevObj;
|
||||
|
||||
delete msg->msg;
|
||||
messages = msg->prev;
|
||||
delete msg;
|
||||
}
|
||||
}
|
||||
|
||||
//Remove objects we already have or that are not ours
|
||||
//any longer.
|
||||
if(obj->originalLock == this || obj->lockGroup != this) {
|
||||
++offset;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Wait for the other group to release it
|
||||
if(obj->originalLock != 0) {
|
||||
//Since we're skipping this tick, put it in the switched
|
||||
//objects queue to tick it.
|
||||
switchedLock.lock();
|
||||
switchedObjects.push_back(obj);
|
||||
switchedLock.release();
|
||||
|
||||
if(offset > 0)
|
||||
addQueue[i - offset] = obj;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Don't do anything until the object has started ticking
|
||||
if(obj->getFlag(objStopTicking)) {
|
||||
if(offset > 0)
|
||||
addQueue[i - offset] = obj;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Add object to list
|
||||
objects.push_back(obj);
|
||||
obj->originalLock = this;
|
||||
if(devices.network->isServer && devices.network->hasSyncedClients)
|
||||
devices.network->sendObject(obj);
|
||||
++offset;
|
||||
}
|
||||
|
||||
queuedChildren -= offset;
|
||||
addQueue.resize(addQueue.size() - offset);
|
||||
addMutex.release();
|
||||
|
||||
unlockGroup(this);
|
||||
}
|
||||
|
||||
bool LockGroup::process(double time, int limit) {
|
||||
#ifdef OBJECT_LOCK_NAGGING
|
||||
if(activeLockGroup && activeLockGroup != this)
|
||||
throw "Trying to process a LockGroup with an object locked.";
|
||||
#endif
|
||||
lockGroup(this);
|
||||
|
||||
if(tickIndex >= objects.size()) {
|
||||
//Add queued objects
|
||||
addMutex.lock();
|
||||
int offset = 0;
|
||||
for(int i = 0, size = (int)addQueue.size(); i < size; ++i) {
|
||||
Object* obj = addQueue[i];
|
||||
|
||||
if(auto* messages = obj->fetchMessages()) {
|
||||
DeferredObjMessage* prev = nullptr;
|
||||
while(messages) {
|
||||
messages->prev = prev;
|
||||
prev = messages;
|
||||
messages = messages->next;
|
||||
}
|
||||
|
||||
messages = prev;
|
||||
while(messages) {
|
||||
auto* msg = messages;
|
||||
|
||||
Object* prevObj = activeObject;
|
||||
activeObject = msg->msg->object;
|
||||
msg->msg->process();
|
||||
activeObject = prevObj;
|
||||
|
||||
delete msg->msg;
|
||||
messages = msg->prev;
|
||||
delete msg;
|
||||
}
|
||||
}
|
||||
|
||||
//Remove objects we already have or that are not ours
|
||||
//any longer.
|
||||
if(obj->originalLock == this || obj->lockGroup != this) {
|
||||
++offset;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Wait for the other group to release it
|
||||
if(obj->originalLock != 0) {
|
||||
//Since we're skipping this tick, put it in the switched
|
||||
//objects queue to tick it.
|
||||
switchedLock.lock();
|
||||
switchedObjects.push_back(obj);
|
||||
switchedLock.release();
|
||||
|
||||
if(offset > 0)
|
||||
addQueue[i - offset] = obj;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Don't do anything until the object has started ticking
|
||||
if(obj->getFlag(objStopTicking)) {
|
||||
if(offset > 0)
|
||||
addQueue[i - offset] = obj;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Add object to list
|
||||
objects.push_back(obj);
|
||||
obj->originalLock = this;
|
||||
if(devices.network->isServer && devices.network->hasSyncedClients)
|
||||
devices.network->sendObject(obj);
|
||||
++offset;
|
||||
}
|
||||
|
||||
queuedChildren -= offset;
|
||||
addQueue.resize(addQueue.size() - offset);
|
||||
addMutex.release();
|
||||
|
||||
//Reset ticking
|
||||
tickIndex = 0;
|
||||
}
|
||||
|
||||
#ifdef PROFILE_PROCESSING
|
||||
double procStart;
|
||||
int procType = -1;
|
||||
int procCount = 0;
|
||||
#endif
|
||||
|
||||
processMessages();
|
||||
|
||||
bool isNetServer = devices.network->isServer;
|
||||
|
||||
//Tick objects
|
||||
int i = 0;
|
||||
unsigned char rnd = (unsigned char)randomi();
|
||||
while(tickIndex < objects.size()) {
|
||||
//Caller can limit amount of ticked objects
|
||||
if(i >= limit)
|
||||
break;
|
||||
|
||||
//Break when a priority lock is detected
|
||||
if(priorityLocks.get() > 0)
|
||||
break;
|
||||
|
||||
//Tick object
|
||||
Object* obj = objects[tickIndex];
|
||||
|
||||
if(obj->lockGroup != this) {
|
||||
//Ignore locks that have switched already
|
||||
obj->originalLock = 0;
|
||||
objects[tickIndex] = objects.back();
|
||||
objects.pop_back();
|
||||
}
|
||||
else if(!obj->isValid()) {
|
||||
//Remove invalid objects from the list
|
||||
obj->originalLock = 0;
|
||||
obj->drop();
|
||||
objects[tickIndex] = objects.back();
|
||||
objects.pop_back();
|
||||
}
|
||||
else {
|
||||
//Only tick if the object wants to be ticked
|
||||
if((time > obj->nextTick || obj->getFlag(objWakeUp)) && !obj->getFlag(objStopTicking)) {
|
||||
#ifdef PROFILE_PROCESSING
|
||||
if(obj->type->id != procType) {
|
||||
double curTime = devices.driver->getAccurateTime();
|
||||
if(procType != -1) {
|
||||
processing::threadProc->measureType(
|
||||
procType,
|
||||
curTime - procStart,
|
||||
procCount);
|
||||
}
|
||||
procStart = curTime;
|
||||
procType = obj->type->id;
|
||||
procCount = 0;
|
||||
}
|
||||
++procCount;
|
||||
#endif
|
||||
if(obj->deferredMessages) {
|
||||
if(auto* messages = obj->fetchMessages()) {
|
||||
DeferredObjMessage* prev = nullptr;
|
||||
while(messages) {
|
||||
messages->prev = prev;
|
||||
prev = messages;
|
||||
messages = messages->next;
|
||||
}
|
||||
|
||||
messages = prev;
|
||||
while(messages) {
|
||||
auto* msg = messages;
|
||||
|
||||
Object* prevObj = activeObject;
|
||||
activeObject = msg->msg->object;
|
||||
msg->msg->process();
|
||||
activeObject = prevObj;
|
||||
|
||||
delete msg->msg;
|
||||
messages = msg->prev;
|
||||
delete msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activeObject = obj;
|
||||
double delay = obj->think(time - obj->lastTick);
|
||||
|
||||
rnd ^= (unsigned char)obj->id;
|
||||
delay *= 0.85f + ((float)rnd / 255.f) * 0.15f;
|
||||
|
||||
obj->lastTick = time;
|
||||
obj->nextTick = time + delay;
|
||||
|
||||
if(isNetServer)
|
||||
devices.network->sendObjectDelta(obj);
|
||||
|
||||
++i;
|
||||
|
||||
if(!messages.empty())
|
||||
processMessages();
|
||||
|
||||
//We need to sleep periodically to guarantee progress at high cpu utilization
|
||||
if(i % 1024 == 0) {
|
||||
unlockGroup(this);
|
||||
threads::sleep(1);
|
||||
lockGroup(this);
|
||||
}
|
||||
}
|
||||
|
||||
++tickIndex;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef PROFILE_PROCESSING
|
||||
if(procType != -1) {
|
||||
double curTime = devices.driver->getAccurateTime();
|
||||
processing::threadProc->measureType(
|
||||
procType,
|
||||
curTime - procStart,
|
||||
procCount);
|
||||
}
|
||||
#endif
|
||||
|
||||
unlockGroup(this);
|
||||
activeObject = nullptr;
|
||||
return tickIndex >= objects.size();
|
||||
}
|
||||
|
||||
LockGroup* getRandomLock() {
|
||||
assert(!lockGroups.empty());
|
||||
return lockGroups[randomi(0, (int)lockGroups.size() - 1)];
|
||||
}
|
||||
|
||||
void initLocks(unsigned lockCount) {
|
||||
assert(tickingLocks == 0);
|
||||
lockQueueLock.lock();
|
||||
for(unsigned i = 0; i < lockCount; ++i) {
|
||||
LockGroup* lock = new LockGroup();
|
||||
lock->id = i;
|
||||
lockGroups.push_back(lock);
|
||||
lockQueue.push_back(lock);
|
||||
}
|
||||
lockQueueLock.release();
|
||||
}
|
||||
|
||||
void destroyLocks() {
|
||||
foreach(it, lockGroups)
|
||||
delete *it;
|
||||
lockGroups.clear();
|
||||
lockQueue.clear();
|
||||
}
|
||||
|
||||
LockGroup* lockObject(Object* obj, bool priority) {
|
||||
LockGroup* other;
|
||||
if(!obj)
|
||||
return 0;
|
||||
|
||||
changedGroup:
|
||||
other = obj->lockGroup;
|
||||
if(!other)
|
||||
return 0;
|
||||
|
||||
lockGroup(other, priority);
|
||||
if(obj->lockGroup != other) {
|
||||
unlockGroup(other);
|
||||
goto changedGroup;
|
||||
}
|
||||
return other;
|
||||
|
||||
//TODO: Reimplement this for secondary locks
|
||||
//if(other == activeLockGroup) {
|
||||
//++other->goodLocks;
|
||||
//return 0;
|
||||
//}
|
||||
|
||||
//if(activeLockGroup)
|
||||
//++other->badLocks;
|
||||
//else
|
||||
//++other->goodLocks;
|
||||
|
||||
////Chance to switch locks
|
||||
//if(write && activeLockGroup) {
|
||||
//if(randomf() < LOCK_SWITCH_CHANCE) {
|
||||
//++switches;
|
||||
//++obj->lockGroup->lostObjects;
|
||||
//++activeLockGroup->gainedObjects;
|
||||
|
||||
//if(activeLockGroup != obj->originalLock)
|
||||
//activeLockGroup->add(obj);
|
||||
//obj->lockGroup = activeLockGroup;
|
||||
|
||||
////Queue a tick if this is the first switch
|
||||
//if(!switchStage && obj->originalLock == other) {
|
||||
//switchedLock.lock();
|
||||
//switchedObjects.push_back(obj);
|
||||
//switchedLock.release();
|
||||
//}
|
||||
|
||||
//other->mutex.release();
|
||||
//return 0;
|
||||
//}
|
||||
//}
|
||||
}
|
||||
|
||||
void lockError(const char* ch) {
|
||||
if(scripts::getActiveManager())
|
||||
scripts::throwException(ch);
|
||||
else
|
||||
throw ch;
|
||||
}
|
||||
|
||||
inline void doLock(LockGroup* group, bool priority) {
|
||||
if(priority)
|
||||
++group->priorityLocks;
|
||||
|
||||
group->mutex.lock();
|
||||
|
||||
if(priority)
|
||||
--group->priorityLocks;
|
||||
}
|
||||
|
||||
void lockGroup(LockGroup* group, bool priority) {
|
||||
//Check if we already have the group locked
|
||||
if(activeLockGroup) {
|
||||
if(group == activeLockGroup) {
|
||||
group->mutex.lock();
|
||||
}
|
||||
else {
|
||||
#ifdef OBJECT_LOCK_NAGGING
|
||||
lockError("Cannot lock object with another object already locked.");
|
||||
return;
|
||||
#else
|
||||
//Compatibility. This is a potential deadlock, but silently
|
||||
//do it anyway if not in nagging mode.
|
||||
doLock(group, priority);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else {
|
||||
activeLockGroup = group;
|
||||
doLock(group, priority);
|
||||
}
|
||||
}
|
||||
|
||||
inline void _unlockGroup(LockGroup* lock) {
|
||||
if(lock) {
|
||||
lock->mutex.release();
|
||||
|
||||
if(!lock->hasLock()) {
|
||||
if(lock == activeLockGroup)
|
||||
activeLockGroup = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void unlockObject(LockGroup* lock) {
|
||||
_unlockGroup(lock);
|
||||
}
|
||||
|
||||
void unlockGroup(LockGroup* lock) {
|
||||
_unlockGroup(lock);
|
||||
}
|
||||
|
||||
bool hasQueuedChildren() {
|
||||
return queuedChildren != 0;
|
||||
}
|
||||
|
||||
bool hasRemainingMessages() {
|
||||
return remainingMessages != 0;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
#include "threads.h"
|
||||
#include "obj/object.h"
|
||||
#include <set>
|
||||
#include <queue>
|
||||
|
||||
#define OBJECT_LOCK_NAGGING
|
||||
|
||||
struct ObjectMessage;
|
||||
|
||||
struct LockGroup {
|
||||
int id;
|
||||
threads::Mutex mutex;
|
||||
threads::atomic_int priorityLocks;
|
||||
std::vector<Object*> objects;
|
||||
unsigned tickIndex;
|
||||
|
||||
bool hasLock();
|
||||
bool hasWriteLock();
|
||||
|
||||
std::vector<Object*> addQueue;
|
||||
threads::Mutex addMutex;
|
||||
|
||||
std::deque<ObjectMessage*> messages;
|
||||
std::unordered_map<Object*, std::vector<ObjectMessage*>*> deferredMessages;
|
||||
threads::Mutex messageLock;
|
||||
|
||||
threads::atomic_int badLocks;
|
||||
threads::atomic_int goodLocks;
|
||||
threads::atomic_int lostObjects;
|
||||
threads::atomic_int gainedObjects;
|
||||
|
||||
LockGroup();
|
||||
|
||||
void addMessage(ObjectMessage* message);
|
||||
void add(Object* obj);
|
||||
bool process(double time, int limit = 1000);
|
||||
void acquireChildren();
|
||||
void processMessages(int limit = 100);
|
||||
};
|
||||
|
||||
struct ObjectMessage {
|
||||
Object* object;
|
||||
|
||||
ObjectMessage(Object* obj) : object(obj) {}
|
||||
virtual void process() = 0;
|
||||
virtual ~ObjectMessage() {}
|
||||
|
||||
void* operator new(size_t bytes);
|
||||
void operator delete(void* p);
|
||||
};
|
||||
|
||||
LockGroup* getRandomLock();
|
||||
void initLocks(unsigned lockCount);
|
||||
void destroyLocks();
|
||||
|
||||
unsigned getLockCount();
|
||||
LockGroup* getLock(unsigned index);
|
||||
void printLockStats();
|
||||
|
||||
void acquireRandomChildren();
|
||||
bool tickRandomLock(double time, int limit = 1000);
|
||||
void tickRandomMessages(int limit = 100);
|
||||
//Attempts to process the messages in a particular group, with no guarantee of any success
|
||||
void tickLockMessages(LockGroup* lock, int limit = 8);
|
||||
|
||||
//If a message can't be processed yet (the object has no lock group), it must be queued for later execution
|
||||
void queueDeferredMessage(ObjectMessage* msg);
|
||||
|
||||
LockGroup* getActiveLockGroup();
|
||||
Object* getActiveObject();
|
||||
void setActiveObject(Object* obj);
|
||||
void queueObjectClear(Object* obj);
|
||||
void delayObjectRelease(Object* obj);
|
||||
|
||||
LockGroup* lockObject(Object* obj, bool priority = false);
|
||||
void unlockObject(LockGroup* lock);
|
||||
|
||||
void lockGroup(LockGroup* group, bool priority = false);
|
||||
void unlockGroup(LockGroup* lock);
|
||||
|
||||
bool hasQueuedChildren();
|
||||
bool hasRemainingMessages();
|
||||
@@ -0,0 +1,263 @@
|
||||
#include "obj_group.h"
|
||||
#include "object.h"
|
||||
#include "physics/physics_world.h"
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
#include "util/save_file.h"
|
||||
#include <map>
|
||||
|
||||
threads::ReadWriteMutex groupIDsLock;
|
||||
threads::atomic_int nextGroupID(1);
|
||||
std::map<int,ObjectGroup*> groups;
|
||||
|
||||
void registerObjectGroup(ObjectGroup* group, int id = -1) {
|
||||
groupIDsLock.writeLock();
|
||||
|
||||
group->grab();
|
||||
|
||||
if(id == -1) {
|
||||
group->id = nextGroupID++;
|
||||
}
|
||||
else {
|
||||
group->id = id;
|
||||
nextGroupID = id+1;
|
||||
}
|
||||
|
||||
groups[group->id] = group;
|
||||
groupIDsLock.release();
|
||||
}
|
||||
|
||||
void unregisterObjectGroup(ObjectGroup* group) {
|
||||
groupIDsLock.writeLock();
|
||||
|
||||
groups.erase(group->id);
|
||||
group->drop();
|
||||
|
||||
groupIDsLock.release();
|
||||
}
|
||||
|
||||
ObjectGroup* ObjectGroup::byID(int id) {
|
||||
ObjectGroup* group = 0;
|
||||
|
||||
groupIDsLock.readLock();
|
||||
auto iter = groups.find(id);
|
||||
if(iter != groups.end())
|
||||
group = iter->second;
|
||||
|
||||
if(group)
|
||||
group->grab();
|
||||
groupIDsLock.release();
|
||||
return group;
|
||||
}
|
||||
|
||||
ObjectGroup::ObjectGroup(unsigned count, int id) : objectCount(count), origObjectCount(count), objects(new Object*[count]), physItem(new PhysicsItem), owner(0) {
|
||||
PhysicsGroup* physGroup = new PhysicsGroup;
|
||||
physGroup->itemCount = count;
|
||||
physGroup->items = new PhysicsItem[count];
|
||||
|
||||
physItem->type = PhysItemType(PIT_Group | PIT_Object);
|
||||
physItem->group = physGroup;
|
||||
|
||||
memset(objects, 0, sizeof(Object*) * count);
|
||||
registerObjectGroup(this, id);
|
||||
}
|
||||
|
||||
bool ObjectGroup::postLoad() {
|
||||
//Remove invalid objects
|
||||
unsigned off = 0;
|
||||
for(unsigned i = 0; i < objectCount; ++i) {
|
||||
Object* obj = objects[i];
|
||||
if(!obj->isValid() || !obj->isInitialized()) {
|
||||
physItem->group->items[i].object = 0;
|
||||
++off;
|
||||
}
|
||||
else {
|
||||
if(off != 0)
|
||||
objects[i-off] = obj;
|
||||
}
|
||||
obj->drop();
|
||||
}
|
||||
objectCount -= off;
|
||||
|
||||
//Check if the group should die
|
||||
if(objectCount == 0) {
|
||||
owner = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
//Check if the owner is valid
|
||||
if(!owner->isValid() || !owner->isInitialized())
|
||||
owner = objects[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
void ObjectGroup::postInit() {
|
||||
//Update physics item
|
||||
physItem->bound.reset(objects[0]->physItem->bound);
|
||||
for(unsigned i = 1; i < objectCount; ++i)
|
||||
physItem->bound.addBox(objects[i]->physItem->bound);
|
||||
|
||||
devices.physics->registerItem(*physItem);
|
||||
}
|
||||
|
||||
ObjectGroup::~ObjectGroup() {
|
||||
devices.physics->unregisterItem(*physItem);
|
||||
delete[] physItem->group->items;
|
||||
delete physItem;
|
||||
delete[] objects;
|
||||
}
|
||||
|
||||
unsigned ObjectGroup::getObjectCount() const {
|
||||
return objectCount;
|
||||
}
|
||||
|
||||
unsigned ObjectGroup::getMaxObjectCount() const {
|
||||
return origObjectCount;
|
||||
}
|
||||
|
||||
Object* ObjectGroup::getObject(unsigned index) {
|
||||
return objects[index];
|
||||
}
|
||||
|
||||
const Object* ObjectGroup::getObject(unsigned index) const {
|
||||
return objects[index];
|
||||
}
|
||||
|
||||
void ObjectGroup::setObject(unsigned index, Object* obj) {
|
||||
objects[index] = obj;
|
||||
if(index == 0 && owner == 0)
|
||||
owner = obj;
|
||||
if(obj)
|
||||
obj->grab();
|
||||
}
|
||||
|
||||
unsigned ObjectGroup::setNextObject(Object* obj) {
|
||||
for(unsigned i = 0; i < objectCount; ++i) {
|
||||
if(objects[i] != 0)
|
||||
continue;
|
||||
objects[i] = obj;
|
||||
if(i == 0 && owner == nullptr)
|
||||
owner = obj;
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Object* ObjectGroup::getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
void ObjectGroup::setOwner(Object* obj) {
|
||||
owner = obj;
|
||||
}
|
||||
|
||||
vec3d ObjectGroup::getCenter() const {
|
||||
return physItem->bound.getCenter();
|
||||
}
|
||||
|
||||
PhysicsItem* ObjectGroup::getPhysicsItem() {
|
||||
return physItem;
|
||||
}
|
||||
|
||||
PhysicsItem* ObjectGroup::getPhysicsItem(unsigned index) const {
|
||||
return &physItem->group->items[index];
|
||||
}
|
||||
|
||||
bool ObjectGroup::removeOwner() {
|
||||
Object* newOwner = 0;
|
||||
double nearest = 9.0e40;
|
||||
|
||||
//Move objects down to the lower indices, and find the nearest object to the previous owner to be the new owner
|
||||
unsigned j = 0;
|
||||
for(unsigned i = 0; i < objectCount; ++i) {
|
||||
Object*& obj = objects[i];
|
||||
|
||||
if(!obj) {
|
||||
++j;
|
||||
}
|
||||
else if(obj->isValid() && obj != owner) {
|
||||
double dist = owner->position.distanceToSQ(obj->position);
|
||||
if(dist < nearest) {
|
||||
nearest = dist;
|
||||
newOwner = obj;
|
||||
}
|
||||
|
||||
if(j != i) {
|
||||
objects[j] = obj;
|
||||
obj = 0;
|
||||
}
|
||||
++j;
|
||||
}
|
||||
}
|
||||
|
||||
objectCount = j;
|
||||
if(objectCount != 0) {
|
||||
if(newOwner)
|
||||
owner = newOwner;
|
||||
else
|
||||
owner = objects[0];
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
owner = 0;
|
||||
unregisterObjectGroup(this);
|
||||
drop();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectGroup::update() {
|
||||
AABBoxd box;
|
||||
box.reset(owner->physItem->bound);
|
||||
|
||||
unsigned j = 1;
|
||||
for(unsigned i = 1; i < objectCount; ++i) {
|
||||
if(!objects[i]) {
|
||||
++j;
|
||||
} else if(objects[i]->isValid()) {
|
||||
box.addBox(objects[i]->physItem->bound);
|
||||
if(j != i) {
|
||||
objects[j] = objects[i];
|
||||
objects[i] = 0;
|
||||
}
|
||||
++j;
|
||||
}
|
||||
}
|
||||
|
||||
objectCount = j;
|
||||
devices.physics->updateItem(*physItem, box);
|
||||
}
|
||||
|
||||
ObjectGroup::ObjectGroup(SaveFile& file) {
|
||||
file >> id >> objectCount >> formationFacing;
|
||||
objects = new Object*[objectCount];
|
||||
|
||||
for(unsigned i = 0; i < objectCount; ++i)
|
||||
file >> objects[i];
|
||||
|
||||
owner = objects[(unsigned short)file];
|
||||
|
||||
PhysicsGroup* physGroup = new PhysicsGroup;
|
||||
physGroup->itemCount = objectCount;
|
||||
physGroup->items = new PhysicsItem[objectCount];
|
||||
|
||||
physItem = new PhysicsItem;
|
||||
physItem->type = PhysItemType(PIT_Group | PIT_Object);
|
||||
physItem->group = physGroup;
|
||||
|
||||
registerObjectGroup(this);
|
||||
}
|
||||
|
||||
void ObjectGroup::save(SaveFile& file) {
|
||||
file << id;
|
||||
file << objectCount;
|
||||
file << formationFacing;
|
||||
|
||||
unsigned ownerIndex = 0;
|
||||
for(unsigned i = 0; i < objectCount; ++i) {
|
||||
file << objects[i];
|
||||
if(objects[i] == owner)
|
||||
ownerIndex = i;
|
||||
}
|
||||
file << (unsigned short)ownerIndex;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include "util/refcount.h"
|
||||
#include "quaternion.h"
|
||||
|
||||
class Object;
|
||||
class SaveFile;
|
||||
struct PhysicsItem;
|
||||
|
||||
class ObjectGroup : public AtomicRefCounted {
|
||||
PhysicsItem* physItem;
|
||||
unsigned objectCount, origObjectCount;
|
||||
Object** objects;
|
||||
Object* owner;
|
||||
public:
|
||||
int id;
|
||||
quaterniond formationFacing;
|
||||
|
||||
ObjectGroup(unsigned count, int id = -1);
|
||||
|
||||
bool postLoad();
|
||||
void postInit();
|
||||
ObjectGroup(SaveFile& file);
|
||||
void save(SaveFile& file);
|
||||
~ObjectGroup();
|
||||
|
||||
unsigned getObjectCount() const;
|
||||
unsigned getMaxObjectCount() const;
|
||||
Object* getObject(unsigned index);
|
||||
const Object* getObject(unsigned index) const;
|
||||
void setObject(unsigned index, Object* obj);
|
||||
unsigned setNextObject(Object* obj);
|
||||
Object* getOwner();
|
||||
void setOwner(Object* obj);
|
||||
vec3d getCenter() const;
|
||||
|
||||
PhysicsItem* getPhysicsItem();
|
||||
PhysicsItem* getPhysicsItem(unsigned index) const;
|
||||
|
||||
//Removes the owner; Returns true if the group is now empty
|
||||
bool removeOwner();
|
||||
void update();
|
||||
|
||||
//Note: grabs the group before returning it
|
||||
static ObjectGroup* byID(int id);
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,325 @@
|
||||
#pragma once
|
||||
#include "vec3.h"
|
||||
#include "vec2.h"
|
||||
#include "quaternion.h"
|
||||
#include "line3d.h"
|
||||
#include "util/refcount.h"
|
||||
#include "util/link_container.h"
|
||||
#include "threads.h"
|
||||
#include "general_states.h"
|
||||
#include "util/random.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#ifndef OBJ_TARGETS
|
||||
#define OBJ_TARGETS 3
|
||||
#endif
|
||||
|
||||
const unsigned ObjectTypeBitOffset = 26;
|
||||
const unsigned ObjectTypeMask = (unsigned)(0xFF << ObjectTypeBitOffset);
|
||||
const unsigned ObjectIDMask = 0xFFFFFFFF >> (32 - ObjectTypeBitOffset);
|
||||
extern unsigned ObjectTypeCount;
|
||||
|
||||
class Object;
|
||||
class ObjectGroup;
|
||||
struct PhysicsItem;
|
||||
struct ObjectMessage;
|
||||
|
||||
class asIScriptFunction;
|
||||
class asIScriptObject;
|
||||
class asITypeInfo;
|
||||
|
||||
class SaveFile;
|
||||
|
||||
enum ScriptObjectCallback {
|
||||
SOC_init,
|
||||
SOC_postInit,
|
||||
SOC_destroy,
|
||||
SOC_groupDestroyed,
|
||||
SOC_tick,
|
||||
SOC_ownerChange,
|
||||
SOC_damage,
|
||||
SOC_repair,
|
||||
SOC_syncInitial,
|
||||
SOC_syncDetailed,
|
||||
SOC_syncDelta,
|
||||
SOC_recvInitial,
|
||||
SOC_recvDetailed,
|
||||
SOC_recvDelta,
|
||||
SOC_save,
|
||||
SOC_load,
|
||||
SOC_postLoad,
|
||||
|
||||
SOC_COUNT
|
||||
};
|
||||
|
||||
struct ScriptObjectType {
|
||||
unsigned id;
|
||||
std::string name;
|
||||
std::string script;
|
||||
mutable threads::atomic_int nextID;
|
||||
|
||||
asITypeInfo* scriptType;
|
||||
asIScriptFunction* functions[SOC_COUNT];
|
||||
|
||||
const StateDefinition* states;
|
||||
std::vector<size_t> componentOffsets;
|
||||
std::vector<bool> optionalComponents;
|
||||
size_t blueprintOffset;
|
||||
|
||||
scripts::GenericType* refType;
|
||||
scripts::GenericType* handleType;
|
||||
|
||||
ScriptObjectType();
|
||||
void bind(const char* module, const char* decl);
|
||||
asIScriptObject* create() const;
|
||||
};
|
||||
|
||||
void prepScriptObjectTypes();
|
||||
void addObjectStateValueTypes();
|
||||
void setScriptObjectStates();
|
||||
void bindScriptObjectTypes();
|
||||
ScriptObjectType* getScriptObjectType(const std::string& name);
|
||||
|
||||
ScriptObjectType* getScriptObjectType(int index);
|
||||
unsigned getScriptObjectTypeCount();
|
||||
|
||||
//Retrieve the object referred to by the given id.
|
||||
// Grabs the object before returning it, so it needs
|
||||
// to be dropped afterwards
|
||||
Object* getObjectByID(unsigned id, bool create = false);
|
||||
ScriptObjectType* getObjectTypeFromID(unsigned id);
|
||||
void invalidateUninitializedObjects();
|
||||
|
||||
enum ObjectFlag : unsigned {
|
||||
//Object is not in the process of being deleted
|
||||
objValid = 0x1,
|
||||
//Object needs to go to sleep (going invalid)
|
||||
objStopTicking = 0x2,
|
||||
//Object has something to do and should ignore timeout delay
|
||||
objWakeUp = 0x4,
|
||||
//Object has been allocated as a reference-only object, and does not yet have data
|
||||
objUninitialized = 0x8,
|
||||
//Object has been flagged to send a data delta the next time it can
|
||||
objSendDelta = 0x10,
|
||||
//Object has been flagged as a focus object, this has various
|
||||
//effects for interpolation and multiplayer
|
||||
objFocus = 0x20,
|
||||
//Object is currently engaged in combat
|
||||
objEngaged = 0x40,
|
||||
//Object does not have physics
|
||||
objNoPhysics = 0x80,
|
||||
//Object is selected
|
||||
objSelected = 0x100,
|
||||
//Object is considered as in combat
|
||||
objCombat = 0x200,
|
||||
//Multiplayer clients should be able to get information even out of vision
|
||||
objMemorable = 0x400,
|
||||
//Whether it has been given a name
|
||||
objNamed = 0x800,
|
||||
//Whether the object should nudge movable objects (enforced by scripts)
|
||||
objNoCollide = 0x1000,
|
||||
//Whether the object can not receive damage by being hit by projectiles
|
||||
objNoDamage = 0x2000,
|
||||
|
||||
|
||||
objQueueDestroy = 0x80000000
|
||||
};
|
||||
|
||||
const unsigned objFlagSaveMask = ~(objSelected);
|
||||
|
||||
namespace net {
|
||||
struct Message;
|
||||
};
|
||||
|
||||
namespace scene {
|
||||
class Node;
|
||||
};
|
||||
|
||||
class Empire;
|
||||
struct LockGroup;
|
||||
class TimedEffect;
|
||||
class DamageEvent;
|
||||
|
||||
struct DeferredObjMessage {
|
||||
DeferredObjMessage* next, *prev;
|
||||
ObjectMessage* msg;
|
||||
};
|
||||
|
||||
class Object {
|
||||
public:
|
||||
static bool GALAXY_CREATION;
|
||||
|
||||
mutable threads::atomic_int references;
|
||||
heldPointer<Object> targets[OBJ_TARGETS];
|
||||
|
||||
//Object tree
|
||||
unsigned id;
|
||||
ObjectGroup* group;
|
||||
|
||||
//Locking
|
||||
LockGroup* lockGroup;
|
||||
LockGroup* originalLock;
|
||||
unsigned lockHint;
|
||||
|
||||
//Messages
|
||||
DeferredObjMessage* deferredMessages;
|
||||
void queueDeferredMessage(ObjectMessage* msg);
|
||||
DeferredObjMessage* fetchMessages();
|
||||
|
||||
//Ownership
|
||||
Empire* owner;
|
||||
|
||||
//Vision
|
||||
bool alwaysVisible;
|
||||
float sightRange;
|
||||
float seeableRange;
|
||||
unsigned char visionTimes[32];
|
||||
unsigned visibleMask, donatedVision, prevVisibleMask, sightedMask, sightDelay;
|
||||
|
||||
//State variables
|
||||
threads::atomic_int flags;
|
||||
|
||||
//Object spatial variables
|
||||
PhysicsItem* physItem;
|
||||
vec3d position, velocity, acceleration;
|
||||
quaterniond rotation;
|
||||
double radius;
|
||||
|
||||
//The last time this Object ran its think()
|
||||
// - the parent is responsible for updating this
|
||||
double lastTick;
|
||||
double nextTick;
|
||||
|
||||
//Effects
|
||||
std::vector<TimedEffect*> effects;
|
||||
void addTimedEffect(const TimedEffect& eff);
|
||||
|
||||
//Generic stats
|
||||
LinkMap stats;
|
||||
|
||||
//Graphics tree
|
||||
scene::Node* node;
|
||||
|
||||
std::string name;
|
||||
|
||||
asIScriptObject* script;
|
||||
|
||||
const ScriptObjectType* type;
|
||||
|
||||
void init();
|
||||
void postInit();
|
||||
void setOwner(Empire* newOwner);
|
||||
bool isLocked() const;
|
||||
|
||||
static Object* create(ScriptObjectType* type, LockGroup* lock = 0, int id = 0);
|
||||
void grab() const;
|
||||
void drop() const;
|
||||
|
||||
inline const ScriptObjectType* GetType() const { return type; }
|
||||
|
||||
void setFlag(ObjectFlag flag, bool val);
|
||||
bool getFlag(ObjectFlag flag) const;
|
||||
//Like set flag, but only succeeds if this call set the flag to that value
|
||||
bool setFlagSecure(ObjectFlag flag, bool val);
|
||||
|
||||
//Flag access wrappers
|
||||
bool isValid() const;
|
||||
bool isInitialized() const;
|
||||
void wake();
|
||||
void sleep(double seconds);
|
||||
|
||||
//Focus is an object property to indicate
|
||||
//priority syncing and calculations.
|
||||
//The focus flag decays after a while and needs to
|
||||
//be set repeatedly on accessed objects.
|
||||
bool isFocus() const;
|
||||
void focus();
|
||||
|
||||
bool isVisibleTo(Empire* emp) const;
|
||||
bool isKnownTo(Empire* emp) const;
|
||||
unsigned updateVision(Object* target, unsigned depth);
|
||||
|
||||
//Damage events
|
||||
void damage(DamageEvent& evt, double position, const vec2d& direction);
|
||||
void repair(double amount);
|
||||
|
||||
//Targeting
|
||||
void updateTargets();
|
||||
|
||||
template<class T>
|
||||
bool findTargets(T& cb, int depth) {
|
||||
for(unsigned i = 0; i < OBJ_TARGETS; ++i) {
|
||||
Object* targ = targets[i].ptr;
|
||||
if(targ == this)
|
||||
continue;
|
||||
if(cb.result(targ))
|
||||
return true;
|
||||
if(depth != 0) {
|
||||
if(targ->findTargets(cb, depth-1))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const unsigned char RANDOMIZE_TARGETS = 0;
|
||||
template<class T>
|
||||
bool findTargets(T& cb, int depth, unsigned char randomizer) {
|
||||
if(randomizer == RANDOMIZE_TARGETS)
|
||||
randomizer = (unsigned char)randomi();
|
||||
else
|
||||
randomizer ^= (unsigned char)id;
|
||||
unsigned index = randomizer % OBJ_TARGETS;
|
||||
for(unsigned i = 0; i < OBJ_TARGETS; ++i, index = (index+1) % OBJ_TARGETS) {
|
||||
Object* targ = targets[index].ptr;
|
||||
if(targ == this)
|
||||
continue;
|
||||
if(cb.result(targ))
|
||||
return true;
|
||||
if(depth != 0) {
|
||||
if(targ->findTargets(cb, depth-1, randomizer))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//Network syncing
|
||||
void sendInitial(net::Message& msg);
|
||||
void sendDetailed(net::Message& msg);
|
||||
bool sendDelta(net::Message& msg);
|
||||
|
||||
void recvDetailed(net::Message& msg, double fromTime);
|
||||
void recvDelta(net::Message& msg, double fromTime);
|
||||
|
||||
//Saving & loading
|
||||
void save(SaveFile& file);
|
||||
void load(SaveFile& file);
|
||||
void postLoad();
|
||||
|
||||
//Management
|
||||
Object(ScriptObjectType* Type, LockGroup* group = 0, int id = 0);
|
||||
~Object();
|
||||
|
||||
double think(double seconds);
|
||||
void flagDestroy() { setFlag(objQueueDestroy, true); setFlag(objWakeUp, true); }
|
||||
|
||||
friend class Universe;
|
||||
|
||||
void clearScripts();
|
||||
private:
|
||||
void destroy(bool fromUniverse = false);
|
||||
};
|
||||
|
||||
Object* recvObjectInitial(net::Message& msg, double fromTime);
|
||||
void clearObjects();
|
||||
|
||||
struct ObjectLock {
|
||||
LockGroup* group;
|
||||
bool released;
|
||||
|
||||
ObjectLock(Object* Obj, bool priority = false);
|
||||
void release();
|
||||
~ObjectLock();
|
||||
};
|
||||
@@ -0,0 +1,157 @@
|
||||
#include "object.h"
|
||||
#include "obj_group.h"
|
||||
#include "util/save_file.h"
|
||||
#include "empire.h"
|
||||
#include "physics/physics_world.h"
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
#include "network/message.h"
|
||||
#include "lock.h"
|
||||
|
||||
//TODO: Handle save files with different script type definitions
|
||||
|
||||
void Object::save(SaveFile& file) {
|
||||
file << id;
|
||||
|
||||
file << name;
|
||||
file << (flags & objFlagSaveMask);
|
||||
|
||||
file << alwaysVisible << sightRange << visibleMask << sightedMask;
|
||||
file << position << velocity << acceleration << rotation;
|
||||
file << radius;
|
||||
file << seeableRange;
|
||||
|
||||
file << lockHint;
|
||||
|
||||
file << (group ? group->id : int(0));
|
||||
file << (owner ? owner->id : INVALID_EMPIRE);
|
||||
|
||||
if(!isValid())
|
||||
return;
|
||||
|
||||
auto* func = type->functions[SOC_save];
|
||||
if(func) {
|
||||
SaveMessage msg(file);
|
||||
|
||||
scripts::Call cl = devices.scripts.server->call(func);
|
||||
cl.setObject(script);
|
||||
cl.push((void*)this);
|
||||
cl.push((void*)&msg);
|
||||
if(cl.call()) {
|
||||
char* pData; net::msize_t size;
|
||||
msg.getAsPacket(pData, size);
|
||||
|
||||
file << size;
|
||||
file.write(pData, size);
|
||||
}
|
||||
else {
|
||||
file << (net::msize_t)0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
file << (net::msize_t)0;
|
||||
}
|
||||
}
|
||||
|
||||
void Object::load(SaveFile& file) {
|
||||
if(!getFlag(objUninitialized))
|
||||
throw SaveFileError("Object already initialized");
|
||||
|
||||
file >> name;
|
||||
file >> flags;
|
||||
|
||||
file >> alwaysVisible >> sightRange >> visibleMask >> sightedMask;
|
||||
file >> position >> velocity >> acceleration >> rotation;
|
||||
file >> radius;
|
||||
if(file < SFV_0001) {
|
||||
double dummy;
|
||||
file >> dummy;
|
||||
}
|
||||
if(file >= SFV_0017)
|
||||
file >> seeableRange;
|
||||
|
||||
if(file >= SFV_0009) {
|
||||
file >> lockHint;
|
||||
if(lockHint > 0)
|
||||
lockGroup = getLock(lockHint % getLockCount());
|
||||
}
|
||||
|
||||
int groupID = file;
|
||||
if(groupID != 0 && isValid()) {
|
||||
group = ObjectGroup::byID(groupID);
|
||||
if(group) {
|
||||
for(unsigned i = 0; i < group->getObjectCount(); ++i) {
|
||||
if(group->getObject(i) == this) {
|
||||
physItem = group->getPhysicsItem(i);
|
||||
physItem->bound = AABBoxd::fromCircle(position, radius);
|
||||
physItem->gridLocation = 0;
|
||||
physItem->type = PIT_Object;
|
||||
physItem->object = this;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!physItem) {
|
||||
error("%s (#%d) missing in group", name.c_str(), id);
|
||||
group->drop();
|
||||
group = nullptr;
|
||||
}
|
||||
}
|
||||
else {
|
||||
error("%s (#%d) missing group", name.c_str(), id);
|
||||
}
|
||||
}
|
||||
|
||||
owner = Empire::getEmpireByID(file);
|
||||
|
||||
if(!isValid())
|
||||
return;
|
||||
|
||||
if(!physItem && !getFlag(objNoPhysics))
|
||||
physItem = devices.physics->registerItem(AABBoxd::fromCircle(position, radius), this);
|
||||
|
||||
void* mixinMem = this + 1;
|
||||
type->states->prepare(mixinMem);
|
||||
|
||||
script = type->create();
|
||||
|
||||
auto* loadFunc = type->functions[SOC_load];
|
||||
SaveMessage scriptTypeData(file);
|
||||
|
||||
net::msize_t size = file;
|
||||
if(size > 0) {
|
||||
char* buffer = (char*)malloc(size);
|
||||
file.read(buffer, size);
|
||||
scriptTypeData.setPacket(buffer, size);
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
//Either load the saved message, or default initialize
|
||||
if(loadFunc) {
|
||||
scripts::Call cl = devices.scripts.server->call(loadFunc);
|
||||
cl.setObject(script);
|
||||
cl.push((void*)this);
|
||||
cl.push((void*)&scriptTypeData);
|
||||
cl.call();
|
||||
}
|
||||
else {
|
||||
scripts::Call cl = devices.scripts.server->call(type->functions[SOC_init]);
|
||||
if(cl.valid()) {
|
||||
cl.setObject(script);
|
||||
cl.push((void*)this);
|
||||
cl.call();
|
||||
}
|
||||
}
|
||||
|
||||
lockGroup->add(this);
|
||||
setFlag(objUninitialized, false);
|
||||
}
|
||||
|
||||
void Object::postLoad() {
|
||||
auto* func = type->functions[SOC_postLoad];
|
||||
if(func && script) {
|
||||
scripts::Call cl = devices.scripts.server->call(func);
|
||||
cl.setObject(script);
|
||||
cl.push((void*)this);
|
||||
cl.call();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
#include "obj/universe.h"
|
||||
#include "empire.h"
|
||||
#include "compat/misc.h"
|
||||
#include "util/random.h"
|
||||
#include "main/references.h"
|
||||
#include "network/network_manager.h"
|
||||
#include "physics/physics_world.h"
|
||||
#include <assert.h>
|
||||
#include <unordered_set>
|
||||
|
||||
void Universe::doQueued() {
|
||||
threads::Lock qlock(queueLock);
|
||||
threads::WriteLock lock(childLock);
|
||||
|
||||
//Remove queued objects
|
||||
unsigned offset = 0;
|
||||
if(!removeQueue.empty()) {
|
||||
for(unsigned i = 0, cnt = (unsigned)children.size(); i < cnt; ++i) {
|
||||
Object* child = children[i];
|
||||
if(removeQueue.find(child) != removeQueue.end()) {
|
||||
++offset;
|
||||
removeTargetsTo(child); //TODO: better
|
||||
child->drop();
|
||||
}
|
||||
else if(offset != 0) {
|
||||
children[i - offset] = children[i];
|
||||
}
|
||||
}
|
||||
if(offset != 0)
|
||||
children.resize(children.size() - (size_t)offset);
|
||||
removeQueue.clear();
|
||||
}
|
||||
|
||||
//Add queued objects
|
||||
foreach(it, addQueue)
|
||||
children.push_back(*it);
|
||||
addQueue.clear();
|
||||
}
|
||||
|
||||
void Universe::addChild(Object* obj) {
|
||||
threads::Lock qlock(queueLock);
|
||||
|
||||
auto it = removeQueue.find(obj);
|
||||
if(it != removeQueue.end())
|
||||
removeQueue.erase(obj);
|
||||
else
|
||||
addQueue.insert(obj);
|
||||
obj->grab();
|
||||
}
|
||||
|
||||
void Universe::removeChild(Object* obj) {
|
||||
threads::Lock qlock(queueLock);
|
||||
auto it = addQueue.find(obj);
|
||||
if(it != addQueue.end()) {
|
||||
addQueue.erase(obj);
|
||||
for(unsigned i = 0; i < OBJ_TARGETS; ++i)
|
||||
obj->targets[i] = nullptr;
|
||||
}
|
||||
else
|
||||
removeQueue.insert(obj);
|
||||
}
|
||||
|
||||
void Universe::destroyAll() {
|
||||
foreach(child, children) {
|
||||
(*child)->destroy(true);
|
||||
(*child)->drop();
|
||||
}
|
||||
children.clear();
|
||||
}
|
||||
|
||||
Object* Universe::getRandomTarget(unsigned randVal) {
|
||||
//No child lock needed, guaranteed through lock ticking
|
||||
if(children.empty())
|
||||
return 0;
|
||||
if(randVal == 0)
|
||||
randVal = randomi();
|
||||
return children[randVal % children.size()];
|
||||
}
|
||||
|
||||
void Universe::setRandomTarget(heldPointer<Object>& targ, unsigned randVal) {
|
||||
//No child lock needed, guaranteed through lock ticking
|
||||
if(children.empty())
|
||||
return;
|
||||
|
||||
if(randVal == 0)
|
||||
randVal = randomi();
|
||||
|
||||
Object* child = children[randVal % children.size()];
|
||||
|
||||
if(child->isValid()) {
|
||||
auto& other = child->targets[randVal % OBJ_TARGETS];
|
||||
if(!other->getFlag(objNoPhysics))
|
||||
other.swap(targ);
|
||||
}
|
||||
}
|
||||
|
||||
void Universe::removeTargetsTo(Object* obj) {
|
||||
//No child lock needed, guaranteed through lock ticking
|
||||
unsigned remaining = OBJ_TARGETS;
|
||||
unsigned i = 0;
|
||||
unsigned cnt = (unsigned)children.size();
|
||||
|
||||
for(unsigned t = 0; t < OBJ_TARGETS; ++t)
|
||||
if(obj->targets[t] == obj)
|
||||
--remaining;
|
||||
|
||||
if(remaining == 0)
|
||||
goto clearTargets;
|
||||
|
||||
for(; i < cnt; ++i) {
|
||||
Object* child = children[i];
|
||||
|
||||
for(unsigned j = 0; j < OBJ_TARGETS; ++j) {
|
||||
if(child->targets[j] == obj && child != obj) {
|
||||
for(unsigned t = 0; t < OBJ_TARGETS; ++t) {
|
||||
if(obj->targets[t] != obj) {
|
||||
child->targets[j].swap(obj->targets[t]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(children[i]->targets[j] != obj);
|
||||
--remaining;
|
||||
if(remaining == 0)
|
||||
goto clearTargets;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearTargets:;
|
||||
for(unsigned i = 0; i < OBJ_TARGETS; ++i)
|
||||
obj->targets[i] = 0;
|
||||
}
|
||||
|
||||
const double coneSlope = 0.02;
|
||||
|
||||
Object* Universe::getClosestOnLine(const line3dd& line) const {
|
||||
threads::ReadLock lock(childLock);
|
||||
|
||||
Empire* empire = Empire::getPlayerEmpire();
|
||||
auto lineDir = line.getDirection();
|
||||
double closest_d = 9e32;
|
||||
double closest_p = 1.0;
|
||||
Object* closest_o = 0;
|
||||
|
||||
//TODO: This should handle the cone more accurately, possibly in multiple steps?
|
||||
//Bound should hold the line, and a considerable radius around it to handle the cone
|
||||
AABBoxd bound(line.start);
|
||||
bound.addBox( AABBoxd::fromCircle(line.end, 1500.0) );
|
||||
|
||||
devices.physics->findInBox(line, [&](const PhysicsItem& item) {
|
||||
Object* obj = item.object;
|
||||
if(!obj || !obj->isVisibleTo(empire))
|
||||
return;
|
||||
|
||||
double distOnLine = (obj->position - line.start).dot(lineDir);
|
||||
if(distOnLine <= 0)
|
||||
return;
|
||||
|
||||
auto p = line.start + (lineDir * distOnLine);
|
||||
double p_d = p.distanceTo(obj->position);
|
||||
double c_d = obj->radius + distOnLine * coneSlope;
|
||||
if(p_d >= c_d)
|
||||
return;
|
||||
|
||||
double pct = p_d / c_d;
|
||||
|
||||
auto dist = p.getLength();
|
||||
if(dist < closest_d && pct < closest_p) {
|
||||
closest_o = obj;
|
||||
closest_d = dist;
|
||||
closest_p = pct;
|
||||
}
|
||||
});
|
||||
|
||||
return closest_o;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
#include "obj/object.h"
|
||||
#include "util/refcount.h"
|
||||
#include "threads.h"
|
||||
#include <unordered_set>
|
||||
|
||||
class Universe : public AtomicRefCounted {
|
||||
public:
|
||||
mutable threads::Mutex queueLock;
|
||||
std::unordered_set<Object*> addQueue;
|
||||
std::unordered_set<Object*> removeQueue;
|
||||
|
||||
mutable threads::ReadWriteMutex childLock;
|
||||
std::vector<Object*> children;
|
||||
|
||||
void doQueued();
|
||||
void addChild(Object* obj);
|
||||
void removeChild(Object* obj);
|
||||
void destroyAll();
|
||||
|
||||
Object* getRandomTarget(unsigned randVal = 0);
|
||||
void setRandomTarget(heldPointer<Object>& targ, unsigned randVal = 0);
|
||||
void removeTargetsTo(Object* obj);
|
||||
|
||||
Object* getClosestOnLine(const line3dd& line) const;
|
||||
};
|
||||
Reference in New Issue
Block a user