Files
starruler-linux/source/game/scripts/bind_threading.cpp
T
2018-07-17 14:15:37 +02:00

669 lines
18 KiB
C++

#include "scripts/binds.h"
#include "threads.h"
#include "util/refcount.h"
#include "context_cache.h"
#include "main/references.h"
#include "../source/as_scriptengine.h"
#include "../source/as_scriptobject.h"
extern void initNewThread();
extern void cleanupThread();
namespace scripts {
using namespace threads;
class refAtomic : public atomic_int {
mutable atomic_int refcount;
public:
refAtomic(int val) : atomic_int(val), refcount(1) {}
void grab() const {
++refcount;
}
void drop() const {
if(--refcount == 0)
delete this;
}
refAtomic& operator=(int value) {
atomic_int::operator=(value);
return *this;
}
int postInc() {
return (*this)++;
}
int postDec() {
return (*this)--;
}
};
refAtomic* makeAtomic(int val) {
return new refAtomic(val);
}
class refMutex : public Mutex {
mutable atomic_int refcount;
public:
refMutex() : refcount(1) {}
void grab() const {
++refcount;
}
void drop() const {
if(--refcount == 0)
delete this;
}
};
refMutex* makeMutex() {
return new refMutex();
}
refMutex* lockMutex(refMutex* mutex) {
mutex->lock();
return mutex;
}
void unlockMutex(refMutex* mutex) {
mutex->release();
}
class refRWMutex : public ReadWriteMutex {
mutable atomic_int refcount;
public:
refRWMutex() : refcount(1) {}
void grab() const {
++refcount;
}
void drop() const {
if(--refcount == 0)
delete this;
}
};
refRWMutex* makeRWMutex() {
return new refRWMutex();
}
refRWMutex* lockReadMutex(refRWMutex* mutex) {
mutex->readLock();
return mutex;
}
refRWMutex* lockWriteMutex(refRWMutex* mutex) {
mutex->writeLock();
return mutex;
}
void unlockRWMutex(refRWMutex* mutex) {
mutex->release();
}
class refSignal : public Signal {
mutable atomic_int refcount;
public:
refSignal() : refcount(1) {}
void grab() const {
++refcount;
}
void drop() const {
if(--refcount == 0)
delete this;
}
};
refSignal* makeSignal() {
return new refSignal();
}
bool validateThreadLocal(asITypeInfo* ot, bool& dontGarbageCollect) {
int typeId = ot->GetSubTypeId();
if(typeId == asTYPEID_VOID)
return false;
if((typeId & asTYPEID_MASK_OBJECT) && !(typeId & asTYPEID_OBJHANDLE)) {
asITypeInfo *st = ot->GetEngine()->GetTypeInfoById(typeId);
int flags = st->GetFlags();
if(flags & asOBJ_REF) {
if( !(flags & asOBJ_GC) )
dontGarbageCollect = true;
for(unsigned i = 0; i < st->GetFactoryCount(); ++i) {
asIScriptFunction *func = st->GetFactoryByIndex(i);
if(func->GetParamCount() == 0)
return true;
}
return false;
}
else if((flags & asOBJ_VALUE) && !(flags & asOBJ_POD))
{
dontGarbageCollect = true;
for(unsigned i = 0; i < st->GetBehaviourCount(); ++i) {
asEBehaviours beh;
asIScriptFunction *func = st->GetBehaviourByIndex(i, &beh);
if(beh != asBEHAVE_CONSTRUCT) continue;
if(func->GetParamCount() == 0)
return true;
}
return false;
}
}
if( !(typeId & asTYPEID_OBJHANDLE) )
dontGarbageCollect = true;
return true;
}
static void* zeroPtr = 0;
class refThreadLocal : public AtomicRefCounted {
threadlocalPointer<void*> ptr;
asITypeInfo* objType;
public:
refThreadLocal(asITypeInfo* ot)
: objType(ot) {
}
~refThreadLocal() {
int typeId = objType->GetSubTypeId();
void* p = ptr.get();
if(!p)
return;
//Free the object
if(typeId & asTYPEID_OBJHANDLE) {
//Release the object we have a handle to
void* cur = *(void**)p;
if(cur)
objType->GetEngine()->ReleaseScriptObject(cur, objType->GetSubType());
//Free the actual pointer
free(p);
}
else if(typeId & ~asTYPEID_MASK_SEQNBR) {
//Free the object being pointed to
objType->GetEngine()->ReleaseScriptObject(p, objType->GetSubType());
}
else {
//Free the actual pointer
free(p);
}
}
void* get() {
void* p = ptr.get();
int typeId = objType->GetSubTypeId();
if(typeId & asTYPEID_OBJHANDLE) {
//Return the pointer to the pointer,
//which is the handle
if(p)
return p;
else
return set(&zeroPtr);
}
else {
if(p)
return p;
else
return set(zeroPtr);
}
}
void* set(void* v) {
void* p = ptr.get();
int typeId = objType->GetSubTypeId();
if(typeId & asTYPEID_OBJHANDLE) {
if(!p) {
p = malloc(sizeof(void*));
*(void**)p = 0;
ptr.set(p);
}
void* cur = *(void**)p;
void* next = *(void**)v;
*(void**)p = next;
if(next)
objType->GetEngine()->AddRefScriptObject(next, objType->GetSubType());
if(cur)
objType->GetEngine()->ReleaseScriptObject(cur, objType->GetSubType());
return p;
}
else if(typeId & ~asTYPEID_MASK_SEQNBR) {
if(!p) {
p = (void*)objType->GetEngine()->CreateScriptObject(objType);
ptr.set(p);
}
if(v)
objType->GetEngine()->AssignScriptObject(p, v, objType);
return p;
}
else if(typeId == asTYPEID_BOOL ||
typeId == asTYPEID_INT8 ||
typeId == asTYPEID_UINT8) {
if(!p) {
p = malloc(sizeof(char));
*(char*)p = 0;
ptr.set(p);
}
if(v)
*(char*)p = *(char*)v;
return p;
}
else if(typeId == asTYPEID_INT16 ||
typeId == asTYPEID_UINT16) {
if(!p) {
p = malloc(sizeof(short));
*(short*)p = 0;
ptr.set(p);
}
if(v)
*(short*)p = *(short*)v;
return p;
}
else if(typeId == asTYPEID_INT32 ||
typeId == asTYPEID_UINT32 ||
typeId == asTYPEID_FLOAT ||
typeId > asTYPEID_DOUBLE) /*enums*/ {
if(!p) {
p = malloc(sizeof(int));
*(int*)p = 0;
ptr.set(p);
}
if(v)
*(int*)p = *(int*)v;
return p;
}
else if(typeId == asTYPEID_INT64 ||
typeId == asTYPEID_UINT64 ||
typeId == asTYPEID_DOUBLE) {
if(!p) {
p = malloc(sizeof(double));
*(double*)p = 0;
ptr.set(p);
}
if(v)
*(double*)p = *(double*)v;
return p;
}
return 0;
}
};
refThreadLocal* createThreadLocal(asITypeInfo* ot) {
return new refThreadLocal(ot);
}
threads::threadreturn threadcall runScriptThread(void* arg);
bool IsHandleCompatibleWithObject(asCScriptEngine* engine, void *obj, int objTypeId, int handleTypeId)
{
// if equal, then it is obvious they are compatible
if( objTypeId == handleTypeId )
return true;
// Get the actual data types from the type ids
asCDataType objDt = engine->GetDataTypeFromTypeId(objTypeId);
asCDataType hdlDt = engine->GetDataTypeFromTypeId(handleTypeId);
// A handle to const cannot be passed to a handle that is not referencing a const object
if( objDt.IsHandleToConst() && !hdlDt.IsHandleToConst() )
return false;
if( objDt.GetTypeInfo() == hdlDt.GetTypeInfo() )
{
// The object type is equal
return true;
}
else if( objDt.IsScriptObject() && obj )
{
// Get the true type from the object instance
asITypeInfo *objType = ((asCScriptObject*)obj)->GetObjectType();
// Check if the object implements the interface, or derives from the base class
// This will also return true, if the requested handle type is an exact match for the object type
if( objType->Implements(hdlDt.GetTypeInfo()) ||
objType->DerivesFrom(hdlDt.GetTypeInfo()) )
return true;
}
return false;
}
struct ScriptThread {
//Two reference counters, one for the resource, one for the scripts
// When the script counter reaches 0, one system reference is lost, and the thread is shut down at the next opportunity
// When the system counter reaches 0, this struct is deleted
threads::atomic_int systemRefs, scriptRefs;
bool gcFlag;
threads::atomic_int runThread;
bool wasError;
Manager* manager;
asIScriptEngine* engine;
asIScriptFunction* function;
threads::Mutex objLock;
void* object;
asITypeInfo* objType;
bool setScriptObject(void* pointer, int typeID) {
threads::Lock lock(objLock);
if(typeID == 0 || pointer == 0 || *(void**)pointer == 0) {
if(object != 0) {
engine->ReleaseScriptObject(object, objType);
object = 0;
}
return true;
}
else if(typeID & asTYPEID_OBJHANDLE) {
object = *(void**)pointer;
objType = engine->GetTypeInfoById(typeID);
engine->AddRefScriptObject(object, objType);
return true;
}
return false;
}
bool getScriptObject(void* pointer, int typeID) {
if(pointer == 0)
return false;
threads::Lock lock(objLock);
if(typeID & asTYPEID_OBJHANDLE) {
if(typeID & asTYPEID_MASK_OBJECT && IsHandleCompatibleWithObject((asCScriptEngine*)engine, object, objType->GetTypeId(), typeID)) {
engine->AddRefScriptObject(object, objType);
*(void**)pointer = object;
return true;
}
}
return false;
}
void stop() {
runThread.compare_exchange_strong(1, 2);
}
bool start(const std::string& func) {
asIScriptContext* ctx = asGetActiveContext();
if(engine != ctx->GetEngine())
return false;
Manager* localMan = &Manager::fromEngine(engine);
if(!localMan)
return false;
asIScriptFunction* localFunc = localMan->getFunction(func, "(double, ScriptThread&)", "double");
if(localFunc) {
if(runThread.compare_exchange_strong(2, 0) == 0) {
function = localFunc;
manager = localMan;
++systemRefs;
wasError = false;
threads::createThread(runScriptThread, this);
return true;
}
}
return false;
}
bool isRunning() const {
return runThread != 0;
}
ScriptThread() : runThread(false), systemRefs(1), scriptRefs(1), gcFlag(false), engine(asGetActiveContext()->GetEngine()), function(0), object(0), wasError(false) {
engine->NotifyGarbageCollectorOfNewObject(this, engine->GetTypeInfoByName("ScriptThread"));
}
ScriptThread(const std::string& func) : runThread(false), systemRefs(1), scriptRefs(1), gcFlag(false), engine(asGetActiveContext()->GetEngine()), function(0), object(0), wasError(false) {
engine->NotifyGarbageCollectorOfNewObject(this, engine->GetTypeInfoByName("ScriptThread"));
start(func);
}
ScriptThread(const std::string& func, void* pointer, int typeID) : runThread(false), systemRefs(1), scriptRefs(1), gcFlag(false), engine(asGetActiveContext()->GetEngine()), function(0), object(0), wasError(false) {
engine->NotifyGarbageCollectorOfNewObject(this, engine->GetTypeInfoByName("ScriptThread"));
setScriptObject(pointer, typeID);
start(func);
}
static ScriptThread* create() {
return new ScriptThread();
}
static ScriptThread* create_f(const std::string& func) {
return new ScriptThread(func);
}
static ScriptThread* create_fo(const std::string& func, void* pointer, int typeID) {
return new ScriptThread(func, pointer, typeID);
}
void systemDrop() {
if(--systemRefs == 0)
delete this;
}
void scriptGrab() {
gcFlag = false;
++scriptRefs;
}
void scriptDrop() {
gcFlag = false;
if(--scriptRefs == 0) {
runThread = 0;
systemDrop();
}
}
void setGCFlag() {
gcFlag = true;
}
bool getGCFlag() const {
return gcFlag;
}
int getRefcount() const {
return scriptRefs;
}
void enumRefs(asIScriptEngine* eng) {
objLock.lock();
if(object)
eng->GCEnumCallback(object);
objLock.release();
}
void releaseRefs(asIScriptEngine* eng) {
setScriptObject(0,0);
}
};
threads::threadreturn threadcall runScriptThread(void* arg) {
ScriptThread& thread = *(ScriptThread*)arg;
initNewThread();
//Server uses game time, client and menu use frame time
bool useGameTime = thread.engine != devices.scripts.client->engine && thread.engine != devices.scripts.menu->engine;
double curTime = useGameTime ? devices.driver->getGameTime() : devices.driver->getFrameTime();
double lastTime = curTime;
double nextTime = lastTime;
thread.manager->scriptThreadCreate();
while(thread.runThread == 2) {
if(!thread.manager->scriptThreadStart())
break;
curTime = useGameTime ? devices.driver->getGameTime() : devices.driver->getFrameTime();
if(curTime >= nextTime) {
double delta = curTime - lastTime;
lastTime = curTime;
#ifdef TRACE_GC_LOCK
thread.manager->markGCImpossible();
#endif
auto call = thread.manager->call(thread.function);
call.push(delta);
call.push(arg);
double delay = 1.0;
if(!call.call(delay)) {
#ifdef TRACE_GC_LOCK
thread.manager->markGCPossible();
#endif
thread.wasError = true;
thread.runThread = 0;
thread.manager->scriptThreadEnd();
break;
}
#ifdef TRACE_GC_LOCK
thread.manager->markGCPossible();
#endif
nextTime = curTime + delay;
}
thread.manager->scriptThreadEnd();
//Sleep until 2ms before the next timer
if(devices.driver->getGameSpeed() < 0.01)
threads::idle();
else {
int sleepTime = (1000.0 * (nextTime - curTime)) - 2.0;
if(sleepTime < 0)
sleepTime = 0;
threads::sleep((unsigned)sleepTime);
}
}
cleanupThread();
thread.manager->scriptThreadDestroy();
thread.systemDrop();
thread.runThread = 0;
return 0;
}
void RegisterThreadingBinds() {
//Register atomic integer
ClassBind aint("atomic_int", asOBJ_REF, 0);
aint.addFactory("atomic_int@ f(int)", asFUNCTION(makeAtomic));
aint.setReferenceFuncs(asMETHOD(refAtomic, grab), asMETHOD(refAtomic, drop));
aint.addMethod("int opPreInc()", asMETHODPR(refAtomic, operator++, (), int));
aint.addMethod("int opPostInc()", asMETHOD(refAtomic, postInc));
aint.addMethod("int opPreDec()", asMETHODPR(refAtomic, operator--, (), int));
aint.addMethod("int opPostDec()", asMETHOD(refAtomic, postDec));
aint.addMethod("atomic_int& opAssign(int)", asMETHODPR(refAtomic, operator=, (int), refAtomic&));
aint.addMethod("void opAddAssign(int)", asMETHODPR(refAtomic, operator+=, (int), int));
aint.addMethod("void opDecAssign(int)", asMETHODPR(refAtomic, operator-=, (int), int));
aint.addMethod("atomic_int& set(int)", asMETHODPR(refAtomic, operator=, (int), refAtomic&));
aint.addMethod("int get()", asMETHODPR(refAtomic, get, () const, int));
aint.addMethod("int get_value()", asMETHODPR(refAtomic, get_basic, (), int));
aint.addMethod("void set_value(int)", asMETHODPR(refAtomic, set_basic, (int), void));
//Register regular mutex and lock
ClassBind mutex("Mutex", asOBJ_REF, 0);
mutex.addFactory("Mutex@ f()", asFUNCTION(makeMutex));
mutex.setReferenceFuncs(asMETHOD(refMutex, grab), asMETHOD(refMutex, drop));
ClassBind lock("Lock", asOBJ_REF | asOBJ_SCOPED, 0);
lock.addFactory("Lock@ f(Mutex&)", asFUNCTION(lockMutex));
lock.addExternBehaviour(asBEHAVE_RELEASE, "void f()", asFUNCTION(unlockMutex));
//Register read-write mutex and lock
ClassBind rw_mutex("ReadWriteMutex", asOBJ_REF, 0);
rw_mutex.addFactory("ReadWriteMutex@ f()", asFUNCTION(makeRWMutex));
rw_mutex.setReferenceFuncs(asMETHOD(refRWMutex, grab), asMETHOD(refRWMutex, drop));
ClassBind readLock("ReadLock", asOBJ_REF | asOBJ_SCOPED, 0);
readLock.addFactory("ReadLock@ f(ReadWriteMutex&)", asFUNCTION(lockReadMutex));
readLock.addExternBehaviour(asBEHAVE_RELEASE, "void f()", asFUNCTION(unlockRWMutex));
ClassBind writeLock("WriteLock", asOBJ_REF | asOBJ_SCOPED, 0);
writeLock.addFactory("WriteLock@ f(ReadWriteMutex&)", asFUNCTION(lockWriteMutex));
writeLock.addExternBehaviour(asBEHAVE_RELEASE, "void f()", asFUNCTION(unlockRWMutex));
//Register signal
ClassBind signal("Signal", asOBJ_REF, 0);
signal.addFactory("Signal@ f()", asFUNCTION(makeSignal));
signal.setReferenceFuncs(asMETHOD(refSignal, grab), asMETHOD(refSignal, drop));
signal.addMethod("void signal(int)", asMETHOD(refSignal, signal));
signal.addMethod("void signalDown()", asMETHOD(refSignal, signalDown));
signal.addMethod("void signalUp()", asMETHOD(refSignal, signalUp));
signal.addMethod("bool check(int)", asMETHOD(refSignal, check));
signal.addMethod("void wait(int)", asMETHOD(refSignal, wait));
signal.addMethod("void waitNot(int)", asMETHOD(refSignal, waitNot));
signal.addMethod("void waitAndSignal(int, int)", asMETHOD(refSignal, waitAndSignal));
//General threading functions
bind("void sleep(uint ms)", asFUNCTION(threads::sleep))
doc("Stops thread processing for some number of milliseconds.",
"Number of milliseconds to sleep (approximately)");
bind("int get_threadID()", asFUNCTION(getThreadID));
//Thread local storage
ClassBind tl("ThreadLocal", asOBJ_REF | asOBJ_TEMPLATE);
tl.addLooseBehaviour(asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in,bool&out)", asFUNCTION(validateThreadLocal));
tl.setReferenceFuncs(asMETHOD(refThreadLocal, grab), asMETHOD(refThreadLocal, drop));
tl.addFactory("ThreadLocal<T>@ f(int&in)", asFUNCTION(createThreadLocal));
tl.addMethod("T& get()", asMETHOD(refThreadLocal, get));
tl.addMethod("T& set(const T&in)", asMETHOD(refThreadLocal, set));
//Thread management
ClassBind thread("ScriptThread", asOBJ_REF | asOBJ_GC);
classdoc(thread, "Creates and controls script threads.");
thread.setReferenceFuncs(asMETHOD(ScriptThread,scriptGrab), asMETHOD(ScriptThread,scriptDrop));
thread.addGarbageCollection(
asMETHOD(ScriptThread,setGCFlag),
asMETHOD(ScriptThread,getGCFlag),
asMETHOD(ScriptThread,getRefcount),
asMETHOD(ScriptThread,enumRefs),
asMETHOD(ScriptThread,releaseRefs) );
thread.addFactory("ScriptThread@ f()", asFUNCTION(ScriptThread::create));
thread.addFactory("ScriptThread@ f(const string &in entry)", asFUNCTION(ScriptThread::create_f))
doc("Starts a thread using the specified function.",
"Entry point of type 'double f(double, ScriptThread&)'. Receives time since last call and a reference to the associated ScripThread. Returns delay till next call.", "");
thread.addFactory("ScriptThread@ f(const string &in entry, ?&in)", asFUNCTION(ScriptThread::create_fo))
doc("Starts a thread using the specified function and script object.",
"Entry point of type 'double f(double, ScriptThread&)'. Receives time since last call and a reference to the associated ScripThread. Returns delay till next call.", "Script object to assign to thread.", "");
thread.addMethod("bool start(const string &in entry)", asMETHOD(ScriptThread,start))
doc("Starts a thread using the specified function.",
"Entry point of type 'double f(double, ScriptThread&)'. Receives time since last call and a reference to the associated ScripThread. Returns delay till next call.", "True if the thread was started");
thread.addMethod("void stop()", asMETHOD(ScriptThread,stop))
doc("Stops the active thread.");
thread.addMethod("bool get_running()", asMETHOD(ScriptThread,isRunning))
doc("", "Whether there is an active thread.");
thread.addMethod("bool setObject(?&)", asMETHOD(ScriptThread,setScriptObject))
doc("Sets the thread's object. Can only receive script object handles.", "", "True if the reference could be taken");
thread.addMethod("bool getObject(?&out)", asMETHOD(ScriptThread,getScriptObject))
doc("Gets the thread's object. Can only receive script object handles.", "", "True if the reference could be stored");
thread.addMember("bool wasError", offsetof(ScriptThread,wasError))
doc("Whether the thread was ended due to an error.");
}
};