A Flash Developer Resource Site

Page 1 of 2 12 LastLast
Results 1 to 20 of 27

Thread: Trying to design some object pooling

  1. #1
    The Cheeze to Your Macaroni ColbyCheeze's Avatar
    Join Date
    Dec 2007
    Location
    Texas
    Posts
    244

    Trying to design some object pooling

    I've done some reading and it seems like using object pooling for creation of many objects is ideal in many ways.

    I am trying to design object pooling for creating the different types of projectiles in my game and am having some trouble deciding how to do it.

    1. I've never created any kind of pooling before...
    2. I'm trying to decide how to design the actual projectile class. Obviously there will be diff types like: Missle, Plasma, Laser etc...

    Everything can derive from "AProjectile" which extends my "MovingEntity" class...however something im struggling with is:

    Should I create multiple pools for different types of projectiles? (obviously a Missle can't be a Laser) or is there some way I could design an object that can mold to fit any type of projectile thus allowing me to derive all from the base projectile class and therefore have one pool of projectiles...

    Also thought I'd see if anyone has some thoughts / examples of how to actually create some object pooling, as I am new to the concept.

    Hopefully that all made sense! Love to see what you guys can come up with. Thanks :P

  2. #2
    Senior Member
    Join Date
    May 2006
    Location
    Manhattan
    Posts
    246
    you won't be able to "apply" a type to an already created instance of a different (or ancestor) type.

    In terms of pooling implementation, you can quite simply create a desired number of every type you'll be using in your application. You could map Class to a pool of that type in a Dictionary object to constrain your pool access to a single method. Offer another method to return an object you're done with to the pool. The pooling management class can identify the Class dynamically and return it to the appropriate pool.

  3. #3
    Knows where you live
    Join Date
    Oct 2004
    Posts
    944
    The chances are that its unlikely that object pooling is really the best solution.

    Except in extreme cases, object creation and collection is not going to be a massive performance bottleneck. Object pooling is moderately complex for what you get, and has the risk of the overhead being greater than simply creating objects on demand. Not only that, but the many potential types of objects that need to be pooled sticks a lot of excess complexity in there.

    I assume that your goal is performance in using large quantities of objects.
    There are alternatives to object pooling that may be more appropriate such as the flyweight pattern (http://en.wikipedia.org/wiki/Flyweight_pattern).

    It is far faster to manage creation and deletion of coordinates (Numbers/ints) than complete objects, and opens the possibility for more optimization on top of that (Manually inlining method calls from the flyweight class).

    Pools are far more suited to things like threads where creation may take orders of magnitude more time than the calculations being done. The example provided in the flyweight pattern (Characters being displayed in word processors) is really far more appropriate for bullets, there is many of them, they vary only in coordinates, it is impractical to have each one be an object in itself...
    Last edited by 691175002; 05-20-2008 at 04:58 PM.
    The greatest pleasure in life is doing what people say you cannot do.
    - Walter Bagehot
    The height of cleverness is to be able to conceal it.
    - Francois de La Rochefoucauld

  4. #4
    Senior Member
    Join Date
    May 2006
    Location
    Manhattan
    Posts
    246
    Instantiation is a pretty decent bottlenecker... and in the case where you're creating loads of objects like you might with projectiles in a game (or game framework if i remember correctly? where it would give the user an option to pool?) it's certainly worth looking into.

    Joa Ebert talks about the huge impact it had on pooling tween objects:
    http://blog.je2050.de/2008/05/07/twe...-object-pools/

  5. #5
    Script kiddie VENGEANCE MX's Avatar
    Join Date
    Jun 2004
    Location
    England
    Posts
    2,590
    I would create multiple pools; one for each type of projectile. I don't see that it'd be any slower than making one unified pool, and it makes the thinking a lot easier. Using a unified pool would probably mean that you need to put more code into instantiating each object, I think? I can see that slowing it down. Besides, the way both me and webgeek used when experimenting with pooling was to create one pool per object type. He understands OOP (and coding in general) far better than me, so I'm guessing that if there was any merit in using a single pool, he would've written it that way instead.

    If you need any help, I can post my sloppily implemented pooling method. It's actually pretty simple (depending on how your engine's currently set to operate), only maybe 8 lines of code per pool, and can be slipped in and out of the engine pretty tidily. Webgeek has a better implementation (does the same thing in the same way, but with less code on your part - just two function calls per pool, I think), but I never figured out how to use it (although that can be attributed to me being useless at working in OOP).
    http://www.birchlabs.co.uk/
    You know you want to.

  6. #6
    Senior Member
    Join Date
    May 2006
    Location
    Manhattan
    Posts
    246
    I wrote this generic pooler real quickly- use should be self explanatory:
    PHP Code:
    package
    {
        
        
    import flash.utils.Dictionary;
        
    import flash.utils.getDefinitionByName;
        
    import flash.utils.getQualifiedClassName;
        
        public class 
    PoolingManager
        
    {
            
            private var 
    _poolMap:Dictionary;
            
            public function 
    PoolingManager()
            {
                
    _poolMap = new Dictionary();
            }
            
            public function 
    getElementFromPooltype:Class, autoFillAmt:int ) : Object
            
    {
                var 
    pool:Array = _poolMaptype ];
                if( 
    autoFillAmt )
                {
                    if( !
    pool || !pool.length )
                    {
                        
    createPooltypeautoFillAmt );
                        
    pool _poolMaptype ];
                    } 
                }
                return 
    pool.pop();
            }
            
            public function 
    getNumElementsInPooltype:Class ) : int
            
    {
                var 
    pool:Array = _poolMaptype ];
                if( 
    pool ) return pool.length;
                return 
    0;
            }
            
            public function 
    returnElementToPoolelement:Object ) : void
            
    {
                var 
    type:Class = getClasselement );
                var 
    pool:Array = _poolMaptype ];
                
    pool.pushelement );
            }
            
            public function 
    createPooltype:Class, numMembers:int 100 ) : void
            
    {
                
                if( !
    _poolMaptype ] ) _poolMaptype ] = new Array();
                var 
    pool:Array = _poolMaptype ];
                
                while( --
    numMembers > -)
                {
                    
    pool.push( new type() );
                }
                
            }
            
            public function 
    removePooltype:Class ) : void
            
    {
                
    _poolMaptype ] = null;
            }
            
            private function 
    getClassinstance:Object ) : Class
            {
                var 
    name:String getQualifiedClassNameinstance );
                return Class( 
    getDefinitionByNamename ) );
            }

        }


  7. #7
    The Cheeze to Your Macaroni ColbyCheeze's Avatar
    Join Date
    Dec 2007
    Location
    Texas
    Posts
    244
    Thanks for the replies. Yea I saw that example from Joa...that was one of the reasons I decided to take that route.

    I suppose it would be best to just create multiple pools then...currently my game is structured as follows (the engine anyways) : http:http://www.cheezeworld.com/files/spa...MunsterUML.png

    So basically what I could do is have a pool for the same "renderer" object, in which case i'd be using BitmapClipRenderer and all I'd have to do is swap the BitmapClip depending on what "type" of projectile im rendering.

    As far as the projectiles I suppose I'll have to create multiple pools for each type of projectile I decide to make...the other solution would to give the "AProjectile" class all the properties than any projectile would need and attach an "actions" class which just has a set of static functions that can be called based on the parameter passed in...( Actions.projectileAction("type", parameters...); )

    That would give me a max of 2 pools for the projectiles however it might end up being messy as all of the actions are being stored in one function...and also it would slow things down because I am now calling an extra function withing each projectiles update()...

    What do you guys think? Just create multiple pools for multiple types of entities or do the "action" approach?

  8. #8
    Senior Member
    Join Date
    May 2006
    Location
    Manhattan
    Posts
    246
    Maybe it's not apparent, but what I posted handles all the type separate pool creation for you...

    it differentiates at the necessary level (Class), which means it only implements logic you'd have to anyway with multiple pool objects. And it keeps everything type safe (which could be painstaking if you need tons of different pools as you'd have to write a different class for each pool type).

    lastly, definitely avoid the "action" approach. it breaks a lot of OOP law imo.

  9. #9
    The Cheeze to Your Macaroni ColbyCheeze's Avatar
    Join Date
    Dec 2007
    Location
    Texas
    Posts
    244
    definitely avoid the "action" approach. it breaks a lot of OOP law imo.
    Yea I agree...it was just a thought I had while writing the reply. NE-ways I looked at your code it looks pretty simple and a good implementation too!

    If you need any help, I can post my sloppily implemented pooling method
    If you wouldn't mind I'd like to see yours and webgeeks version? Just to get an idea of some different options to try.

  10. #10
    Senior Member
    Join Date
    Nov 2003
    Location
    Las Vegas
    Posts
    770
    Quote Originally Posted by 691175002
    Except in extreme cases, object creation and collection is not going to be a massive performance bottleneck.
    Try a simple loop test with sufficient iterations, do one test that creates new geom objects (rectangles, points, etc), and do another one that uses object pooling. I think you'll find that object creation with some of Flash's native classes can be a very big bottleneck.

    One special case in the originator's game is the lasers. Generally speaking, a laser will be virtually instantaneous from origin to target. Due to this, you can use a simple pooling method that only has to loop from numberDestroyed to numberCreated, rather than having to rearrange a linked list based on destruction. For bullets and missles this won't work due to some objects being destroyed out of the creation order.

  11. #11
    Script kiddie VENGEANCE MX's Avatar
    Join Date
    Jun 2004
    Location
    England
    Posts
    2,590
    Here's a trimmed-down version of one of my Invasion tests, then. Put this script on your main frame (told you it's sloppy):

    PHP Code:
    stage.scaleMode "noScale";
    stage.quality "LOW";

    var 
    infSheetBMP:infSheet = new infSheet();
    var 
    troops:Sprite = new Sprite();
    var 
    troopPool:Array = new Array();

    var 
    frameRate:int=31;
    var 
    lowest:int=frameRate;
    var 
    checkCounter:int checkRate 8;
    var 
    startTime getTimer();
    var 
    lowestResetCnt=frameRate*5;
    var 
    fps:int 31;

    var 
    tI:int 0;
    var 
    t;

    var 
    te:TextField = new TextField();
    te.text "FPS: 31\nTroops: 0";
    te.autoSize TextFieldAutoSize.LEFT;
    addChild(te);

    function 
    createTroop(tr:Number):void {
        if (
    troopPool.length>0) {
            
    myTroop troops.addChild(troopPool[0]);
            
    troopPool.splice(01);
        } else {
            var 
    myTroop:Infantry = new Infantry(this);
            var 
    infSprite:Bitmap = new Bitmap(infSheetBMP);
            
    myTroop.addChild(infSprite);
            
    troops.addChild(myTroop);
        }
        
    myTroop.init();
    }

    stage.addEventListener(Event.ENTER_FRAMEEnterFrame);
    function 
    EnterFrame(event:Event):void {
        
    createTroop();
        
        
    tI troops.numChildren;
        while (
    tI--) {
            
    troops.getChildAt(tI);
            
    t.eF();
        }
        if(--
    checkCounter==0) {
            
    fr checkRate/((getTimer() - startTime)/1000);
            
    fps int(fr*10)/10;
            if (
    fps<=lowest){
                
    powest=fps;
            } else {
                if(
    fps>frameRate){
                    
    fps=frameRate;
                }
            }
            
    startTime getTimer();
            
    checkCounter checkRate;
        }
        if (--
    lowestResetCnt==0){
            
    lowestResetCnt=frameRate;
            
    lowest=frameRate;
        }
        
    te.text "FPS: "+String(fps)+"\nMCs: "+String(troops.numChildren);

    You'll need to download and put this into your Library:



    And Export it for ActionScript with a Linkage Identifier of infSheet.

    Finally, you'll need an Infantry.as in your project folder saying this:

    PHP Code:
    package {
        
    import flash.display.Sprite;
        
    import flash.display.Bitmap;
        
    import flash.display.BitmapData;
        
    import flash.geom.Rectangle;

        public class 
    Infantry extends Sprite {
            public var 
    _root;
            public var 
    speed:int;
            public var 
    frame:int;
            public var 
    objMode:String;
            public var 
    cond:Boolean;
            public var 
    inc:int;
            
            public var 
    obj:String "Inf";
            
            public var 
    WIDTH:int 35;
            public var 
    HEIGHT:int 54;
            public var 
    FRAMES:int 17;
            
            public var 
    rect:Rectangle;
            
            public function 
    Infantry(mr:Object) {
                
    _root mr;
                
    cacheAsBitmap true;
                
    scrollRect = new Rectangle(0,0WIDTHHEIGHT);
                
    draw();
            }
            public function 
    draw():void {
                
    rect scrollRect;
                
    rect.frame WIDTH;
                
    scrollRect rect;
            }
            public function 
    init():void {
                
    Math.floor(245-Math.random()*12)
                
    = -50-Math.random()*20;
                
    frame 0;
                
    speed 3;
                
    objMode  "Walk";
                
    cond true;
                
    inc Math.round(Math.random()*5);
                
    WIDTH 35;
                
    HEIGHT 54;
                
    FRAMES 17;
                
    scrollRect = new Rectangle(0,0WIDTHHEIGHT);
            }
            public function 
    eF():void {
                if (
    <= 300) {
                    
    x-=-speed;
                } else if (
    x>300) {
                    
    -= 21 ;
                    
    -= -10;
                    
    objMode "Die";
                    
    frame 0;
                    
    FRAMES 19;
                    
    WIDTH 54;
                    
    HEIGHT 44;
                    
    scrollRect = new Rectangle(0121WIDTHHEIGHT);
                }
                
    draw();
                
    frame++;
                if (
    objMode=="Die") {
                    if (
    frame==FRAMES) {
                        
    _root.troops.removeChildAt(_root.tI);
                        
    _root.troopPool.push(this);
                    }
                } else {
                    if (
    frame==FRAMES) {
                        
    frame 0;
                    }
                }
            }
        }

    Sorry if it doesn't work; I haven't tested that, so I might've removed some unnecessary stuff. I'd compile it all for you, but Flash 9 doesn't run on my Intel Mac. As for webgeek's version... you'd need to ask him for that.

    Oh, bear in mind that while object pooling does get rid of the bottleneck of object instantiation, you can still create a far faster entity engine using copyPixels(). My non-pooling copyPixels() engine runs about 20 times faster than this. Comparison:

    Pooling sprites (FPS capped at 31, so note that this isn't a very fair benchmark):
    http://www.birchlabs.co.uk/Invasion5F9.swf
    copyPixels():
    http://www.birchlabs.co.uk/Invasion_5.html
    Last edited by VENGEANCE MX; 05-21-2008 at 12:20 PM.
    http://www.birchlabs.co.uk/
    You know you want to.

  12. #12
    Hype over content... Squize's Avatar
    Join Date
    Apr 2001
    Location
    Lost forever in a happy crowd...
    Posts
    5,926
    V, pooling isn't limited to sprites mate.

    I'm sure we've spoken about this before, you can still use bobs with pooling rather than just sprites.

    Squize.

  13. #13
    Script kiddie VENGEANCE MX's Avatar
    Join Date
    Jun 2004
    Location
    England
    Posts
    2,590
    I know it's not limited to sprites. But if the engine can handle 7,000 bobs (or 2,000 with collision detection) fine without pooling, you don't really need to go to the extra length of pooling it. The bobs in my copyPixels() engine only have the properties x,y,frame,HP and objMode, so object instantiation isn't a pressing overhead.

    Nevertheless, I'm going to try pooling my copyPixels() engine in a few weeks, once my GCSEs have finished, and see if it makes any significant difference.
    http://www.birchlabs.co.uk/
    You know you want to.

  14. #14
    Knows where you live
    Join Date
    Oct 2004
    Posts
    944
    My point is simply that even for something like lasers, as Vengeance said, you can drop a bit of the OOP fluff and make something both fast and simple.

    A copyPixels engine is really quite similar to a flyweight pattern as you have a single object representing many with data being swapped in through arrays.

    While object instantiation compared with reuse or pools in a simple for loop will show major performance gains, toss in a reasonably sized copyPixels or draw operation in the loop and the difference suddenly becomes much smaller. Considering each object may draw itself 30+ times during its lifetime I feel the following test is far more telling:
    Code:
     Results of test:
    Constant Reinstantiation: 551
    Reuse: 537
    Code:
    var bd:BitmapData = new BitmapData(800,600);
    var cpy:BitmapData = new BitmapData(200,200);
    bd.lock();
    
    var p:Point = new Point (0,0);
    var r:Rectangle = new Rectangle (0,0,40,40);
    
    
    var n:Number = getTimer();
    var pp:Point;
    var rr:Rectangle;
    for (var i:int = 0; i < 10000; i++) {
    	// A basic bullet symbolized by a point and rectangle
    	pp = new Point();
    	rr = new Rectangle();
    	for (var j:int = 0; j < 30; j++) {
    		bd.copyPixels(cpy,r,p);
    	}
    }
    trace ("Constant Reinstantiation: " + (getTimer() - n));
    
    n = getTimer();
    for (i = 0; i < 10000; i++) {
    	// A basic bullet symbolized by a point and rectangle
    	pp.x = 0;
    	pp.y = 0;
    	rr.x = 0;
    	rr.y = 0;
    	rr.width = 0;
    	rr.height = 0;
    	for (j = 0; j < 30; j++) {
    		bd.copyPixels(cpy,r,p);
    	}
    }
    trace ("Reuse: " + (getTimer() - n));
    You will notice that even when two completely new objects are being created each iteration, there is less than a 3% speed difference as soon as you throw in some realistic operations. This isn't even being compared to a pool, just reusing the same two objects.


    I generally try to design out situations that involve hundreds or thousands of objects cycling each frame to begin with. The cases where an object pool is truly necessary (And better than the many alternatives) are far between.
    The greatest pleasure in life is doing what people say you cannot do.
    - Walter Bagehot
    The height of cleverness is to be able to conceal it.
    - Francois de La Rochefoucauld

  15. #15
    Script kiddie VENGEANCE MX's Avatar
    Join Date
    Jun 2004
    Location
    England
    Posts
    2,590
    Thank you, 691175002. Was hoping for some evidence along those lines. Webgeek said (if I recall correctly) that object pooling is only necessary when you're instantiating big objects that inherit loads of methods and properties, which is why the Sprite example benefits from it (inherits DisplayObject, which is massive). Point() and Rectangle() don't seem to be too intensive, but they still have a not insignificant number of properties and methods in them. A typical bob object might have even less overhead than one of these.

    I'd be interested in seeing the speed difference between a Point being created, and a Sprite.
    http://www.birchlabs.co.uk/
    You know you want to.

  16. #16
    Knows where you live
    Join Date
    Oct 2004
    Posts
    944
    Here is the same test, changed to sprites:
    Code:
    Constant Reinstantiation: 636
    Reuse: 549
    A little more of a difference, but considering how heavy a sprite is, on top of the fact that a sprite will probably live longer than 30 frames, make me still think that there are better alternatives.

    As well, on this scale a pool with both a hashtable lookup as well as a push/pop will have overhead.
    Last edited by 691175002; 05-21-2008 at 08:45 PM.
    The greatest pleasure in life is doing what people say you cannot do.
    - Walter Bagehot
    The height of cleverness is to be able to conceal it.
    - Francois de La Rochefoucauld

  17. #17
    Senior Member
    Join Date
    Nov 2003
    Location
    Las Vegas
    Posts
    770
    I think there are a couple of problems with your test for reinstantiation, but I could be thinking about it the wrong way, so please correct me if I'm wrong here.

    The rectangle and point objects used in your copy pixel call are not the ones created. This may not make a difference, but should be fixed.
    PHP Code:
                bd.copyPixels(cpy,r,p);

    // should be

            
    bd.copyPixels(cpy,rr,pp); 
    The second issue is that you are in fact using object pooling in both tests, due to the fact that you reuse the same rectangle and point objects in the second loop of each test. This should be done in the reuse test, but not in the reinstantiation test if you want results with no pooling. Perhaps remove the second loop and instead use more objects. That or:
    PHP Code:
    for (var i:int 010000i++) {
        
    // A basic bullet symbolized by a point and rectangle
        
    for (var j:int 030j++) {
            
    pp = new Point();
            
    rr = new Rectangle();
            
    bd.copyPixels(cpy,rr,pp);
        }
    }
    trace ("Constant Reinstantiation: " + (getTimer() - n));

    getTimer();
    for (
    010000i++) {
        
    // A basic bullet symbolized by a point and rectangle
        
    for (030j++) {
            
    pp.0;
            
    pp.0;
            
    rr.0;
            
    rr.0;
            
    rr.width 0;
            
    rr.height 0;
            
    bd.copyPixels(cpy,rr,pp);
        }
    }
    trace ("Reuse: " + (getTimer() - n)); 
    Either way, the results of your tests show reuse (pooling) to be faster, so why argue against it?

  18. #18
    Knows where you live
    Join Date
    Oct 2004
    Posts
    944
    You misunderstand what is being tested.

    The first test works like follows:
    I assume that someone is creating a set of lightweight object, keeping each one for thirty frames (Each frame performing a single copypixel operation) then discarding it.
    In the inner loop, the point and rectangle are kept the same, because it is assumed that each instance will have its own point and rectangle for personal use (Or an external class which handles drawing will also be reusing the same Point/Rect). The drawing is irrelevant to the test, I am simply padding the loop with plausible operations to show that in real life, perhaps 1% of execution will be spent creating new objects in comparison to what the objects are doing, even with something so short lived.

    The second test is essentially the same, except that we are reusing instead of creating new Rectangles and Points.


    Now, keep in mind that this is a test between Reuse and complete reinstantiation. Not a test between a pool and a flyweight. This just demonstrates that the most optimal solution, not creating or storing any new objects is less than 3% faster than the slowest possible solution with lightweight objects.

    Now, if you wanted to simulate a pool you could toss in a hashtable lookup and a push/pop in an external class in each iteration, on several arrays of hundreds of entries in length. The gap starts to close.
    I am quite confident that when you are using lightweight objects such as bullets, there will be no practical performance gain to a pool, and potentially a performance loss if your class is lightweight.


    I still stand by my statement that in almost all cases, pools are either unnecessary or solve a problem that could have been avoided in the first place, even more so for something like bullets.
    Last edited by 691175002; 05-21-2008 at 09:25 PM.
    The greatest pleasure in life is doing what people say you cannot do.
    - Walter Bagehot
    The height of cleverness is to be able to conceal it.
    - Francois de La Rochefoucauld

  19. #19
    The Cheeze to Your Macaroni ColbyCheeze's Avatar
    Join Date
    Dec 2007
    Location
    Texas
    Posts
    244
    Thank you for all of the data, I really appreciate it. I think perhaps just creation objects won't be so bad but I'll still use a factory for it all just in case later I can swap in some type of pooling on the backside and have it all still work fine.

    I do actually create a few things each time a projectile is created so pooling would probably give some sort of gains on situations with large amounts of things flying around...however from your tests I suppose I can just focus on that later if I really feel it is even a cause of concern for performance.

    I was just trying to juice as much performance out of all the small stuff as I could to make as much room for the AI / Collision once I get to that.

    Overall good discussion so far though methinks! :P

  20. #20
    Senior Member
    Join Date
    Nov 2003
    Location
    Las Vegas
    Posts
    770
    Thanks for the explanation. I first used pooling with heavy object usage with good results, so I have always used it since, without taking into regard a lightweight case. One of those traps you fall into from good results.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  




Click Here to Expand Forum to Full Width

HTML5 Development Center