I was checking Instruments today to see what was at the top of the list for consuming CPU, and I was very surprised to see that it was in CCSpriteSheet's draw method... because it was in the part specifically related to "Optimization: Fast Dispatch"
The lines eating up the cpu were not the part doing the dispatching, but rather the lines that assign the references to the selectors to be used by the fast dispatch calls.
SEL selDirty = @selector(dirty);
SEL selUpdate = @selector(updateTransform);
This is actually easily remedied by storing this info in the class and assigning them in the init method instead.
However while I was looking over the code I was thinking that the implementation of the remainder of the fast dispatch code is flawed slightly:
(original is posted below for reference, with drawing code stripped out to focus attention on how the dispatching is working)
-(void) draw
{
// Optimization: Fast Dispatch
typedef BOOL (*DIRTY_IMP)(id, SEL);
typedef BOOL (*UPDATE_IMP)(id, SEL);
SEL selDirty = @selector(dirty);
SEL selUpdate = @selector(updateTransform);
DIRTY_IMP dirtyMethod = nil;
UPDATE_IMP updateMethod = nil;
for( CCSprite *child in descendants_ )
{
if( ! dirtyMethod ) {
// Optimization: Fast Dispatch
dirtyMethod = (DIRTY_IMP) [child methodForSelector:selDirty];
updateMethod = (UPDATE_IMP) [child methodForSelector:selUpdate];
}
// fast dispatch
if( dirtyMethod(child, selDirty) )
updateMethod(child, selUpdate);
}
}
Stepping through this means the following will happen for EVERY frame:
1. Ask the Objective-C runtime for the selector for the method "dirty" and assign it to a local variable "selDirty"
2. Ask the Objective-C runtime for the selector for the method "updateTransform" and assign it to a local variable "selUpdate"
3. Make a local variable "dirtyMethod" to store the actual "dirty" method and set it to nil.
4. Make a local variable "updateMethod" to store the actual "updateTransform" method and set it to nil.
5. The following will happen for every frame for every child:
5.1 Check if "dirtyMethod" is nil and if so assign both the dirty and updateTransform methods for this child to our local variables.
5.2 Run the "dirtyMethod" stored from step 5.1 for this child (and all children afterward) and if it returns TRUE then run the "updateMethod" stored from step 5.1 for this child (and all children afterward)
Now as as I stated above, the remedy for items #1 and #2 is to just move the storage up to the class and initialize them with the class inside init.
Now the flaw with the rest is a bit stickier because the actual method implementation we need to store for each child CAN be specific and different for each child. If the child is a subclass of CCSprite and it overrode the dirty/updateTransform methods.
I think supporting this case is impractical... so let's assume that we have law abiding developers who will never want to override these two methods. In this case the above code will function. But it can be optimized further. Because of the assumption/requirement that all of the children must not override the dirty/updateTransform methods, we can actually store this in the class and initialize it at the same time as the items from #1 and #2.
Now with all that said, I'm going to move forward and make these changes and then benchmark it all against not doing fast dispatching at all and let you all know if the performance increase is worth the LAW that is introduced.
:)