mirror of
https://github.com/kevinbentley/Descent3.git
synced 2026-04-07 02:00:05 -04:00
Initial import
This commit is contained in:
536
AngelScript/source/as_gc.cpp
Normal file
536
AngelScript/source/as_gc.cpp
Normal file
@@ -0,0 +1,536 @@
|
||||
/*
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user