سؤال

I just finished debugging the hell out of my project after I found it was working fine on the simulator but would crash when tested on a device with an EXC BAD ACCESS error at the @autoreleasepool using ARC.

I finally narrowed the problem down to where I had a custom init method for a custom class I created that accepted two structs as parameters. The structs were based off of the same definition, containing only 3 GLfloats to represent x, y and z data for use with positioning and rotation.

When I modified the custom init method to instead accept 6 GLfloats instead of two structs each containing 3 GLfloats, and then had the init method assign these GLfloats to the two appropriate instance variable structs of the class instead of assigning the previously passed structs directly to the instance variable structs, it all worked fine without error.

For a bit of clarification:

I had a struct defined like so:

struct xyz{
    GLfloat x;
    GLfloat y;
    GLfloat z;
};

I then had two ivars of a custom class (lets call it foo) called position and rotation, both based off of this struct.

I then had a custom init method simplified to this:

-(id) initWithPosition: (struct xyz) setPosition rotation: (struct xyz) setRotation
{
    self = [super init];
    if(self) {
        position = setPosition;
        rotation = setRotation;
    }
    return self;
}

Which I called using something similar to:

struct xyz position;
struct xyz rotation;

// Fill position / rotation with data here....

foo *bar = [[foo alloc] initWithPosition: position rotation: rotation];

Which resulted in the EXC BAD ACCESS. So I changed the init method to this:

-(id) initWithPositionX: (GLfloat) xp Y: (GLfloat) yp Z: (GLfloat) zp RotationX: (GLfloat) xr Y: (GLfloat) yr Z: (GLfloat) zr
{
    self = [super init];
    if(self) {
        position.x = xp;
        position.y = yp;
        position.z = zp;

        rotation.x = xr;
        rotation.y = yr;
        rotation.z = zr;

    }
    return self;
}

And... all is well.

I mean, Im glad I "fixed" it, but Im not sure why I fixed it. Ive read that ARC has issues with using structs of objects, but my structs are only comprised of simple GLfloats, which aren't objects... right? Id rather know why this is working before I move on, and hopefully help some others out there experiencing the same problem.

Thanks, - Adam Eisfeld

EDIT: Adding Source

Try as I might I cant seem to duplicate the issue in a test project, so there must be something wrong with my code as others have suggested and I haven't fixed the problem at all, only masked it until later, which is what I was afraid of.

So if anyone can take a look at this code to point out what might be going wrong here (if the issue is indeed in the init method, which maybe its not), Id greatly appreciate it. Its rather large so I understand if nobody is interested, but Ive commented it and Ill explain whats going on in further detail after the code snippet:

-(id) initWithName: (NSString*) setName fromFile: (NSString*) file
{
    self = [super init];
    if(self) {

        //Initialize ivars
        name = setName;
        joints = [[NSMutableArray alloc] init];
        animations = [[NSMutableArray alloc] init];
        animationCount = 0;
        frameCount = 0;

        //Init file objects for reading
        NSError *fileError;
        NSStringEncoding *encoding;

        //Load the specified file's contents into an NSString
        NSString *fileData = [[NSString alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:file ofType:@"dat"] usedEncoding:encoding error:&fileError];

        //Create a new NGLMaterial to apply to all of the loaded models from the file
        NGLMaterial *material = [[NGLMaterial alloc] init];
        material = [NGLMaterial material];
        material.diffuseMap = [NGLTexture texture2DWithFile:@"resources/models/diffuse.bmp"];

        //Check for nil file data
        if(fileData == nil)
        {
            NSLog(@"Error reading mesh file");
        }
        else
        {
            //Separate the NSString of the file's contents into an array, one line per indice
            NSMutableArray *fileLines = [[NSMutableArray alloc] initWithArray:[fileData componentsSeparatedByString:@"\n"] copyItems: YES];

            //Create a pseudo counter variable
            int i = -1;

            //Allocate a nul NSString for looping
            NSString *line = [[NSString alloc] initWithFormat:@""];

            //Loop through each of the lines in the fileLines array, parsing the line's data
            for (line in fileLines) {

                //Increase the pseudo counter variable to determine what line we're currently on
                i++;

                if (i == 0) {
                    //The first line of the file refers to the number of animations for the player
                    animationCount = [line intValue];
                }else
                {

                    if (i == [fileLines count]-2) {
                        //The last line of the file refers to the frame count for the player
                        frameCount = [line intValue];

                    }else
                    {
                        //The lines inbetween the first and last contain the names of the .obj files to load

                        //Obtain the current .obj path by combining the name of the model with it's path
                        NSString *objPath = [[NSString alloc] initWithFormat:@"resources/models/%@.obj", line];

                        //Instantiate a new NGLMesh with the objPath NSString
                        NGLMesh *newMesh = [[NGLMesh alloc] initWithOBJFile:objPath];

                        //Apply various settings to the mesh such as material
                        newMesh.material = material;
                        newMesh.rotationOrder = NGLRotationOrderZYX;

                        //Compile the changes to the mesh
                        [newMesh compileCoreMesh];

                        //Add the mesh to this player's joints array
                        [joints addObject:newMesh];

                        //Read the animation data for this joint from it's associated file
                        NSLog(@"Reading animation data for: %@", line);

                        //The associated animation file for this model is found at (model's name).anim
                        NSString *animData =  [[NSString alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:line ofType:@"anim"] usedEncoding:encoding error:&fileError];

                        //Check for nil animation data
                        if(animData == nil)
                        {
                            NSLog(@"Error reading animation file");
                        }
                        else
                        {

                            //Construct temporary position and rotation structs to store the read xyz data from each frame of animation
                            struct xyz position;
                            struct xyz rotation;

                            //Create a new scanner to scan the current animation file for it's xyz position / rotation data per frame
                            NSScanner *scanner = [[NSScanner alloc] initWithString:animData];
                            while([scanner isAtEnd] == NO)
                            {

                                //Extract position data
                                [scanner scanFloat:&position.x];
                                [scanner scanFloat:&position.y];
                                [scanner scanFloat:&position.z];

                                //Extract rotation data
                                [scanner scanFloat:&rotation.x];
                                [scanner scanFloat:&rotation.y];
                                [scanner scanFloat:&rotation.z];



                                //OLD CODE NOT WORKING:
                                //AEFrame *frame = [[AEFrame alloc] initWithPosition: position rotation: rotation];

                                //Initialize new frame instance using new working init method
                                AEFrame *frame = [[AEFrame alloc] initWithPositionX:position.x Y:position.y Z:position.z RotationX:rotation.x Y:rotation.y Z:rotation.z];

                                //Add the created frame instace to the player's animations array
                                [animations addObject:frame];

                            }  
                        }   
                    }
                }
            }
        }
    }
    return self;
}

Alright so essentially I am writing the code that handles animating the 3D joints of a given player in my engine. I wrote a script for Maya in MEL that allows you to select a series of animated models in the viewport (in this case the joints of a robot character IE the upper and lower arms, the head, the torso, etc) and then run the script which will go through each selected model and export a .anim file. This .anim file contains the xyz position and rotation of the joint the file refers to at each frame of it's animation, so that it is structured like this:

Frame 1 X Position
Frame 1 Y Position
Frame 1 Z Position
Frame 1 X Rotation
Frame 1 Y Rotation
Frame 1 Z Rotation
Frame 2 X Position
Frame 2 Y Position
etc...

However this animation file consists purely of the floats refering to the xyz positions and rotations, there is no actual text labeling each line as shown above, thats only for reference.

When the exporting of .anim files is done for each selected joint of the animated character, the script exports one final file with a .dat extension. This file contains the amount of animations exported (this value is set in the script, for example you might be exporting 3 animations to a .anim file such as Run, Walk and Idle), it then contains a list of names. These names refer to both the names of the .obj files to load into the 3D engine and also the corresponding .anim files to load for the loaded .obj files.

With that explained, Ill describe how Im handling this in my project:

  1. The user instantiates a new "AEPlayer" class using the above method -initWithName: File:

  2. This method first opens up the .dat file exported from maya to find the names of the files to load in for the player's joints.

  3. For every joint name found in the .dat file, the system proceeds to then instantiate a new NGLMesh instance using the joint's name with a ".obj" at the end of it, and then open up the current joint's .anim file. The system also add's the instantiated NGLMesh to the player's joints array.

  4. For each joint's .anim file, it reads through the file using an NSScanner. It scans in 6 lines at a time (the xyz position and xyz rotation of each frame). After scanning in one frame's data, it instantiates a class called AEFrame and sets the frame's position data to the loaded position data and rotation data to the loaded rotation data. This is the class that I was having trouble with passing the xyz position struct and xyz rotation struct to in it's init parameters but "fixed" by passing in 6 GLfloats instead, as shown in the above code.

  5. When the frame has been instantiated, this frame is added to the current player's animations array (an NSMutableArray). This animations array ends up getting fairly large, as it stores all of the animation data for every joint in the model. The model I am testing with has 42 joints, each joint has 12 frames of animation, each frame has 6 GLfloats.

  6. When the system is finished loading all of the .obj files found in the .dat file and their associated .obj and .anim files into memory, it is finished.

  7. In the main run loop of the program, I simply loop through all of the created AEPlayers, and for each AEPLayer I loop through the player's joints array, setting the position and rotation of the NGLMesh stored at the current player's joint to whatever position / rotation is found in the player's animations array for the current joint.

I realize its a long shot to get anyone to read all of that, but I thought Id throw it out there. Also, Im aware that I have a few dead allocations in the above snippet that I need to get rid of, but they're not contributing to the problem (I added them after the problem arose to see what was happening memory wise using Instruments).

Thanks a lot for any help again, - Adam Eisfeld

هل كانت مفيدة؟

المحلول

It's possible some other code is corrupting the stack, or doing something else that's undefined. If you're doing anything like that (which can be really tricky to debug), all bets are off, and simple changes might mask or unmask the real bug.

When you change between device and simulator, you're also building for ARM instead of i386 and are building optimized instead of debug. Those are all huge changes and could change the result of the undefined behavior. For example, writing one byte past the end of an array could result in no harm in a debug build, but catastrophic failure in a release build. Changing the structs to floats could change the way the parameters are passed into your method, which could either mask or unmask the bug.

You should try to isolate your problem to a small sample project. For example, can you reproduce the problem with just one class, with just that one init method?

This set of articles from the LLVM blog might be helpful if you're not already familiar with this topic:

Update:

It looks like your problem may be here:

NSStringEncoding *encoding;
//Load the specified file's contents into an NSString
NSString *fileData = [[NSString alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:file ofType:@"dat"] usedEncoding:encoding error:&fileError];

The usedEncoding parameter of -[NSString initWithContentsOfFile:usedEncoding:error] is an out parameter, meaning that you provide storage and pass a pointer to that storage and the function will write some output to it. You've provided a pointer, but it doesn't point at anything valid, and when -[NSString initWithContentsOfFile:usedEncoding:error] writes to the usedEncoding pointer, it will overwrite something.

You should change that code to this:

NSStringEncoding encoding;
//Load the specified file's contents into an NSString
NSString *fileData = [[NSString alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:file ofType:@"dat"] usedEncoding:&encoding error:&fileError];

Now you've provided a pointer to a valid string encoding variable.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top