PageRenderTime 37ms CodeModel.GetById 7ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 1ms

/xbmc/visualizations/Vortex/angelscript/angelscript/source/as_gc.cpp

http://github.com/xbmc/xbmc
C++ | 536 lines | 324 code | 82 blank | 130 comment | 45 complexity | ab44432e4c15bb30577f421232483bde MD5 | raw file
  1/*
  2   AngelCode Scripting Library
  3   Copyright (c) 2003-2009 Andreas Jonsson
  4
  5   This software is provided 'as-is', without any express or implied
  6   warranty. In no event will the authors be held liable for any
  7   damages arising from the use of this software.
  8
  9   Permission is granted to anyone to use this software for any
 10   purpose, including commercial applications, and to alter it and
 11   redistribute it freely, subject to the following restrictions:
 12
 13   1. The origin of this software must not be misrepresented; you
 14      must not claim that you wrote the original software. If you use
 15      this software in a product, an acknowledgment in the product
 16      documentation would be appreciated but is not required.
 17
 18   2. Altered source versions must be plainly marked as such, and
 19      must not be misrepresented as being the original software.
 20
 21   3. This notice may not be removed or altered from any source
 22      distribution.
 23
 24   The original version of this library can be located at:
 25   http://www.angelcode.com/angelscript/
 26
 27   Andreas Jonsson
 28   andreas@angelcode.com
 29*/
 30
 31
 32//
 33// as_gc.cpp
 34//
 35// The implementation of the garbage collector
 36//
 37
 38
 39#include <stdlib.h>
 40
 41#include "as_gc.h"
 42#include "as_scriptengine.h"
 43#include "as_scriptobject.h"
 44
 45BEGIN_AS_NAMESPACE
 46
 47asCGarbageCollector::asCGarbageCollector()
 48{
 49	engine       = 0;
 50	detectState  = clearCounters_init;
 51	destroyState = destroyGarbage_init;
 52	numDestroyed = 0;
 53	numDetected  = 0;
 54}
 55
 56void asCGarbageCollector::AddScriptObjectToGC(void *obj, asCObjectType *objType)
 57{
 58	engine->CallObjectMethod(obj, objType->beh.addref);
 59	asSObjTypePair ot = {obj, objType};
 60
 61	// Add the data to the gcObjects array in a critical section as
 62	// another thread might be calling this method at the same time
 63	ENTERCRITICALSECTION(gcCritical);
 64	gcObjects.PushLast(ot);
 65	LEAVECRITICALSECTION(gcCritical);
 66}
 67
 68int asCGarbageCollector::GarbageCollect(asDWORD flags)
 69{
 70	// The application is responsible for making sure
 71	// the gc is only executed by one thread at a time.
 72
 73	bool doDetect  = (flags & asGC_DETECT_GARBAGE)  || !(flags & asGC_DESTROY_GARBAGE);
 74	bool doDestroy = (flags & asGC_DESTROY_GARBAGE) || !(flags & asGC_DETECT_GARBAGE);
 75
 76	if( flags & asGC_FULL_CYCLE )
 77	{
 78		// Reset the state
 79		if( doDetect )
 80			detectState  = clearCounters_init;
 81		if( doDestroy )
 82			destroyState = destroyGarbage_init;
 83
 84		int r = 1;
 85		unsigned int count = (unsigned int)gcObjects.GetLength();
 86		for(;;)
 87		{
 88			// Detect all garbage with cyclic references
 89			if( doDetect )
 90				while( (r = IdentifyGarbageWithCyclicRefs()) == 1 );
 91
 92			// Now destroy all known garbage
 93			if( doDestroy )
 94				while( (r = DestroyGarbage()) == 1 );
 95
 96			// Run another iteration if any garbage was destroyed
 97			if( count != gcObjects.GetLength() )
 98				count = (unsigned int)gcObjects.GetLength();
 99			else
100				break;
101		}
102
103		// Take the opportunity to clear unused types as well
104		engine->ClearUnusedTypes();
105
106		return 0;
107	}
108	else
109	{
110		// Destroy the garbage that we know of
111		if( doDestroy )
112			DestroyGarbage();
113
114		// Run another incremental step of the identification of cyclic references
115		if( doDetect )
116			IdentifyGarbageWithCyclicRefs();
117	}
118
119	// Return 1 to indicate that the cycle wasn't finished
120	return 1;
121}
122
123void asCGarbageCollector::GetStatistics(asUINT *currentSize, asUINT *totalDestroyed, asUINT *totalDetected)
124{
125	// It's not necessary to protect this access, as
126	// it doesn't matter if another thread is currently
127	// appending a new object.
128	if( currentSize )
129		*currentSize = (asUINT)gcObjects.GetLength();
130
131	if( totalDestroyed )
132		*totalDestroyed = numDestroyed;
133
134	if( totalDetected )
135		*totalDetected = numDetected;
136}
137
138void asCGarbageCollector::ClearMap()
139{
140	// Decrease reference counter for all objects removed from the map
141	asSMapNode<void*, asSIntTypePair> *cursor = 0;
142	gcMap.MoveFirst(&cursor);
143	while( cursor )
144	{
145		void *obj = gcMap.GetKey(cursor);
146		asSIntTypePair it = gcMap.GetValue(cursor);
147
148		engine->CallObjectMethod(obj, it.type->beh.release);
149
150		gcMap.MoveNext(&cursor, cursor);
151	}
152
153	gcMap.EraseAll();
154}
155
156asCGarbageCollector::asSObjTypePair asCGarbageCollector::GetObjectAtIdx(int idx)
157{
158	// We need to protect this access with a critical section as
159	// another thread might be appending an object at the same time
160	ENTERCRITICALSECTION(gcCritical);
161	asSObjTypePair gcObj = gcObjects[idx];
162	LEAVECRITICALSECTION(gcCritical);
163
164	return gcObj;
165}
166
167void asCGarbageCollector::RemoveObjectAtIdx(int idx)
168{
169	// We need to protect this update with a critical section as
170	// another thread might be appending an object at the same time
171	ENTERCRITICALSECTION(gcCritical);
172	if( idx == (int)gcObjects.GetLength() - 1)
173		gcObjects.PopLast();
174	else
175		gcObjects[idx] = gcObjects.PopLast();
176	LEAVECRITICALSECTION(gcCritical);
177}
178
179int asCGarbageCollector::DestroyGarbage()
180{
181	for(;;)
182	{
183		switch( destroyState )
184		{
185		case destroyGarbage_init:
186		{
187			// If there are no objects to be freed then don't start
188			if( gcObjects.GetLength() == 0 )
189				return 0;
190
191			destroyIdx = (asUINT)-1;
192			destroyState = destroyGarbage_loop;
193		}
194		break;
195
196		case destroyGarbage_loop:
197		case destroyGarbage_haveMore:
198		{
199			// If the refCount has reached 1, then only the GC still holds a
200			// reference to the object, thus we don't need to worry about the
201			// application touching the objects during collection.
202
203			// Destroy all objects that have refCount == 1. If any objects are
204			// destroyed, go over the list again, because it may have made more
205			// objects reach refCount == 1.
206			while( ++destroyIdx < gcObjects.GetLength() )
207			{
208				asSObjTypePair gcObj = GetObjectAtIdx(destroyIdx);
209				if( engine->CallObjectMethodRetInt(gcObj.obj, gcObj.type->beh.gcGetRefCount) == 1 )
210				{
211					// Release the object immediately
212
213					// Make sure the refCount is really 0, because the
214					// destructor may have increased the refCount again.
215					bool addRef = false;
216					if( gcObj.type->flags & asOBJ_SCRIPT_OBJECT )
217					{
218						// Script objects may actually be resurrected in the destructor
219						int refCount = ((asCScriptObject*)gcObj.obj)->Release();
220						if( refCount > 0 ) addRef = true;
221					}
222					else
223						engine->CallObjectMethod(gcObj.obj, gcObj.type->beh.release);
224
225					// Was the object really destroyed?
226					if( !addRef )
227					{
228						numDestroyed++;
229						RemoveObjectAtIdx(destroyIdx);
230						destroyIdx--;
231					}
232					else
233					{
234						// Since the object was resurrected in the
235						// destructor, we must add our reference again
236						engine->CallObjectMethod(gcObj.obj, gcObj.type->beh.addref);
237					}
238
239					destroyState = destroyGarbage_haveMore;
240
241					// Allow the application to work a little
242					return 1;
243				}
244			}
245
246			// Only move to the next step if no garbage was detected in this step
247			if( destroyState == destroyGarbage_haveMore )
248				destroyState = destroyGarbage_init;
249			else
250				return 0;
251		}
252		break;
253		}
254	}
255
256	// Shouldn't reach this point
257	UNREACHABLE_RETURN;
258}
259
260int asCGarbageCollector::IdentifyGarbageWithCyclicRefs()
261{
262	for(;;)
263	{
264		switch( detectState )
265		{
266		case clearCounters_init:
267		{
268			ClearMap();
269			detectState = clearCounters_loop;
270			detectIdx = 0;
271		}
272		break;
273
274		case clearCounters_loop:
275		{
276			// Build a map of objects that will be checked, the map will
277			// hold the object pointer as key, and the gcCount and the
278			// object's type as value. As objects are added to the map the
279			// gcFlag must be set in the objects, so we can be verify if
280			// the object is accessed during the GC cycle.
281
282			// If an object is removed from the gcObjects list during the
283			// iteration of this step, it is possible that an object won't
284			// be used during the analyzing for cyclic references. This
285			// isn't a problem, as the next time the GC cycle starts the
286			// object will be verified.
287			while( detectIdx < gcObjects.GetLength() )
288			{
289				// Add the gc count for this object
290				asSObjTypePair gcObj = GetObjectAtIdx(detectIdx);
291				int refCount = engine->CallObjectMethodRetInt(gcObj.obj, gcObj.type->beh.gcGetRefCount);
292				if( refCount > 1 )
293				{
294					asSIntTypePair it = {refCount-1, gcObj.type};
295					gcMap.Insert(gcObj.obj, it);
296
297					// Increment the object's reference counter when putting it in the map
298					engine->CallObjectMethod(gcObj.obj, gcObj.type->beh.addref);
299
300					// Mark the object so that we can
301					// see if it has changed since read
302					engine->CallObjectMethod(gcObj.obj, gcObj.type->beh.gcSetFlag);
303
304					detectIdx++;
305
306					// Let the application work a little
307					return 1;
308				}
309				else
310					detectIdx++;
311			}
312
313			detectState = countReferences_init;
314		}
315		break;
316
317		case countReferences_init:
318		{
319			detectIdx = (asUINT)-1;
320			gcMap.MoveFirst(&gcMapCursor);
321			detectState = countReferences_loop;
322		}
323		break;
324
325		case countReferences_loop:
326		{
327			// Call EnumReferences on all objects in the map to count the number
328			// of references reachable from between objects in the map. If all
329			// references for an object in the map is reachable from other objects
330			// in the map, then we know that no outside references are held for
331			// this object, thus it is a potential dead object in a circular reference.
332
333			// If the gcFlag is cleared for an object we consider the object alive
334			// and referenced from outside the GC, thus we don't enumerate its references.
335
336			// Any new objects created after this step in the GC cycle won't be
337			// in the map, and is thus automatically considered alive.
338			while( gcMapCursor )
339			{
340				void *obj = gcMap.GetKey(gcMapCursor);
341				asCObjectType *type = gcMap.GetValue(gcMapCursor).type;
342				gcMap.MoveNext(&gcMapCursor, gcMapCursor);
343
344				if( engine->CallObjectMethodRetBool(obj, type->beh.gcGetFlag) )
345				{
346					engine->CallObjectMethod(obj, engine, type->beh.gcEnumReferences);
347
348					// Allow the application to work a little
349					return 1;
350				}
351			}
352
353			detectState = detectGarbage_init;
354		}
355		break;
356
357		case detectGarbage_init:
358		{
359			detectIdx = (asUINT)-1;
360			gcMap.MoveFirst(&gcMapCursor);
361			liveObjects.SetLength(0);
362			detectState = detectGarbage_loop1;
363		}
364		break;
365
366		case detectGarbage_loop1:
367		{
368			// All objects that are known not to be dead must be removed from the map,
369			// along with all objects they reference. What remains in the map after
370			// this pass is sure to be dead objects in circular references.
371
372			// An object is considered alive if its gcFlag is cleared, or all the
373			// references were not found in the map.
374
375			// Add all alive objects from the map to the liveObjects array
376			while( gcMapCursor )
377			{
378				asSMapNode<void*, asSIntTypePair> *cursor = gcMapCursor;
379				gcMap.MoveNext(&gcMapCursor, gcMapCursor);
380
381				void *obj = gcMap.GetKey(cursor);
382				asSIntTypePair it = gcMap.GetValue(cursor);
383
384				bool gcFlag = engine->CallObjectMethodRetBool(obj, it.type->beh.gcGetFlag);
385				if( !gcFlag || it.i > 0 )
386				{
387					liveObjects.PushLast(obj);
388
389					// Allow the application to work a little
390					return 1;
391				}
392			}
393
394			detectState = detectGarbage_loop2;
395		}
396		break;
397
398		case detectGarbage_loop2:
399		{
400			// In this step we are actually removing the alive objects from the map.
401			// As the object is removed, all the objects it references are added to the
402			// liveObjects list, by calling EnumReferences. Only objects still in the map
403			// will be added to the liveObjects list.
404			while( liveObjects.GetLength() )
405			{
406				void *gcObj = liveObjects.PopLast();
407				asCObjectType *type = 0;
408
409				// Remove the object from the map to mark it as alive
410				asSMapNode<void*, asSIntTypePair> *cursor = 0;
411				if( gcMap.MoveTo(&cursor, gcObj) )
412				{
413					type = gcMap.GetValue(cursor).type;
414					gcMap.Erase(cursor);
415
416					// We need to decrease the reference count again as we remove the object from the map
417					engine->CallObjectMethod(gcObj, type->beh.release);
418
419					// Enumerate all the object's references so that they too can be marked as alive
420					engine->CallObjectMethod(gcObj, engine, type->beh.gcEnumReferences);
421				}
422
423				// Allow the application to work a little
424				return 1;
425			}
426
427			detectState = verifyUnmarked;
428		}
429		break;
430
431		case verifyUnmarked:
432		{
433			// In this step we must make sure that none of the objects still in the map
434			// has been touched by the application. If they have then we must run the
435			// detectGarbage loop once more.
436			gcMap.MoveFirst(&gcMapCursor);
437			while( gcMapCursor )
438			{
439				void *gcObj = gcMap.GetKey(gcMapCursor);
440				asCObjectType *type = gcMap.GetValue(gcMapCursor).type;
441
442				bool gcFlag = engine->CallObjectMethodRetBool(gcObj, type->beh.gcGetFlag);
443				if( !gcFlag )
444				{
445					// The unmarked object was touched, rerun the detectGarbage loop
446					detectState = detectGarbage_init;
447					return 1;
448				}
449
450				gcMap.MoveNext(&gcMapCursor, gcMapCursor);
451			}
452
453			// No unmarked object was touched, we can now be sure
454			// that objects that have gcCount == 0 really is garbage
455			detectState = breakCircles_init;
456		}
457		break;
458
459		case breakCircles_init:
460		{
461			detectIdx = (asUINT)-1;
462			gcMap.MoveFirst(&gcMapCursor);
463			detectState = breakCircles_loop;
464		}
465		break;
466
467		case breakCircles_loop:
468		case breakCircles_haveGarbage:
469		{
470			// All objects in the map are now known to be dead objects
471			// kept alive through circular references. To be able to free
472			// these objects we need to force the breaking of the circle
473			// by having the objects release their references.
474			while( gcMapCursor )
475			{
476				numDetected++;
477				void *gcObj = gcMap.GetKey(gcMapCursor);
478				asCObjectType *type = gcMap.GetValue(gcMapCursor).type;
479				engine->CallObjectMethod(gcObj, engine, type->beh.gcReleaseAllReferences);
480
481				gcMap.MoveNext(&gcMapCursor, gcMapCursor);
482
483				detectState = breakCircles_haveGarbage;
484
485				// Allow the application to work a little
486				return 1;
487			}
488
489			// If no garbage was detected we can finish now
490			if( detectState != breakCircles_haveGarbage )
491			{
492				// Restart the GC
493				detectState = clearCounters_init;
494				return 0;
495			}
496			else
497			{
498				// Restart the GC
499				detectState = clearCounters_init;
500				return 1;
501			}
502		}
503		break;
504		} // switch
505	}
506
507	// Shouldn't reach this point
508	UNREACHABLE_RETURN;
509}
510
511void asCGarbageCollector::GCEnumCallback(void *reference)
512{
513	if( detectState == countReferences_loop )
514	{
515		// Find the reference in the map
516		asSMapNode<void*, asSIntTypePair> *cursor = 0;
517		if( gcMap.MoveTo(&cursor, reference) )
518		{
519			// Decrease the counter in the map for the reference
520			gcMap.GetValue(cursor).i--;
521		}
522	}
523	else if( detectState == detectGarbage_loop2 )
524	{
525		// Find the reference in the map
526		asSMapNode<void*, asSIntTypePair> *cursor = 0;
527		if( gcMap.MoveTo(&cursor, reference) )
528		{
529			// Add the object to the list of objects to mark as alive
530			liveObjects.PushLast(reference);
531		}
532	}
533}
534
535END_AS_NAMESPACE
536