Re-using particle emitters in Cocos2D

I'm currently working on a Cocos2D game which uses a lot of particles. Several times a second an explosion is triggered an every three seconds the background particle emitter changes. When I first coded the game I wasn't worrying about optimisation so I just loaded the CCParticleSystems from file each time I needed them. Now that I'm nearing completion, I've been looking at optimisation. When I profiled my game using Instruments, by checking allocations, I noticed that my emitters were causing a horrible memory leak. My game would start off running at a snappy 60 fps. After sever minutes the frame rate had dropped to 50 fps then 40, 30... No leak showed in the leaks section of Instruments so I checked the allocations panel. Here I could see the number of active CCParticleSystems continuously creeping up and up. After several minutes there would be 700 living particle systems using about 15mb of memory. Although I used the

  1. [system autoRemoveOnFinish]

method which is available for CCParticleSystems it seemed that the particle systems weren't being correctly released. After some looking around on the internet saw a suggestion to use an @autorelease pool. Although this solved my memory leak it didn't seem like the most efficient solution - I was still loading hundreds of emitters from file every minute. By looking at the CCParticleSystem source code you can see that the process of loading a new emitter from file is complex and calling it a lot has a real impact on game performance. I decided to create a ParticleManager class to allow me to pool and re-use my emitters.

Creating a Particle Manager class to re-use emitters

This class is fairly simple and gave my game a major boost in performance.

Particle Manager Interface

  1. //
  2. // BParticleManager.h
  3. // FirstGame
  4. //
  5. // Created by Ben Smiley-Andrews on 01/05/2012.
  6. // Copyright (c) 2012 Deluge. All rights reserved.
  7. //
  8.  
  9. #import <Foundation/Foundation.h>
  10.  
  11. @interface BParticleManager : NSObject {
  12.  
  13. // Map the file name of the emmitter to a tag number which will be set on the emitter
  14. NSMutableDictionary * _stringToTagMap;
  15.  
  16. // Array to store a pool of particles
  17. NSMutableArray * _particlePool;
  18.  
  19. // Store the maximum tag used
  20. NSInteger _maxTag;
  21. }
  22.  
  23. -(id) getParticle:(NSString *)type;
  24.  
  25. @end

Particle Manager Implementation

  1. //
  2. // BParticleManager.m
  3. // FirstGame
  4. //
  5. // Created by Ben Smiley-Andrews on 01/05/2012.
  6. // Copyright (c) 2012 Deluge. All rights reserved.
  7. //
  8.  
  9. #import "BParticleManager.h"
  10.  
  11. @implementation BParticleManager
  12.  
  13. -(id) init {
  14. if((self=[super init])) {
  15. _particlePool = [NSMutableArray new];
  16. _stringToTagMap = [NSMutableDictionary new];
  17. _maxTag = 0;
  18. }
  19. return self;
  20. }
  21.  
  22.  
  23. /*
  24.  * This class will return an CCParticleSystemQuad with the
  25.  * supplied resource path i.e. .plist file. If a pooled
  26.  * system is available that will be returned. An system
  27.  * is considered available if it is inactive and has no
  28.  * active particles.
  29.  */
  30. -(id) getParticle:(NSString *)type {
  31. NSNumber * tag = [_stringToTagMap objectForKey:type];
  32.  
  33. /*
  34.   * If the mapping doesn't exist in the dictionary add it. We use this
  35.   * mapping because we want to define the particle type by it's .plist
  36.   * file path but the CCParticleSystem object can only tag systems with
  37.   * an int.
  38.   */
  39. if(tag==Nil) {
  40. tag = [NSNumber numberWithInt:_maxTag++];
  41. [_stringToTagMap setObject:tag forKey:type];
  42. }
  43.  
  44. // If possible return a system from the pool
  45. CCParticleSystemQuad * system;
  46.  
  47. // Search through the pool for an available object of the
  48. // required type and if one is found return it
  49. for(system in _particlePool) {
  50. if(system.tag==[tag intValue] && (system.particleCount==0 ) && !system.active ) {
  51. [system resetSystem];
  52. return system;
  53. }
  54. }
  55.  
  56. // Otherwise return a new system
  57. system = [CCParticleSystemQuad particleWithFile:type];
  58. system.tag = [tag intValue];
  59. [_particlePool addObject:system];
  60.  
  61. return system;
  62. }
  63.  
  64. -(void) dealloc {
  65. [_particlePool release];
  66. [_stringToTagMap release];
  67. [super dealloc];
  68. }
  69.  
  70. @end

To use this manager it's just necessary to call the getParticle method with the path to a .plist particle definition file i.e. @"StarBurst.plist". The manager maps this path to an int which is used to tag the emitter. Each emitter which is created is added to the pool. Then for each new request the pool is checked to see if an appropriate pre-existing emitter can be found. If so the emitter is returned otherwise a new emitter is created and added to the pool.

An interesting side note is that in order to know whether an emitter has finished it's necessary to check both the active property and the particleCount property. This is because the active flag will set to no as soon as the emitter stops emitting. This could be because it's reached it's duration or because a stopSystem call has been received. In either of these cases it's not certain that all of the particles have disappeared. If we just went by this flag the emitter could be re-used while particles were still alive - this could cause the particles to disappear suddenly which isn't desirable. By checking the particle count as well we can make sure that the emitter is truly dead i.e. it's has no active particles and is stopped.

By using this class I found that the number of CCParticleSystems living after 2 minutes dropped from 700 to 34 with a large boost in performance.

Tweet: 

Comments

good tutorial for reuse particlesystem.

Hey man thanks for posting this. I plugged it into my game and it seems to be running well.

One thing I noticed though, particle systems are never removed from _particlePool, so it feels like each time a particle system gets added as a child, it should never be removed or else getParticle could mistakenly return a deallocated system. ie: autoRemoveOnFinish must be NO? Does this make sense?

Add new comment

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <c>, <cpp>, <drupal5>, <drupal6>, <java>, <javascript>, <php>, <python>, <ruby>. The supported tag styles are: <foo>, [foo].
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.