mirror of
https://github.com/kevinbentley/Descent3.git
synced 2026-04-05 23:00:03 -04:00
467 lines
16 KiB
C++
467 lines
16 KiB
C++
/*
|
|
AngelCode Scripting Library
|
|
Copyright (c) 2003-2009 Andreas Jonsson
|
|
|
|
This software is provided 'as-is', without any express or implied
|
|
warranty. In no event will the authors be held liable for any
|
|
damages arising from the use of this software.
|
|
|
|
Permission is granted to anyone to use this software for any
|
|
purpose, including commercial applications, and to alter it and
|
|
redistribute it freely, subject to the following restrictions:
|
|
|
|
1. The origin of this software must not be misrepresented; you
|
|
must not claim that you wrote the original software. If you use
|
|
this software in a product, an acknowledgment in the product
|
|
documentation would be appreciated but is not required.
|
|
|
|
2. Altered source versions must be plainly marked as such, and
|
|
must not be misrepresented as being the original software.
|
|
|
|
3. This notice may not be removed or altered from any source
|
|
distribution.
|
|
|
|
The original version of this library can be located at:
|
|
http://www.angelcode.com/angelscript/
|
|
|
|
Andreas Jonsson
|
|
andreas@angelcode.com
|
|
*/
|
|
|
|
//
|
|
// as_gc.cpp
|
|
//
|
|
// The implementation of the garbage collector
|
|
//
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "as_gc.h"
|
|
#include "as_scriptengine.h"
|
|
#include "as_scriptobject.h"
|
|
|
|
BEGIN_AS_NAMESPACE
|
|
|
|
asCGarbageCollector::asCGarbageCollector() {
|
|
engine = 0;
|
|
detectState = clearCounters_init;
|
|
destroyState = destroyGarbage_init;
|
|
numDestroyed = 0;
|
|
numDetected = 0;
|
|
}
|
|
|
|
void asCGarbageCollector::AddScriptObjectToGC(void *obj, asCObjectType *objType) {
|
|
engine->CallObjectMethod(obj, objType->beh.addref);
|
|
asSObjTypePair ot = {obj, objType};
|
|
|
|
// Add the data to the gcObjects array in a critical section as
|
|
// another thread might be calling this method at the same time
|
|
ENTERCRITICALSECTION(gcCritical);
|
|
gcObjects.PushLast(ot);
|
|
LEAVECRITICALSECTION(gcCritical);
|
|
}
|
|
|
|
int asCGarbageCollector::GarbageCollect(asDWORD flags) {
|
|
// The application is responsible for making sure
|
|
// the gc is only executed by one thread at a time.
|
|
|
|
bool doDetect = (flags & asGC_DETECT_GARBAGE) || !(flags & asGC_DESTROY_GARBAGE);
|
|
bool doDestroy = (flags & asGC_DESTROY_GARBAGE) || !(flags & asGC_DETECT_GARBAGE);
|
|
|
|
if (flags & asGC_FULL_CYCLE) {
|
|
// Reset the state
|
|
if (doDetect)
|
|
detectState = clearCounters_init;
|
|
if (doDestroy)
|
|
destroyState = destroyGarbage_init;
|
|
|
|
int r = 1;
|
|
unsigned int count = (unsigned int)gcObjects.GetLength();
|
|
for (;;) {
|
|
// Detect all garbage with cyclic references
|
|
if (doDetect)
|
|
while ((r = IdentifyGarbageWithCyclicRefs()) == 1)
|
|
;
|
|
|
|
// Now destroy all known garbage
|
|
if (doDestroy)
|
|
while ((r = DestroyGarbage()) == 1)
|
|
;
|
|
|
|
// Run another iteration if any garbage was destroyed
|
|
if (count != gcObjects.GetLength())
|
|
count = (unsigned int)gcObjects.GetLength();
|
|
else
|
|
break;
|
|
}
|
|
|
|
// Take the opportunity to clear unused types as well
|
|
engine->ClearUnusedTypes();
|
|
|
|
return 0;
|
|
} else {
|
|
// Destroy the garbage that we know of
|
|
if (doDestroy)
|
|
DestroyGarbage();
|
|
|
|
// Run another incremental step of the identification of cyclic references
|
|
if (doDetect)
|
|
IdentifyGarbageWithCyclicRefs();
|
|
}
|
|
|
|
// Return 1 to indicate that the cycle wasn't finished
|
|
return 1;
|
|
}
|
|
|
|
void asCGarbageCollector::GetStatistics(asUINT *currentSize, asUINT *totalDestroyed, asUINT *totalDetected) {
|
|
// It's not necessary to protect this access, as
|
|
// it doesn't matter if another thread is currently
|
|
// appending a new object.
|
|
if (currentSize)
|
|
*currentSize = (asUINT)gcObjects.GetLength();
|
|
|
|
if (totalDestroyed)
|
|
*totalDestroyed = numDestroyed;
|
|
|
|
if (totalDetected)
|
|
*totalDetected = numDetected;
|
|
}
|
|
|
|
void asCGarbageCollector::ClearMap() {
|
|
// Decrease reference counter for all objects removed from the map
|
|
asSMapNode<void *, asSIntTypePair> *cursor = 0;
|
|
gcMap.MoveFirst(&cursor);
|
|
while (cursor) {
|
|
void *obj = gcMap.GetKey(cursor);
|
|
asSIntTypePair it = gcMap.GetValue(cursor);
|
|
|
|
engine->CallObjectMethod(obj, it.type->beh.release);
|
|
|
|
gcMap.MoveNext(&cursor, cursor);
|
|
}
|
|
|
|
gcMap.EraseAll();
|
|
}
|
|
|
|
asCGarbageCollector::asSObjTypePair asCGarbageCollector::GetObjectAtIdx(int idx) {
|
|
// We need to protect this access with a critical section as
|
|
// another thread might be appending an object at the same time
|
|
ENTERCRITICALSECTION(gcCritical);
|
|
asSObjTypePair gcObj = gcObjects[idx];
|
|
LEAVECRITICALSECTION(gcCritical);
|
|
|
|
return gcObj;
|
|
}
|
|
|
|
void asCGarbageCollector::RemoveObjectAtIdx(int idx) {
|
|
// We need to protect this update with a critical section as
|
|
// another thread might be appending an object at the same time
|
|
ENTERCRITICALSECTION(gcCritical);
|
|
if (idx == (int)gcObjects.GetLength() - 1)
|
|
gcObjects.PopLast();
|
|
else
|
|
gcObjects[idx] = gcObjects.PopLast();
|
|
LEAVECRITICALSECTION(gcCritical);
|
|
}
|
|
|
|
int asCGarbageCollector::DestroyGarbage() {
|
|
for (;;) {
|
|
switch (destroyState) {
|
|
case destroyGarbage_init: {
|
|
// If there are no objects to be freed then don't start
|
|
if (gcObjects.GetLength() == 0)
|
|
return 0;
|
|
|
|
destroyIdx = (asUINT)-1;
|
|
destroyState = destroyGarbage_loop;
|
|
} break;
|
|
|
|
case destroyGarbage_loop:
|
|
case destroyGarbage_haveMore: {
|
|
// If the refCount has reached 1, then only the GC still holds a
|
|
// reference to the object, thus we don't need to worry about the
|
|
// application touching the objects during collection.
|
|
|
|
// Destroy all objects that have refCount == 1. If any objects are
|
|
// destroyed, go over the list again, because it may have made more
|
|
// objects reach refCount == 1.
|
|
while (++destroyIdx < gcObjects.GetLength()) {
|
|
asSObjTypePair gcObj = GetObjectAtIdx(destroyIdx);
|
|
if (engine->CallObjectMethodRetInt(gcObj.obj, gcObj.type->beh.gcGetRefCount) == 1) {
|
|
// Release the object immediately
|
|
|
|
// Make sure the refCount is really 0, because the
|
|
// destructor may have increased the refCount again.
|
|
bool addRef = false;
|
|
if (gcObj.type->flags & asOBJ_SCRIPT_OBJECT) {
|
|
// Script objects may actually be resurrected in the destructor
|
|
int refCount = ((asCScriptObject *)gcObj.obj)->Release();
|
|
if (refCount > 0)
|
|
addRef = true;
|
|
} else
|
|
engine->CallObjectMethod(gcObj.obj, gcObj.type->beh.release);
|
|
|
|
// Was the object really destroyed?
|
|
if (!addRef) {
|
|
numDestroyed++;
|
|
RemoveObjectAtIdx(destroyIdx);
|
|
destroyIdx--;
|
|
} else {
|
|
// Since the object was resurrected in the
|
|
// destructor, we must add our reference again
|
|
engine->CallObjectMethod(gcObj.obj, gcObj.type->beh.addref);
|
|
}
|
|
|
|
destroyState = destroyGarbage_haveMore;
|
|
|
|
// Allow the application to work a little
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// Only move to the next step if no garbage was detected in this step
|
|
if (destroyState == destroyGarbage_haveMore)
|
|
destroyState = destroyGarbage_init;
|
|
else
|
|
return 0;
|
|
} break;
|
|
}
|
|
}
|
|
|
|
// Shouldn't reach this point
|
|
UNREACHABLE_RETURN;
|
|
}
|
|
|
|
int asCGarbageCollector::IdentifyGarbageWithCyclicRefs() {
|
|
for (;;) {
|
|
switch (detectState) {
|
|
case clearCounters_init: {
|
|
ClearMap();
|
|
detectState = clearCounters_loop;
|
|
detectIdx = 0;
|
|
} break;
|
|
|
|
case clearCounters_loop: {
|
|
// Build a map of objects that will be checked, the map will
|
|
// hold the object pointer as key, and the gcCount and the
|
|
// object's type as value. As objects are added to the map the
|
|
// gcFlag must be set in the objects, so we can be verify if
|
|
// the object is accessed during the GC cycle.
|
|
|
|
// If an object is removed from the gcObjects list during the
|
|
// iteration of this step, it is possible that an object won't
|
|
// be used during the analyzing for cyclic references. This
|
|
// isn't a problem, as the next time the GC cycle starts the
|
|
// object will be verified.
|
|
while (detectIdx < gcObjects.GetLength()) {
|
|
// Add the gc count for this object
|
|
asSObjTypePair gcObj = GetObjectAtIdx(detectIdx);
|
|
int refCount = engine->CallObjectMethodRetInt(gcObj.obj, gcObj.type->beh.gcGetRefCount);
|
|
if (refCount > 1) {
|
|
asSIntTypePair it = {refCount - 1, gcObj.type};
|
|
gcMap.Insert(gcObj.obj, it);
|
|
|
|
// Increment the object's reference counter when putting it in the map
|
|
engine->CallObjectMethod(gcObj.obj, gcObj.type->beh.addref);
|
|
|
|
// Mark the object so that we can
|
|
// see if it has changed since read
|
|
engine->CallObjectMethod(gcObj.obj, gcObj.type->beh.gcSetFlag);
|
|
|
|
detectIdx++;
|
|
|
|
// Let the application work a little
|
|
return 1;
|
|
} else
|
|
detectIdx++;
|
|
}
|
|
|
|
detectState = countReferences_init;
|
|
} break;
|
|
|
|
case countReferences_init: {
|
|
detectIdx = (asUINT)-1;
|
|
gcMap.MoveFirst(&gcMapCursor);
|
|
detectState = countReferences_loop;
|
|
} break;
|
|
|
|
case countReferences_loop: {
|
|
// Call EnumReferences on all objects in the map to count the number
|
|
// of references reachable from between objects in the map. If all
|
|
// references for an object in the map is reachable from other objects
|
|
// in the map, then we know that no outside references are held for
|
|
// this object, thus it is a potential dead object in a circular reference.
|
|
|
|
// If the gcFlag is cleared for an object we consider the object alive
|
|
// and referenced from outside the GC, thus we don't enumerate its references.
|
|
|
|
// Any new objects created after this step in the GC cycle won't be
|
|
// in the map, and is thus automatically considered alive.
|
|
while (gcMapCursor) {
|
|
void *obj = gcMap.GetKey(gcMapCursor);
|
|
asCObjectType *type = gcMap.GetValue(gcMapCursor).type;
|
|
gcMap.MoveNext(&gcMapCursor, gcMapCursor);
|
|
|
|
if (engine->CallObjectMethodRetBool(obj, type->beh.gcGetFlag)) {
|
|
engine->CallObjectMethod(obj, engine, type->beh.gcEnumReferences);
|
|
|
|
// Allow the application to work a little
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
detectState = detectGarbage_init;
|
|
} break;
|
|
|
|
case detectGarbage_init: {
|
|
detectIdx = (asUINT)-1;
|
|
gcMap.MoveFirst(&gcMapCursor);
|
|
liveObjects.SetLength(0);
|
|
detectState = detectGarbage_loop1;
|
|
} break;
|
|
|
|
case detectGarbage_loop1: {
|
|
// All objects that are known not to be dead must be removed from the map,
|
|
// along with all objects they reference. What remains in the map after
|
|
// this pass is sure to be dead objects in circular references.
|
|
|
|
// An object is considered alive if its gcFlag is cleared, or all the
|
|
// references were not found in the map.
|
|
|
|
// Add all alive objects from the map to the liveObjects array
|
|
while (gcMapCursor) {
|
|
asSMapNode<void *, asSIntTypePair> *cursor = gcMapCursor;
|
|
gcMap.MoveNext(&gcMapCursor, gcMapCursor);
|
|
|
|
void *obj = gcMap.GetKey(cursor);
|
|
asSIntTypePair it = gcMap.GetValue(cursor);
|
|
|
|
bool gcFlag = engine->CallObjectMethodRetBool(obj, it.type->beh.gcGetFlag);
|
|
if (!gcFlag || it.i > 0) {
|
|
liveObjects.PushLast(obj);
|
|
|
|
// Allow the application to work a little
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
detectState = detectGarbage_loop2;
|
|
} break;
|
|
|
|
case detectGarbage_loop2: {
|
|
// In this step we are actually removing the alive objects from the map.
|
|
// As the object is removed, all the objects it references are added to the
|
|
// liveObjects list, by calling EnumReferences. Only objects still in the map
|
|
// will be added to the liveObjects list.
|
|
while (liveObjects.GetLength()) {
|
|
void *gcObj = liveObjects.PopLast();
|
|
asCObjectType *type = 0;
|
|
|
|
// Remove the object from the map to mark it as alive
|
|
asSMapNode<void *, asSIntTypePair> *cursor = 0;
|
|
if (gcMap.MoveTo(&cursor, gcObj)) {
|
|
type = gcMap.GetValue(cursor).type;
|
|
gcMap.Erase(cursor);
|
|
|
|
// We need to decrease the reference count again as we remove the object from the map
|
|
engine->CallObjectMethod(gcObj, type->beh.release);
|
|
|
|
// Enumerate all the object's references so that they too can be marked as alive
|
|
engine->CallObjectMethod(gcObj, engine, type->beh.gcEnumReferences);
|
|
}
|
|
|
|
// Allow the application to work a little
|
|
return 1;
|
|
}
|
|
|
|
detectState = verifyUnmarked;
|
|
} break;
|
|
|
|
case verifyUnmarked: {
|
|
// In this step we must make sure that none of the objects still in the map
|
|
// has been touched by the application. If they have then we must run the
|
|
// detectGarbage loop once more.
|
|
gcMap.MoveFirst(&gcMapCursor);
|
|
while (gcMapCursor) {
|
|
void *gcObj = gcMap.GetKey(gcMapCursor);
|
|
asCObjectType *type = gcMap.GetValue(gcMapCursor).type;
|
|
|
|
bool gcFlag = engine->CallObjectMethodRetBool(gcObj, type->beh.gcGetFlag);
|
|
if (!gcFlag) {
|
|
// The unmarked object was touched, rerun the detectGarbage loop
|
|
detectState = detectGarbage_init;
|
|
return 1;
|
|
}
|
|
|
|
gcMap.MoveNext(&gcMapCursor, gcMapCursor);
|
|
}
|
|
|
|
// No unmarked object was touched, we can now be sure
|
|
// that objects that have gcCount == 0 really is garbage
|
|
detectState = breakCircles_init;
|
|
} break;
|
|
|
|
case breakCircles_init: {
|
|
detectIdx = (asUINT)-1;
|
|
gcMap.MoveFirst(&gcMapCursor);
|
|
detectState = breakCircles_loop;
|
|
} break;
|
|
|
|
case breakCircles_loop:
|
|
case breakCircles_haveGarbage: {
|
|
// All objects in the map are now known to be dead objects
|
|
// kept alive through circular references. To be able to free
|
|
// these objects we need to force the breaking of the circle
|
|
// by having the objects release their references.
|
|
while (gcMapCursor) {
|
|
numDetected++;
|
|
void *gcObj = gcMap.GetKey(gcMapCursor);
|
|
asCObjectType *type = gcMap.GetValue(gcMapCursor).type;
|
|
engine->CallObjectMethod(gcObj, engine, type->beh.gcReleaseAllReferences);
|
|
|
|
gcMap.MoveNext(&gcMapCursor, gcMapCursor);
|
|
|
|
detectState = breakCircles_haveGarbage;
|
|
|
|
// Allow the application to work a little
|
|
return 1;
|
|
}
|
|
|
|
// If no garbage was detected we can finish now
|
|
if (detectState != breakCircles_haveGarbage) {
|
|
// Restart the GC
|
|
detectState = clearCounters_init;
|
|
return 0;
|
|
} else {
|
|
// Restart the GC
|
|
detectState = clearCounters_init;
|
|
return 1;
|
|
}
|
|
} break;
|
|
} // switch
|
|
}
|
|
|
|
// Shouldn't reach this point
|
|
UNREACHABLE_RETURN;
|
|
}
|
|
|
|
void asCGarbageCollector::GCEnumCallback(void *reference) {
|
|
if (detectState == countReferences_loop) {
|
|
// Find the reference in the map
|
|
asSMapNode<void *, asSIntTypePair> *cursor = 0;
|
|
if (gcMap.MoveTo(&cursor, reference)) {
|
|
// Decrease the counter in the map for the reference
|
|
gcMap.GetValue(cursor).i--;
|
|
}
|
|
} else if (detectState == detectGarbage_loop2) {
|
|
// Find the reference in the map
|
|
asSMapNode<void *, asSIntTypePair> *cursor = 0;
|
|
if (gcMap.MoveTo(&cursor, reference)) {
|
|
// Add the object to the list of objects to mark as alive
|
|
liveObjects.PushLast(reference);
|
|
}
|
|
}
|
|
}
|
|
|
|
END_AS_NAMESPACE
|