Line data Source code
1 0 : /*
2 :
3 : OODebugMonitor.m
4 :
5 :
6 : Oolite debug support
7 :
8 : Copyright (C) 2007-2013 Jens Ayton
9 :
10 : Permission is hereby granted, free of charge, to any person obtaining a copy
11 : of this software and associated documentation files (the "Software"), to deal
12 : in the Software without restriction, including without limitation the rights
13 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 : copies of the Software, and to permit persons to whom the Software is
15 : furnished to do so, subject to the following conditions:
16 :
17 : The above copyright notice and this permission notice shall be included in all
18 : copies or substantial portions of the Software.
19 :
20 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 : SOFTWARE.
27 :
28 : */
29 :
30 : #ifndef OO_EXCLUDE_DEBUG_SUPPORT
31 :
32 :
33 : #import "OODebugMonitor.h"
34 : #import "OOCollectionExtractors.h"
35 : #import "OOLoggingExtended.h"
36 : #import "ResourceManager.h"
37 : #import "NSStringOOExtensions.h"
38 :
39 : #import "OOJSConsole.h"
40 : #import "OOJSScript.h"
41 : #import "OOJSEngineTimeManagement.h"
42 : #import "OOJSSpecialFunctions.h"
43 :
44 : #import "NSObjectOOExtensions.h"
45 : #import "OOTexture.h"
46 : #import "OOConcreteTexture.h"
47 : #import "OODrawable.h"
48 :
49 :
50 0 : static OODebugMonitor *sSingleton = nil;
51 :
52 :
53 : @interface OODebugMonitor (Private) <OOJavaScriptEngineMonitor>
54 :
55 0 : - (void) setUpDebugConsoleScript;
56 0 : - (void) javaScriptEngineWillReset:(NSNotification *)notification;
57 :
58 0 : - (void)disconnectDebuggerWithMessage:(NSString *)message;
59 :
60 0 : - (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 0 : - (NSMutableDictionary *)normalizeConfigDictionary:(NSDictionary *)dictionary;
67 0 : - (id)normalizeConfigValue:(id)value forKey:(NSString *)key;
68 :
69 0 : - (NSArray *)loadSourceFile:(NSString *)filePath;
70 :
71 : @end
72 :
73 :
74 : @implementation OODebugMonitor
75 : #if OOLITE_GNUSTEP
76 : NSString *NSApplicationWillTerminateNotification = @"ApplicationWillTerminate";
77 : #endif
78 :
79 0 : - (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 :
99 : OOJavaScriptEngine *jsEng = [OOJavaScriptEngine sharedEngine];
100 : #if OOJSENGINE_MONITOR_SUPPORT
101 : [jsEng setMonitor:self];
102 : #endif
103 :
104 : [self setUpDebugConsoleScript];
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 0 : - (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 : {
139 : [[OOJavaScriptEngine sharedEngine] removeGCObjectRoot:&_jsSelf];
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 0 : - (oneway void)performJSConsoleCommand:(in NSString *)command
202 : {
203 : JSContext *context = OOJSAcquireContext();
204 : jsval commandVal = OOJSValueFromNativeObject(context, command);
205 : OOJSStartTimeLimiterWithTimeLimit(kOOJSLongTimeLimit);
206 : [_script callMethod:OOJSID("consolePerformJSCommand") inContext:context withArguments:&commandVal count:1 result:NULL];
207 : OOJSStopTimeLimiter();
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;
217 : OOJSPauseTimeLimiter();
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 : }
229 : OOJSResumeTimeLimiter();
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 : {
244 : OOJSPauseTimeLimiter();
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 : }
253 : OOJSResumeTimeLimiter();
254 : }
255 :
256 :
257 : - (void)showJSConsole
258 : {
259 : OOJSPauseTimeLimiter();
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 : }
268 : OOJSResumeTimeLimiter();
269 : }
270 :
271 :
272 0 : - (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 0 : - (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 0 : - (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 :
374 0 : static 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 :
412 0 : typedef struct
413 : {
414 0 : NSMutableSet *entityTextures;
415 0 : NSMutableSet *visibleEntityTextures;
416 0 : NSMutableSet *seenEntities;
417 0 : unsigned seenCount;
418 0 : size_t totalEntityObjSize;
419 0 : size_t totalDrawableSize;
420 : } EntityDumpState;
421 :
422 :
423 0 : - (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 0 : - (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 0 : - (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 0 : - (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 :
739 : - (void) setUpDebugConsoleScript
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 :
777 : OOJSConsoleDestroy();
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.
821 : FIXME: 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 0 : - (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!
919 : OOJSScript *thisScript = [[OOJSScript currentlyRunningScript] weakRetain];
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 0 : - (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 0 : - (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.
995 : See Cocoa Fundamentals Guide: Creating a Singleton Instance.
996 : See also +sharedDebugMonitor above.
997 :
998 : NOTE: assumes single-threaded access.
999 : */
1000 :
1001 0 : + (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 0 : - (id)copyWithZone:(NSZone *)inZone
1013 : {
1014 : return self;
1015 : }
1016 :
1017 :
1018 0 : - (id)retain
1019 : {
1020 : return self;
1021 : }
1022 :
1023 :
1024 0 : - (NSUInteger)retainCount
1025 : {
1026 : return UINT_MAX;
1027 : }
1028 :
1029 :
1030 0 : - (void)release
1031 : {}
1032 :
1033 :
1034 0 : - (id)autorelease
1035 : {
1036 : return self;
1037 : }
1038 :
1039 : @end
1040 :
1041 : #endif /* OO_EXCLUDE_DEBUG_SUPPORT */
|