Oolite 1.91.0.7604-240417-a536cbe
Loading...
Searching...
No Matches
OODebugMonitor.m
Go to the documentation of this file.
1/*
2
3OODebugMonitor.m
4
5
6Oolite debug support
7
8Copyright (C) 2007-2013 Jens Ayton
9
10Permission is hereby granted, free of charge, to any person obtaining a copy
11of this software and associated documentation files (the "Software"), to deal
12in the Software without restriction, including without limitation the rights
13to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14copies of the Software, and to permit persons to whom the Software is
15furnished to do so, subject to the following conditions:
16
17The above copyright notice and this permission notice shall be included in all
18copies or substantial portions of the Software.
19
20THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26SOFTWARE.
27
28*/
29
30#ifndef OO_EXCLUDE_DEBUG_SUPPORT
31
32
33#import "OODebugMonitor.h"
35#import "OOLoggingExtended.h"
36#import "ResourceManager.h"
38
39#import "OOJSConsole.h"
40#import "OOJSScript.h"
43
45#import "OOTexture.h"
46#import "OOConcreteTexture.h"
47#import "OODrawable.h"
48
49
51
52
53@interface OODebugMonitor (Private) <OOJavaScriptEngineMonitor>
54
56- (void) javaScriptEngineWillReset:(NSNotification *)notification;
57
58- (void)disconnectDebuggerWithMessage:(NSString *)message;
59
60- (NSDictionary *)mergedConfiguration;
61
62/* Convert a configuration dictionary to a standard form. In particular,
63 convert all colour specifiers to RGBA arrays with values in [0, 1], and
64 converts "show-console" values to booleans.
65*/
66- (NSMutableDictionary *)normalizeConfigDictionary:(NSDictionary *)dictionary;
67- (id)normalizeConfigValue:(id)value forKey:(NSString *)key;
68
69- (NSArray *)loadSourceFile:(NSString *)filePath;
70
71@end
72
73
74@implementation OODebugMonitor
75#if OOLITE_GNUSTEP
76 NSString *NSApplicationWillTerminateNotification = @"ApplicationWillTerminate";
77#endif
78
79- (id)init
80{
81 NSUserDefaults *defaults = nil;
82 NSMutableDictionary *config = nil;
83
84 self = [super init];
85 if (self != nil)
86 {
87 config = [[[ResourceManager dictionaryFromFilesNamed:@"debugConfig.plist"
88 inFolder:@"Config"
89 andMerge:YES] mutableCopy] autorelease];
90 _configFromOXPs = [[self normalizeConfigDictionary:config] copy];
91
92 defaults = [NSUserDefaults standardUserDefaults];
93 config = [self normalizeConfigDictionary:[defaults dictionaryForKey:@"debug-settings-override"]];
94 if (config == nil) config = [NSMutableDictionary dictionary];
95 _configOverrides = [config retain];
96
97 _TCPIgnoresDroppedPackets = NO;
98
100#if OOJSENGINE_MONITOR_SUPPORT
101 [jsEng setMonitor:self];
102#endif
103
105
106 [[NSNotificationCenter defaultCenter] addObserver:self
107 selector:@selector(applicationWillTerminate:)
108 name:NSApplicationWillTerminateNotification
109 object:nil];
110
111 [[NSNotificationCenter defaultCenter] addObserver:self
112 selector:@selector(javaScriptEngineWillReset:)
113 name:kOOJavaScriptEngineWillResetNotification
114 object:jsEng];
115
116 [[NSNotificationCenter defaultCenter] addObserver:self
117 selector:@selector(setUpDebugConsoleScript)
118 name:kOOJavaScriptEngineDidResetNotification
119 object:jsEng];
120 }
121
122 return self;
123}
124
125
126- (void)dealloc
127{
128 [self disconnectDebuggerWithMessage:@"Debug controller object destroyed while debugging in progress."];
129
130 [_configFromOXPs release];
131 [_configOverrides release];
132
133 [_fgColors release];
134 [_bgColors release];
135 [_sourceFiles release];
136
137 if (_jsSelf != NULL)
138 {
140 }
141
142 [super dealloc];
143}
144
145
146+ (OODebugMonitor *) sharedDebugMonitor
147{
148 // NOTE: assumes single-threaded access. The debug monitor is not, on the whole, thread safe.
149 if (sSingleton == nil)
150 {
151 sSingleton = [[self alloc] init];
152 }
153
154 return sSingleton;
155}
156
157
158- (BOOL)setDebugger:(id<OODebuggerInterface>)newDebugger
159{
160 NSString *error = nil;
161
162 if (newDebugger != _debugger)
163 {
164 // Disconnect existing debugger, if any.
165 if (newDebugger != nil)
166 {
167 [self disconnectDebuggerWithMessage:@"New debugger set."];
168 }
169 else
170 {
171 [self disconnectDebuggerWithMessage:@"Debugger disconnected programatically."];
172 }
173
174 // If a new debugger was specified, try to connect it.
175 if (newDebugger != nil)
176 {
177 @try
178 {
179 if ([newDebugger connectDebugMonitor:self errorMessage:&error])
180 {
181 [newDebugger debugMonitor:self
182 noteConfiguration:[self mergedConfiguration]];
183 _debugger = [newDebugger retain];
184 }
185 else
186 {
187 OOLog(@"debugMonitor.setDebugger.failed", @"Could not connect to debugger %@, because an error occurred: %@", newDebugger, error);
188 }
189 }
190 @catch (NSException *exception)
191 {
192 OOLog(@"debugMonitor.setDebugger.failed", @"Could not connect to debugger %@, because an exception occurred: %@ -- %@", newDebugger, [exception name], [exception reason]);
193 }
194 }
195 }
196
197 return _debugger == newDebugger;
198}
199
200
201- (oneway void)performJSConsoleCommand:(in NSString *)command
202{
203 JSContext *context = OOJSAcquireContext();
204 jsval commandVal = OOJSValueFromNativeObject(context, command);
206 [_script callMethod:OOJSID("consolePerformJSCommand") inContext:context withArguments:&commandVal count:1 result:NULL];
208 OOJSRelinquishContext(context);
209}
210
211
212- (void)appendJSConsoleLine:(id)string
213 colorKey:(NSString *)colorKey
214 emphasisRange:(NSRange)emphasisRange
215{
216 if (string == nil) return;
218 @try
219 {
220 [_debugger debugMonitor:self
221 jsConsoleOutput:string
222 colorKey:colorKey
223 emphasisRange:emphasisRange];
224 }
225 @catch (NSException *exception)
226 {
227 OOLog(@"debugMonitor.debuggerConnection.exception", @"Exception while attempting to send JavaScript console text to debugger: %@ -- %@", [exception name], [exception reason]);
228 }
230}
231
232
233- (void)appendJSConsoleLine:(id)string
234 colorKey:(NSString *)colorKey
235{
236 [self appendJSConsoleLine:string
237 colorKey:colorKey
238 emphasisRange:NSMakeRange(0, 0)];
239}
240
241
242- (void)clearJSConsole
243{
245 @try
246 {
247 [_debugger debugMonitorClearConsole:self];
248 }
249 @catch (NSException *exception)
250 {
251 OOLog(@"debugMonitor.debuggerConnection.exception", @"Exception while attempting to clear JavaScript console: %@ -- %@", [exception name], [exception reason]);
252 }
254}
255
256
257- (void)showJSConsole
258{
260 @try
261 {
262 [_debugger debugMonitorShowConsole:self];
263 }
264 @catch (NSException *exception)
265 {
266 OOLog(@"debugMonitor.debuggerConnection.exception", @"Exception while attempting to show JavaScript console: %@ -- %@", [exception name], [exception reason]);
267 }
269}
270
271
272- (id)configurationValueForKey:(in NSString *)key
273{
274 return [self configurationValueForKey:key class:Nil defaultValue:nil];
275}
276
277
278- (id)configurationValueForKey:(NSString *)key class:(Class)class defaultValue:(id)value
279{
280 id result = nil;
281
282 if (class == Nil) class = [NSObject class];
283
284 result = [_configOverrides objectForKey:key];
285 if (![result isKindOfClass:class] && result != [NSNull null]) result = [_configFromOXPs objectForKey:key];
286 if (![result isKindOfClass:class] && result != [NSNull null]) result = [[value retain] autorelease];
287 if (result == [NSNull null]) result = nil;
288
289 return result;
290}
291
292
293- (long long)configurationIntValueForKey:(NSString *)key defaultValue:(long long)value
294{
295 long long result;
296 id object = nil;
297
298 object = [self configurationValueForKey:key];
299 if ([object respondsToSelector:@selector(longLongValue)]) result = [object longLongValue];
300 else if ([object respondsToSelector:@selector(intValue)]) result = [object intValue];
301 else result = value;
302
303 return result;
304}
305
306
307- (void)setConfigurationValue:(in id)value forKey:(in NSString *)key
308{
309 if (key == nil) return;
310
311 value = [self normalizeConfigValue:value forKey:key];
312
313 if (value == nil)
314 {
315 [_configOverrides removeObjectForKey:key];
316 }
317 else
318 {
319 if (_configOverrides == nil) _configOverrides = [[NSMutableDictionary alloc] init];
320 [_configOverrides setObject:value forKey:key];
321 }
322
323 // Send changed value to debugger
324 if (value == nil)
325 {
326 // Setting a nil value removes an override, and may reveal an underlying OXP-defined value
327 value = [self configurationValueForKey:key];
328 }
329 @try
330 {
331 [_debugger debugMonitor:self
332 noteChangedConfigrationValue:value
333 forKey:key];
334 }
335 @catch (NSException *exception)
336 {
337 OOLog(@"debugMonitor.debuggerConnection.exception", @"Exception while attempting to send configuration update to debugger: %@ -- %@", [exception name], [exception reason]);
338 }
339}
340
341
342- (NSArray *)configurationKeys
343{
344 NSMutableSet *result = nil;
345
346 result = [NSMutableSet setWithCapacity:[_configFromOXPs count] + [_configOverrides count]];
347 [result addObjectsFromArray:[_configFromOXPs allKeys]];
348 [result addObjectsFromArray:[_configOverrides allKeys]];
349
350 return [[result allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
351}
352
353
354- (BOOL) debuggerConnected
355{
356 return _debugger != nil;
357}
358
359
360- (void) writeMemStat:(NSString *)format, ...
361{
362 va_list args;
363 va_start(args, format);
364 NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
365 va_end(args);
366
367 OOLog(@"debug.memStats", @"%@", message);
368 [self appendJSConsoleLine:message colorKey:@"command-result"];
369
370 [message release];
371}
372
373
374static NSString *SizeString(size_t size)
375{
376 enum
377 {
378 kThreshold = 2 // 2 KiB, 2 MiB etc.
379 };
380
381 unsigned magnitude = 0;
382 NSString *suffix = @"";
383
384 if (size < kThreshold << 10)
385 {
386 return [NSString stringWithFormat:@"%zu bytes", size];
387 }
388 if (size < kThreshold << 20)
389 {
390 magnitude = 1;
391 suffix = @"KiB";
392 }
393 else if (size < ((size_t)kThreshold << 30))
394 {
395 magnitude = 2;
396 suffix = @"MiB";
397 }
398 else
399 {
400 magnitude = 3;
401 suffix = @"GiB";
402 }
403
404 float unit = 1 << (magnitude * 10);
405 float sizef = (float)size / unit;
406 sizef = round(sizef * 100.0f) / 100.f;
407
408 return [NSString stringWithFormat:@"%.2f %@", sizef, suffix];
409}
410
411
412typedef struct
413{
414 NSMutableSet *entityTextures;
416 NSMutableSet *seenEntities;
417 unsigned seenCount;
419 size_t totalDrawableSize;
421
422
423- (void) dumpEntity:(id)entity withState:(EntityDumpState *)state parentVisible:(BOOL)parentVisible
424{
425 if ([state->seenEntities containsObject:entity] || entity == nil) return;
426 [state->seenEntities addObject:entity];
427
428 state->seenCount++;
429
430 size_t entitySize = [entity oo_objectSize];
431 size_t drawableSize = 0;
432 if ([entity isKindOfClass:[OOEntityWithDrawable class]])
433 {
434 OODrawable *drawable = [entity drawable];
435 drawableSize = [drawable totalSize];
436 }
437
438 BOOL visible = parentVisible && [entity isVisible];
439
440 NSSet *textures = [entity allTextures];
441 if (textures != nil)
442 {
443 [state->entityTextures unionSet:textures];
444 if (visible) [state->visibleEntityTextures unionSet:textures];
445 }
446
447 NSString *extra = @"";
448 if (visible)
449 {
450 extra = [extra stringByAppendingString:@", visible"];
451 }
452
453 if (drawableSize != 0)
454 {
455 extra = [extra stringByAppendingFormat:@", drawable: %@", SizeString(drawableSize)];
456 }
457
458 [self writeMemStat:@"%@: %@%@", [entity shortDescription], SizeString(entitySize), extra];
459
460 state->totalEntityObjSize += entitySize;
461 state->totalDrawableSize += drawableSize;
462
463 OOLogIndent();
464 if ([entity isShip])
465 {
466 NSEnumerator *subEnum = nil;
467 id subentity = nil;
468 for (subEnum = [entity subEntityEnumerator]; (subentity = [subEnum nextObject]); )
469 {
470 [self dumpEntity:subentity withState:state parentVisible:visible];
471 }
472
473 if ([entity isPlayer])
474 {
475 NSUInteger i, count = [entity dialMaxMissiles];
476 for (i = 0; i < count; i++)
477 {
478 subentity = [entity missileForPylon:i];
479 if (subentity != nil) [self dumpEntity:subentity withState:state parentVisible:NO];
480 }
481 }
482 }
483 if ([entity isPlanet])
484 {
485#if NEW_PLANETS
486 // FIXME: dump atmosphere texture.
487#else
488 PlanetEntity *atmosphere = [entity atmosphere];
489 if (atmosphere != nil)
490 {
491 [self dumpEntity:atmosphere withState:state parentVisible:visible];
492 }
493#endif
494 }
495 if ([entity isWormhole])
496 {
497 NSEnumerator *shipEnum = nil;
498 NSDictionary *shipInfo = nil;
499 for (shipEnum = [[entity shipsInTransit] objectEnumerator]; (shipInfo = [shipEnum nextObject]); )
500 {
501 ShipEntity *ship = [shipInfo objectForKey:@"ship"];
502 [self dumpEntity:ship withState:state parentVisible:NO];
503 }
504 }
505 OOLogOutdent();
506}
507
508
509- (void) dumpMemoryStatistics
510{
511 OOLog(@"debug.memStats", @"%@", @"Memory statistics:");
512 OOLogIndent();
513
514 // Get texture retain counts before the entity dumper starts messing with them.
515 NSSet *allTextures = [OOTexture allTextures];
516 NSMutableDictionary *textureRefCounts = [NSMutableDictionary dictionaryWithCapacity:[allTextures count]];
517
518 OOTexture *tex = nil;
519 NSEnumerator *texEnum = nil;
520 for (texEnum = [allTextures objectEnumerator]; (tex = [texEnum nextObject]); )
521 {
522 // We subtract one because allTextures retains the textures.
523 [textureRefCounts setObject:[NSNumber numberWithUnsignedInteger:[tex retainCount] - 1] forKey:[NSValue valueWithNonretainedObject:tex]];
524 }
525
526 size_t totalSize = 0;
527
528 [self writeMemStat:@"Entitites:"];
529 OOLogIndent();
530
531 NSArray *entities = [UNIVERSE entityList];
532 EntityDumpState entityDumpState =
533 {
534 .entityTextures = [NSMutableSet set],
535 .visibleEntityTextures = [NSMutableSet set],
536 .seenEntities = [NSMutableSet set]
537 };
538
539 id entity = nil;
540 NSEnumerator *entityEnum = nil;
541 for (entityEnum = [entities objectEnumerator]; (entity = [entityEnum nextObject]); )
542 {
543 [self dumpEntity:entity withState:&entityDumpState parentVisible:YES];
544 }
545 for (entityEnum = [[PLAYER scannedWormholes] objectEnumerator]; (entity = [entityEnum nextObject]); )
546 {
547 [self dumpEntity:entity withState:&entityDumpState parentVisible:YES];
548 }
549
550 OOLogOutdent();
551 [self writeMemStat:@"Total entity size (excluding %u entities not accounted for): %@ (%@ entity objects, %@ drawables)",
552 gLiveEntityCount - entityDumpState.seenCount,
553 SizeString(entityDumpState.totalEntityObjSize + entityDumpState.totalDrawableSize),
554 SizeString(entityDumpState.totalEntityObjSize),
555 SizeString(entityDumpState.totalDrawableSize)];
556 totalSize += entityDumpState.totalEntityObjSize + entityDumpState.totalDrawableSize;
557
558 /* Sort textures so that textures in the "recent cache" come first by age,
559 followed by others.
560 */
561 NSMutableArray *textures = [[[OOTexture cachedTexturesByAge] mutableCopy] autorelease];
562
563 for (texEnum = [allTextures objectEnumerator]; (tex = [texEnum nextObject]); )
564 {
565 if ([textures indexOfObject:tex] == NSNotFound)
566 {
567 [textures addObject:tex];
568 }
569 }
570
571 size_t totalTextureObjSize = 0;
572 size_t totalTextureDataSize = 0;
573 size_t visibleTextureDataSize = 0;
574
575 [self writeMemStat:@"Textures:"];
576 OOLogIndent();
577
578 for (texEnum = [textures objectEnumerator]; (tex = [texEnum nextObject]); )
579 {
580 size_t objSize = [tex oo_objectSize];
581 size_t dataSize = [tex dataSize];
582
583#if OOTEXTURE_RELOADABLE
584 NSString *byteCountSuffix = @"";
585#else
586 NSString *byteCountSuffix = @" (* 2)";
587#endif
588
589 NSString *usage = @"";
590 if ([entityDumpState.visibleEntityTextures containsObject:tex])
591 {
592 visibleTextureDataSize += dataSize; // NOT doubled if !OOTEXTURE_RELOADABLE, because we're interested in what the GPU sees.
593 usage = @", visible";
594 }
595 else if ([entityDumpState.entityTextures containsObject:tex])
596 {
597 usage = @", active";
598 }
599
600 unsigned refCount = [textureRefCounts oo_unsignedIntForKey:[NSValue valueWithNonretainedObject:tex]];
601
602 [self writeMemStat:@"%@: [%u refs%@] %@%@",
603 [tex name],
604 refCount,
605 usage,
606 SizeString(objSize + dataSize),
607 byteCountSuffix];
608
609 totalTextureDataSize += dataSize;
610 totalTextureObjSize += objSize;
611 }
612 totalSize += totalTextureObjSize + totalTextureDataSize;
613
614 OOLogOutdent();
615
616#if !OOTEXTURE_RELOADABLE
617 totalTextureDataSize *= 2;
618#endif
619 [self writeMemStat:@"Total texture size: %@ (%@ object overhead, %@ data, %@ visible texture data)",
620 SizeString(totalTextureObjSize + totalTextureDataSize),
621 SizeString(totalTextureObjSize),
622 SizeString(totalTextureDataSize),
623 SizeString(visibleTextureDataSize)];
624
625 totalSize += [self dumpJSMemoryStatistics];
626
627 [self writeMemStat:@"Total: %@", SizeString(totalSize)];
628
629 OOLogOutdent();
630}
631
632
633- (size_t) dumpJSMemoryStatistics
634{
635 JSContext *context = OOJSAcquireContext();
636
637 JSRuntime *runtime = JS_GetRuntime(context);
638 size_t jsSize = JS_GetGCParameter(runtime, JSGC_BYTES);
639 size_t jsMax = JS_GetGCParameter(runtime, JSGC_MAX_BYTES);
640 uint32_t jsGCCount = JS_GetGCParameter(runtime, JSGC_NUMBER);
641
642 OOJSRelinquishContext(context);
643
644 [self writeMemStat:@"JavaScript heap: %@ (limit %@, %u collections to date)", SizeString(jsSize), SizeString(jsMax), jsGCCount];
645 return jsSize;
646}
647
648
649- (void) setTCPIgnoresDroppedPackets:(BOOL)flag
650{
651 if (_TCPIgnoresDroppedPackets != flag)
652 {
653 OOLog(@"debugMonitor.TCPSettings", @"The TCP console will %@ TCP packets.",
654 (flag ? @"try to stay connected, ignoring dropped" : @"disconnect if an error affects"));
655 }
656 _TCPIgnoresDroppedPackets = flag;
657}
658
659
660- (BOOL) TCPIgnoresDroppedPackets
661{
662 return _TCPIgnoresDroppedPackets;
663}
664
665
666- (void) setUsingPlugInController:(BOOL)flag
667{
668 _usingPlugInController = flag;
669}
670
671
672- (BOOL) usingPlugInController
673{
674 return _usingPlugInController;
675}
676
677
678- (NSString *)sourceCodeForFile:(in NSString *)filePath line:(in unsigned)line
679{
680 id linesForFile = nil;
681
682 linesForFile = [_sourceFiles objectForKey:filePath];
683
684 if (linesForFile == nil)
685 {
686 linesForFile = [self loadSourceFile:filePath];
687 if (linesForFile == nil) linesForFile = [NSArray arrayWithObject:[NSString stringWithFormat:@"<Can't load file %@>", filePath]];
688
689 if (_sourceFiles == nil) _sourceFiles = [[NSMutableDictionary alloc] init];
690 [_sourceFiles setObject:linesForFile forKey:filePath];
691 }
692
693 if ([linesForFile count] < line || line == 0) return @"<line out of range!>";
694
695 return [linesForFile objectAtIndex:line - 1];
696}
697
698
699- (void)disconnectDebugger:(in id<OODebuggerInterface>)debugger
700 message:(in NSString *)message
701{
702 if (debugger == nil) return;
703
704 if (debugger == _debugger)
705 {
706 [self disconnectDebuggerWithMessage:message];
707 }
708 else
709 {
710 OOLog(@"debugMonitor.disconnect.ignored", @"Attempt to disconnect debugger %@, which is not current debugger; ignoring.", debugger);
711 }
712}
713
714
715#if OOLITE_GNUSTEP
716- (void) applicationWillTerminate
717{
718 [[NSNotificationCenter defaultCenter] postNotificationName:NSApplicationWillTerminateNotification object:nil];
719}
720#endif
721
722
723- (void)applicationWillTerminate:(NSNotification *)notification
724{
725 if (_configOverrides != nil)
726 {
727 [[NSUserDefaults standardUserDefaults] setObject:_configOverrides forKey:@"debug-settings-override"];
728 }
729
730 [self disconnectDebuggerWithMessage:@"Oolite is terminating."];
731}
732
733
734@end
735
736
737@implementation OODebugMonitor (Private)
738
740{
741 JSContext *context = OOJSAcquireContext();
742 /* The path to the console script is saved in this here static variable
743 so that we can reload it when resetting into strict mode.
744 -- Ahruman 2011-02-06
745 */
746 static NSString *path = nil;
747
748 if (path == nil)
749 {
750 path = [[ResourceManager pathForFileNamed:@"oolite-debug-console.js" inFolder:@"Scripts"] retain];
751 }
752 if (path != nil)
753 {
754 NSDictionary *jsProps = [NSDictionary dictionaryWithObjectsAndKeys:
755 self, @"console",
756 JSSpecialFunctionsObjectWrapper(context), @"special",
757 nil];
758 _script = [[OOJSScript scriptWithPath:path properties:jsProps] retain];
759 }
760
761 // If no script, just make console visible globally as debugConsole.
762 if (_script == nil)
763 {
764 JSObject *global = [[OOJavaScriptEngine sharedEngine] globalObject];
765 JS_DefineProperty(context, global, "debugConsole", [self oo_jsValueInContext:context], NULL, NULL, JSPROP_ENUMERATE);
766 }
767
768 OOJSRelinquishContext(context);
769}
770
771
772- (void) javaScriptEngineWillReset:(NSNotification *)notification
773{
774 DESTROY(_script);
775 _jsSelf = NULL;
776
778}
779
780
781- (void)disconnectDebuggerWithMessage:(NSString *)message
782{
783 @try
784 {
785 [_debugger disconnectDebugMonitor:self message:message];
786 }
787 @catch (NSException *exception)
788 {
789 OOLog(@"debugMonitor.debuggerConnection.exception", @"Exception while attempting to disconnect debugger: %@ -- %@", [exception name], [exception reason]);
790 }
791
792 id debugger = _debugger;
793 _debugger = nil;
794 [debugger release];
795}
796
797
798- (NSDictionary *)mergedConfiguration
799{
800 NSMutableDictionary *result = nil;
801
802 result = [NSMutableDictionary dictionary];
803 if (_configFromOXPs != nil) [result addEntriesFromDictionary:_configFromOXPs];
804 if (_configOverrides != nil) [result addEntriesFromDictionary:_configOverrides];
805
806 return result;
807}
808
809
810- (NSArray *)loadSourceFile:(NSString *)filePath
811{
812 NSString *contents = nil;
813 NSArray *lines = nil;
814
815 if (filePath == nil) return nil;
816
817 contents = [NSString stringWithContentsOfUnicodeFile:filePath];
818 if (contents == nil) return nil;
819
820 /* Extract lines from file.
821FIXME: this works with CRLF and LF, but not CR.
822 */
823 lines = [contents componentsSeparatedByString:@"\n"];
824 return lines;
825}
826
827
828- (NSMutableDictionary *)normalizeConfigDictionary:(NSDictionary *)dictionary
829{
830 NSMutableDictionary *result = nil;
831 NSEnumerator *keyEnum = nil;
832 NSString *key = nil;
833 id value = nil;
834
835 result = [NSMutableDictionary dictionaryWithCapacity:[dictionary count]];
836 for (keyEnum = [dictionary keyEnumerator]; (key = [keyEnum nextObject]); )
837 {
838 value = [dictionary objectForKey:key];
839 value = [self normalizeConfigValue:value forKey:key];
840
841 if (key != nil && value != nil) [result setObject:value forKey:key];
842 }
843
844 return result;
845}
846
847
848- (id)normalizeConfigValue:(id)value forKey:(NSString *)key
849{
850 OOColor *color = nil;
851 BOOL boolValue;
852
853 if (value != nil)
854 {
855 if ([key hasSuffix:@"-color"] || [key hasSuffix:@"-colour"])
856 {
857 color = [OOColor colorWithDescription:value];
858 value = [color normalizedArray];
859 }
860 else if ([key hasPrefix:@"show-console"])
861 {
862 boolValue = OOBooleanFromObject(value, NO);
863 value = [NSNumber numberWithBool:boolValue];
864 }
865 }
866
867 return value;
868}
869
870
871- (oneway void)jsEngine:(in byref OOJavaScriptEngine *)engine
872 context:(in JSContext *)context
873 error:(in JSErrorReport *)errorReport
874 stackSkip:(in unsigned)stackSkip
875 showingLocation:(in BOOL)showLocation
876 withMessage:(in NSString *)message
877{
878 NSString *colorKey = nil;
879 NSString *prefix = nil;
880 NSString *filePath = nil;
881 NSString *sourceLine = nil;
882 NSString *scriptLine = nil;
883 NSMutableString *formattedMessage = nil;
884 NSRange emphasisRange;
885 NSString *showKey = nil;
886
887 if (_debugger == nil) return;
888
889 if (errorReport->flags & JSREPORT_WARNING)
890 {
891 colorKey = @"warning";
892 prefix = @"Warning";
893 }
894 else if (errorReport->flags & JSREPORT_EXCEPTION)
895 {
896 colorKey = @"exception";
897 prefix = @"Exception";
898 }
899 else
900 {
901 colorKey = @"error";
902 prefix = @"Error";
903 }
904
905 if (errorReport->flags & JSREPORT_STRICT)
906 {
907 prefix = [prefix stringByAppendingString:@" (strict mode)"];
908 }
909
910 // Prefix and subsequent colon should be bold:
911 emphasisRange = NSMakeRange(0, [prefix length] + 1);
912
913 formattedMessage = [NSMutableString stringWithFormat:@"%@: %@", prefix, message];
914
915 // Note that the "active script" isn't necessarily the one causing the
916 // error, since one script can call another's methods.
917
918 // avoid windows DEP exceptions!
920 scriptLine = [[thisScript weakRefUnderlyingObject] displayName];
921 [thisScript release];
922
923 if (scriptLine != nil)
924 {
925 [formattedMessage appendFormat:@"\n Active script: %@", scriptLine];
926 }
927
928 if (showLocation && stackSkip == 0)
929 {
930 // Append file name and line
931 if (errorReport->filename != NULL) filePath = [NSString stringWithUTF8String:errorReport->filename];
932 if ([filePath length] != 0)
933 {
934 [formattedMessage appendFormat:@"\n %@, line %u", [filePath lastPathComponent], errorReport->lineno];
935
936 // Append source code
937 sourceLine = [self sourceCodeForFile:filePath line:errorReport->lineno];
938 if (sourceLine != nil)
939 {
940 [formattedMessage appendFormat:@":\n %@", sourceLine];
941 }
942 }
943 }
944
945 [self appendJSConsoleLine:formattedMessage
946 colorKey:colorKey
947 emphasisRange:emphasisRange];
948
949 if (errorReport->flags & JSREPORT_WARNING) showKey = @"show-console-on-warning";
950 else showKey = @"show-console-on-error"; // if not a warning, it's a proper error.
951 if (OOBooleanFromObject([self configurationValueForKey:showKey], NO))
952 {
953 [self showJSConsole];
954 }
955}
956
957
958- (oneway void)jsEngine:(in byref OOJavaScriptEngine *)engine
959 context:(in JSContext *)context
960 logMessage:(in NSString *)message
961 ofClass:(in NSString *)messageClass
962{
963 [self appendJSConsoleLine:message colorKey:@"log"];
964 if (OOBooleanFromObject([self configurationValueForKey:@"show-console-on-log"], NO))
965 {
966 [self showJSConsole];
967 }
968}
969
970
971- (jsval)oo_jsValueInContext:(JSContext *)context
972{
973 if (_jsSelf == NULL)
974 {
975 _jsSelf = DebugMonitorToJSConsole(context, self);
976 if (_jsSelf != NULL)
977 {
978 if (!OOJSAddGCObjectRoot(context, &_jsSelf, "debug console"))
979 {
980 _jsSelf = NULL;
981 }
982 }
983 }
984
985 if (_jsSelf != NULL) return OBJECT_TO_JSVAL(_jsSelf);
986 else return JSVAL_NULL;
987}
988
989@end
990
991
992@implementation OODebugMonitor (Singleton)
993
994/* Canonical singleton boilerplate.
995See Cocoa Fundamentals Guide: Creating a Singleton Instance.
996See also +sharedDebugMonitor above.
997
998NOTE: assumes single-threaded access.
999*/
1000
1001+ (id)allocWithZone:(NSZone *)inZone
1002{
1003 if (sSingleton == nil)
1004 {
1005 sSingleton = [super allocWithZone:inZone];
1006 return sSingleton;
1007 }
1008 return nil;
1009}
1010
1011
1012- (id)copyWithZone:(NSZone *)inZone
1013{
1014 return self;
1015}
1016
1017
1018- (id)retain
1019{
1020 return self;
1021}
1022
1023
1024- (NSUInteger)retainCount
1025{
1026 return UINT_MAX;
1027}
1028
1029
1030- (void)release
1031{}
1032
1033
1034- (id)autorelease
1035{
1036 return self;
1037}
1038
1039@end
1040
1041#endif /* OO_EXCLUDE_DEBUG_SUPPORT */
#define DESTROY(x)
Definition OOCocoa.h:77
BOOL OOBooleanFromObject(id object, BOOL defaultValue)
static OODebugMonitor * sSingleton
JSObject * DebugMonitorToJSConsole(JSContext *context, OODebugMonitor *monitor)
void OOJSConsoleDestroy(void)
#define OOJSStopTimeLimiter()
#define kOOJSLongTimeLimit
#define OOJSStartTimeLimiterWithTimeLimit(limit)
static JSBool EntityDumpState(JSContext *context, uintN argc, jsval *vp)
Definition OOJSEntity.m:400
void OOJSPauseTimeLimiter(void)
OOINLINE jsval OOJSValueFromNativeObject(JSContext *context, id object)
OOINLINE JSContext * OOJSAcquireContext(void)
#define OOJSAddGCObjectRoot(context, root, name)
OOINLINE void OOJSRelinquishContext(JSContext *context)
void OOJSResumeTimeLimiter(void)
void OOLogOutdent(void)
Definition OOLogging.m:376
#define OOLog(class, format,...)
Definition OOLogging.h:88
void OOLogIndent(void)
Definition OOLogging.m:366
return self
unsigned count
return nil
#define PLAYER
NSDictionary * mergedConfiguration()
OOColor * colorWithDescription:(id description)
Definition OOColor.m:127
NSArray * normalizedArray()
Definition OOColor.m:511
void setUpDebugConsoleScript()
NSMutableDictionary * normalizeConfigDictionary:(NSDictionary *dictionary)
size_t totalSize()
Definition OODrawable.m:95
id scriptWithPath:properties:(NSString *path,[properties] NSDictionary *properties)
Definition OOJSScript.m:112
OOJSScript * currentlyRunningScript()
Definition OOJSScript.m:339
OOJavaScriptEngine * sharedEngine()
void removeGCObjectRoot:(JSObject **rootPtr)
NSSet * allTextures()
Definition OOTexture.m:405
NSString * name()
Definition OOTexture.m:430
NSArray * cachedTexturesByAge()
Definition OOTexture.m:399
size_t dataSize()
Definition OOTexture.m:419
NSString * pathForFileNamed:inFolder:(NSString *fileName,[inFolder] NSString *folderName)
NSDictionary * dictionaryFromFilesNamed:inFolder:andMerge:(NSString *fileName,[inFolder] NSString *folderName,[andMerge] BOOL mergeFiles)
voidpf void uLong size
Definition ioapi.h:134
typedef long(ZCALLBACK *tell_file_func) OF((voidpf opaque
NSMutableSet * visibleEntityTextures
NSMutableSet * seenEntities
NSMutableSet * entityTextures