// // GlkSession.m // CocoaGlk // // Created by Andrew Hunter on Wed Jun 11 2003. // Copyright (c) 2003 Andrew Hunter. All rights reserved. // #import "GlkSession.h" #import "GlkWindowView.h" #import "GlkFileStream.h" #import "GlkMemoryStream.h" #import "GlkStatus.h" #import "glkstart.h" @implementation GlkSession #define propI @"Gill Sans" #define propII @"Times-Italic" #define propIII @"Times-Bold" #define fixFont @"Courier" #define propSize 14 #define fixSize 12 NSString* GlkSessionKey = @"GlkSessionKey"; NSString* GlkStatusKey = @"GlkStatus"; NSString* GlkFlushYourBuffers = @"GlkFlushDemBuffers"; enum glkSessionConditions { GlkSessionNoEvents = 0, GlkSessionEventsWaiting }; #define runningInMainThread ([NSThread currentThread] == mainThread) // == Startup == + (void) initialize { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSDictionary *appDefaults = [NSDictionary dictionaryWithObject:NSHomeDirectory() forKey:@"SaveDirectory"]; [defaults registerDefaults:appDefaults]; } // == Initialisation == - (id) init { self = [super init]; if (self) { // Setup rootWindow = [[GlkWindow allocWithZone: [self zone]] initWithSession: self]; currentWindow = nil; currentStream = nil; currentWindow = [rootWindow retain]; currentStream = [[currentWindow stream] retain]; arrangeWindow = nil; mainThread = [NSThread currentThread]; mainRunLoop = [NSRunLoop currentRunLoop]; objectValue = nil; // Images imageCache = [[NSMutableArray allocWithZone: [self zone]] init]; imageCacheNumbers = [[NSMutableArray allocWithZone: [self zone]] init]; // Events eventQueue = [[NSMutableArray allocWithZone: [self zone]] init]; currentEvent = nil; // Threads mainthreadConnection = nil; glkthreadConnection = nil; port1 = nil; port2 = nil; // Create the user interface elements sessionWindow = [[NSWindow allocWithZone: [self zone]] initWithContentRect: NSMakeRect(100, 0, 800, 800) styleMask:NSTitledWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask|NSResizableWindowMask backing: NSBackingStoreBuffered defer: YES]; [sessionWindow setDelegate: self]; // Fonts fixedFont = [[NSFont fontWithName: fixFont size: fixSize] retain]; propFont = [[NSFont fontWithName: propI size: propSize] retain]; // Styles int x; for (x=0; x<style_NUMSTYLES; x++) { int y; for (y=0; y<stylehint_NUMHINTS; y++) { defaultHint[y][x] = 0; } defaultHint[stylehint_TextColor][x] = 0; defaultHint[stylehint_BackColor][x] = 0xffffff; defaultHint[stylehint_Justification][x] = stylehint_just_LeftFlush; defaultHint[stylehint_Proportional][x] = 1; } // Default stylehints defaultHint[stylehint_Weight][style_Subheader] = 1; defaultHint[stylehint_Weight][style_Alert] = 1; defaultHint[stylehint_Weight][style_Note] = -1; defaultHint[stylehint_Weight][style_BlockQuote] = 1; defaultHint[stylehint_Weight][style_Input] = 1; defaultHint[stylehint_Oblique][style_Emphasized] = 1; defaultHint[stylehint_Oblique][style_Alert] = 1; defaultHint[stylehint_Size][style_Alert] = 1; defaultHint[stylehint_Size][style_Header] = 4; defaultHint[stylehint_Size][style_Subheader] = 1; defaultHint[stylehint_Justification][style_Header] = stylehint_just_Centered; defaultHint[stylehint_Proportional][style_Preformatted] = 0; defaultHint[stylehint_Indentation][style_BlockQuote] = 10; for (x=0; x<style_NUMSTYLES; x++) { int y; for (y=0; y<stylehint_NUMHINTS; y++) { styleHint[y][x] = defaultHint[y][x]; } } // Setup the UI, display the window [sessionWindow setDelegate: self]; [sessionWindow setContentView: [rootWindow view]]; [sessionWindow orderFront: self]; } return self; } - (void) dealloc { [rootWindow release]; [currentWindow release]; [currentStream release]; [sessionWindow release]; [fixedFont release]; [propFont release]; if (mainthreadConnection) [mainthreadConnection release]; if (glkthreadConnection) [glkthreadConnection release]; if (port1) [port1 release]; if (port2) [port2 release]; if (objectValue) [objectValue release]; [imageCache release]; [imageCacheNumbers release]; [eventQueue release]; if (currentEvent) [currentEvent release]; [super dealloc]; } // == User functions == // ----------------------------------------------------------------------------- // glkMain: // Run the specified game in this session. This synthesizes a command line // based on the file path passed in and hands that to the user's // glkunix_startup_code() function. Since we only allow one running game // right now, this quits the application after that. // // REVISIONS: // 2004-03-13 witness Created. // ----------------------------------------------------------------------------- - (void) glkMain: (NSString*)filename { char* args[2]; glkunix_startup_t fakeArgs = { 2, args }; GlkStatus* stat = [[GlkStatus allocWithZone: [self zone]] init]; [stat autorelease]; [[[NSThread currentThread] threadDictionary] setObject: stat forKey: GlkStatusKey]; args[0] = [[[NSBundle mainBundle] executablePath] fileSystemRepresentation]; args[1] = [filename fileSystemRepresentation]; glkunix_startup_code( &fakeArgs ); glk_main(); [NSApp terminate: self]; } // ----------------------------------------------------------------------------- // _threadMain: // This hands on the file name of the game file to run to glkMain:. // // REVISIONS: // 2004-03-13 witness Added filename parameter. // ----------------------------------------------------------------------------- - (void) _threadMain: (NSString*)filename { threadPool = [[NSAutoreleasePool allocWithZone: [self zone]] init]; glkThread = [NSThread currentThread]; glkRunLoop = [NSRunLoop currentRunLoop]; [[glkThread threadDictionary] setObject: self forKey: GlkSessionKey]; [[NSRunLoop currentRunLoop] addPort: port2 forMode: NSDefaultRunLoopMode]; glkthreadConnection = [[NSConnection allocWithZone: [self zone]] initWithReceivePort: port2 sendPort: port1]; [glkthreadConnection setRootObject: self]; [self glkMain: filename]; [self exit]; } - (void) threadPoolFree { [threadPool release]; threadPool = [[NSAutoreleasePool allocWithZone: [self zone]] init]; } // ----------------------------------------------------------------------------- // queueTheMusic: // Actually starts this session and runs it in a separate thread. // This hands on the file name of the game file to run to _threadMain:. // // REVISIONS: // 2004-03-13 witness Added filename parameter. // ----------------------------------------------------------------------------- - (void) queueTheMusic: (NSString*)filename { if (mainthreadConnection != nil) { return; } mainThread = [NSThread currentThread]; mainRunLoop = [NSRunLoop currentRunLoop]; port1 = [[NSPort port] retain]; port2 = [[NSPort port] retain]; mainthreadConnection = [[NSConnection allocWithZone: [self zone]] initWithReceivePort: port1 sendPort: port2]; [mainthreadConnection setRootObject: self]; [NSThread detachNewThreadSelector: @selector(_threadMain:) toTarget: self withObject: filename]; } // == Session management == // Top-level glk functions - (void) exit { if (runningInMainThread) { NSLog(@"Calling exit from the main thread not implemented yet"); abort(); } else { [[NSNotificationCenter defaultCenter] postNotificationName: GlkFlushYourBuffers object: self]; [[self rootWindow] flushBuffer]; [[self rootWindow] updateOpportunity]; [[[NSThread currentThread] threadDictionary] removeObjectForKey: GlkSessionKey]; [[[NSThread currentThread] threadDictionary] removeObjectForKey: GlkStatusKey]; [threadPool release]; threadPool = [[NSAutoreleasePool allocWithZone: [self zone]] init]; [glkthreadConnection release]; [mainthreadConnection release]; [port1 release]; [port2 release]; glkthreadConnection = nil; mainthreadConnection = nil; port1 = nil; port2 = nil; [threadPool release]; [NSThread exit]; } } - (void) tick { NSDate* tock = [NSDate date]; [tock addTimeInterval: 0.01]; [[NSRunLoop currentRunLoop] acceptInputForMode: NSDefaultRunLoopMode beforeDate: tock]; } - (void) setInterruptHandler: (id) handler withSelector: (SEL) selector { BUG(@"*** BUG: FUNCTION NOT IMPLEMENTED\n"); } // Gestalt functions - (glui32) gestaltForSel: (glui32) sel withVal: (glui32) val { return [self gestaltForSel: sel withVal: val buf: NULL length: 0]; } - (glui32) gestaltForSel: (glui32) sel withVal: (glui32) val buf: (glui32*) buf length: (glui32) bufLen { switch (sel) { case gestalt_Version: return 0x00000601; case gestalt_CharInput: // Grr if (val > 32 || (glsi32)val < 0) return 1; else return 0; break; case gestalt_LineInput: if ((glsi32)val > 32 && (glsi32)val < 256) { return 1; } else { return 0; } break; case gestalt_CharOutput: if ((glsi32)val > 32 && (glsi32)val < 256) { if (buf) *buf = 1; return gestalt_CharOutput_ExactPrint; } else { if (buf) *buf = 0; return 0; } break; case gestalt_MouseInput: return 0; // IMPLEMENT ME :-) case gestalt_Timer: return 0; // DITTO case gestalt_Graphics: return 1; case gestalt_DrawImage: switch (val) { case 0: return 1; case wintype_Graphics: return 1; case wintype_TextBuffer: return 1; // IMPLEMENT ME case wintype_TextGrid: default: return 0; } break; case gestalt_GraphicsTransparency: return 1; case gestalt_Sound: case gestalt_SoundVolume: case gestalt_SoundNotify: case gestalt_Hyperlinks: case gestalt_HyperlinkInput: case gestalt_SoundMusic: default: return 0; // IMPLEMENT THIS LOT } } - (unsigned char) charToLower: (unsigned char) ch { BUG(@"*** BUG: FUNCTION NOT IMPLEMENTED\n"); } - (unsigned char) charToUpper: (unsigned char) ch { BUG(@"*** BUG: FUNCTION NOT IMPLEMENTED\n"); } // == Window management == - (GlkWindow*) rootWindow { if (runningInMainThread) { // Return the window in this thread return rootWindow; } else { // Return a reference to the window return [(GlkSession*)[glkthreadConnection rootProxy] rootWindow]; } // return rootWindow; } - (void) setGlkWindow: (GlkWindow*) window { if (currentWindow) [currentWindow autorelease]; currentWindow = [window retain]; [self setCurrentStream: [currentWindow stream]]; } // == Stream management == - (GlkStream*) openFile: (GlkFileRef*) ref withMode: (glui32) mode rock: (glui32) rock { GlkFileStream* str; str = [[GlkFileStream allocWithZone: [self zone]] initByOpeningFileRef: ref withMode: mode rock: rock]; if (str) { return [str autorelease]; } else { return nil; } } // ----------------------------------------------------------------------------- // openFileWithForcedName:usage:withMode:rock: // This method allows opening a GlkStream for *any* file. This is used // by glkunix_stream_open_pathname() to allow opening streams to game files // at startup. // // REVISIONS: // 2004-03-13 witness Created. // ----------------------------------------------------------------------------- -(GlkStream*) openFileWithForcedName: (NSString*) fname usage: (glui32)usage withMode: (glui32) mode rock: (glui32) rock { GlkFileStream* str; GlkFileRef* ref = [[GlkFileRef allocWithZone: [self zone]] initWithForcedName: fname withUsage: usage forSession: self]; str = [[GlkFileStream allocWithZone: [self zone]] initByOpeningFileRef: ref withMode: mode rock: rock]; if (str) { return [str autorelease]; } else { return nil; } } - (GlkStream*) openMemory: (char*) buf length: (glui32) buflen withMode: (glui32) mode rock: (glui32) rock { // Memory is owned by whoever creates the stream // Currently we ignore mode... GlkMemoryStream* str; str = [[GlkMemoryStream allocWithZone: [self zone]] initWithBuffer: buf length: buflen rock: rock]; return [str autorelease]; } - (void) setCurrentStream: (GlkStream*) stream { if (currentStream) [currentStream release]; currentStream = [stream retain]; } - (GlkStream*) currentStream { return currentStream; } - (void) putChar: (unsigned char) ch { [currentStream putChar: ch]; } - (void) putString: (const char*) s { [currentStream putString: [NSString stringWithCString: s]]; } - (void) putBuffer: (const char*) buf length: (glui32) len { [currentStream putBuffer: [NSData dataWithBytes: buf length: len]]; } - (void) setGlkStyle: (glui32) styl { [currentStream setGlkStyle: styl]; } - (void) setStyleHint: (glui32) winType style: (glui32) styl hint: (glui32) hint value: (glsi32) val { if (!runningInMainThread) { [(GlkSession*)[glkthreadConnection rootProxy] setStyleHint: winType style: styl hint: hint value: val]; return; } styleHint[hint][styl] = val; } - (void) clearStyleHint: (glui32) wintype style: (glui32) styl hint: (glui32) hint { if (!runningInMainThread) { [(GlkSession*)[glkthreadConnection rootProxy] clearStyleHint: wintype style: styl hint: hint]; return; } styleHint[hint][styl] = defaultHint[hint][styl]; } - (glsi32) hintForStyle: (glui32) styl hint: (glui32) hint winType: (glui32) wintype { return styleHint[hint][styl]; } // == Filerefs == - (GlkFileRef*) fileRefTemp: (glui32) rock withUsage: (glui32) usage { GlkFileRef* res = [[GlkFileRef allocWithZone: [self zone]] initWithTempFileForSession: self withUsage: usage]; if (res) { [res setRock: rock]; return [res autorelease]; } return nil; } - (GlkFileRef*) fileRefForName: (char*) name rock: (glui32) rock withUsage: (glui32) usage { GlkFileRef* res = [[GlkFileRef allocWithZone: [self zone]] initWithName: [NSString stringWithCString: name] withUsage: usage forSession: self]; if (res) { [res setRock: rock]; return [res autorelease]; } return nil; } - (void) doneSaving: (NSString*) filename { if (runningInMainThread) { [(GlkSession*)[mainthreadConnection rootProxy] doneSaving: filename]; } else { savePanelFinished = YES; if (filename) { savePanelFilename = [filename retain]; } else { savePanelFilename = nil; } } } - (GlkFileRef*) fileRefByPromptingForMode: (glui32) fmode rock: (glui32) rock withUsage: (glui32) usage { if (!runningInMainThread) { // Send a request that the dialog be shown savePanelFinished = NO; [(GlkSession*)[glkthreadConnection rootProxy] showSavePanelForMode: [NSNumber numberWithInt: fmode]]; // Wait for the dialog to finish while (!savePanelFinished) { NSAutoreleasePool* loopPool = [[NSAutoreleasePool allocWithZone: [self zone]] init]; [glkRunLoop acceptInputForMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]; [loopPool release]; } if (savePanelFilename) { GlkFileRef* ref; ref = [[GlkFileRef allocWithZone: [self zone]] initWithForcedName: savePanelFilename withUsage: usage forSession: self]; [savePanelFilename release]; return [ref autorelease]; } return nil; /* return [(GlkSession*)[glkthreadConnection rootProxy] fileRefByPromptingForMode: fmode rock: rock withUsage: usage]; */ } else { NSLog(@"fileRefByPromptingForMode cannot be run in the main thread"); return nil; } } - (GlkFileRef*) fileRefFromFileRef: (GlkFileRef*) ref rock: (glui32) rock withUsage: (glui32) usage { BUG(@"*** BUG: FUNCTION NOT IMPLEMENTED\n"); } // == Dealing with save/open panels == - (void) showSavePanelForMode: (NSNumber*) fm { glui32 fmode = [fm intValue]; NSSavePanel* thePanel = nil; BOOL openPanel = NO; switch (fmode) { case filemode_Write: case filemode_WriteAppend: thePanel = [NSSavePanel savePanel]; break; case filemode_Read: case filemode_ReadWrite: thePanel = [NSOpenPanel openPanel]; openPanel = YES; break; } if (thePanel == nil) { // Unknown mode [glkRunLoop performSelector: @selector(doneSaving:) target: self argument: nil order: 128 modes: [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSModalPanelRunLoopMode, nil]]; return; } // Display the panel if (!openPanel) { [thePanel beginSheetForDirectory:[[NSUserDefaults standardUserDefaults] objectForKey: @"SaveDirectory"] file:nil modalForWindow:sessionWindow modalDelegate:self didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:nil]; } else { [(NSOpenPanel*)thePanel beginSheetForDirectory:[[NSUserDefaults standardUserDefaults] objectForKey: @"SaveDirectory"] file:nil types:nil modalForWindow:sessionWindow modalDelegate:self didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:nil]; } } - (void)savePanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo { if (returnCode == NSOKButton) { [[NSUserDefaults standardUserDefaults] setObject: [sheet directory] forKey: @"SaveDirectory"]; [self doneSaving: [sheet filename]]; } else { [self doneSaving: nil]; } } - (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo { if (returnCode == NSOKButton) { [[NSUserDefaults standardUserDefaults] setObject: [sheet directory] forKey: @"SaveDirectory"]; [self doneSaving: [sheet filename]]; } else { [self doneSaving: nil]; } } // == The iteration functions == - (GlkWindow*) windowIterate: (GlkWindow*) win rock: (glui32*) rock { if (win == nil) { return rootWindow; } if ([win left]) { return [win left]; } else { GlkWindow* newWin, *lastWin; lastWin = win; newWin = [win parent]; while (newWin != nil && [newWin left] != lastWin) { lastWin = newWin; newWin = [newWin parent]; } if (newWin == nil) { return nil; } return [newWin right]; } } - (GlkStream*) streamIterate: (GlkStream*) stream rock: (glui32*) rock { BUG(@"*** BUG: FUNCTION NOT IMPLEMENTED\n"); } // == Glk events == - (void) _preSelect { [[NSNotificationCenter defaultCenter] postNotificationName: GlkFlushYourBuffers object: self]; if (arrangeWindow != nil) { // Queue an arrange event GlkEvent* evt = [GlkEvent eventWithType: evtype_Arrange win: arrangeWindow val1: 0 val2: 0]; arrangeWindow = nil; [self queueEvent: evt]; } } - (void) _wakeupThread { // Dummy method called to force an NSRunloop to retun wakeup = YES; } - (GlkEvent*) select: (event_t*) event { GlkEvent* theEvent; if (runningInMainThread) { NSLog(@"Warning: select: called from main thread"); } // Setup [self _preSelect]; // Flush any buffers [[self rootWindow] flushBuffer]; [[self rootWindow] updateOpportunity]; // Release any autoreleased objects that are pending [threadPool release]; threadPool = [[NSAutoreleasePool allocWithZone: [self zone]] init]; // Get the next event theEvent = [GlkEvent eventWithEvent: [self nextEvent]]; // Wait for an event to arrive (if none are waiting) while (theEvent == nil) { NSAutoreleasePool* loopPool = [[NSAutoreleasePool allocWithZone: [self zone]] init]; // Do the RunLoop thang (allow other threads to call us) [[NSRunLoop currentRunLoop] acceptInputForMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]; [loopPool release]; if (wakeup) { // Use eventWithEvent to create a local copy of the event theEvent = [GlkEvent eventWithEvent: [self nextEvent]]; wakeup = NO; } } if ([theEvent type] == evtype_LineInput) { GlkWindow* theWin = [theEvent win]; theEvent = [theWin cancelLineEvent]; } event->type = [theEvent type]; event->win = (winid_t)[theEvent win]; event->val1 = [theEvent val1]; event->val2 = [theEvent val2]; return theEvent; } - (GlkEvent*) selectPoll { // Run only in the main thread if (!runningInMainThread) { return [(GlkSession*)[glkthreadConnection rootProxy] selectPoll]; } NSEnumerator* evEnum = [eventQueue objectEnumerator]; GlkEvent* ev; while (ev = [evEnum nextObject]) { glui32 type = [ev type]; if (type != evtype_CharInput && type != evtype_LineInput && type != evtype_MouseInput) { break; } } if (ev == nil) { return nil; } [ev retain]; [eventQueue removeObjectIdenticalTo: ev]; return [ev autorelease]; } - (void) requestTimerEvents: (glui32) millisecs { BUG(@"*** BUG: FUNCTION NOT IMPLEMENTED\n"); } // == Housekeeping == - (void) setRootWindow: (GlkWindow*) newRoot { if (newRoot != nil) { [newRoot retain]; [rootWindow release]; rootWindow = newRoot; [[newRoot view] forceViewReorder]; [sessionWindow setContentView: [newRoot view]]; } else { [sessionWindow setContentView: nil]; [rootWindow release]; rootWindow = nil; } } - (void) windowNeedsArranging { [rootWindow windowNeedsArranging]; [[rootWindow view] setNeedsDisplay: YES]; } - (NSFont*) fixedPitchFont { return fixedFont; } - (NSFont*) proportionalFont { return propFont; } - (id) objectValue { return objectValue; } - (void) setObjectValue: (id) object { objectValue = [(NSObject*)object retain]; } // Threading - (BOOL) isMainThread { return [NSThread currentThread] == mainThread; } // The window - (NSWindow*) window { return sessionWindow; } // == Event handling == - (void) queueEvent: (GlkEvent*) event { if (!runningInMainThread) { [(GlkSession*)[glkthreadConnection rootProxy] queueEvent: event]; } else { if (event == nil) { NSLog(@"Attempt to queue nil event"); return; } [eventQueue addObject: event]; // Signal the thread if (mainthreadConnection) { [(GlkSession*)[mainthreadConnection rootProxy] _wakeupThread]; } } } - (void) cancelEventsForWindow: (GlkWindow*) win { if (!runningInMainThread) { NSLog(@"Warning: cancelEventsForWindow called from the wrong thread"); [(GlkSession*)[glkthreadConnection rootProxy] cancelEventsForWindow: win]; } else { if (currentEvent != nil && [win isEqualTo: [currentEvent win]]) { [currentEvent release]; currentEvent = nil; } NSEnumerator* evtEnum = [eventQueue objectEnumerator]; NSMutableArray* toRemove = [NSMutableArray array]; GlkEvent* evt; while (evt = [evtEnum nextObject]) { if ([win isEqualTo: [evt win]]) { [toRemove addObject: evt]; } } evtEnum = [toRemove objectEnumerator]; while (evt = [evtEnum nextObject]) { [eventQueue removeObjectIdenticalTo: evt]; } } } - (GlkEvent*) nextEvent { // Only execute this in the main thread if (!runningInMainThread) { return [(GlkSession*)[glkthreadConnection rootProxy] nextEvent]; } else { // Pop the next event if (currentEvent) { [currentEvent release]; currentEvent = nil; } if ([eventQueue count] <= 0) { // No events return nil; } currentEvent = [[eventQueue objectAtIndex: 0] retain]; [eventQueue removeObjectAtIndex: 0]; // Unlock the lock return currentEvent; } } - (void) windowHasBeenArranged: (GlkWindow*) win { if (!runningInMainThread) { NSLog(@"windowHasBeenArranged can only be called from the main thread"); return; } if (arrangeWindow != nil) { if (arrangeWindow != win) { arrangeWindow = rootWindow; } } else { arrangeWindow = win; } } // == Window events == - (void)windowDidResize:(NSNotification *)aNotification { if (arrangeWindow != nil) { // Queue an arrange event GlkEvent* evt = [GlkEvent eventWithType: evtype_Arrange win: arrangeWindow val1: 0 val2: 0]; arrangeWindow = nil; [self queueEvent: evt]; } } - (void)windowWillClose:(NSNotification *)aNotification { if (rootWindow) { [rootWindow close]; } if (rootWindow) { [rootWindow release]; rootWindow = nil; } [sessionWindow setContentView: nil]; if (currentWindow) { [currentWindow release]; currentWindow = nil; } if (currentStream) { [currentStream release]; currentStream = nil; } if (currentEvent) { [currentEvent release]; currentEvent = nil; } [sessionWindow setDelegate: nil]; sessionWindow = nil; [self release]; } // == Images == static const int IMAGECACHESIZE = 32; - (NSImage*) createImageResource: (glui32) image { return nil; } - (NSImage*) getImageResource: (glui32) image { if (!runningInMainThread) { return [(GlkSession*)[glkthreadConnection rootProxy] getImageResource: image]; } int cachePos = [imageCacheNumbers indexOfObject: [NSNumber numberWithInt: image]]; if (cachePos == NSNotFound) { // Create the image NSImage* img = [[self createImageResource: image] retain]; if (img == nil) { NSLog(@"Image %i not found", image); return nil; } [imageCache addObject: img]; [imageCacheNumbers addObject: [NSNumber numberWithInt: image]]; if ([imageCache count] > IMAGECACHESIZE) { [imageCache removeObjectAtIndex: 0]; [imageCacheNumbers removeObjectAtIndex: 0]; } return [img autorelease]; } else { // Get the image fromt the cache NSImage* img = [[imageCache objectAtIndex: cachePos] retain]; // Move the object to the front of the cache [imageCache removeObjectAtIndex: cachePos]; [imageCacheNumbers removeObjectAtIndex: cachePos]; [imageCache addObject: img]; [imageCacheNumbers addObject: [NSNumber numberWithInt: image]]; return [img autorelease]; } } - (void) uncacheImageResource: (glui32) image { int cachePos = [imageCacheNumbers indexOfObject: [NSNumber numberWithInt: image]]; if (cachePos != NSNotFound) { [imageCache removeObjectAtIndex: cachePos]; [imageCacheNumbers removeObjectAtIndex: cachePos]; } } - (NSImage*) logoImage { if (!runningInMainThread) { return [(GlkSession*)[glkthreadConnection rootProxy] logoImage]; } return [[[NSImage allocWithZone:[self zone]] initWithContentsOfFile: [[NSBundle mainBundle] pathForResource:@"logo" ofType:@"png" inDirectory:nil]] autorelease]; } @end |