Oolite
Loading...
Searching...
No Matches
OOShipGroup.m
Go to the documentation of this file.
1/*
2OOShipGroup.m
3
4IMPLEMENTATION NOTE:
5This is implemented as a dynamic array rather than a hash table for the
6following reasons:
7 * Ship groups are generally quite small, not motivating a more complex
8 implementation.
9 * The code ship groups replace was all array-based and not a significant
10 bottleneck.
11 * Ship groups are compacted (i.e., dead weak references removed) as a side
12 effect of iteration.
13 * Many uses of ship groups involve iterating over the whole group anyway.
14
15
16Oolite
17Copyright (C) 2004-2013 Giles C Williams and contributors
18
19This program is free software; you can redistribute it and/or
20modify it under the terms of the GNU General Public License
21as published by the Free Software Foundation; either version 2
22of the License, or (at your option) any later version.
23
24This program is distributed in the hope that it will be useful,
25but WITHOUT ANY WARRANTY; without even the implied warranty of
26MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27GNU General Public License for more details.
28
29You should have received a copy of the GNU General Public License
30along with this program; if not, write to the Free Software
31Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
32MA 02110-1301, USA.
33
34*/
35
36#import "ShipEntity.h"
37#import "OOShipGroup.h"
38#import "OOMaths.h"
39
40
41enum
42{
44 kMaxFreeSpace = 128
45};
46
47
48@interface OOShipGroupEnumerator: NSEnumerator
49{
50 // ivars are public so ShipGroupIterate() can peek at both these and OOShipGroup's. Naughty!
51@public
53 NSUInteger _index, _updateCount;
55}
56
57- (id) initWithShipGroup:(OOShipGroup *)group;
58
59- (NSUInteger) index;
60- (void) setPerformCleanup:(BOOL)flag;
61
62@end
63
64
65@interface OOShipGroup (Private)
66
67- (BOOL) resizeTo:(NSUInteger)newCapacity;
68- (void) cleanUp;
69
70- (NSUInteger) updateCount;
71
72@end
73
74
76
77
78@implementation OOShipGroup
79
80- (id) init
81{
82 return [self initWithName:nil];
83}
84
85
86- (id) initWithName:(NSString *)name
87{
88 if ((self = [super init]))
89 {
90 _capacity = kMinSize;
91 _members = malloc(sizeof *_members * _capacity);
92 if (_members == NULL)
93 {
94 [self release];
95 return nil;
96 }
97
98 [self setName:name];
99 }
100
101 return self;
102}
103
104
105+ (instancetype) groupWithName:(NSString *)name
106{
107 return [[[self alloc] initWithName:name] autorelease];
108}
109
110
111+ (instancetype) groupWithName:(NSString *)name leader:(ShipEntity *)leader
112{
113 OOShipGroup *result = [self groupWithName:name];
114 [result setLeader:leader];
115 return result;
116}
117
118
119- (void) dealloc
120{
121 NSUInteger i;
122
123 for (i = 0; i < _count; i++)
124 {
125 [_members[i] release];
126 }
127 free(_members);
128 [_name release];
129
130 [super dealloc];
131}
132
133
134- (NSString *) descriptionComponents
135{
136 NSString *desc = [NSString stringWithFormat:@"%llu ships", (unsigned long long)_count];
137 if ([self name] != nil)
138 {
139 desc = [NSString stringWithFormat:@"\"%@\", %@", [self name], desc];
140 }
141 if ([self leader] != nil)
142 {
143 desc = [NSString stringWithFormat:@"%@, leader: %@", desc, [[self leader] shortDescription]];
144 }
145 return desc;
146}
147
148
149- (NSString *) name
150{
151 return _name;
152}
153
154
155- (void) setName:(NSString *)name
156{
157 _updateCount++;
158
159 if (_name != name)
160 {
161 [_name release];
162 _name = [name retain];
163 }
164}
165
166
167- (ShipEntity *) leader
168{
169 ShipEntity *result = [_leader weakRefUnderlyingObject];
170
171 // If reference is stale, delete weakref object.
172 if (result == nil && _leader != nil)
173 {
174 [_leader release];
175 _leader = nil;
176 }
177
178 return result;
179}
180
181
182- (void) setLeader:(ShipEntity *)leader
183{
184 _updateCount++;
185
186 if (leader != [self leader])
187 {
188 [_leader release];
189 [self addShip:leader];
190 _leader = [leader weakRetain];
191 }
192}
193
194
195- (NSEnumerator *) objectEnumerator
196{
197 return [[[OOShipGroupEnumerator alloc] initWithShipGroup:self] autorelease];
198}
199
200
201- (NSEnumerator *) mutationSafeEnumerator
202{
203 return [[self memberArray] objectEnumerator];
204}
205
206
207- (NSSet *) members
208{
209 return [NSSet setWithArray:[self memberArray]];
210}
211
212
213- (NSSet *) membersExcludingLeader
214{
215 return [NSSet setWithArray:[self memberArrayExcludingLeader]];
216}
217
218
219- (NSArray *) memberArray
220{
221 id *objects = NULL;
222 NSUInteger count = 0;
223 NSArray *result = nil;
224
225 if (_count == 0) return [NSArray array];
226
227 objects = malloc(sizeof *objects * _count);
228 for (id ship in self)
229 {
230 objects[count++] = ship;
231 }
232
233 result = [NSArray arrayWithObjects:objects count:count];
234 free(objects);
235
236 return result;
237}
238
239
240- (NSArray *) memberArrayExcludingLeader
241{
242 id *objects = NULL;
243 NSUInteger count = 0;
244 NSArray *result = nil;
245 ShipEntity *leader = nil;
246
247 if (_count == 0) return [NSArray array];
248 leader = self.leader;
249
250 objects = malloc(sizeof *objects * _count);
251 for (id ship in self)
252 {
253 if (ship != leader)
254 {
255 objects[count++] = ship;
256 }
257 }
258
259 result = [NSArray arrayWithObjects:objects count:count];
260 free(objects);
261
262 return result;
263}
264
265
266- (BOOL) containsShip:(ShipEntity *)ship
267{
268 ShipEntity *containedShip = nil;
269
270 for (containedShip in self)
271 {
272 if ([ship isEqual:containedShip])
273 {
274 return YES;
275 }
276 }
277
278 return NO;
279}
280
281- (BOOL) addShip:(ShipEntity *)ship
282{
283 _updateCount++;
284
285 if ([self containsShip:ship]) return YES; // it's in the group already, result!
286
287 // Ensure there's space.
288 if (_count == _capacity)
289 {
290 if (![self resizeTo:(_capacity > kMaxFreeSpace) ? (_capacity + kMaxFreeSpace) : (_capacity * 2)])
291 {
292 if (![self resizeTo:_capacity + 1])
293 {
294 // Out of memory?
295 return NO;
296 }
297 }
298 }
299
300 _members[_count++] = [ship weakRetain];
301 return YES;
302}
303
304
305- (BOOL) removeShip:(ShipEntity *)ship
306{
307 OOShipGroupEnumerator *shipEnum = nil;
308 ShipEntity *containedShip = nil;
309 NSUInteger index;
310 BOOL foundIt = NO;
311
312 _updateCount++;
313
314 if (ship == [self leader]) [self setLeader:nil];
315
316 shipEnum = (OOShipGroupEnumerator *)[self objectEnumerator];
317 [shipEnum setPerformCleanup:NO];
318 while ((containedShip = [shipEnum nextObject]))
319 {
320 if ([ship isEqual:containedShip])
321 {
322 index = [shipEnum index] - 1;
323 _members[index] = _members[--_count];
324 foundIt = YES;
325
326 // Clean up
327 [ship setGroup:nil];
328 [ship setOwner:ship];
329 [self cleanUp];
330 break;
331 }
332 }
333 return foundIt;
334}
335
336/* TODO post-1.78: profiling indicates this is a noticeable
337 * contributor to ShipEntity::update time. Consider optimisation: may
338 * be possible to return _count if invalidation of weakref and group
339 * removal in ShipEntity::dealloc keeps the data consistent anyway -
340 * CIM */
341
342- (NSUInteger) count
343{
344 NSEnumerator *memberEnum = nil;
345 NSUInteger result = 0;
346
347 if (_count != 0)
348 {
349 memberEnum = [self objectEnumerator];
350 while ([memberEnum nextObject] != nil) result++;
351 }
352
353 assert(result == _count);
354
355 return result;
356}
357
358
359- (BOOL) isEmpty
360{
361 if (_count == 0) return YES;
362
363 return [[self objectEnumerator] nextObject] == nil;
364}
365
366
367- (BOOL) resizeTo:(NSUInteger)newCapacity
368{
369 OOWeakReference **temp = NULL;
370
371 if (newCapacity < _count) return NO;
372
373 temp = realloc(_members, newCapacity * sizeof *_members);
374 if (temp == NULL) return NO;
375
376 _members = temp;
377 _capacity = newCapacity;
378 return YES;
379}
380
381
382- (void) cleanUp
383{
384 NSUInteger newCapacity = _capacity;
385
386 if (_count >= kMaxFreeSpace)
387 {
388 if (_capacity > _count + kMaxFreeSpace)
389 {
390 newCapacity = _count + 1; // +1 keeps us at powers of two + multiples of kMaxFreespace.
391 }
392 }
393 else
394 {
395 if (_capacity > _count * 2)
396 {
397 newCapacity = OORoundUpToPowerOf2_NS(_count);
398 if (newCapacity < kMinSize) newCapacity = kMinSize;
399 }
400 }
401
402 if (newCapacity != _capacity) [self resizeTo:newCapacity];
403}
404
405
406- (NSUInteger) updateCount
407{
408 return _updateCount;
409}
410
411
413{
414 // The work is done here so that we can have access to both OOShipGroup's and OOShipGroupEnumerator's ivars.
415
416 OOShipGroup *group = enumerator->_group;
417 ShipEntity *result = nil;
418 BOOL cleanupNeeded = NO;
419
420 if (enumerator->_updateCount != group->_updateCount)
421 {
422 [NSException raise:NSGenericException format:@"Collection <OOShipGroup: %p> was mutated while being enumerated.", group];
423 }
424
425 while (enumerator->_index < group->_count)
426 {
427 result = [group->_members[enumerator->_index] weakRefUnderlyingObject];
428 if (result != nil)
429 {
430 enumerator->_index++;
431 break;
432 }
433
434 // If we got here, the group contains a stale reference to a dead ship.
435 group->_members[enumerator->_index] = group->_members[--group->_count];
436 cleanupNeeded = YES;
437 }
438
439 // Clean-up handling. Only perform actual clean-up at end of iteration.
440 if (enumerator->_considerCleanup)
441 {
442 enumerator->_cleanupNeeded = enumerator->_cleanupNeeded && cleanupNeeded;
443 if (enumerator->_cleanupNeeded && result == nil)
444 {
445 [group cleanUp];
446 }
447 }
448
449 return result;
450}
451
452
453- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len
454{
455 NSUInteger srcIndex, dstIndex = 0;
456 ShipEntity *item = nil;
457 BOOL cleanupNeeded = NO;
458
459 srcIndex = state->state;
460 while (srcIndex < _count && dstIndex < len)
461 {
462 item = [_members[srcIndex] weakRefUnderlyingObject];
463 if (item != nil)
464 {
465 stackbuf[dstIndex++] = item;
466 srcIndex++;
467 }
468 else
469 {
470 _members[srcIndex] = _members[--_count];
471 cleanupNeeded = YES;
472 }
473 }
474
475 if (cleanupNeeded) [self cleanUp];
476
477 state->state = srcIndex;
478 state->itemsPtr = stackbuf;
479 state->mutationsPtr = &_updateCount;
480
481 return dstIndex;
482}
483
484
485/* This method exists purely to suppress Clang static analyzer warnings that
486 this ivar is unused (but may be used by categories, which they are).
487 FIXME: there must be a feature macro we can use to avoid actually building
488 this into the app, but I can't find it in docs.
489*/
490- (BOOL) suppressClangStuff
491{
492 return !_jsSelf;
493}
494
495@end
496
497
498@implementation OOShipGroupEnumerator
499
500- (id) initWithShipGroup:(OOShipGroup *)group
501{
502 assert(group != nil);
503
504 if ((self = [super init]))
505 {
506 _group = [group retain];
507 _considerCleanup = YES;
508 _updateCount = [_group updateCount];
509 }
510
511 return self;
512}
513
514
515- (void) dealloc
516{
517 DESTROY(_group);
518
519 [super dealloc];
520}
521
522
523- (id) nextObject
524{
525 return ShipGroupIterate(self);
526}
527
528
529- (NSUInteger) index
530{
531 return _index;
532}
533
534
535- (void) setPerformCleanup:(BOOL)flag
536{
537 _considerCleanup = flag;
538}
539
540@end
#define DESTROY(x)
Definition OOCocoa.h:75
unsigned count
return nil
@ kMaxFreeSpace
Definition OOShipGroup.m:44
@ kMinSize
Definition OOShipGroup.m:43
static id ShipGroupIterate(OOShipGroupEnumerator *enumerator)
NSUInteger updateCount()
NSUInteger _updateCount
Definition OOShipGroup.m:53
void setPerformCleanup:(BOOL flag)
OOShipGroup * _group
Definition OOShipGroup.m:52
OOWeakReference ** _members
Definition OOShipGroup.h:39
void setLeader:(ShipEntity *leader)
< NSFastEnumeration > unsigned long _updateCount
Definition OOShipGroup.h:38
void setGroup:(OOShipGroup *group)
void setOwner:(Entity *who_owns_entity)