├── .gitignore ├── LICENSE ├── README.md ├── SpriteKit+QuickLook.h ├── SpriteKit+QuickLook.m ├── SpriteKitDebug.py ├── install.sh └── lldbinit /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | *.xccheckout 19 | 20 | #CocoaPods 21 | Pods 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Steffen Itterheim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SpriteKit-QuickLook 2 | =================== 3 | 4 | Tired of not seeing any details of Sprite Kit classes in the debugger? 5 | 6 | These categories add QuickLook strings (requires Xcode 5.1+) to Sprite Kit nodes for easier debugging of Sprite Kit apps. 7 | 8 | License: **MIT License** 9 | 10 | Watch a demo video (Sprite Kit demo at 1:03): 11 | https://www.youtube.com/watch?v=ToH4YbTSDAU 12 | 13 | To use: 14 | ------- 15 | - add SpriteKit+QuickLook.h and .m to your project 16 | - while debugging, click on a Sprite Kit object variable's QuickLook (eye) icon or the info (i) icon (provided the variable is a reference of a Sprite Kit class) 17 | 18 | Learn more about Xcode 5.1 QuickLook here (has details on how to bring up the QuickLook view): 19 | https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/CustomClassDisplay_in_QuickLook/CH01-quick_look_for_custom_objects/CH01-quick_look_for_custom_objects.html 20 | 21 | To use the Summary strings: 22 | ------------- 23 | 24 | This is an unsupported, optional feature. 25 | 26 | - create or update ~/.lldbinit with the contents of the lldbinit file 27 | - copy SpriteKitDebug.py to ~/SpriteKitDebug.py (or create a symlink to SpriteKitDebug.py as ~/SpriteKitDebug.py) 28 | - restart Xcode 29 | 30 | The Xcode variables display should now show Sprite Kit objects' summary strings (description) next to the variable. This provides some useful context without having to go through QuickLook, for example a SKTexture object will display the texture name and size next to it, which is probably the most important info you need from an SKTexture object. 31 | 32 | Example output: 33 | ------------ 34 | 35 | Instead of just getting with no info on property values, you can quicklook or log Sprite Kit classes with all their properties. You can issue a quicklook anytime anywhere from within the debugger, so you don't need to NSLog anything in order to get this info. It's right there where and when you need it the most! 36 | 37 | Here are some example output strings. Note how even private ivars and their values are being dumped, and the variables are sorted by name. 38 | 39 | SKLabelNode Example 40 | ----------- 41 | ``` 42 | name:'(null)' text:'' fontName:'Helvetica' position:{0, 0} 43 | SKNode: 44 | alpha = 1 45 | children = () 46 | controller = 47 | csprite = 48 | frame = NSRect: {{0, 0}, {0, 0}} 49 | hidden = 0 50 | info = 51 | kkScene = 52 | model = 53 | name = 54 | node = 55 | parent = 56 | paused = 0 57 | physicsBody = 58 | position = NSPoint: {0, 0} 59 | scene = 60 | speed = 1 61 | userData = 62 | userInteractionEnabled = 0 63 | xRotation = 0 64 | xScale = 1 65 | yOffsetInPoints = 0 66 | yRotation = 0 67 | yScale = 1 68 | zPosition = 0 69 | zRotation = 0 70 | _actions = () 71 | _actionsToRemove = () 72 | _anchorPoint = NSPoint: {0, 0} 73 | _deleteList = () 74 | _info = 75 | _keyedActions = {} 76 | _keyedSubSprites = {} 77 | _needsDelete = 0 78 | _showBounds = 0 79 | _size = NSSize: {0, 0} 80 | _spritesNeedsRemove = 0 81 | _spritesToRemove = () 82 | _untransformedBounds = NSRect: {{inf, inf}, {inf, inf}} 83 | SKLabelNode: 84 | blendMode = Alpha 85 | color = 86 | colorBlendFactor = 0 87 | fontColor = UIDeviceRGBColorSpace 1 1 1 1 88 | fontName = 'Helvetica' 89 | fontSize = 32 90 | horizontalAlignmentMode = Center 91 | text = '' 92 | verticalAlignmentMode = Baseline 93 | _bmf = 94 | _labelBlendMode = 0 95 | _labelColor = 96 | _labelColorBlend = 0 97 | _textRect = NSRect: {{0, 0}, {0, 0}} 98 | _textSprite = 99 | _textSprites = () 100 | ``` 101 | 102 | SKView Example 103 | ----------- 104 | ``` 105 | > 106 | SKView: 107 | asynchronous = 1 108 | frameInterval = 1 109 | ignoresSiblingOrder = 0 110 | paused = 0 111 | pixelSize = NSSize: {0, 0} 112 | scene = 113 | showsDrawCount = 0 114 | showsFPS = 0 115 | showsNodeCount = 0 116 | showsPhysics = 0 117 | _colorRenderBuffer = 0 118 | _context = 119 | _depthStencilRenderBuffer = 0 120 | _disableInput = 0 121 | _displayLink = 122 | _fps = 0 123 | _frameBuffer = 0 124 | _frames = 0 125 | _hasRenderedForCurrentUpdate = 0 126 | _hasRenderedOnce = 0 127 | _isInTransition = 0 128 | _nextScene = 129 | _prevBackingScaleFactor = 0 130 | _prevSpritesRendered = 0 131 | _prevSpritesRenderedSubmitted = 0 132 | _prevViewAspect = 0 133 | _renderer = 134 | _renderQueue = 135 | _scene = 136 | _shouldCenterStats = 0 137 | _showsCoreAnimationFPS = 0 138 | _showsCPUStats = 0 139 | _showsCulledNodesInNodeCount = 0 140 | _showsGPUStats = 0 141 | _showsSpriteBounds = 0 142 | _showsTotalAreaRendered = 0 143 | _spriteArrayHint = 144 | _spriteRenderCount = -1431655766 145 | _spritesRendered = 0 146 | _spritesSubmitted = 0 147 | _spriteSubmitCount = -1431655766 148 | _statsLabel = 149 | _timeBeginFrameCount = -1 150 | _timePreviousUpdate = -1 151 | _touchMap = {} 152 | _transitionDuration = 0 153 | _transitionTime = 0 154 | _updateQueue = 155 | _usesAsyncUpdateQueue = 0 156 | ``` 157 | -------------------------------------------------------------------------------- /SpriteKit+QuickLook.h: -------------------------------------------------------------------------------- 1 | // 2 | // SpriteKit+QuickLook.h 3 | // SB+SpriteKit 4 | // 5 | // Created by Steffen Itterheim on 13/03/14. 6 | // Distributed under MIT License 7 | // 8 | 9 | #import 10 | 11 | @interface QuickLookHelper : NSObject 12 | @end 13 | 14 | @interface SKNode (QuickLook) 15 | -(id) debugQuickLookObject; 16 | @end 17 | 18 | @interface SKTexture (QuickLook) 19 | -(id) debugQuickLookObject; 20 | @end 21 | @interface SKTextureAtlas (QuickLook) 22 | -(id) debugQuickLookObject; 23 | @end 24 | @interface SKAction (QuickLook) 25 | -(id) debugQuickLookObject; 26 | @end 27 | @interface SKView (QuickLook) 28 | -(id) debugQuickLookObject; 29 | @end 30 | @interface SKPhysicsBody (QuickLook) 31 | -(id) debugQuickLookObject; 32 | @end 33 | @interface SKPhysicsWorld (QuickLook) 34 | -(id) debugQuickLookObject; 35 | @end 36 | @interface SKPhysicsJoint (QuickLook) 37 | -(id) debugQuickLookObject; 38 | @end 39 | @interface SKTransition (QuickLook) 40 | -(id) debugQuickLookObject; 41 | @end 42 | @interface SKKeyframeSequence (QuickLook) 43 | -(id) debugQuickLookObject; 44 | @end 45 | -------------------------------------------------------------------------------- /SpriteKit+QuickLook.m: -------------------------------------------------------------------------------- 1 | // 2 | // SpriteKit+QuickLook.m 3 | // SB+SpriteKit 4 | // 5 | // Created by Steffen Itterheim on 13/03/14. 6 | // Distributed under MIT License 7 | // 8 | 9 | #import "SpriteKit+QuickLook.h" 10 | #import 11 | 12 | static void dumpIvarNamesForClass(Class klass) 13 | { 14 | NSMutableString* names = [NSMutableString stringWithFormat:@"%@ ivars:\n", NSStringFromClass(klass)]; 15 | 16 | unsigned int count = 0; 17 | Ivar* ivars = class_copyIvarList(klass, &count); 18 | for (unsigned int i = 0; i < count; i++) 19 | { 20 | [names appendFormat:@"%s (%s)\n", ivar_getName(ivars[i]), ivar_getTypeEncoding(ivars[i])]; 21 | } 22 | 23 | NSLog(@"%@\n", names); 24 | } 25 | 26 | @interface QuickLookHelper () 27 | +(NSString*) debugDescriptionStringWithDelimiter:(NSString*)delimiter spriteKitObject:(id)object; 28 | +(NSString*) commonDebugDescriptionForSpriteKitObject:(id)object; 29 | @end 30 | 31 | 32 | @implementation SKNode (QuickLook) 33 | -(id) valueForUndefinedKey:(NSString*)key { NSLog(@"%@: KVC ignored undefined key '%@', returning nil", NSStringFromClass([self class]), key); return nil; } 34 | -(NSString*) debugDescription { return [QuickLookHelper commonDebugDescriptionForSpriteKitObject:self]; } 35 | -(id) debugQuickLookObject { return [QuickLookHelper debugDescriptionStringWithDelimiter:@"\n" spriteKitObject:self]; } 36 | @end 37 | @implementation SKTexture (QuickLook) 38 | -(id) valueForUndefinedKey:(NSString*)key { NSLog(@"%@: KVC ignored undefined key '%@', returning nil", NSStringFromClass([self class]), key); return nil; } 39 | -(NSString*) debugDescription { return [QuickLookHelper commonDebugDescriptionForSpriteKitObject:self]; } 40 | -(id) debugQuickLookObject { return [QuickLookHelper debugDescriptionStringWithDelimiter:@"\n" spriteKitObject:self]; } 41 | @end 42 | @implementation SKTextureAtlas (QuickLook) 43 | -(id) valueForUndefinedKey:(NSString*)key { NSLog(@"%@: KVC ignored undefined key '%@', returning nil", NSStringFromClass([self class]), key); return nil; } 44 | -(NSString*) debugDescription { return [QuickLookHelper commonDebugDescriptionForSpriteKitObject:self]; } 45 | -(id) debugQuickLookObject { return [QuickLookHelper debugDescriptionStringWithDelimiter:@"\n" spriteKitObject:self]; } 46 | @end 47 | @implementation SKAction (QuickLook) 48 | -(id) valueForUndefinedKey:(NSString*)key { NSLog(@"%@: KVC ignored undefined key '%@', returning nil", NSStringFromClass([self class]), key); return nil; } 49 | -(NSString*) debugDescription { return [QuickLookHelper commonDebugDescriptionForSpriteKitObject:self]; } 50 | -(id) debugQuickLookObject { return [QuickLookHelper debugDescriptionStringWithDelimiter:@"\n" spriteKitObject:self]; } 51 | @end 52 | @implementation SKView (QuickLook) 53 | -(id) valueForUndefinedKey:(NSString*)key { NSLog(@"%@: KVC ignored undefined key '%@', returning nil", NSStringFromClass([self class]), key); return nil; } 54 | -(NSString*) debugDescription { return [QuickLookHelper commonDebugDescriptionForSpriteKitObject:self]; } 55 | -(id) debugQuickLookObject { return [QuickLookHelper debugDescriptionStringWithDelimiter:@"\n" spriteKitObject:self]; } 56 | @end 57 | @implementation SKPhysicsBody (QuickLook) 58 | -(id) valueForUndefinedKey:(NSString*)key { NSLog(@"%@: KVC ignored undefined key '%@', returning nil", NSStringFromClass([self class]), key); return nil; } 59 | -(NSString*) debugDescription { return [QuickLookHelper commonDebugDescriptionForSpriteKitObject:self]; } 60 | -(id) debugQuickLookObject { return [QuickLookHelper debugDescriptionStringWithDelimiter:@"\n" spriteKitObject:self]; } 61 | @end 62 | @implementation SKPhysicsWorld (QuickLook) 63 | -(id) valueForUndefinedKey:(NSString*)key { NSLog(@"%@: KVC ignored undefined key '%@', returning nil", NSStringFromClass([self class]), key); return nil; } 64 | -(NSString*) debugDescription { return [QuickLookHelper commonDebugDescriptionForSpriteKitObject:self]; } 65 | -(id) debugQuickLookObject { return [QuickLookHelper debugDescriptionStringWithDelimiter:@"\n" spriteKitObject:self]; } 66 | @end 67 | @implementation SKPhysicsJoint (QuickLook) 68 | -(id) valueForUndefinedKey:(NSString*)key { NSLog(@"%@: KVC ignored undefined key '%@', returning nil", NSStringFromClass([self class]), key); return nil; } 69 | -(NSString*) debugDescription { return [QuickLookHelper commonDebugDescriptionForSpriteKitObject:self]; } 70 | -(id) debugQuickLookObject { return [QuickLookHelper debugDescriptionStringWithDelimiter:@"\n" spriteKitObject:self]; } 71 | @end 72 | @implementation SKTransition (QuickLook) 73 | -(id) valueForUndefinedKey:(NSString*)key { NSLog(@"%@: KVC ignored undefined key '%@', returning nil", NSStringFromClass([self class]), key); return nil; } 74 | -(NSString*) debugDescription { return [QuickLookHelper commonDebugDescriptionForSpriteKitObject:self]; } 75 | -(id) debugQuickLookObject { return [QuickLookHelper debugDescriptionStringWithDelimiter:@"\n" spriteKitObject:self]; } 76 | @end 77 | @implementation SKKeyframeSequence (QuickLook) 78 | -(id) valueForUndefinedKey:(NSString*)key { NSLog(@"%@: KVC ignored undefined key '%@', returning nil", NSStringFromClass([self class]), key); return nil; } 79 | -(NSString*) debugDescription { return [QuickLookHelper commonDebugDescriptionForSpriteKitObject:self]; } 80 | -(id) debugQuickLookObject { return [QuickLookHelper debugDescriptionStringWithDelimiter:@"\n" spriteKitObject:self]; } 81 | @end 82 | 83 | 84 | @implementation QuickLookHelper 85 | 86 | -(id) init 87 | { 88 | self = [super init]; 89 | if (self) 90 | { 91 | // avoid compiler warning 92 | dumpIvarNamesForClass([SKNode class]); 93 | 94 | /* 95 | dumpIvarNamesForClass([SKAction class]); 96 | dumpIvarNamesForClass([SKKeyframeSequence class]); 97 | dumpIvarNamesForClass([SKTexture class]); 98 | dumpIvarNamesForClass([SKTextureAtlas class]); 99 | dumpIvarNamesForClass([SKTransition class]); 100 | dumpIvarNamesForClass([SKView class]); 101 | 102 | dumpIvarNamesForClass([SKPhysicsBody class]); 103 | dumpIvarNamesForClass([SKPhysicsContact class]); 104 | dumpIvarNamesForClass([SKPhysicsJoint class]); 105 | dumpIvarNamesForClass([SKPhysicsJointFixed class]); 106 | dumpIvarNamesForClass([SKPhysicsJointLimit class]); 107 | dumpIvarNamesForClass([SKPhysicsJointPin class]); 108 | dumpIvarNamesForClass([SKPhysicsJointSliding class]); 109 | dumpIvarNamesForClass([SKPhysicsJointSpring class]); 110 | dumpIvarNamesForClass([SKPhysicsWorld class]); 111 | 112 | dumpIvarNamesForClass([SKNode class]); 113 | dumpIvarNamesForClass([SKScene class]); 114 | dumpIvarNamesForClass([SKCropNode class]); 115 | dumpIvarNamesForClass([SKEffectNode class]); 116 | dumpIvarNamesForClass([SKEmitterNode class]); 117 | dumpIvarNamesForClass([SKLabelNode class]); 118 | dumpIvarNamesForClass([SKShapeNode class]); 119 | dumpIvarNamesForClass([SKSpriteNode class]); 120 | dumpIvarNamesForClass([SKVideoNode class]); 121 | 122 | // private classes 123 | dumpIvarNamesForClass(NSClassFromString(@"SKTextureCache")); 124 | dumpIvarNamesForClass(NSClassFromString(@"SKBitmapFont")); 125 | */ 126 | 127 | //dumpIvarNamesForClass([SKLabelNode class]); 128 | 129 | /* 130 | SKNode* node = [SKLabelNode node]; 131 | NSLog(@"%@", [node debugQuickLookObject]); 132 | SKView* view = [[SKView alloc] init]; 133 | NSLog(@"%@", [view debugQuickLookObject]); 134 | */ 135 | } 136 | return self; 137 | } 138 | 139 | +(BOOL) isSpriteKitClass:(Class)klass 140 | { 141 | return ([klass isSubclassOfClass:[SKNode class]] || 142 | [klass isSubclassOfClass:[SKTexture class]] || 143 | [klass isSubclassOfClass:[SKTextureAtlas class]] || 144 | [klass isSubclassOfClass:[SKAction class]] || 145 | [klass isSubclassOfClass:[SKView class]] || 146 | [klass isSubclassOfClass:[SKPhysicsBody class]] || 147 | [klass isSubclassOfClass:[SKPhysicsWorld class]] || 148 | [klass isSubclassOfClass:[SKPhysicsJoint class]] || 149 | [klass isSubclassOfClass:[SKTransition class]] || 150 | [klass isSubclassOfClass:[SKKeyframeSequence class]]); 151 | } 152 | 153 | +(NSDictionary*) debugClassVarDictionaryForSpriteKitObject:(id)object 154 | { 155 | NSMutableDictionary* classesDict = [NSMutableDictionary dictionary]; 156 | NSMutableArray* classOrder = [NSMutableArray array]; 157 | [classesDict setObject:classOrder forKey:@"classes"]; 158 | 159 | Class klass = [object class]; 160 | while ([QuickLookHelper isSpriteKitClass:klass]) 161 | { 162 | NSMutableDictionary* ivarsDict = [NSMutableDictionary dictionary]; 163 | 164 | unsigned int count = 0; 165 | objc_property_t* properties = class_copyPropertyList(klass, &count); 166 | for (unsigned int i = 0; i < count; i++) 167 | { 168 | objc_property_t prop = properties[i]; 169 | NSString* propName = [NSString stringWithCString:property_getName(prop) encoding:NSUTF8StringEncoding]; 170 | id value = [object valueForKey:propName]; 171 | [ivarsDict setObject:value ? value : [NSNull null] forKey:propName]; 172 | } 173 | 174 | count = 0; 175 | Ivar* ivars = class_copyIvarList(klass, &count); 176 | for (unsigned int i = 0; i < count; i++) 177 | { 178 | Ivar var = ivars[i]; 179 | NSString* ivarName = [NSString stringWithCString:ivar_getName(var) encoding:NSUTF8StringEncoding]; 180 | 181 | // only add ivars if there's no corresponding property of the same name 182 | NSString* ivarNameWithoutUnderscore = [ivarName substringFromIndex:1]; 183 | if ([ivarsDict objectForKey:ivarNameWithoutUnderscore] == NO) 184 | { 185 | id value = [object valueForKey:ivarName]; 186 | [ivarsDict setObject:value ? value : [NSNull null] forKey:ivarName]; 187 | } 188 | } 189 | 190 | NSString* className = NSStringFromClass(klass); 191 | [classesDict setObject:ivarsDict forKey:className]; 192 | [classOrder insertObject:className atIndex:0]; 193 | 194 | klass = [klass superclass]; 195 | } 196 | 197 | return classesDict; 198 | } 199 | 200 | +(NSString*) formattedStringForValue:(id)value varName:(NSString*)varName 201 | { 202 | // TODO: physics body bitMasks 203 | 204 | NSString* valueString = nil; 205 | if ([varName isEqualToString:@"blendMode"] || [varName isEqualToString:@"particleBlendMode"]) 206 | { 207 | switch ([value integerValue]) { 208 | case SKBlendModeAdd: 209 | valueString = @"Add"; 210 | break; 211 | case SKBlendModeAlpha: 212 | valueString = @"Alpha"; 213 | break; 214 | case SKBlendModeMultiply: 215 | valueString = @"Multiply"; 216 | break; 217 | case SKBlendModeMultiplyX2: 218 | valueString = @"MultiplyX2"; 219 | break; 220 | case SKBlendModeReplace: 221 | valueString = @"Replace"; 222 | break; 223 | case SKBlendModeScreen: 224 | valueString = @"Screen"; 225 | break; 226 | case SKBlendModeSubtract: 227 | valueString = @"Subtract"; 228 | break; 229 | default: 230 | valueString = [NSString stringWithFormat:@"%ld (unknown)", (unsigned long)[value integerValue]]; 231 | break; 232 | } 233 | } 234 | else if ([varName isEqualToString:@"scaleMode"]) 235 | { 236 | switch ([value integerValue]) { 237 | case SKSceneScaleModeAspectFill: 238 | valueString = @"AspectFill"; 239 | break; 240 | case SKSceneScaleModeAspectFit: 241 | valueString = @"AspectFit"; 242 | break; 243 | case SKSceneScaleModeFill: 244 | valueString = @"Fill"; 245 | break; 246 | case SKSceneScaleModeResizeFill: 247 | valueString = @"ResizeFill"; 248 | break; 249 | default: 250 | valueString = [NSString stringWithFormat:@"%ld (unknown)", (unsigned long)[value integerValue]]; 251 | break; 252 | } 253 | } 254 | else if ([varName isEqualToString:@"verticalAlignmentMode"]) 255 | { 256 | switch ([value integerValue]) { 257 | case SKLabelVerticalAlignmentModeBaseline: 258 | valueString = @"Baseline"; 259 | break; 260 | case SKLabelVerticalAlignmentModeBottom: 261 | valueString = @"Bottom"; 262 | break; 263 | case SKLabelVerticalAlignmentModeCenter: 264 | valueString = @"Center"; 265 | break; 266 | case SKLabelVerticalAlignmentModeTop: 267 | valueString = @"Top"; 268 | break; 269 | default: 270 | valueString = [NSString stringWithFormat:@"%ld (unknown)", (unsigned long)[value integerValue]]; 271 | break; 272 | } 273 | } 274 | else if ([varName isEqualToString:@"horizontalAlignmentMode"]) 275 | { 276 | switch ([value integerValue]) { 277 | case SKLabelHorizontalAlignmentModeCenter: 278 | valueString = @"Center"; 279 | break; 280 | case SKLabelHorizontalAlignmentModeLeft: 281 | valueString = @"Left"; 282 | break; 283 | case SKLabelHorizontalAlignmentModeRight: 284 | valueString = @"Right"; 285 | break; 286 | default: 287 | valueString = [NSString stringWithFormat:@"%ld (unknown)", (unsigned long)[value integerValue]]; 288 | break; 289 | } 290 | } 291 | else if ([varName isEqualToString:@"filteringMode"]) 292 | { 293 | switch ([value integerValue]) { 294 | case SKTextureFilteringLinear: 295 | valueString = @"Linear"; 296 | break; 297 | case SKTextureFilteringNearest: 298 | valueString = @"Nearest"; 299 | break; 300 | default: 301 | valueString = [NSString stringWithFormat:@"%ld (unknown)", (unsigned long)[value integerValue]]; 302 | break; 303 | } 304 | } 305 | else if ([value isKindOfClass:[NSString class]]) 306 | { 307 | valueString = [NSString stringWithFormat:@"'%@'", value]; 308 | } 309 | else 310 | { 311 | valueString = [[value description] stringByReplacingOccurrencesOfString:@"\n" withString:@""]; 312 | } 313 | 314 | return valueString; 315 | } 316 | 317 | +(NSString*) debugDescriptionStringWithDelimiter:(NSString*)delimiter spriteKitObject:(id)object 318 | { 319 | NSMutableString* desc = [NSMutableString string]; 320 | [desc appendFormat:@"%@%@", [object debugDescription], delimiter]; 321 | 322 | NSDictionary* classVars = [QuickLookHelper debugClassVarDictionaryForSpriteKitObject:object]; 323 | 324 | for (NSString* className in classVars[@"classes"]) 325 | { 326 | [desc appendFormat:@"%@:%@", className, delimiter]; 327 | 328 | NSDictionary* ivars = classVars[className]; 329 | NSArray* keys = [ivars allKeys]; 330 | keys = [keys sortedArrayUsingComparator:^(id obj1, id obj2) 331 | { 332 | if ([(NSString*)obj1 hasPrefix:@"_"] && [(NSString*)obj2 hasPrefix:@"_"] == NO) 333 | { 334 | return NSOrderedDescending; 335 | } 336 | else if ([(NSString*)obj1 hasPrefix:@"_"] == NO && [(NSString*)obj2 hasPrefix:@"_"]) 337 | { 338 | return NSOrderedAscending; 339 | } 340 | return [obj1 localizedCaseInsensitiveCompare:obj2]; 341 | }]; 342 | 343 | for (NSString* ivarName in keys) 344 | { 345 | id value = [ivars objectForKey:ivarName]; 346 | [desc appendFormat:@" %@ = %@%@", ivarName, [QuickLookHelper formattedStringForValue:value varName:ivarName], delimiter]; 347 | } 348 | } 349 | 350 | //NSLog(@"desc: \n%@ \n", desc); 351 | 352 | return desc; 353 | } 354 | 355 | +(NSString*) commonDebugDescriptionForSpriteKitObject:(id)object 356 | { 357 | return [NSString stringWithFormat:@"<%p> %@", object, [object description]]; 358 | } 359 | 360 | @end 361 | -------------------------------------------------------------------------------- /SpriteKitDebug.py: -------------------------------------------------------------------------------- 1 | import lldb 2 | # import lldb.runtime.objc.objc_runtime 3 | import lldb.formatters.Logger 4 | 5 | 6 | # Synthetic children provider example for class MaskedData 7 | # to use me: 8 | # command script import ./example.py --allow-reload 9 | # type synthetic add MaskedData --python-class example.MaskedData_SyntheticChildrenProvider 10 | ''' 11 | class SKLabelNode_SyntheticChildrenProvider: 12 | def __init__(self, valobj, dict): 13 | self.valobj = valobj # remember the SBValue since you will not have another chance to get it :-) 14 | lldb.formatters.Logger._lldb_formatters_debug_level = 2 15 | logger = lldb.formatters.Logger.Logger() 16 | # logger >> "valobj is .... " >> self.valobj.GetObjectDescription() 17 | 18 | def num_children(self): 19 | # you could perform calculations involving the SBValue and/or its children to determine this value 20 | # here, we have an hardcoded value - but since you have stored the SBValue you could use it to 21 | # help figure out the correct thing to return here. if you return a number N, you should be prepared to 22 | # answer questions about N children 23 | return 10 24 | 25 | def has_children(self): 26 | # we simply say True here because we know we have 4 children 27 | # in general, you want to make this calculation as simple as possible 28 | # and return True if in doubt (you can always return num_children == 0 later) 29 | return True 30 | 31 | def get_child_index(self,name): 32 | # given a name, return its index 33 | # you can return None if you don't know the answer for a given name 34 | if name == "description": 35 | return 0 36 | if name == "debugDescription": 37 | return 1 38 | 39 | # here, we are using a reserved C++ keyword as a child name - we could not do that in the source code 40 | # but we are free to use the names we like best in the synthetic children provider class 41 | # we are also not respecting the order of declaration in the C++ class itself - as long as 42 | # we are consistent, we can do that freely 43 | if name == "operator": 44 | return 1 45 | if name == "mask": 46 | return 2 47 | # this member does not exist in the original class - we will compute its value and show it to the user 48 | # when returning synthetic children, there is no need to only stick to what already exists in memory 49 | if name == "apply()": 50 | return 3 51 | return None # no clue, just say none 52 | 53 | def get_child_at_index(self,index): 54 | # precautionary measures 55 | if index < 0: 56 | return None 57 | if index > self.num_children(): 58 | return None 59 | if self.valobj.IsValid() == False: 60 | return None 61 | 62 | if index == 0: 63 | return self.valobj.CreateValueFromExpression("description", self.valobj.GetSummary()) 64 | if index == 1: 65 | return self.valobj.CreateValueFromExpression("_text", "the text") 66 | 67 | 68 | if index == 1: 69 | # fetch the value of the operator 70 | op_chosen = self.valobj.GetChildMemberWithName("oper").GetValueAsUnsigned() 71 | # if it is a known value, return a descriptive string for it 72 | # we are not doing this in the most efficient possible way, but the code is very readable 73 | # and easy to maintain - if you change the values on the C++ side, the same changes must be made here 74 | if op_chosen == 0: 75 | return self.valobj.CreateValueFromExpression("operator",'(const char*)"none"') 76 | elif op_chosen == 1: 77 | return self.valobj.CreateValueFromExpression("operator",'(const char*)"AND"') 78 | elif op_chosen == 2: 79 | return self.valobj.CreateValueFromExpression("operator",'(const char*)"OR"') 80 | elif op_chosen == 3: 81 | return self.valobj.CreateValueFromExpression("operator",'(const char*)"XOR"') 82 | elif op_chosen == 4: 83 | return self.valobj.CreateValueFromExpression("operator",'(const char*)"NAND"') 84 | elif op_chosen == 5: 85 | return self.valobj.CreateValueFromExpression("operator",'(const char*)"NOR"') 86 | else: 87 | return self.valobj.CreateValueFromExpression("operator",'(const char*)"unknown"') # something else 88 | if index == 2: 89 | return self.valobj.GetChildMemberWithName("mask") 90 | if index == 3: 91 | # for this, we must fetch all the other elements 92 | # in an efficient implementation, we would be caching this data for efficiency 93 | value = self.valobj.GetChildMemberWithName("value").GetValueAsUnsigned() 94 | operator = self.valobj.GetChildMemberWithName("oper").GetValueAsUnsigned() 95 | mask = self.valobj.GetChildMemberWithName("mask").GetValueAsUnsigned() 96 | # compute the masked value according to the operator 97 | if operator == 1: 98 | value = value & mask 99 | elif operator == 2: 100 | value = value | mask 101 | elif operator == 3: 102 | value = value ^ mask 103 | elif operator == 4: 104 | value = ~(value & mask) 105 | elif operator == 5: 106 | value = ~(value | mask) 107 | else: 108 | pass 109 | value &= 0xFFFFFFFF # make sure Python does not extend our values to 64-bits 110 | # return it - again, not the most efficient possible way. we should actually be pushing the computed value 111 | # into an SBData, and using the SBData to create an SBValue - this has the advantage of readability 112 | return self.valobj.CreateValueFromExpression("apply()",'(uint32_t)(' + str(value) + ')') 113 | 114 | def update(self): 115 | # we do not do anything special in update - but this would be the right place to lookup 116 | # the data we use in get_child_at_index and cache it 117 | pass 118 | ''' 119 | 120 | 121 | def SK_Summary(valueObject, dictionary): 122 | desc = valueObject.GetObjectDescription() 123 | return desc 124 | 125 | def __lldb_init_module(debugger, dictionary): 126 | # debugger.HandleCommand('type synthetic add SKLabelNode --python-class SpriteKitDebug.SKLabelNode_SyntheticChildrenProvider'); 127 | 128 | # use the ObjC description string as summary 129 | debugger.HandleCommand('type summary add SKView -F SpriteKitDebug.SK_Summary'); 130 | debugger.HandleCommand('type summary add SKScene -F SpriteKitDebug.SK_Summary'); 131 | debugger.HandleCommand('type summary add SKNode -F SpriteKitDebug.SK_Summary'); 132 | debugger.HandleCommand('type summary add SKCropNode -F SpriteKitDebug.SK_Summary'); 133 | debugger.HandleCommand('type summary add SKEffectNode -F SpriteKitDebug.SK_Summary'); 134 | debugger.HandleCommand('type summary add SKEmitterNode -F SpriteKitDebug.SK_Summary'); 135 | debugger.HandleCommand('type summary add SKLabelNode -F SpriteKitDebug.SK_Summary'); 136 | debugger.HandleCommand('type summary add SKSpriteNode -F SpriteKitDebug.SK_Summary'); 137 | debugger.HandleCommand('type summary add SKShapeNode -F SpriteKitDebug.SK_Summary'); 138 | debugger.HandleCommand('type summary add SKVideoNode -F SpriteKitDebug.SK_Summary'); 139 | 140 | debugger.HandleCommand('type summary add SKPhysicsBody -F SpriteKitDebug.SK_Summary'); 141 | debugger.HandleCommand('type summary add SKPhysicsContact -F SpriteKitDebug.SK_Summary'); 142 | debugger.HandleCommand('type summary add SKPhysicsJoint -F SpriteKitDebug.SK_Summary'); 143 | debugger.HandleCommand('type summary add SKPhysicsJointFixed -F SpriteKitDebug.SK_Summary'); 144 | debugger.HandleCommand('type summary add SKPhysicsJointLimit -F SpriteKitDebug.SK_Summary'); 145 | debugger.HandleCommand('type summary add SKPhysicsJointPin -F SpriteKitDebug.SK_Summary'); 146 | debugger.HandleCommand('type summary add SKPhysicsJointSliding -F SpriteKitDebug.SK_Summary'); 147 | debugger.HandleCommand('type summary add SKPhysicsJointSpring -F SpriteKitDebug.SK_Summary'); 148 | debugger.HandleCommand('type summary add SKPhysicsWorld -F SpriteKitDebug.SK_Summary'); 149 | 150 | debugger.HandleCommand('type summary add SKTransition -F SpriteKitDebug.SK_Summary'); 151 | debugger.HandleCommand('type summary add SKTexture -F SpriteKitDebug.SK_Summary'); 152 | debugger.HandleCommand('type summary add SKTextureAtlas -F SpriteKitDebug.SK_Summary'); 153 | debugger.HandleCommand('type summary add SKKeyframeSequence -F SpriteKitDebug.SK_Summary'); 154 | 155 | debugger.HandleCommand('type summary add SKAction -F SpriteKitDebug.SK_Summary'); 156 | debugger.HandleCommand('type summary add SKAnimate -F SpriteKitDebug.SK_Summary'); 157 | debugger.HandleCommand('type summary add SKColorize -F SpriteKitDebug.SK_Summary'); 158 | debugger.HandleCommand('type summary add SKCustomAction -F SpriteKitDebug.SK_Summary'); 159 | debugger.HandleCommand('type summary add SKFade -F SpriteKitDebug.SK_Summary'); 160 | debugger.HandleCommand('type summary add SKFollowPath -F SpriteKitDebug.SK_Summary'); 161 | debugger.HandleCommand('type summary add SKGroup -F SpriteKitDebug.SK_Summary'); 162 | debugger.HandleCommand('type summary add SKMove -F SpriteKitDebug.SK_Summary'); 163 | debugger.HandleCommand('type summary add SKPerformSelector -F SpriteKitDebug.SK_Summary'); 164 | debugger.HandleCommand('type summary add SKPlaySound -F SpriteKitDebug.SK_Summary'); 165 | debugger.HandleCommand('type summary add SKRemove -F SpriteKitDebug.SK_Summary'); 166 | debugger.HandleCommand('type summary add SKRepeat -F SpriteKitDebug.SK_Summary'); 167 | debugger.HandleCommand('type summary add SKResize -F SpriteKitDebug.SK_Summary'); 168 | debugger.HandleCommand('type summary add SKRotate -F SpriteKitDebug.SK_Summary'); 169 | debugger.HandleCommand('type summary add SKRunAction -F SpriteKitDebug.SK_Summary'); 170 | debugger.HandleCommand('type summary add SKRunBlock -F SpriteKitDebug.SK_Summary'); 171 | debugger.HandleCommand('type summary add SKScale -F SpriteKitDebug.SK_Summary'); 172 | debugger.HandleCommand('type summary add SKSequence -F SpriteKitDebug.SK_Summary'); 173 | debugger.HandleCommand('type summary add SKSpeed -F SpriteKitDebug.SK_Summary'); 174 | debugger.HandleCommand('type summary add SKWait -F SpriteKitDebug.SK_Summary'); 175 | 176 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "This does nothing yet ..." 4 | -------------------------------------------------------------------------------- /lldbinit: -------------------------------------------------------------------------------- 1 | command script import ~/SpriteKitDebug.py 2 | --------------------------------------------------------------------------------