SKPhysicsBody bodyWithPolygonFromPath memory leaks
Asked Answered
G

5

9

I have a strange memory leaks when creating Sprite Kit physics bodies with custom shapes. This is how my implementation looks:

CGFloat offsetX = self.frame.size.width * self.anchorPoint.x;
CGFloat offsetY = self.frame.size.height * self.anchorPoint.y;

CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 4 - offsetX, 3 - offsetY);
CGPathAddLineToPoint(path, NULL, 66 - offsetX, 3 - offsetY);
CGPathAddLineToPoint(path, NULL, 35 - offsetX, 57 - offsetY);
CGPathCloseSubpath(path);

self.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:path];

CGPathRelease(path);

Everything is going on inside SKSpriteNode method. Instruments tells me about several memory leaks after creating such bodies:

Leaked object: 
  Malloc 32 Bytes
Size:
  32 Bytes
Responsible Library: 
  PhysicsKit
Responsible Frame:
  std::__1::__split_buffer<PKPoint, std::__1::allocator<PKPoint>&>::__split_buffer(unsigned long, unsigned long, std::__1::allocator<PKPoint>&)

The CGPathRelease(path); line is necessary - without it I'm getting more memory leaks about CGPath which is understandable. When I'm using this implementation instead (for testing purposes):

CGFloat radius = MAX(self.frame.size.width, self.frame.size.height) * 0.5f;
self.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:radius];

...everything is working well, without memory leaks. I wonder if this is Sprite Kit bug or I'm doing something wrong.

Graiggrail answered 22/11, 2013 at 0:48 Comment(4)
I don't see what you could do differently... I think it obviously must have something to do with the call to bodyWithPolygonFromPath... Perhaps you can ask on the Apple Dev forums?Mohammedmohammedan
Call tree, if it's useful: [SKPhysicsBody bodyWithPolygonFromPath:], [PKPhysicsBody bodyWithPolygonFromPath:], [PKPhysicsBody initWithPolygonFromPath:], [CGPathApply], CG::Path::Sequence::apply, CG::Path::Subpath::apply, CG::Chunk::apply, ::adaptor::callback, PKPathApplyCGPath, std::__1::vector::__push_back_slow_path, std::__1::__split_buffer::__split_bufferBethesda
I have the same problem, except with bodyWithEdgeChain - It looks like a bug in Sprite Kits handling of CGPath with SKPhysicsBody and SKShapeNodeFoamflower
I'm not able to reproduce the problem. Is this happening on the device as well as the simulator? The simulator seems to have some leaks that the device does not.Ranzini
F
4

This is a bug in sprite kit. You will have to wait for a fix.

Foamflower answered 5/12, 2013 at 8:38 Comment(2)
Has this bug been reported to Apple? I am experiencing the same problem.Storm
I reported it a while back, but subsequently stopped using sprite kit, so I don't know if its been fixed in 7.1Foamflower
S
2

Does anything change if you do the following?

CGPathRef pathCopy = CGPathCreateCopy(path);
CGPathRelease(path);
self.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:pathCopy];
CGPathRelease(pathCopy);
Sumpter answered 29/11, 2013 at 22:7 Comment(0)
B
2

The only restriction on the path parameter is:

A convex polygonal path with counterclockwise winding and no self intersections. The points are specified relative to the owning node’s origin.

I do not know the values of offsetX and offsetY so I do not know whether the path is correct or not, but assuming that they are both 0, it seems to me like this path is clockwise and not counterclockwise. I would create the path using constants and no variables, just to make sure that it is correct and if it is still leaking I would say it's a bug in PhysicsKit.

Bibliopole answered 29/11, 2013 at 22:10 Comment(2)
Hmm... I will double check the path, but I am aware of those rules and I am pretty sure I created this path properly. The body with this convex polygonal path acting correctly, just like it should, the memory leaks are only issues I have noticed.Graiggrail
@Darrarski, If you are using the framework according to specification and you are getting leaks in the framework, then there is a bug in the framework. Not even the developers at Apple are perfect. Is it leaking a lot or is it something you can live with?Bibliopole
P
2

As commented in several places, this looks like a bug in the implementation of SKPhysicsBody, which persists at least until iOS 7.1. The reason for this is:

SKPhysicsBody holds an instance variable, '_path', which holds a copy of the initial CGPathRef passed when calling 'bodyWithEdgeChainFromPath' or similiar constructors. This instance variable never gets released, so all paths will stay in memory.

However, you may implement a workaround for this by

(1) subclassing the SKShapeNode, that should hold the SKPhysicsBody,

(2) after creating and assigning the SKPhysicsBody for this node, retrieve the instance variable referring to the CGPathRef of the SKPhysicsBody,

(3) when the shape node is deallocated, check the retain count of the path. If it's > 0, release it and the memory leaks are gone.

There's little code overhead (beside subclassing all shape nodes, that use physics bodies relying on CGPath's). Just do this:

Add an instance variable to your subclass:

{
    CGPathRef myPath;
}

Implement the method to retrieve the value for this CGPath in any subclassed SKShapeNode implementation. You might also consider to add this as a general category on SKNode:

- (CGPathRef) getPhysicsBodyPath
{
    CGPathRef path = nil;
    if ( self.physicsBody ){
        object_getInstanceVariable(self.physicsBody, "_path", (void**) &path);
    }
    return(path);
}

This code will return the CGPathRef instance used by the node's physics body. Note, however, that this must be done immediately after assigning the physics body to the node. At a later time (i.e. in dealloc(), this might return a null value. So, after creating the body, store this value in the instance variable 'myPath'. To make this code work even after Apple might fix the bug, we'll add an additonal retain, which will make sure we can access this object once our SKNode is deallocated (see below):

    /*
     *  we need to keep a copy of the final path reference, that has been created for the physics body.
     *  retrieving this value during deallocation won't work any more...
     */
    myPath = CFRetain([self getPhysicsBodyPath]);

Finally, overwrite the 'dealloc' method and release the path, after your SKNode is released:

- (void) dealloc
{
    self.physicsBody = nil;
    [super dealloc];
    if ( myPath != nil ) {
        /* this will work, because we've retained the path for this instance */
        CFIndex rc = CFGetRetainCount (myPath);
        /* this is our own release ... */
        CGPathRelease(myPath);
        /* in case Apple has fixed the release, this is OK, otherwise we'll need
         * to release the path twice to avoid memory leaks
         */
        if ( rc > 1 ) {
            CGPathRelease(myPath);
        }
    }
}

This will finally release this path and fix the memory leaks. This code works for all iOS version up to 7.1 and it also should work on further versions, once Apple finally fixes this bug and SKPhysicsBoy actually releases the path (as they are supposed to do).

Purgatory answered 26/7, 2014 at 16:49 Comment(2)
This is the best solution I've seen by far. Tested, verified and implemented: github.com/Tantas/SKShapeNodeLeakFree.Kremenchug
UPDATE: for iOS Releases > 7.1 it looks, like that has been fixed. Avoid calling CFRetain([self getPhysicsBodyPath]) if you have a later version. by checking the tagret device's iOS version. The rest will then still work as described.Purgatory
M
2

I've also experienced similar memory leak, but it was fixed after I removed one line from my viewDidLoad function
skView.showsPhysics = YES;

Mortician answered 28/1, 2016 at 22:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.