Implemented OOWeakSet - like NSMutableSet, but uses weakrefs and cleans them up as needed.

Use OOWeakSet for _shipsOnHold in StationEntity.

This implementation is pretty naive. I have a "better" version, only it consistently crashes a lot.

Also, integrated unit tests (when building with Xcode 4). Tests are run at the end of TestRelease and Deployment builds, and can be run manually with Product -> Test.


git-svn-id: http://svn.berlios.de/svnroot/repos/oolite-linux/trunk@5109 127b21dd-08f5-0310-b4b7-95ae10353056
This commit is contained in:
Jens Ayton 2012-07-19 17:42:24 +00:00
parent 3e6098fa3c
commit 5cafda59d9
8 changed files with 483 additions and 55 deletions

View File

@ -667,6 +667,8 @@
1AED2D0C0C04586C004A1118 /* OOGraphicsResetManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 1AED2D0A0C04586C004A1118 /* OOGraphicsResetManager.h */; };
1AED2D0D0C04586C004A1118 /* OOGraphicsResetManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AED2D0B0C04586C004A1118 /* OOGraphicsResetManager.m */; };
1AEF57D312E51DDB00546444 /* OOJSEngineNativeWrappers.h in Headers */ = {isa = PBXBuildFile; fileRef = 1AEF57D212E51DDB00546444 /* OOJSEngineNativeWrappers.h */; };
1AF4AF4A15B858AA009243BE /* OOWeakSet.h in Headers */ = {isa = PBXBuildFile; fileRef = 1AF4AF4815B858AA009243BE /* OOWeakSet.h */; };
1AF4AF4B15B858AA009243BE /* OOWeakSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AF4AF4915B858AA009243BE /* OOWeakSet.m */; };
2512833E09BA27C100F43D55 /* Octree.m in Sources */ = {isa = PBXBuildFile; fileRef = 2512833C09BA27C100F43D55 /* Octree.m */; settings = {COMPILER_FLAGS = $OO_MATHS_OPTS; }; };
2512833F09BA27C100F43D55 /* Octree.h in Headers */ = {isa = PBXBuildFile; fileRef = 2512833D09BA27C100F43D55 /* Octree.h */; };
2512834209BA27EC00F43D55 /* Geometry.h in Headers */ = {isa = PBXBuildFile; fileRef = 2512834009BA27EC00F43D55 /* Geometry.h */; };
@ -800,6 +802,13 @@
remoteGlobalIDString = 8D5B49B6048680CD000E48DA;
remoteInfo = DebugOXP;
};
1AB2D62315B86EA500177AAF /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 1AB2D61C15B86EA400177AAF /* OoliteUnitTests.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 1A0CD53615AF0BCE00970505;
remoteInfo = OoliteUnitTests;
};
1AB7760112CA2E53001478BB /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 1AB775FC12CA2E53001478BB /* libjs.xcodeproj */;
@ -1854,6 +1863,7 @@
1AB2AAF80C4CE0CC0008CF4E /* OOOXPVerifier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OOOXPVerifier.h; sourceTree = "<group>"; };
1AB2AAF90C4CE0CC0008CF4E /* OOOXPVerifier.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OOOXPVerifier.m; sourceTree = "<group>"; };
1AB2AB120C4CE4070008CF4E /* verifyOXP.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; name = verifyOXP.plist; path = ../../../Resources/Config/verifyOXP.plist; sourceTree = "<group>"; };
1AB2D61C15B86EA400177AAF /* OoliteUnitTests.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = OoliteUnitTests.xcodeproj; path = tests/OCUnitTests/OoliteUnitTests.xcodeproj; sourceTree = "<group>"; };
1AB4AEB60D688AD9003076D6 /* OOLogHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OOLogHeader.h; sourceTree = "<group>"; };
1AB4AEB70D688AD9003076D6 /* OOLogHeader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OOLogHeader.m; sourceTree = "<group>"; };
1AB5E1ED12BD628500C334DD /* OOJoystickManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OOJoystickManager.h; sourceTree = "<group>"; };
@ -1946,6 +1956,8 @@
1AED2D0A0C04586C004A1118 /* OOGraphicsResetManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OOGraphicsResetManager.h; sourceTree = "<group>"; };
1AED2D0B0C04586C004A1118 /* OOGraphicsResetManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OOGraphicsResetManager.m; sourceTree = "<group>"; };
1AEF57D212E51DDB00546444 /* OOJSEngineNativeWrappers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OOJSEngineNativeWrappers.h; sourceTree = "<group>"; };
1AF4AF4815B858AA009243BE /* OOWeakSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OOWeakSet.h; sourceTree = "<group>"; };
1AF4AF4915B858AA009243BE /* OOWeakSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OOWeakSet.m; sourceTree = "<group>"; };
1AF8E33A0CC169F500CA6001 /* contributors.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = contributors.txt; sourceTree = "<group>"; };
2512833C09BA27C100F43D55 /* Octree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Octree.m; sourceTree = "<group>"; };
2512833D09BA27C100F43D55 /* Octree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Octree.h; sourceTree = "<group>"; };
@ -2909,6 +2921,8 @@
1AEB4919119D5AAA007BD514 /* OORegExpMatcher.m */,
1A062C8711B28D8A00727C1D /* NSObjectOOExtensions.h */,
1A062C8811B28D8A00727C1D /* NSObjectOOExtensions.m */,
1AF4AF4815B858AA009243BE /* OOWeakSet.h */,
1AF4AF4915B858AA009243BE /* OOWeakSet.m */,
);
name = Utilities;
sourceTree = "<group>";
@ -3135,6 +3149,14 @@
path = OXPVerifier;
sourceTree = "<group>";
};
1AB2D61D15B86EA400177AAF /* Products */ = {
isa = PBXGroup;
children = (
1AB2D62415B86EA500177AAF /* OoliteUnitTests.octest */,
);
name = Products;
sourceTree = "<group>";
};
1AB775FD12CA2E53001478BB /* Products */ = {
isa = PBXGroup;
children = (
@ -3219,6 +3241,14 @@
name = Products;
sourceTree = "<group>";
};
1AF4AF4C15B8616F009243BE /* Unit tests */ = {
isa = PBXGroup;
children = (
1AB2D61C15B86EA400177AAF /* OoliteUnitTests.xcodeproj */,
);
name = "Unit tests";
sourceTree = "<group>";
};
29B97314FDCFA39411CA2CEA /* Oolite_GUSTO */ = {
isa = PBXGroup;
children = (
@ -3230,6 +3260,7 @@
1A5BF2710916D45B00BF238F /* External bundles */,
29B97323FDCFA39411CA2CEA /* Frameworks */,
19C28FACFE9D520D11CA2CBB /* Products */,
1AF4AF4C15B8616F009243BE /* Unit tests */,
1AEA229D12CBD18600EC0F43 /* CoreServices.framework */,
1A033F90132687DC006F9DB7 /* Quartz.framework */,
);
@ -3479,6 +3510,7 @@
1A6F665314DF323900695C11 /* OODefaultShaderSynthesizer.h in Headers */,
1ABA415E15ACBB6700F7E841 /* DockEntity.h in Headers */,
1ABA416215ADAB8D00F7E841 /* OOJSDock.h in Headers */,
1AF4AF4A15B858AA009243BE /* OOWeakSet.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -3569,6 +3601,10 @@
ProductGroup = 1A3E018F11C574AC000FF226 /* Products */;
ProjectRef = 1A3E018E11C574AC000FF226 /* Oolite-importer.xcodeproj */;
},
{
ProductGroup = 1AB2D61D15B86EA400177AAF /* Products */;
ProjectRef = 1AB2D61C15B86EA400177AAF /* OoliteUnitTests.xcodeproj */;
},
{
ProductGroup = 1AE3455212CB77AC00FD8C62 /* Products */;
ProjectRef = 1AE3455112CB77AC00FD8C62 /* Vorbis.xcodeproj */;
@ -3625,6 +3661,13 @@
remoteRef = 1A8FAA9D12F0E44D008FF5A2 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
1AB2D62415B86EA500177AAF /* OoliteUnitTests.octest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = OoliteUnitTests.octest;
remoteRef = 1AB2D62315B86EA500177AAF /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
1AB7760212CA2E53001478BB /* libjs_for_oolite.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@ -3933,6 +3976,7 @@
1A6F665414DF323900695C11 /* OODefaultShaderSynthesizer.m in Sources */,
1ABA415F15ACBB6700F7E841 /* DockEntity.m in Sources */,
1ABA416315ADAB8D00F7E841 /* OOJSDock.m in Sources */,
1AF4AF4B15B858AA009243BE /* OOWeakSet.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -4071,6 +4115,7 @@
);
PRODUCT_NAME = Oolite;
SECTORDER_FLAGS = "";
TEST_AFTER_BUILD = YES;
USER_HEADER_SEARCH_PATHS = "\"$(DEPS_INCLUDE_PATH)/ogg\" \"$(DEPS_INCLUDE_PATH)/js\" \"$(DEPS_INCLUDE_PATH)/vorbis\" \"$(DEPS_INCLUDE_PATH)/png\" \"$(SRCROOT)/src/Core/\" \"$(SRCROOT)/src/Core/Tables/\" \"$(SRCROOT)/src/Core/Entites/\" \"$(SRCROOT)/src/Core/Materials/\" \"$(SRCROOT)/src/BSDCompat/\"";
WARNING_CFLAGS = (
"-Wall",
@ -4137,6 +4182,7 @@
);
PRODUCT_NAME = Oolite;
SECTORDER_FLAGS = "";
TEST_AFTER_BUILD = YES;
USER_HEADER_SEARCH_PATHS = "\"$(DEPS_INCLUDE_PATH)/ogg\" \"$(DEPS_INCLUDE_PATH)/js\" \"$(DEPS_INCLUDE_PATH)/vorbis\" \"$(DEPS_INCLUDE_PATH)/png\" \"$(SRCROOT)/src/Core/\" \"$(SRCROOT)/src/Core/Tables/\" \"$(SRCROOT)/src/Core/Entites/\" \"$(SRCROOT)/src/Core/Materials/\" \"$(SRCROOT)/src/BSDCompat/\"";
WARNING_CFLAGS = (
"-Wall",

View File

@ -42,6 +42,16 @@
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1A0CD53515AF0BCE00970505"
BuildableName = "OoliteUnitTests.octest"
BlueprintName = "OoliteUnitTests"
ReferencedContainer = "container:tests/OCUnitTests/OoliteUnitTests.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference

View File

@ -34,6 +34,20 @@
ReferencedContainer = "container:Oolite.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1A0CD53515AF0BCE00970505"
BuildableName = "OoliteUnitTests.octest"
BlueprintName = "OoliteUnitTests"
ReferencedContainer = "container:tests/OCUnitTests/OoliteUnitTests.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
@ -42,6 +56,16 @@
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Deployment">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1A0CD53515AF0BCE00970505"
BuildableName = "OoliteUnitTests.octest"
BlueprintName = "OoliteUnitTests"
ReferencedContainer = "container:tests/OCUnitTests/OoliteUnitTests.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
@ -55,7 +79,7 @@
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Deployment"

View File

@ -21,7 +21,7 @@
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "NO"
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
@ -34,6 +34,20 @@
ReferencedContainer = "container:Oolite.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1A0CD53515AF0BCE00970505"
BuildableName = "OoliteUnitTests.octest"
BlueprintName = "OoliteUnitTests"
ReferencedContainer = "container:tests/OCUnitTests/OoliteUnitTests.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
@ -42,6 +56,16 @@
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "TestRelease">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1A0CD53515AF0BCE00970505"
BuildableName = "OoliteUnitTests.octest"
BlueprintName = "OoliteUnitTests"
ReferencedContainer = "container:tests/OCUnitTests/OoliteUnitTests.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference

View File

@ -26,7 +26,9 @@ MA 02110-1301, USA.
#import "ShipEntity.h"
#import "Universe.h"
#import "legacy_random.h"
@class OOWeakSet;
typedef enum
{
@ -45,10 +47,11 @@ typedef enum
#define DOCKING_CLEARANCE_WINDOW 126.0
@interface StationEntity: ShipEntity
{
@private
NSMutableSet *_shipsOnHold;
OOWeakSet *_shipsOnHold;
DockEntity *player_reserved_dock;
double last_launch_time;
double approach_spacing;

View File

@ -44,6 +44,7 @@ MA 02110-1301, USA.
#import "OOJSScript.h"
#import "OODebugGLDrawing.h"
#import "OODebugFlags.h"
#import "OOWeakSet.h"
@interface StationEntity (OOPrivate)
@ -57,8 +58,6 @@ MA 02110-1301, USA.
- (NSDictionary *) holdPositionInstructionForShip:(ShipEntity *)ship;
- (void) compactShipsOnHold;
@end
@ -244,28 +243,11 @@ MA 02110-1301, USA.
// if all docks have no ships on approach
[shipAI message:@"DOCKING_COMPLETE"];
}
[self compactShipsOnHold];
}
- (void) compactShipsOnHold
{
NSArray *ships = [_shipsOnHold allObjects];
OOUInteger i, count = [ships count];
for (i = 0; i < count; i++)
{
OOWeakReference *ref = [ships objectAtIndex:i];
if ([ref weakRefUnderlyingObject] == nil)
{
[_shipsOnHold removeObject:ref];
}
}
}
// only used by player - everything else ends up in a Dock's launch queue
- (void) launchShip:(ShipEntity*)ship
- (void) launchShip:(ShipEntity *)ship
{
NSEnumerator *subEnum = nil;
DockEntity* sub = nil;
@ -303,17 +285,13 @@ MA 02110-1301, USA.
- (void) abortAllDockings
{
NSEnumerator *subEnum = nil;
DockEntity* sub = nil;
DockEntity *sub = nil;
for (subEnum = [self dockSubEntityEnumerator]; (sub = [subEnum nextObject]); )
{
[sub abortAllDockings];
}
OOWeakReference *ref = nil;
foreach (ref, _shipsOnHold)
{
[[ref weakRefUnderlyingObject] sendAIMessage:@"DOCKING_ABORTED"];
}
[_shipsOnHold makeObjectsPerformSelector:@selector(sendAIMessage:) withObject:@"DOCKING_ABORTED"];
[_shipsOnHold removeAllObjects];
[shipAI message:@"DOCKING_COMPLETE"];
@ -323,14 +301,11 @@ MA 02110-1301, USA.
- (void) autoDockShipsOnHold
{
OOWeakReference *ref = nil;
foreach (ref, _shipsOnHold)
NSEnumerator *onHoldEnum = [_shipsOnHold objectEnumerator];
ShipEntity *ship = nil;
while ((ship = [onHoldEnum nextObject]))
{
ShipEntity *ship = [ref weakRefUnderlyingObject];
if (ship != nil)
{
[self pullInShipIfPermitted:ship];
}
[self pullInShipIfPermitted:ship];
}
[_shipsOnHold removeAllObjects];
@ -488,9 +463,7 @@ NSDictionary *OOMakeDockingInstructions(StationEntity *station, Vector coords, f
}
// we made it through holding!
OOWeakReference *weakShip = [ship weakRetain];
[_shipsOnHold removeObject:weakShip];
[weakShip release];
[_shipsOnHold removeObject:ship];
[shipAI reactToMessage:@"DOCKING_REQUESTED" context:@"requestDockingCoordinates"]; // react to the request
@ -498,15 +471,13 @@ NSDictionary *OOMakeDockingInstructions(StationEntity *station, Vector coords, f
}
- (NSDictionary *) holdPositionInstructionForShip:(ShipEntity *)ship
- (NSDictionary *)holdPositionInstructionForShip:(ShipEntity *)ship
{
OOWeakReference *weakShip = [ship weakRetain];
if (![_shipsOnHold containsObject:weakShip])
if (![_shipsOnHold containsObject:ship])
{
[self sendExpandedMessage:@"[station-acknowledges-hold-position]" toShip:ship];
[_shipsOnHold addObject:weakShip];
[_shipsOnHold addObject:ship];
}
[weakShip release];
return OOMakeDockingInstructions(self, [ship position], 0, 100, @"HOLD_POSITION", nil, NO);
}
@ -516,9 +487,7 @@ NSDictionary *OOMakeDockingInstructions(StationEntity *station, Vector coords, f
{
[ship sendAIMessage:@"DOCKING_ABORTED"];
OOWeakReference *weakShip = [ship weakRetain];
[_shipsOnHold removeObject:weakShip];
[weakShip release];
[_shipsOnHold removeObject:ship];
NSEnumerator *subEnum = nil;
DockEntity *sub = nil;
@ -546,7 +515,7 @@ NSDictionary *OOMakeDockingInstructions(StationEntity *station, Vector coords, f
if (self != nil)
{
isStation = YES;
_shipsOnHold = [[NSMutableSet alloc] init];
_shipsOnHold = [[OOWeakSet alloc] init];
}
return self;
@ -2296,17 +2265,17 @@ NSDictionary *OOMakeDockingInstructions(StationEntity *station, Vector coords, f
} */
// Ships on hold list, only used with moving stations (= carriers)
[self compactShipsOnHold];
if([_shipsOnHold count] > 0)
{
OOLog(@"dumpState.stationEntity", @"%i Ships on hold (unsorted):", [_shipsOnHold count]);
OOWeakReference *ref = nil;
OOLogIndent();
unsigned i = 1;
foreach (ref, _shipsOnHold)
NSEnumerator *onHoldEnum = [_shipsOnHold objectEnumerator];
ShipEntity *ship = nil;
unsigned i = 1;
while ((ship = [onHoldEnum nextObject]))
{
ShipEntity *ship = [ref weakRefUnderlyingObject];
OOLog(@"dumpState.stationEntity", @"Nr %i: %@ at distance %g with role: %@", i++, [ship displayName], distance(position, [ship position]), [ship primaryRole]);
OOLog(@"dumpState.stationEntity", @"Nr %i: %@ at distance %g with role: %@", i++, [ship displayName], distance([self position], [ship position]), [ship primaryRole]);
}
OOLogOutdent();
}

54
src/Core/OOWeakSet.h Normal file
View File

@ -0,0 +1,54 @@
/*
OOWeakSet.h
A mutable set of weak references to objects conforming to OOWeakReferenceSupport.
Semantics:
* When an object in the set is deallocated, the object is removed from the
set and the set's count drops. There is no notification for this. As such,
there is no such thing as an immutable weak set.
* Objects are uniqued by pointer equality, not isEquals:.
* OOWeakSet is not thread-safe. It not only requires that all operations on
it happen on one thread, but also that objects it's watching are (finally)
released on that thread.
LIMITATION: fast enumeration and Oolite's foreach() macro are not supported.
Written by Jens Ayton in 2012 for Oolite.
This code is hereby placed in the public domain.
*/
#import "OOWeakReference.h"
@interface OOWeakSet: NSObject <NSCopying, NSMutableCopying>
{
@private
NSMutableSet *_objects;
}
- (id) init;
- (id) initWithCapacity:(NSUInteger)capacity; // As with Foundation collections, capacity is only a hint.
+ (id) set;
+ (id) setWithCapacity:(NSUInteger)capacity;
- (NSUInteger) count;
- (BOOL) containsObject:(id<OOWeakReferenceSupport>)object;
- (NSEnumerator *) objectEnumerator;
- (void) addObject:(id<OOWeakReferenceSupport>)object; // Unlike NSSet, adding nil fails silently.
- (void) removeObject:(id<OOWeakReferenceSupport>)object; // Like NSSet, does not complain if object is not already a member.
- (void) addObjectsByEnumerating:(NSEnumerator *)enumerator;
- (void) makeObjectsPerformSelector:(SEL)selector;
- (void) makeObjectsPerformSelector:(SEL)selector withObject:(id)argument;
- (void) removeAllObjects;
@end

298
src/Core/OOWeakSet.m Normal file
View File

@ -0,0 +1,298 @@
/*
OOWeakSet.h
Written by Jens Ayton in 2012 for Oolite.
This code is hereby placed in the public domain.
*/
#import "OOWeakSet.h"
#import "OOCocoa.h"
@interface OOWeakRefUnpackingEnumerator: NSEnumerator
{
@private
NSEnumerator *_enumerator;
}
- (id) initWithEnumerator:(NSEnumerator *)enumerator;
+ (id) enumeratorWithCollection:(id)collection; // Collection must implement -objectEnumerator
@end
@interface OOWeakSet (OOPrivate)
- (void) compact; // Remove any zeroed entries.
@end
@implementation OOWeakSet
- (id) init
{
return [self initWithCapacity:0];
}
- (id) initWithCapacity:(OOUInteger)capacity
{
if ((self = [super init]))
{
_objects = [[NSMutableSet alloc] initWithCapacity:capacity];
if (_objects == NULL)
{
[self release];
return nil;
}
}
return self;
}
+ (id) set
{
return [[[self alloc] init] autorelease];
}
+ (id) setWithCapacity:(OOUInteger)capacity
{
return [[[self alloc] initWithCapacity:capacity] autorelease];
}
- (void) dealloc
{
DESTROY(_objects);
[super dealloc];
}
- (NSString *) description
{
NSMutableString *result = [NSMutableString stringWithFormat:@"<%@ %p>{", [self class], self];
NSEnumerator *selfEnum = [self objectEnumerator];
id object = nil;
BOOL first = YES;
while ((object = [selfEnum nextObject]))
{
if (!first) [result appendString:@", "];
else first = NO;
NSString *desc = nil;
if ([object respondsToSelector:@selector(shortDescription)]) desc = [object shortDescription];
else desc = [object description];
[result appendString:desc];
}
[result appendString:@"}"];
return result;
}
// MARK: Protocol conformance
- (id) copyWithZone:(NSZone *)zone
{
[self compact];
OOWeakSet *result = [[OOWeakSet allocWithZone:zone] init];
[result addObjectsByEnumerating:[self objectEnumerator]];
return result;
}
- (id) mutableCopyWithZone:(NSZone *)zone
{
return [self copyWithZone:zone];
}
- (BOOL) isEqual:(id)other
{
if (![other isKindOfClass:[OOWeakSet class]]) return NO;
if ([self count] != [other count]) return NO;
BOOL result = YES;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSEnumerator *selfEnum = [self objectEnumerator];
id object = nil;
while ((object = [selfEnum nextObject]))
{
if (![other containsObject:object])
{
result = NO;
break;
}
}
DESTROY(pool);
return result;
}
// MARK: Meat and potatoes
- (OOUInteger) count
{
[self compact];
return [_objects count];
}
- (BOOL) containsObject:(id<OOWeakReferenceSupport>)object
{
[self compact];
OOWeakReference *weakObj = [object weakRetain];
BOOL result = [_objects containsObject:weakObj];
[weakObj release];
return result;
}
- (NSEnumerator *) objectEnumerator
{
return [OOWeakRefUnpackingEnumerator enumeratorWithCollection:_objects];
}
- (void) addObject:(id<OOWeakReferenceSupport>)object
{
if (object == nil) return;
NSAssert([object conformsToProtocol:@protocol(OOWeakReferenceSupport)], @"Attempt to add object to OOWeakSet which does not conform to OOWeakReferenceSupport.");
OOWeakReference *weakObj = [object weakRetain];
[_objects addObject:weakObj];
[weakObj release];
}
- (void) removeObject:(id<OOWeakReferenceSupport>)object
{
OOWeakReference *weakObj = [object weakRetain];
[_objects removeObject:weakObj];
[weakObj release];
}
- (void) addObjectsByEnumerating:(NSEnumerator *)enumerator
{
id object = nil;
[self compact];
while ((object = [enumerator nextObject]))
{
[self addObject:object];
}
}
- (void) makeObjectsPerformSelector:(SEL)selector
{
OOWeakReference *weakRef = nil;
foreach (weakRef, _objects)
{
[[weakRef weakRefUnderlyingObject] performSelector:selector];
}
}
- (void) makeObjectsPerformSelector:(SEL)selector withObject:(id)argument
{
OOWeakReference *weakRef = nil;
foreach (weakRef, _objects)
{
[[weakRef weakRefUnderlyingObject] performSelector:selector withObject:argument];
}
}
- (void) removeAllObjects
{
[_objects removeAllObjects];
}
- (void) compact
{
OOWeakReference *weakRef = nil;
BOOL compactRequired = NO;
foreach (weakRef, _objects)
{
if ([weakRef weakRefUnderlyingObject] == nil)
{
compactRequired = YES;
break;
}
}
if (compactRequired)
{
NSMutableSet *newObjects = [[NSMutableSet alloc] initWithCapacity:[_objects count]];
foreach (weakRef, _objects)
{
if ([weakRef weakRefUnderlyingObject] != nil)
{
[newObjects addObject:weakRef];
}
}
[_objects release];
_objects = newObjects;
}
}
@end
@implementation OOWeakRefUnpackingEnumerator
- (id) initWithEnumerator:(NSEnumerator *)enumerator
{
if (enumerator == nil)
{
[self release];
return nil;
}
if ((self = [super init]))
{
_enumerator = [enumerator retain];
}
return self;
}
+ (id) enumeratorWithCollection:(id)collection
{
return [[[self alloc] initWithEnumerator:[collection objectEnumerator]] autorelease];
}
- (void) dealloc
{
[_enumerator release];
[super dealloc];
}
- (id) nextObject
{
id next = nil;
while ((next = [_enumerator nextObject]))
{
next = [next weakRefUnderlyingObject];
if (next != nil) return next;
}
return nil;
}
@end