2002-11-19 05:37:42 +00:00
|
|
|
/* Implementation of garbage collecting classe framework
|
|
|
|
|
|
|
|
Copyright (C) 2002 Free Software Foundation, Inc.
|
|
|
|
|
|
|
|
Written by: Richard Frith-Macdonald <rfm@gnu.org>
|
|
|
|
Inspired by gc classes of Ovidiu Predescu and Mircea Oancea
|
|
|
|
|
|
|
|
This file is part of the GNUstep Base Library.
|
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU Library General Public
|
|
|
|
License as published by the Free Software Foundation; either
|
|
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
Library General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Library General Public
|
|
|
|
License along with this library; if not, write to the Free
|
|
|
|
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
|
|
|
|
|
|
|
|
AutogsdocSource: Additions/GCObject.m
|
|
|
|
AutogsdocSource: Additions/GCArray.m
|
|
|
|
AutogsdocSource: Additions/GCDictionary.m
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
2003-01-26 19:38:42 +00:00
|
|
|
#include "config.h"
|
2002-11-19 05:37:42 +00:00
|
|
|
#include <Foundation/NSAutoreleasePool.h>
|
2002-11-26 06:33:57 +00:00
|
|
|
#include <Foundation/NSNotification.h>
|
2002-11-19 05:37:42 +00:00
|
|
|
#include <Foundation/NSString.h>
|
2002-11-26 06:33:57 +00:00
|
|
|
#include <Foundation/NSThread.h>
|
2002-11-19 05:37:42 +00:00
|
|
|
|
|
|
|
#include <gnustep/base/GCObject.h>
|
2002-12-03 02:50:07 +00:00
|
|
|
#include "GSCompatibility.h"
|
2002-11-19 05:37:42 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The head of a linked list of all garbage collecting objects is a
|
|
|
|
* special object which is never deallocated.
|
|
|
|
*/
|
|
|
|
@interface _GCObjectList : GCObject
|
|
|
|
@end
|
|
|
|
@implementation _GCObjectList
|
|
|
|
- (void) dealloc
|
|
|
|
{
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2002-11-26 06:33:57 +00:00
|
|
|
* <p>The GCObject class is both the base class for all garbage collected
|
|
|
|
* objects, and an infrastructure for handling garbage collection.
|
|
|
|
* </p>
|
|
|
|
* <p>It maintains a list of all garbage collectable objects and provides
|
2002-11-19 05:37:42 +00:00
|
|
|
* a method to run a garbage collection pass on those objects.
|
2002-11-26 06:33:57 +00:00
|
|
|
* </p>
|
2002-11-19 05:37:42 +00:00
|
|
|
*/
|
|
|
|
@implementation GCObject
|
|
|
|
|
|
|
|
static GCObject *allObjects = nil;
|
|
|
|
static BOOL isCollecting = NO;
|
|
|
|
|
2002-12-03 02:50:07 +00:00
|
|
|
#ifdef NeXT_RUNTIME
|
|
|
|
static void *allocationLock = NULL;
|
|
|
|
#define objc_mutex_allocate() NULL
|
|
|
|
#define objc_mutex_lock(lock)
|
|
|
|
#define objc_mutex_unlock(lock)
|
|
|
|
#else
|
2002-11-26 06:33:57 +00:00
|
|
|
static objc_mutex_t allocationLock = NULL;
|
2002-12-03 02:50:07 +00:00
|
|
|
#endif
|
2002-11-26 06:33:57 +00:00
|
|
|
|
|
|
|
+ (void) _becomeMultiThreaded: (NSNotification *)aNotification
|
|
|
|
{
|
|
|
|
if (allocationLock == 0)
|
|
|
|
{
|
|
|
|
allocationLock = objc_mutex_allocate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Allocates an instance of the class and links it into the list of
|
|
|
|
* all garbage collectable objects. Returns the new instance.<br />
|
|
|
|
*/
|
2002-11-19 05:37:42 +00:00
|
|
|
+ (id) allocWithZone: (NSZone*)zone
|
|
|
|
{
|
|
|
|
GCObject *o = [super allocWithZone: zone];
|
|
|
|
|
2002-11-26 06:33:57 +00:00
|
|
|
if (allocationLock != 0)
|
|
|
|
{
|
|
|
|
objc_mutex_lock(allocationLock);
|
|
|
|
}
|
2002-11-19 05:37:42 +00:00
|
|
|
o->gc.next = allObjects;
|
|
|
|
o->gc.previous = allObjects->gc.previous;
|
|
|
|
allObjects->gc.previous->gc.next = o;
|
|
|
|
allObjects->gc.previous = o;
|
|
|
|
o->gc.flags.refCount = 1;
|
2002-11-26 06:33:57 +00:00
|
|
|
if (allocationLock != 0)
|
|
|
|
{
|
|
|
|
objc_mutex_unlock(allocationLock);
|
|
|
|
}
|
2002-11-19 05:37:42 +00:00
|
|
|
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* <p>This method runs a garbage collection, causing unreferenced objects to
|
|
|
|
* be deallocated. This is done using a simple three pass algorithm -
|
|
|
|
* </p>
|
|
|
|
* <deflist>
|
|
|
|
* <term>Pass 1</term>
|
|
|
|
* <desc>
|
|
|
|
* All the garbage collectable objects are sent a
|
|
|
|
* -gcDecrementRefCountOfContainedObjects message.
|
|
|
|
* </desc>
|
|
|
|
* <term>Pass 2</term>
|
|
|
|
* <desc>
|
|
|
|
* All objects having a refCount greater than 0 are sent an
|
|
|
|
* -gcIncrementRefCountOfContainedObjects message.
|
|
|
|
* </desc>
|
|
|
|
* <term>Pass 3</term>
|
|
|
|
* <desc>
|
|
|
|
* All the objects that still have the refCount of 0
|
|
|
|
* are part of cyclic graphs and none of the objects from this graph
|
|
|
|
* are held by some object outside graph. These objects receive the
|
|
|
|
* -dealloc message. In this method they should send the -dealloc message
|
|
|
|
* to any garbage collectable (GCObject and subclass) instances they
|
|
|
|
* contain.
|
|
|
|
* </desc>
|
|
|
|
* </deflist>
|
|
|
|
* <p>During garbage collection, the +gcIsCollecting method returns YES.
|
|
|
|
* </p>
|
|
|
|
*/
|
|
|
|
+ (void) gcCollectGarbage
|
|
|
|
{
|
|
|
|
GCObject *object;
|
|
|
|
GCObject *last;
|
|
|
|
|
2002-11-26 06:33:57 +00:00
|
|
|
if (allocationLock != 0)
|
|
|
|
{
|
|
|
|
objc_mutex_lock(allocationLock);
|
|
|
|
}
|
2002-11-19 05:37:42 +00:00
|
|
|
if (isCollecting == YES)
|
|
|
|
{
|
2002-11-26 06:33:57 +00:00
|
|
|
if (allocationLock != 0)
|
|
|
|
{
|
|
|
|
objc_mutex_unlock(allocationLock);
|
|
|
|
}
|
2002-11-19 05:37:42 +00:00
|
|
|
return; // Don't allow recursion.
|
|
|
|
}
|
|
|
|
isCollecting = YES;
|
|
|
|
|
|
|
|
// Pass 1
|
|
|
|
object = allObjects->gc.next;
|
|
|
|
while (object != allObjects)
|
|
|
|
{
|
|
|
|
[object gcDecrementRefCountOfContainedObjects];
|
|
|
|
// object->gc.flags.visited = 0;
|
|
|
|
// object = object->gc.next;
|
|
|
|
[object gcSetVisited: NO];
|
|
|
|
object = [object gcNextObject];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pass 2
|
|
|
|
object = allObjects->gc.next;
|
|
|
|
while (object != allObjects)
|
|
|
|
{
|
|
|
|
if ([object retainCount] > 0)
|
|
|
|
{
|
|
|
|
[object gcIncrementRefCountOfContainedObjects];
|
|
|
|
}
|
|
|
|
// object = object->gc.next;
|
|
|
|
object = [object gcNextObject];
|
|
|
|
}
|
|
|
|
|
|
|
|
last = allObjects;
|
|
|
|
object = last->gc.next;
|
|
|
|
while (object != allObjects)
|
|
|
|
{
|
|
|
|
if ([object retainCount] == 0)
|
|
|
|
{
|
|
|
|
GCObject *next;
|
|
|
|
|
|
|
|
// next = object->gc.next;
|
|
|
|
// next->gc.previous = last;
|
|
|
|
// last->gc.next = next;
|
|
|
|
// object->gc.next = object;
|
|
|
|
// object->gc.previous = object;
|
|
|
|
next = [object gcNextObject];
|
|
|
|
[next gcSetPreviousObject: last];
|
|
|
|
[last gcSetNextObject: next];
|
|
|
|
[object gcSetNextObject: object];
|
|
|
|
[object gcSetPreviousObject: object];
|
|
|
|
[object dealloc];
|
|
|
|
object = next;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
last = object;
|
|
|
|
// object = object->gc.next;
|
|
|
|
object = [object gcNextObject];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
isCollecting = NO;
|
2002-11-26 06:33:57 +00:00
|
|
|
if (allocationLock != 0)
|
|
|
|
{
|
|
|
|
objc_mutex_unlock(allocationLock);
|
|
|
|
}
|
2002-11-19 05:37:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
+ (void) initialize
|
|
|
|
{
|
|
|
|
if (self == [GCObject class])
|
|
|
|
{
|
|
|
|
allObjects = (_GCObjectList*)
|
|
|
|
NSAllocateObject([_GCObjectList class], 0, NSDefaultMallocZone());
|
2002-11-19 15:50:31 +00:00
|
|
|
allObjects->gc.next = allObjects;
|
|
|
|
allObjects->gc.previous = allObjects;
|
2002-11-26 06:33:57 +00:00
|
|
|
if ([NSThread isMultiThreaded] == YES)
|
|
|
|
{
|
|
|
|
[self _becomeMultiThreaded: nil];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[[NSNotificationCenter defaultCenter]
|
|
|
|
addObserver: self
|
|
|
|
selector: @selector(_becomeMultiThreaded:)
|
|
|
|
name: NSWillBecomeMultiThreadedNotification
|
|
|
|
object: nil];
|
|
|
|
}
|
2002-11-19 05:37:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a flag to indicate whether a garbage collection is in progress.
|
|
|
|
*/
|
|
|
|
+ (BOOL) gcIsCollecting
|
|
|
|
{
|
|
|
|
return isCollecting;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2002-11-26 06:33:57 +00:00
|
|
|
* Called to remove anObject from the list of garbage collectable objects.<br />
|
|
|
|
* This method is provided so that classes which are not subclasses of
|
|
|
|
* GCObject (but which have the same initial instance variable layout) can
|
|
|
|
* use multiple inheritance (behaviors) to act as GCObject instances, but
|
|
|
|
* can have their own -dealloc methods.<br />
|
|
|
|
* These classes should call this in their own -dealloc methods.
|
2002-11-19 05:37:42 +00:00
|
|
|
*/
|
|
|
|
+ (void) gcObjectWillBeDeallocated: (GCObject*)anObject
|
|
|
|
{
|
|
|
|
GCObject *p;
|
|
|
|
GCObject *n;
|
|
|
|
|
2002-11-26 06:33:57 +00:00
|
|
|
if (allocationLock != 0)
|
|
|
|
{
|
|
|
|
objc_mutex_lock(allocationLock);
|
|
|
|
}
|
2002-11-19 05:37:42 +00:00
|
|
|
// p = anObject->gc.previous;
|
|
|
|
// n = anObject->gc.next;
|
|
|
|
// p->gc.next = n;
|
|
|
|
// n->gc.previous = p;
|
|
|
|
p = [anObject gcPreviousObject];
|
|
|
|
n = [anObject gcNextObject];
|
|
|
|
[p gcSetNextObject: n];
|
|
|
|
[n gcSetPreviousObject: p];
|
2002-11-26 06:33:57 +00:00
|
|
|
if (allocationLock != 0)
|
|
|
|
{
|
|
|
|
objc_mutex_unlock(allocationLock);
|
|
|
|
}
|
2002-11-19 05:37:42 +00:00
|
|
|
}
|
|
|
|
|
2002-11-26 06:33:57 +00:00
|
|
|
/**
|
|
|
|
* Copies the receiver (using the NSCopyObject() function) and links
|
|
|
|
* the resulting object into the list of all garbage collactable
|
|
|
|
* objects. Returns the newly created object.
|
|
|
|
*/
|
2002-11-19 05:37:42 +00:00
|
|
|
- (id) copyWithZone: (NSZone*)zone
|
|
|
|
{
|
|
|
|
GCObject *o = (GCObject*)NSCopyObject(self, 0, zone);
|
|
|
|
|
2002-11-26 06:33:57 +00:00
|
|
|
if (allocationLock != 0)
|
|
|
|
{
|
|
|
|
objc_mutex_lock(allocationLock);
|
|
|
|
}
|
2002-11-19 05:37:42 +00:00
|
|
|
o->gc.next = allObjects;
|
|
|
|
o->gc.previous = allObjects->gc.previous;
|
|
|
|
allObjects->gc.previous->gc.next = o;
|
|
|
|
allObjects->gc.previous = o;
|
|
|
|
o->gc.flags.refCount = 1;
|
2002-11-26 06:33:57 +00:00
|
|
|
if (allocationLock != 0)
|
|
|
|
{
|
|
|
|
objc_mutex_unlock(allocationLock);
|
|
|
|
}
|
2002-11-19 05:37:42 +00:00
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
2002-11-26 06:33:57 +00:00
|
|
|
/**
|
|
|
|
* Removes the receiver from the list of garbage collectable objects and
|
|
|
|
* then calls the superclass implementation to complete deallocation of
|
|
|
|
* th receiver and freeing of the memory it uses.<br />
|
|
|
|
* Subclasses should call this at the end of their -dealloc methods as usual.
|
|
|
|
*/
|
|
|
|
- (void) dealloc
|
|
|
|
{
|
|
|
|
GCObject *p;
|
|
|
|
GCObject *n;
|
|
|
|
|
|
|
|
if (allocationLock != 0)
|
|
|
|
{
|
|
|
|
objc_mutex_lock(allocationLock);
|
|
|
|
}
|
|
|
|
// p = anObject->gc.previous;
|
|
|
|
// n = anObject->gc.next;
|
|
|
|
// p->gc.next = n;
|
|
|
|
// n->gc.previous = p;
|
|
|
|
p = [self gcPreviousObject];
|
|
|
|
n = [self gcNextObject];
|
|
|
|
[p gcSetNextObject: n];
|
|
|
|
[n gcSetPreviousObject: p];
|
|
|
|
if (allocationLock != 0)
|
|
|
|
{
|
|
|
|
objc_mutex_unlock(allocationLock);
|
|
|
|
}
|
|
|
|
[super dealloc];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2002-11-19 05:37:42 +00:00
|
|
|
* Decrements the garbage collection reference count for the receiver.<br />
|
|
|
|
*/
|
|
|
|
- (void) gcDecrementRefCount
|
|
|
|
{
|
2002-11-26 06:33:57 +00:00
|
|
|
/*
|
|
|
|
* No locking needed since this is only called when garbage collecting
|
|
|
|
* and the collection method handles locking.
|
|
|
|
*/
|
2002-11-19 05:37:42 +00:00
|
|
|
gc.flags.refCount--;
|
|
|
|
}
|
|
|
|
|
2002-11-26 06:33:57 +00:00
|
|
|
/**
|
2002-11-19 05:37:42 +00:00
|
|
|
* <p>Marks the receiver as not having been visited in the current garbage
|
|
|
|
* collection process (first pass of collection).
|
|
|
|
* </p>
|
|
|
|
* <p>All container subclasses should override this method to call the super
|
|
|
|
* implementation then decrement the ref counts of their contents as well as
|
|
|
|
* sending the -gcDecrementRefCountOfContainedObjects
|
|
|
|
* message to each of them.
|
|
|
|
* </p>
|
|
|
|
*/
|
|
|
|
- (void) gcDecrementRefCountOfContainedObjects
|
|
|
|
{
|
2002-11-26 06:33:57 +00:00
|
|
|
/*
|
|
|
|
* No locking needed since this is only called when garbage collecting
|
|
|
|
* and the collection method handles locking.
|
|
|
|
*/
|
2002-11-19 05:37:42 +00:00
|
|
|
gc.flags.visited = 0;
|
|
|
|
}
|
|
|
|
|
2002-11-26 06:33:57 +00:00
|
|
|
/**
|
2002-11-19 05:37:42 +00:00
|
|
|
* Increments the garbage collection reference count for the receiver.<br />
|
|
|
|
*/
|
|
|
|
- (void) gcIncrementRefCount
|
|
|
|
{
|
2002-11-26 06:33:57 +00:00
|
|
|
/*
|
|
|
|
* No locking needed since this is only called when garbage collecting
|
|
|
|
* and the collection method handles locking.
|
|
|
|
*/
|
2002-11-19 05:37:42 +00:00
|
|
|
gc.flags.refCount++;
|
|
|
|
}
|
|
|
|
|
2002-11-26 06:33:57 +00:00
|
|
|
/**
|
2002-11-19 05:37:42 +00:00
|
|
|
* <p>Checks to see if the receiver has already been visited in the
|
|
|
|
* current garbage collection process, and either marks the receiver as
|
|
|
|
* visited (and returns YES) or returns NO to indicate that it had already
|
|
|
|
* been visited.
|
|
|
|
* </p>
|
|
|
|
* <p>All container subclasses should override this method to call the super
|
|
|
|
* implementation then, if the method returns YES, increment the reference
|
|
|
|
* count of any contained objects and send the
|
|
|
|
* -gcIncrementRefCountOfContainedObjects
|
|
|
|
* to each of the contained objects too.
|
|
|
|
* </p>
|
|
|
|
*/
|
|
|
|
- (BOOL) gcIncrementRefCountOfContainedObjects
|
|
|
|
{
|
2002-11-26 06:33:57 +00:00
|
|
|
/*
|
|
|
|
* No locking needed since this is only called when garbage collecting
|
|
|
|
* and the collection method handles locking.
|
|
|
|
*/
|
2002-11-19 05:37:42 +00:00
|
|
|
if (gc.flags.visited == 1)
|
|
|
|
{
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
gc.flags.visited = 1;
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
2002-11-26 06:33:57 +00:00
|
|
|
/**
|
|
|
|
* Decrements the receivers reference count, and if zero, rmoveis it
|
|
|
|
* from the list of garbage collectable objects and deallocates it.
|
|
|
|
*/
|
2002-11-19 05:37:42 +00:00
|
|
|
- (oneway void) release
|
|
|
|
{
|
2002-11-26 06:33:57 +00:00
|
|
|
if (allocationLock != 0)
|
|
|
|
{
|
|
|
|
objc_mutex_lock(allocationLock);
|
|
|
|
}
|
2002-11-19 05:37:42 +00:00
|
|
|
if (gc.flags.refCount > 0 && gc.flags.refCount-- == 1)
|
|
|
|
{
|
|
|
|
[GCObject gcObjectWillBeDeallocated: self];
|
|
|
|
[self dealloc];
|
|
|
|
}
|
2002-11-26 06:33:57 +00:00
|
|
|
if (allocationLock != 0)
|
|
|
|
{
|
|
|
|
objc_mutex_unlock(allocationLock);
|
|
|
|
}
|
2002-11-19 05:37:42 +00:00
|
|
|
}
|
|
|
|
|
2002-11-26 06:33:57 +00:00
|
|
|
/**
|
|
|
|
* Increments the receivers reference count and returns the receiver.
|
|
|
|
*/
|
2002-11-19 05:37:42 +00:00
|
|
|
- (id) retain
|
|
|
|
{
|
2002-11-26 06:33:57 +00:00
|
|
|
if (allocationLock != 0)
|
|
|
|
{
|
|
|
|
objc_mutex_lock(allocationLock);
|
|
|
|
}
|
2002-11-19 05:37:42 +00:00
|
|
|
gc.flags.refCount++;
|
2002-11-26 06:33:57 +00:00
|
|
|
if (allocationLock != 0)
|
|
|
|
{
|
|
|
|
objc_mutex_unlock(allocationLock);
|
|
|
|
}
|
2002-11-19 05:37:42 +00:00
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2002-11-26 06:33:57 +00:00
|
|
|
/**
|
|
|
|
* Returns the receivers reference count.
|
|
|
|
*/
|
2002-11-19 05:37:42 +00:00
|
|
|
- (unsigned int) retainCount
|
|
|
|
{
|
|
|
|
return gc.flags.refCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
2002-11-26 06:33:57 +00:00
|
|
|
/**
|
|
|
|
* This category implements accessor methods for the instance variables
|
|
|
|
* used for garbage collecting. If/when we can ensure that all garbage
|
|
|
|
* collecting classes use the same initial ivar layout, we can remove
|
|
|
|
* these methods and the garbage collector can access the ivars directly,
|
|
|
|
* making a pretty big performance improvement during collecting.<br />
|
|
|
|
* NB. These methods must *only* be used by the garbage collecting process
|
|
|
|
* or in methods called from the garbage collector. Anything else is not
|
|
|
|
* thread-safe.
|
|
|
|
*/
|
2002-11-19 05:37:42 +00:00
|
|
|
@implementation GCObject (Extra)
|
|
|
|
|
|
|
|
- (BOOL) gcAlreadyVisited
|
|
|
|
{
|
|
|
|
if (gc.flags.visited == 1)
|
|
|
|
{
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (GCObject*) gcNextObject
|
|
|
|
{
|
|
|
|
return gc.next;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (GCObject*) gcPreviousObject
|
|
|
|
{
|
|
|
|
return gc.previous;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (GCObject*) gcSetNextObject: (GCObject*)anObject
|
|
|
|
{
|
|
|
|
gc.next = anObject;
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (GCObject*) gcSetPreviousObject: (GCObject*)anObject
|
|
|
|
{
|
|
|
|
gc.previous = anObject;
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) gcSetVisited: (BOOL)flag
|
|
|
|
{
|
|
|
|
if (flag == YES)
|
|
|
|
{
|
|
|
|
gc.flags.visited = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gc.flags.visited = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|