123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998 |
- /*
- AngelCode Scripting Library
- Copyright (c) 2003-2018 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"
- #include "as_texts.h"
- BEGIN_AS_NAMESPACE
- asCGarbageCollector::asCGarbageCollector()
- {
- engine = 0;
- detectState = clearCounters_init;
- destroyNewState = destroyGarbage_init;
- destroyOldState = destroyGarbage_init;
- numDestroyed = 0;
- numNewDestroyed = 0;
- numDetected = 0;
- numAdded = 0;
- isProcessing = false;
- seqAtSweepStart[0] = 0;
- seqAtSweepStart[1] = 0;
- seqAtSweepStart[2] = 0;
- circularRefDetectCallbackFunc = 0;
- circularRefDetectCallbackParam = 0;
- }
- asCGarbageCollector::~asCGarbageCollector()
- {
- // This local typedef is done to workaround a compiler error on
- // MSVC6 when using the typedef declared in the class definition
- typedef asSMapNode_t node_t;
- for( asUINT n = 0; n < freeNodes.GetLength(); n++ )
- asDELETE(freeNodes[n], node_t);
- freeNodes.SetLength(0);
- }
- int asCGarbageCollector::AddScriptObjectToGC(void *obj, asCObjectType *objType)
- {
- if( obj == 0 || objType == 0 )
- {
- engine->WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_GC_RECEIVED_NULL_PTR);
- return asINVALID_ARG;
- }
- engine->CallObjectMethod(obj, objType->beh.addref);
- asSObjTypePair ot = {obj, objType, 0};
- // Invoke the garbage collector to destroy a little garbage as new comes in
- // This will maintain the number of objects in the GC at a maintainable level without
- // halting the application, and without burdening the application with manually invoking the
- // garbage collector.
- if( engine->ep.autoGarbageCollect && gcNewObjects.GetLength() )
- {
- // If the GC is already processing in another thread, then don't try this again
- if( TRYENTERCRITICALSECTION(gcCollecting) )
- {
- // Skip this if the GC is already running in this thread
- if( !isProcessing )
- {
- isProcessing = true;
- // TODO: The number of iterations should be dynamic, and increase
- // if the number of objects in the garbage collector grows high
- // Run one step of DetectGarbage
- if( gcOldObjects.GetLength() )
- {
- IdentifyGarbageWithCyclicRefs();
- DestroyOldGarbage();
- }
- // Run a few steps of DestroyGarbage
- int iter = (int)gcNewObjects.GetLength();
- if( iter > 10 ) iter = 10;
- while( iter-- > 0 )
- DestroyNewGarbage();
- isProcessing = false;
- }
- LEAVECRITICALSECTION(gcCollecting);
- }
- }
- // 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);
- ot.seqNbr = numAdded++;
- gcNewObjects.PushLast(ot);
- LEAVECRITICALSECTION(gcCritical);
- return ot.seqNbr;
- }
- int asCGarbageCollector::GetObjectInGC(asUINT idx, asUINT *seqNbr, void **obj, asITypeInfo **type)
- {
- if( seqNbr ) *seqNbr = 0;
- if( obj ) *obj = 0;
- if( type ) *type = 0;
- ENTERCRITICALSECTION(gcCritical);
- asSObjTypePair *o = 0;
- asUINT newObjs = asUINT(gcNewObjects.GetLength());
- if( idx < newObjs )
- o = &gcNewObjects[idx];
- else if( idx < gcOldObjects.GetLength() + newObjs )
- o = &gcOldObjects[idx-newObjs];
- else
- {
- LEAVECRITICALSECTION(gcCritical);
- return asINVALID_ARG;
- }
- if( seqNbr ) *seqNbr = o->seqNbr;
- if( obj ) *obj = o->obj;
- if( type ) *type = o->type;
- LEAVECRITICALSECTION(gcCritical);
- return asSUCCESS;
- }
- // TODO: Should have a flag to tell the garbage collector to automatically determine how many iterations are needed
- // It should then gather statistics such as how many objects has been created since last run, and how many objects
- // are destroyed per iteration, and how many objects are detected as cyclic garbage per iteration.
- // It should try to reach a stable number of objects, i.e. so that on average the number of objects added to
- // the garbage collector is the same as the number of objects destroyed. And it should try to minimize the number
- // of iterations of detections that must be executed per cycle while still identifying the cyclic garbage
- // These variables should also be available for inspection through the gcstatistics.
- int asCGarbageCollector::GarbageCollect(asDWORD flags, asUINT iterations)
- {
- // If the GC is already processing in another thread, then don't enter here again
- if( TRYENTERCRITICALSECTION(gcCollecting) )
- {
- // If the GC is already processing in this thread, then don't enter here again
- if( isProcessing )
- {
- LEAVECRITICALSECTION(gcCollecting);
- return 1;
- }
- isProcessing = true;
- 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 )
- {
- // Move all new objects to the old list, so we guarantee that all is detected
- MoveAllObjectsToOldList();
- detectState = clearCounters_init;
- }
- if( doDestroy )
- {
- destroyNewState = destroyGarbage_init;
- destroyOldState = destroyGarbage_init;
- }
- // The full cycle only works with the objects in the old list so that the
- // set of objects scanned for garbage is fixed even if new objects are added
- // by other threads in parallel.
- unsigned int count = (unsigned int)(gcOldObjects.GetLength());
- for(;;)
- {
- // Detect all garbage with cyclic references
- if( doDetect )
- while( IdentifyGarbageWithCyclicRefs() == 1 ) {}
- // Now destroy all known garbage
- if( doDestroy )
- {
- if( !doDetect )
- while( DestroyNewGarbage() == 1 ) {}
- while( DestroyOldGarbage() == 1 ) {}
- }
- // Run another iteration if any garbage was destroyed
- if( count != (unsigned int)(gcOldObjects.GetLength()) )
- count = (unsigned int)(gcOldObjects.GetLength());
- else
- break;
- }
- isProcessing = false;
- LEAVECRITICALSECTION(gcCollecting);
- return 0;
- }
- else
- {
- while( iterations-- > 0 )
- {
- // Destroy the garbage that we know of
- if( doDestroy )
- {
- DestroyNewGarbage();
- DestroyOldGarbage();
- }
- // Run another incremental step of the identification of cyclic references
- if( doDetect && gcOldObjects.GetLength() > 0 )
- IdentifyGarbageWithCyclicRefs();
- }
- }
- isProcessing = false;
- LEAVECRITICALSECTION(gcCollecting);
- }
-
- // Return 1 to indicate that the cycle wasn't finished
- return 1;
- }
- // TODO: Additional statistics to gather
- //
- // - How many objects are added on average between each destroyed object
- // - How many objects are added on average between each detected object
- // - how many iterations are needed for each destroyed object
- // - how many iterations are needed for each detected object
- //
- // The average must have a decay so that long running applications will not suffer
- // from objects being created early on in the application and then never destroyed.
- //
- // This ought to be possible to accomplish by holding two buckets.
- // Numbers will be accumulated in one bucket while the other is held fixed.
- // When returning the average it should use a weighted average between the two buckets using the size as weight.
- // When a bucket is filled up, the buckets are switched, and then new bucket is emptied to gather new statistics.
- void asCGarbageCollector::GetStatistics(asUINT *currentSize, asUINT *totalDestroyed, asUINT *totalDetected, asUINT *newObjects, asUINT *totalNewDestroyed) const
- {
- // It is not necessary to protect this with critical sections, however
- // as it is not protected the variables can be filled in slightly different
- // moments and might not match perfectly when inspected by the application
- // afterwards.
- if( currentSize )
- *currentSize = (asUINT)(gcNewObjects.GetLength() + gcOldObjects.GetLength());
- if( totalDestroyed )
- *totalDestroyed = numDestroyed;
- if( totalDetected )
- *totalDetected = numDetected;
- if( newObjects )
- *newObjects = (asUINT)gcNewObjects.GetLength();
- if( totalNewDestroyed )
- *totalNewDestroyed = numNewDestroyed;
- }
- asCGarbageCollector::asSObjTypePair asCGarbageCollector::GetNewObjectAtIdx(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 = gcNewObjects[idx];
- LEAVECRITICALSECTION(gcCritical);
- return gcObj;
- }
- asCGarbageCollector::asSObjTypePair asCGarbageCollector::GetOldObjectAtIdx(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 = gcOldObjects[idx];
- LEAVECRITICALSECTION(gcCritical);
- return gcObj;
- }
- void asCGarbageCollector::RemoveNewObjectAtIdx(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)gcNewObjects.GetLength() - 1)
- gcNewObjects.PopLast();
- else
- gcNewObjects[idx] = gcNewObjects.PopLast();
- LEAVECRITICALSECTION(gcCritical);
- }
- void asCGarbageCollector::RemoveOldObjectAtIdx(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)gcOldObjects.GetLength() - 1)
- gcOldObjects.PopLast();
- else
- gcOldObjects[idx] = gcOldObjects.PopLast();
- LEAVECRITICALSECTION(gcCritical);
- }
- void asCGarbageCollector::MoveObjectToOldList(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);
- gcOldObjects.PushLast(gcNewObjects[idx]);
- if( idx == (int)gcNewObjects.GetLength() - 1)
- gcNewObjects.PopLast();
- else
- gcNewObjects[idx] = gcNewObjects.PopLast();
- LEAVECRITICALSECTION(gcCritical);
- }
- void asCGarbageCollector::MoveAllObjectsToOldList()
- {
- // 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( gcOldObjects.Concatenate(gcNewObjects) )
- gcNewObjects.SetLength(0);
- LEAVECRITICALSECTION(gcCritical);
- }
- int asCGarbageCollector::DestroyNewGarbage()
- {
- // This function will only be called within the critical section gcCollecting
- asASSERT(isProcessing);
- for(;;)
- {
- switch( destroyNewState )
- {
- case destroyGarbage_init:
- {
- // If there are no objects to be freed then don't start
- if( gcNewObjects.GetLength() == 0 )
- return 0;
- // Update the seqAtSweepStart which is used to determine when
- // to move an object from the new set to the old set
- seqAtSweepStart[0] = seqAtSweepStart[1];
- seqAtSweepStart[1] = seqAtSweepStart[2];
- seqAtSweepStart[2] = numAdded;
- destroyNewIdx = (asUINT)-1;
- destroyNewState = 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.
- if( ++destroyNewIdx < gcNewObjects.GetLength() )
- {
- asSObjTypePair gcObj = GetNewObjectAtIdx(destroyNewIdx);
- 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++;
- numNewDestroyed++;
- RemoveNewObjectAtIdx(destroyNewIdx);
- destroyNewIdx--;
- }
- else
- {
- // Since the object was resurrected in the
- // destructor, we must add our reference again
- engine->CallObjectMethod(gcObj.obj, gcObj.type->beh.addref);
- }
- destroyNewState = destroyGarbage_haveMore;
- }
- // Check if this object has been inspected 3 times already, and if so move it to the
- // set of old objects that are less likely to become garbage in a short time
- // TODO: Is 3 really a good value? Should the number of times be dynamic?
- else if( gcObj.seqNbr < seqAtSweepStart[0] )
- {
- // We've already verified this object multiple times. It is likely
- // to live for quite a long time so we'll move it to the list if old objects
- MoveObjectToOldList(destroyNewIdx);
- destroyNewIdx--;
- }
- // Allow the application to work a little
- return 1;
- }
- else
- {
- if( destroyNewState == destroyGarbage_haveMore )
- {
- // Restart the cycle
- destroyNewState = destroyGarbage_init;
- }
- else
- {
- // Restart the cycle
- destroyNewState = destroyGarbage_init;
- // Return 0 to tell the application that there
- // is no more garbage to destroy at the moment
- return 0;
- }
- }
- }
- break;
- }
- }
- // Shouldn't reach this point
- UNREACHABLE_RETURN;
- }
- int asCGarbageCollector::ReportAndReleaseUndestroyedObjects()
- {
- // This function will only be called as the engine is shutting down
- int items = 0;
- for( asUINT n = 0; n < gcOldObjects.GetLength(); n++ )
- {
- asSObjTypePair gcObj = GetOldObjectAtIdx(n);
- int refCount = 0;
- if( gcObj.type->beh.gcGetRefCount && engine->scriptFunctions[gcObj.type->beh.gcGetRefCount] )
- refCount = engine->CallObjectMethodRetInt(gcObj.obj, gcObj.type->beh.gcGetRefCount);
- // Report the object as not being properly destroyed
- asCString msg;
- msg.Format(TXT_d_GC_CANNOT_FREE_OBJ_OF_TYPE_s_REF_COUNT_d, gcObj.seqNbr, gcObj.type->name.AddressOf(), refCount - 1);
- engine->WriteMessage("", 0, 0, asMSGTYPE_ERROR, msg.AddressOf());
- // Add additional info for builtin types
- if( gcObj.type->name == "$func" )
- {
- // Unfortunately we can't show the function declaration here, because the engine may have released the parameter list already so the declaration would only be misleading
- // We need to show the function type too as for example delegates do not have a name
- msg.Format(TXT_PREV_FUNC_IS_NAMED_s_TYPE_IS_d, reinterpret_cast<asCScriptFunction*>(gcObj.obj)->GetName(), reinterpret_cast<asCScriptFunction*>(gcObj.obj)->GetFuncType());
- engine->WriteMessage("", 0, 0, asMSGTYPE_INFORMATION, msg.AddressOf());
- }
- else if( gcObj.type->name == "$obj" )
- {
- msg.Format(TXT_PREV_TYPE_IS_NAMED_s, reinterpret_cast<asCObjectType*>(gcObj.obj)->GetName());
- engine->WriteMessage("", 0, 0, asMSGTYPE_INFORMATION, msg.AddressOf());
- }
- // Release the reference that the GC holds if the release functions is still available
- if( gcObj.type->beh.release && engine->scriptFunctions[gcObj.type->beh.release] )
- engine->CallObjectMethod(gcObj.obj, gcObj.type->beh.release);
- items++;
- }
- return items;
- }
- int asCGarbageCollector::DestroyOldGarbage()
- {
- // This function will only be called within the critical section gcCollecting
- asASSERT(isProcessing);
- for(;;)
- {
- switch( destroyOldState )
- {
- case destroyGarbage_init:
- {
- // If there are no objects to be freed then don't start
- if( gcOldObjects.GetLength() == 0 )
- return 0;
- destroyOldIdx = (asUINT)-1;
- destroyOldState = 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.
- if( ++destroyOldIdx < gcOldObjects.GetLength() )
- {
- asSObjTypePair gcObj = GetOldObjectAtIdx(destroyOldIdx);
- if( gcObj.type->beh.gcGetRefCount == 0 )
- {
- // If circular references are formed with registered types that hasn't
- // registered the GC behaviours, then the engine may be forced to free
- // the object type before the actual object instance. In this case we
- // will be forced to skip the destruction of the objects, so as not to
- // crash the application.
- asCString msg;
- msg.Format(TXT_d_GC_CANNOT_FREE_OBJ_OF_TYPE_s, gcObj.seqNbr, gcObj.type->name.AddressOf());
- engine->WriteMessage("", 0, 0, asMSGTYPE_ERROR, msg.AddressOf());
- // Just remove the object, as we will not bother to destroy it
- numDestroyed++;
- RemoveOldObjectAtIdx(destroyOldIdx);
- destroyOldIdx--;
- }
- else 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++;
- RemoveOldObjectAtIdx(destroyOldIdx);
- destroyOldIdx--;
- }
- else
- {
- // Since the object was resurrected in the
- // destructor, we must add our reference again
- engine->CallObjectMethod(gcObj.obj, gcObj.type->beh.addref);
- }
- destroyOldState = destroyGarbage_haveMore;
- }
- // Allow the application to work a little
- return 1;
- }
- else
- {
- if( destroyOldState == destroyGarbage_haveMore )
- {
- // Restart the cycle
- destroyOldState = destroyGarbage_init;
- }
- else
- {
- // Restart the cycle
- destroyOldState = destroyGarbage_init;
- // Return 0 to tell the application that there
- // is no more garbage to destroy at the moment
- return 0;
- }
- }
- }
- break;
- }
- }
- // Shouldn't reach this point
- UNREACHABLE_RETURN;
- }
- int asCGarbageCollector::IdentifyGarbageWithCyclicRefs()
- {
- // This function will only be called within the critical section gcCollecting
- asASSERT(isProcessing);
- for(;;)
- {
- switch( detectState )
- {
- case clearCounters_init:
- detectState = clearCounters_loop;
- break;
- case clearCounters_loop:
- {
- // Decrease reference counter for all objects removed from the map
- asSMapNode<void*, asSIntTypePair> *cursor = 0;
- gcMap.MoveFirst(&cursor);
- if( cursor )
- {
- void *obj = gcMap.GetKey(cursor);
- asSIntTypePair it = gcMap.GetValue(cursor);
- engine->CallObjectMethod(obj, it.type->beh.release);
- ReturnNode(gcMap.Remove(cursor));
- return 1;
- }
- detectState = buildMap_init;
- }
- break;
- case buildMap_init:
- detectIdx = 0;
- detectState = buildMap_loop;
- break;
- case buildMap_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.
- if( detectIdx < gcOldObjects.GetLength() )
- {
- // Add the gc count for this object
- asSObjTypePair gcObj = GetOldObjectAtIdx(detectIdx);
-
- int refCount = 0;
- if( gcObj.type->beh.gcGetRefCount )
- refCount = engine->CallObjectMethodRetInt(gcObj.obj, gcObj.type->beh.gcGetRefCount);
- if( refCount > 1 )
- {
- asSIntTypePair it = {refCount-1, gcObj.type};
- gcMap.Insert(GetNode(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
- detectState = countReferences_init;
- }
- break;
- case countReferences_init:
- {
- 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.
- if( 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;
- }
- else
- detectState = detectGarbage_init;
- }
- break;
- case detectGarbage_init:
- {
- 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
- if( 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;
- }
- else
- 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.
- if( 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;
- ReturnNode(gcMap.Remove(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;
- }
- else
- detectState = verifyUnmarked_init;
- }
- break;
- case verifyUnmarked_init:
- gcMap.MoveFirst(&gcMapCursor);
- detectState = verifyUnmarked_loop;
- break;
- case verifyUnmarked_loop:
- {
- // 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.
- if( 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;
- }
- else
- gcMap.MoveNext(&gcMapCursor, gcMapCursor);
- // Allow the application to work a little
- return 1;
- }
- else
- {
- // 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:
- {
- gcMap.MoveFirst(&gcMapCursor);
- detectState = breakCircles_loop;
- // If the application has requested a callback for detected circular references,
- // then make that callback now for all the objects in the list. This step is not
- // done in incremental steps as it is only meant for debugging purposes and thus
- // doesn't require interactivity
- if (gcMapCursor && circularRefDetectCallbackFunc)
- {
- while (gcMapCursor)
- {
- void *gcObj = gcMap.GetKey(gcMapCursor);
- asCObjectType *type = gcMap.GetValue(gcMapCursor).type;
- circularRefDetectCallbackFunc(type, gcObj, circularRefDetectCallbackParam);
- gcMap.MoveNext(&gcMapCursor, gcMapCursor);
- }
- // Reset iterator
- gcMap.MoveFirst(&gcMapCursor);
- }
- }
- 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.
- if( gcMapCursor )
- {
- numDetected++;
- void *gcObj = gcMap.GetKey(gcMapCursor);
- asCObjectType *type = gcMap.GetValue(gcMapCursor).type;
- if( type->flags & asOBJ_SCRIPT_OBJECT )
- {
- // For script objects we must call the class destructor before
- // releasing the references, otherwise the destructor may not
- // be able to perform the necessary clean-up as the handles will
- // be null.
- reinterpret_cast<asCScriptObject*>(gcObj)->CallDestructor();
- }
- engine->CallObjectMethod(gcObj, engine, type->beh.gcReleaseAllReferences);
- gcMap.MoveNext(&gcMapCursor, gcMapCursor);
- detectState = breakCircles_haveGarbage;
- // Allow the application to work a little
- return 1;
- }
- else
- {
- // 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;
- }
- }
- }
- } // switch
- }
- // Shouldn't reach this point
- UNREACHABLE_RETURN;
- }
- asCGarbageCollector::asSMapNode_t *asCGarbageCollector::GetNode(void *obj, asSIntTypePair it)
- {
- // This function will only be called within the critical section gcCollecting
- asASSERT(isProcessing);
- asSMapNode_t *node;
- if( freeNodes.GetLength() )
- node = freeNodes.PopLast();
- else
- {
- node = asNEW(asSMapNode_t);
- if( !node )
- {
- // Out of memory
- return 0;
- }
- }
-
- node->Init(obj, it);
- return node;
- }
- void asCGarbageCollector::ReturnNode(asSMapNode_t *node)
- {
- // This function will only be called within the critical section gcCollecting
- asASSERT(isProcessing);
- if( node )
- freeNodes.PushLast(node);
- }
- void asCGarbageCollector::GCEnumCallback(void *reference)
- {
- // This function will only be called within the critical section gcCollecting
- asASSERT(isProcessing);
- 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
|