Oolite
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 NDEBUG
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 id subentity = nil;
467 foreach (subentity, [entity subEntityEnumerator])
468 {
469 [self dumpEntity:subentity withState:state parentVisible:visible];
470 }
471
472 if ([entity isPlayer])
473 {
474 NSUInteger i, count = [entity dialMaxMissiles];
475 for (i = 0; i < count; i++)
476 {
477 subentity = [entity missileForPylon:i];
478 if (subentity != nil) [self dumpEntity:subentity withState:state parentVisible:NO];
479 }
480 }
481 }
482 if ([entity isPlanet])
483 {
484#if NEW_PLANETS
485 // FIXME: dump atmosphere texture.
486#else
487 PlanetEntity *atmosphere = [entity atmosphere];
488 if (atmosphere != nil)
489 {
490 [self dumpEntity:atmosphere withState:state parentVisible:visible];
491 }
492#endif
493 }
494 if ([entity isWormhole])
495 {
496 NSDictionary *shipInfo = nil;
497 foreach (shipInfo, [entity shipsInTransit])
498 {
499 ShipEntity *ship = [shipInfo objectForKey:@"ship"];
500 [self dumpEntity:ship withState:state parentVisible:NO];
501 }
502 }
503 OOLogOutdent();
504}
505
506
507- (void) dumpMemoryStatistics
508{
509 OOLog(@"debug.memStats", @"%@", @"Memory statistics:");
510 OOLogIndent();
511
512 // Get texture retain counts before the entity dumper starts messing with them.
513 NSSet *allTextures = [OOTexture allTextures];
514 NSMutableDictionary *textureRefCounts = [NSMutableDictionary dictionaryWithCapacity:[allTextures count]];
515
516 OOTexture *tex = nil;
517 foreach (tex, allTextures)
518 {
519 // We subtract one because allTextures retains the textures.
520 [textureRefCounts setObject:[NSNumber numberWithUnsignedInteger:[tex retainCount] - 1] forKey:[NSValue valueWithNonretainedObject:tex]];
521 }
522
523 size_t totalSize = 0;
524
525 [self writeMemStat:@"Entitites:"];
526 OOLogIndent();
527
528 NSArray *entities = [UNIVERSE entityList];
529 EntityDumpState entityDumpState =
530 {
531 .entityTextures = [NSMutableSet set],
532 .visibleEntityTextures = [NSMutableSet set],
533 .seenEntities = [NSMutableSet set]
534 };
535
536 id entity = nil;
537 foreach (entity, entities)
538 {
539 [self dumpEntity:entity withState:&entityDumpState parentVisible:YES];
540 }
541 foreach (entity, [PLAYER scannedWormholes])
542 {
543 [self dumpEntity:entity withState:&entityDumpState parentVisible:YES];
544 }
545
546 OOLogOutdent();
547 [self writeMemStat:@"Total entity size (excluding %u entities not accounted for): %@ (%@ entity objects, %@ drawables)",
548 gLiveEntityCount - entityDumpState.seenCount,
549 SizeString(entityDumpState.totalEntityObjSize + entityDumpState.totalDrawableSize),
550 SizeString(entityDumpState.totalEntityObjSize),
551 SizeString(entityDumpState.totalDrawableSize)];
552 totalSize += entityDumpState.totalEntityObjSize + entityDumpState.totalDrawableSize;
553
554 /* Sort textures so that textures in the "recent cache" come first by age,
555 followed by others.
556 */
557 NSMutableArray *textures = [[[OOTexture cachedTexturesByAge] mutableCopy] autorelease];
558
559 foreach (tex, allTextures)
560 {
561 if ([textures indexOfObject:tex] == NSNotFound)
562 {
563 [textures addObject:tex];
564 }
565 }
566
567 size_t totalTextureObjSize = 0;
568 size_t totalTextureDataSize = 0;
569 size_t visibleTextureDataSize = 0;
570
571 [self writeMemStat:@"Textures:"];
572 OOLogIndent();
573
574 foreach (tex, textures)
575 {
576 size_t objSize = [tex oo_objectSize];
577 size_t dataSize = [tex dataSize];
578
579#if OOTEXTURE_RELOADABLE
580 NSString *byteCountSuffix = @"";
581#else
582 NSString *byteCountSuffix = @" (* 2)";
583#endif
584
585 NSString *usage = @"";
586 if ([entityDumpState.visibleEntityTextures containsObject:tex])
587 {
588 visibleTextureDataSize += dataSize; // NOT doubled if !OOTEXTURE_RELOADABLE, because we're interested in what the GPU sees.
589 usage = @", visible";
590 }
591 else if ([entityDumpState.entityTextures containsObject:tex])
592 {
593 usage = @", active";
594 }
595
596 unsigned refCount = [textureRefCounts oo_unsignedIntForKey:[NSValue valueWithNonretainedObject:tex]];
597
598 [self writeMemStat:@"%@: [%u refs%@] %@%@",
599 [tex name],
600 refCount,
601 usage,
602 SizeString(objSize + dataSize),
603 byteCountSuffix];
604
605 totalTextureDataSize += dataSize;
606 totalTextureObjSize += objSize;
607 }
608 totalSize += totalTextureObjSize + totalTextureDataSize;
609
610 OOLogOutdent();
611
612#if !OOTEXTURE_RELOADABLE
613 totalTextureDataSize *= 2;
614#endif
615 [self writeMemStat:@"Total texture size: %@ (%@ object overhead, %@ data, %@ visible texture data)",
616 SizeString(totalTextureObjSize + totalTextureDataSize),
617 SizeString(totalTextureObjSize),
618 SizeString(totalTextureDataSize),
619 SizeString(visibleTextureDataSize)];
620
621 totalSize += [self dumpJSMemoryStatistics];
622
623 [self writeMemStat:@"Total: %@", SizeString(totalSize)];
624
625 OOLogOutdent();
626}
627
628
629- (size_t) dumpJSMemoryStatistics
630{
631 JSContext *context = OOJSAcquireContext();
632
633 JSRuntime *runtime = JS_GetRuntime(context);
634 size_t jsSize = JS_GetGCParameter(runtime, JSGC_BYTES);
635 size_t jsMax = JS_GetGCParameter(runtime, JSGC_MAX_BYTES);
636 uint32_t jsGCCount = JS_GetGCParameter(runtime, JSGC_NUMBER);
637
638 OOJSRelinquishContext(context);
639
640 [self writeMemStat:@"JavaScript heap: %@ (limit %@, %u collections to date)", SizeString(jsSize), SizeString(jsMax), jsGCCount];
641 return jsSize;
642}
643
644
645- (void) setTCPIgnoresDroppedPackets:(BOOL)flag
646{
647 if (_TCPIgnoresDroppedPackets != flag)
648 {
649 OOLog(@"debugMonitor.TCPSettings", @"The TCP console will %@ TCP packets.",
650 (flag ? @"try to stay connected, ignoring dropped" : @"disconnect if an error affects"));
651 }
652 _TCPIgnoresDroppedPackets = flag;
653}
654
655
656- (BOOL) TCPIgnoresDroppedPackets
657{
658 return _TCPIgnoresDroppedPackets;
659}
660
661
662- (void) setUsingPlugInController:(BOOL)flag
663{
664 _usingPlugInController = flag;
665}
666
667
668- (BOOL) usingPlugInController
669{
670 return _usingPlugInController;
671}
672
673
674- (NSString *)sourceCodeForFile:(in NSString *)filePath line:(in unsigned)line
675{
676 id linesForFile = nil;
677
678 linesForFile = [_sourceFiles objectForKey:filePath];
679
680 if (linesForFile == nil)
681 {
682 linesForFile = [self loadSourceFile:filePath];
683 if (linesForFile == nil) linesForFile = [NSArray arrayWithObject:[NSString stringWithFormat:@"<Can't load file %@>", filePath]];
684
685 if (_sourceFiles == nil) _sourceFiles = [[NSMutableDictionary alloc] init];
686 [_sourceFiles setObject:linesForFile forKey:filePath];
687 }
688
689 if ([linesForFile count] < line || line == 0) return @"<line out of range!>";
690
691 return [linesForFile objectAtIndex:line - 1];
692}
693
694
695- (void)disconnectDebugger:(in id<OODebuggerInterface>)debugger
696 message:(in NSString *)message
697{
698 if (debugger == nil) return;
699
700 if (debugger == _debugger)
701 {
702 [self disconnectDebuggerWithMessage:message];
703 }
704 else
705 {
706 OOLog(@"debugMonitor.disconnect.ignored", @"Attempt to disconnect debugger %@, which is not current debugger; ignoring.", debugger);
707 }
708}
709
710
711#if OOLITE_GNUSTEP
712- (void) applicationWillTerminate
713{
714 [[NSNotificationCenter defaultCenter] postNotificationName:NSApplicationWillTerminateNotification object:nil];
715}
716#endif
717
718
719- (void)applicationWillTerminate:(NSNotification *)notification
720{
721 if (_configOverrides != nil)
722 {
723 [[NSUserDefaults standardUserDefaults] setObject:_configOverrides forKey:@"debug-settings-override"];
724 }
725
726 [self disconnectDebuggerWithMessage:@"Oolite is terminating."];
727}
728
729
730@end
731
732
733@implementation OODebugMonitor (Private)
734
736{
737 JSContext *context = OOJSAcquireContext();
738 /* The path to the console script is saved in this here static variable
739 so that we can reload it when resetting into strict mode.
740 -- Ahruman 2011-02-06
741 */
742 static NSString *path = nil;
743
744 if (path == nil)
745 {
746 path = [[ResourceManager pathForFileNamed:@"oolite-debug-console.js" inFolder:@"Scripts"] retain];
747 }
748 if (path != nil)
749 {
750 NSDictionary *jsProps = [NSDictionary dictionaryWithObjectsAndKeys:
751 self, @"console",
752 JSSpecialFunctionsObjectWrapper(context), @"special",
753 nil];
754 _script = [[OOJSScript scriptWithPath:path properties:jsProps] retain];
755 }
756
757 // If no script, just make console visible globally as debugConsole.
758 if (_script == nil)
759 {
760 JSObject *global = [[OOJavaScriptEngine sharedEngine] globalObject];
761 JS_DefineProperty(context, global, "debugConsole", [self oo_jsValueInContext:context], NULL, NULL, JSPROP_ENUMERATE);
762 }
763
764 OOJSRelinquishContext(context);
765}
766
767
768- (void) javaScriptEngineWillReset:(NSNotification *)notification
769{
770 DESTROY(_script);
771 _jsSelf = NULL;
772
774}
775
776
777- (void)disconnectDebuggerWithMessage:(NSString *)message
778{
779 @try
780 {
781 [_debugger disconnectDebugMonitor:self message:message];
782 }
783 @catch (NSException *exception)
784 {
785 OOLog(@"debugMonitor.debuggerConnection.exception", @"Exception while attempting to disconnect debugger: %@ -- %@", [exception name], [exception reason]);
786 }
787
788 id debugger = _debugger;
789 _debugger = nil;
790 [debugger release];
791}
792
793
794- (NSDictionary *)mergedConfiguration
795{
796 NSMutableDictionary *result = nil;
797
798 result = [NSMutableDictionary dictionary];
799 if (_configFromOXPs != nil) [result addEntriesFromDictionary:_configFromOXPs];
800 if (_configOverrides != nil) [result addEntriesFromDictionary:_configOverrides];
801
802 return result;
803}
804
805
806- (NSArray *)loadSourceFile:(NSString *)filePath
807{
808 NSString *contents = nil;
809 NSArray *lines = nil;
810
811 if (filePath == nil) return nil;
812
813 contents = [NSString stringWithContentsOfUnicodeFile:filePath];
814 if (contents == nil) return nil;
815
816 /* Extract lines from file.
817FIXME: this works with CRLF and LF, but not CR.
818 */
819 lines = [contents componentsSeparatedByString:@"\n"];
820 return lines;
821}
822
823
824- (NSMutableDictionary *)normalizeConfigDictionary:(NSDictionary *)dictionary
825{
826 NSMutableDictionary *result = nil;
827 NSString *key = nil;
828 id value = nil;
829
830 result = [NSMutableDictionary dictionaryWithCapacity:[dictionary count]];
831 foreachkey (key, dictionary)
832 {
833 value = [dictionary objectForKey:key];
834 value = [self normalizeConfigValue:value forKey:key];
835
836 if (key != nil && value != nil) [result setObject:value forKey:key];
837 }
838
839 return result;
840}
841
842
843- (id)normalizeConfigValue:(id)value forKey:(NSString *)key
844{
845 OOColor *color = nil;
846 BOOL boolValue;
847
848 if (value != nil)
849 {
850 if ([key hasSuffix:@"-color"] || [key hasSuffix:@"-colour"])
851 {
852 color = [OOColor colorWithDescription:value];
853 value = [color normalizedArray];
854 }
855 else if ([key hasPrefix:@"show-console"])
856 {
857 boolValue = OOBooleanFromObject(value, NO);
858 value = [NSNumber numberWithBool:boolValue];
859 }
860 }
861
862 return value;
863}
864
865
866- (oneway void)jsEngine:(in byref OOJavaScriptEngine *)engine
867 context:(in JSContext *)context
868 error:(in JSErrorReport *)errorReport
869 stackSkip:(in unsigned)stackSkip
870 showingLocation:(in BOOL)showLocation
871 withMessage:(in NSString *)message
872{
873 NSString *colorKey = nil;
874 NSString *prefix = nil;
875 NSString *filePath = nil;
876 NSString *sourceLine = nil;
877 NSString *scriptLine = nil;
878 NSMutableString *formattedMessage = nil;
879 NSRange emphasisRange;
880 NSString *showKey = nil;
881
882 if (_debugger == nil) return;
883
884 if (errorReport->flags & JSREPORT_WARNING)
885 {
886 colorKey = @"warning";
887 prefix = @"Warning";
888 }
889 else if (errorReport->flags & JSREPORT_EXCEPTION)
890 {
891 colorKey = @"exception";
892 prefix = @"Exception";
893 }
894 else
895 {
896 colorKey = @"error";
897 prefix = @"Error";
898 }
899
900 if (errorReport->flags & JSREPORT_STRICT)
901 {
902 prefix = [prefix stringByAppendingString:@" (strict mode)"];
903 }
904
905 // Prefix and subsequent colon should be bold:
906 emphasisRange = NSMakeRange(0, [prefix length] + 1);
907
908 formattedMessage = [NSMutableString stringWithFormat:@"%@: %@", prefix, message];
909
910 // Note that the "active script" isn't necessarily the one causing the
911 // error, since one script can call another's methods.
912
913 // avoid windows DEP exceptions!
915 scriptLine = [[thisScript weakRefUnderlyingObject] displayName];
916 [thisScript release];
917
918 if (scriptLine != nil)
919 {
920 [formattedMessage appendFormat:@"\n Active script: %@", scriptLine];
921 }
922
923 if (showLocation && stackSkip == 0)
924 {
925 // Append file name and line
926 if (errorReport->filename != NULL) filePath = [NSString stringWithUTF8String:errorReport->filename];
927 if ([filePath length] != 0)
928 {
929 [formattedMessage appendFormat:@"\n %@, line %u", [filePath lastPathComponent], errorReport->lineno];
930
931 // Append source code
932 sourceLine = [self sourceCodeForFile:filePath line:errorReport->lineno];
933 if (sourceLine != nil)
934 {
935 [formattedMessage appendFormat:@":\n %@", sourceLine];
936 }
937 }
938 }
939
940 [self appendJSConsoleLine:formattedMessage
941 colorKey:colorKey
942 emphasisRange:emphasisRange];
943
944 if (errorReport->flags & JSREPORT_WARNING) showKey = @"show-console-on-warning";
945 else showKey = @"show-console-on-error"; // if not a warning, it's a proper error.
946 if (OOBooleanFromObject([self configurationValueForKey:showKey], NO))
947 {
948 [self showJSConsole];
949 }
950}
951
952
953- (oneway void)jsEngine:(in byref OOJavaScriptEngine *)engine
954 context:(in JSContext *)context
955 logMessage:(in NSString *)message
956 ofClass:(in NSString *)messageClass
957{
958 [self appendJSConsoleLine:message colorKey:@"log"];
959 if (OOBooleanFromObject([self configurationValueForKey:@"show-console-on-log"], NO))
960 {
961 [self showJSConsole];
962 }
963}
964
965
966- (jsval)oo_jsValueInContext:(JSContext *)context
967{
968 if (_jsSelf == NULL)
969 {
970 _jsSelf = DebugMonitorToJSConsole(context, self);
971 if (_jsSelf != NULL)
972 {
973 if (!OOJSAddGCObjectRoot(context, &_jsSelf, "debug console"))
974 {
975 _jsSelf = NULL;
976 }
977 }
978 }
979
980 if (_jsSelf != NULL) return OBJECT_TO_JSVAL(_jsSelf);
981 else return JSVAL_NULL;
982}
983
984@end
985
986
987@implementation OODebugMonitor (Singleton)
988
989/* Canonical singleton boilerplate.
990See Cocoa Fundamentals Guide: Creating a Singleton Instance.
991See also +sharedDebugMonitor above.
992
993NOTE: assumes single-threaded access.
994*/
995
996+ (id)allocWithZone:(NSZone *)inZone
997{
998 if (sSingleton == nil)
999 {
1000 sSingleton = [super allocWithZone:inZone];
1001 return sSingleton;
1002 }
1003 return nil;
1004}
1005
1006
1007- (id)copyWithZone:(NSZone *)inZone
1008{
1009 return self;
1010}
1011
1012
1013- (id)retain
1014{
1015 return self;
1016}
1017
1018
1019- (NSUInteger)retainCount
1020{
1021 return UINT_MAX;
1022}
1023
1024
1025- (void)release
1026{}
1027
1028
1029- (id)autorelease
1030{
1031 return self;
1032}
1033
1034@end
1035
1036#endif /* NDEBUG */
#define DESTROY(x)
Definition OOCocoa.h:75
#define foreachkey(VAR, DICT)
Definition OOCocoa.h:353
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)
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:338
OOJavaScriptEngine * sharedEngine()
void removeGCObjectRoot:(JSObject **rootPtr)
NSSet * allTextures()
Definition OOTexture.m:405
NSString * name()
Definition OOTexture.m:429
NSArray * cachedTexturesByAge()
Definition OOTexture.m:399
size_t dataSize()
Definition OOTexture.m:418
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