This is RoachesView.m in view mode; [Download] [Up]
/* RoachesView is Copyright 1994 by Brian Hill <bhill@physics.ucla.edu>. */ #import "Roach.h" #import "RoachesView.h" #import <dpsclient/dpswraps.h> #import "RoachWraps.h" #import "point.h" #ifdef TIMING #import "Timing.h" #endif #define WINDOW_TITLE "Icch!" #define INTERVAL 50000 /* Pacemaker interval in microseconds. */ void freeRectangles(windowRectangle *aWindowRectangle) { windowRectangle *nextOne; while ((nextOne = aWindowRectangle->next)) { free(aWindowRectangle); aWindowRectangle = nextOne; } free(aWindowRectangle); return; } BOOL RGBString2Color(const char *colorString, NXColor *color) { float rgb[3]; if (colorString == (char *)NULL || sscanf(colorString, "%f %f %f", &rgb[0], &rgb[1], &rgb[2]) != 3) { *color = NXConvertRGBToColor(1.0f, 1.0f, 1.0f); return NO; /* On error, color is white. */ } *color = NXConvertRGBToColor(rgb[0], rgb[1], rgb[2]); return YES; } /* colorString must hold MAX_LENGTH characters (counting null terminator). */ /* Assuming colors are non-negative, less than or equal to 1.0, and written */ /* with the default precision of 6, MAX_LENGTH = 27. */ #define MAX_LENGTH 27 void color2RGBString(const NXColor *color, char *colorString) { sprintf(colorString, "%f %f %f", NXRedComponent(*color), NXGreenComponent(*color), NXBlueComponent(*color)); return; } @implementation RoachesView - initFrame:(const NXRect *)frameRect { unsigned i, initialNumber; srandom((int)getpid()); /* pid_t is an int in NS. */ [super initFrame:frameRect]; ctxt = DPSGetCurrentContext(); [self allocateGState]; [self setClipping:NO]; [self clearSharedImages]; /* roachZone is not freeable for best performance so * must explicitly be destroyed in free method. */ roachZone = NXCreateZone(vm_page_size, vm_page_size, NO); /* In some sense, windowRectangles points to the -1st window in the list. * The only member of windowRectangles that is ever consulted is next. */ windowRectangles = (windowRectangle *)NXZoneMalloc(roachZone, sizeof(windowRectangle)); windowRectangles->next = (windowRectangle *)NULL; lastWindowRectangle = windowRectangles; [self readBackgroundColor]; [self readRoachesColor]; [self readRoachesSpeed]; initialNumber = [self readRoachesNumber]; roachList = [[List allocFromZone:roachZone] initCount:initialNumber]; for (i = 0; i < initialNumber; ++i) { [self addRoach]; } sizeInvalid = YES; backgroundColor = NX_COLORBLACK; #ifdef TIMING stderrStream = NXOpenFile(fileno(stderr), NX_WRITEONLY); timer0 = [[Timing newWithTag:0] reset]; timer1 = [[Timing newWithTag:1] reset]; timer2 = [[Timing newWithTag:2] reset]; timer10 = [[Timing newWithTag:10] reset]; timer11 = [[Timing newWithTag:11] reset]; timer12 = [[Timing newWithTag:12] reset]; timer3 = [[Timing newWithTag:3] reset]; #endif pacemaker =[[Pacemaker allocFromZone:roachZone] initInterval:(long)INTERVAL]; return self; } - readBackgroundColor { const char *colorString; if ([self shouldDrawColor]) { colorString = NXReadDefault("NeXT1", "BackgroundColor"); while (RGBString2Color(colorString, &backgroundColor) == NO) { /* Background color never set or dwrite botched. */ colorString = "0.333338 0.333338 0.466674"; } } else { colorString = NXReadDefault("NeXT1", "BWBackgroundColor"); while (RGBString2Color(colorString, &backgroundColor) == NO) { /* Black and white background color never set or dwrite botched. */ colorString = "0.333338 0.333338 0.333338"; /* Strange rounding. */ } } return self; } - readRoachesSpeed { const char *speedString; if (!(speedString = NXReadDefault("BackSpace", "RoachesSpeed"))) { speedString = SPEED_STRING; } sscanf(speedString, "%u", &speed); return self; } - (float)scaledSpeed { return scaledSpeed; } - (unsigned)readRoachesNumber { unsigned number; const char *numberString; if (!(numberString = NXReadDefault("BackSpace", "RoachesNumber"))) { numberString = NUMBER_STRING; } sscanf(numberString, "%u", &number); return number; } /* This only gets called once. roachesColor will only change when * the colorWell changes it. */ - readRoachesColor { const char *colorString; colorString = NXReadDefault("BackSpace", "RoachesColor"); if (colorString) { RGBString2Color(colorString, &roachesColor); } else { /* User's preference doesn't exist. Also, colorWell may not be * instantiated and connected yet, so constants defined in * RoachesView.h are used. */ roachesColor = COLORROACHCOLOR; } return self; } - (NXColor)backgroundColor { return backgroundColor; } - takeSpeedFrom:sender { char speedString[10]; speed = [sender intValue]; [speedField setIntValue:speed]; sprintf(speedString, "%d", speed); NXWriteDefault("BackSpace", "RoachesSpeed", speedString); return self; } - takeNumberFrom:sender { int oldNumber, newNumber; char numberString[10]; oldNumber = [roachList count]; newNumber = [sender intValue]; if (oldNumber < newNumber) { while (oldNumber++ < newNumber) { [self addRoach]; } sizeInvalid = YES; } else { while (oldNumber-- > newNumber) { [self deleteRoach:[roachList lastObject]]; } } sprintf(numberString, "%d", [roachList count]); NXWriteDefault("BackSpace", "RoachesNumber", numberString); return self; } - takeColorFrom:sender { unsigned i, count; char colorString[MAX_LENGTH]; roachesColor = [sender color]; [self clearSharedImages]; count = [roachList count]; for (i = 0; i < count; ++i) { [[roachList objectAt:i] setColor:roachesColor]; } color2RGBString(&roachesColor, colorString); NXWriteDefault("BackSpace", "RoachesColor", colorString); return self; } - (BOOL *)imageClean { return imageClean; } - (NXImage **)sharedImages { return sharedImages; } /* It will be necessary to call clearSharedImages in two places: (1) If the * Roaches color changes, and (2) if the background color changes. */ - clearSharedImages { unsigned i; for (i = 0; i < RESOLUTION; ++i) imageClean[i] = NO; return self; } - (BOOL)useBufferedWindow { return NO; /* On my non-turbo color machine, a full-screen buffered window takes * annoyingly long to redraw, so I am back to unbuffered windows. * To reduce flicker in this case, I have interleaved the draw and * erase messages in oneStep. This necessitated a change in the * Roach okPosition method: the position cannot conflict with either * the present or the previous position. */ } - addRoach; { id newRoach; newRoach = [Roach allocFromZone:roachZone]; [roachList addObject:newRoach]; [[newRoach initRoachesView:self] setColor:roachesColor]; if (numberField && numberSlider) { /* Only try to set if instantiated. */ [numberField setIntValue:[roachList count]]; [numberSlider setIntValue:[roachList count]]; } return newRoach; } - deleteRoach:roachGettingDeleted { /* The following combination erases the last drawn image. */ [self lockFocus]; PSsetrgbcolor(NXRedComponent(backgroundColor), NXGreenComponent(backgroundColor), NXBlueComponent(backgroundColor)); [roachGettingDeleted erase]; [self unlockFocus]; [roachList removeObject:roachGettingDeleted]; [roachGettingDeleted free]; if (numberField && numberSlider) { /* Only try to set if instantiated. */ [numberField setIntValue:[roachList count]]; [numberSlider setIntValue:[roachList count]]; } return self; } - free { int i; freeRectangles(windowRectangles); /* free instances of list */ [[roachList freeObjects] free]; for (i = 0; i < RESOLUTION; ++i) { if (sharedImages[i]) [sharedImages[i] free]; } [pacemaker free]; NXDestroyZone(roachZone); return [super free]; } - repositionRoaches { unsigned i; for (i = 0; i < [roachList count]; ++i) { id roachToReposition; roachToReposition = [roachList objectAt:i]; if (![roachToReposition reposition]) { [self deleteRoach:roachToReposition]; i--; /* Do object at i over. */ } } return self; } /* Update one windowRectangle. */ - update { int yesOrNo; /* psops.h says PSgetboolean takes a pointer to an int. */ float x, y, width, height; int windowNumber; #ifdef TIMING [timer2 enter:PSTIME]; #endif RPSupdate(next, &yesOrNo, &x, &y, &width, &height, &windowNumber, ¤tWindowNumber); if (yesOrNo == YES) { /* Malloc space if necessary */ if (!lastWindowRectangle->next) { lastWindowRectangle->next = (windowRectangle *)NXZoneMalloc(roachZone, sizeof(windowRectangle)); lastWindowRectangle->next->next = (windowRectangle *)NULL; } lastWindowRectangle->next->rectangle.size.height = height; lastWindowRectangle->next->rectangle.size.width = width; lastWindowRectangle->next->rectangle.origin.y = y; lastWindowRectangle->next->rectangle.origin.x = x; lastWindowRectangle->next->number = windowNumber; lastWindowRectangle = lastWindowRectangle->next; next++; } else { if (lastWindowRectangle->next) { freeRectangles(lastWindowRectangle->next); } lastWindowRectangle->next = (windowRectangle *)NULL; lastWindowRectangle = windowRectangles; next = 0; } #ifdef TIMING [timer2 leave]; #endif return self; } - oneStep { /* For this particular screen saver, the behavior depends on the position * of other application's windows. Hence it must be done while in contact * with the window server. drawSelf does a fallback amount of drawing if * I'm not on screen. Otherwise, it just calls this method. this method * is called directly by BackSpace, but only when I'm on screen. BackSpace * does lockFocus before calling this method. */ unsigned i, count; NXRect windowFrame; /* Make sure roachesViewOffset is sensibly set here. Probably should put a * hook into moveTo:: or something so that roachesViewOffset gets * recalculated less often. RoachesViewOffset is the RoachesView origin * in Window coordinates plus the Window origin in screen coordinates. */ #ifdef TIMING [timer1 enter:PSTIME]; [timer10 enter:PSTIME]; #endif zero(&roachesViewOffset); [self convertPoint:&roachesViewOffset toView:nil]; [window getFrame:&windowFrame]; translate(&roachesViewOffset, &windowFrame.origin); if (sizeInvalid == YES) { [self repositionRoaches]; sizeInvalid = NO; } #ifdef TIMING [timer10 leave]; [timer11 enter:PSTIME]; #endif [roachList makeObjectsPerform:@selector(oneStep)]; PSsetrgbcolor(NXRedComponent(backgroundColor), NXGreenComponent(backgroundColor), NXBlueComponent(backgroundColor)); PSsetexposurecolor(); [self update]; count = [roachList count]; #ifdef TIMING [timer11 leave]; [timer12 enter:PSTIME]; #endif { long actualInterval; float ratio; actualInterval = [pacemaker doze]; if (actualInterval > 2 * INTERVAL) { ratio = 2.0f; } else { ratio = (float)actualInterval / (float)INTERVAL; } scaledSpeed = ratio * speed; } for (i = 0; i < count; ++i) { [[roachList objectAt:i] prepareToDraw]; [[roachList objectAt:i] erase]; [[roachList objectAt:i] compositeDraw]; } #ifdef TIMING [timer12 leave]; [timer1 leave]; #endif /* Timing results (20 Roaches--non-turbo color): (gdb) print [timer0 summary:stderrStream] Timer 0 : 247 trials App: 2.576158 Server: 0.976000 Percent Server: 0.274763 Total: 3.552158 $8 = (struct Timing *) 0x18bee0 (gdb) print [timer1 summary:stderrStream] Timer 1 : 247 trials App: 1.515907 Server: 1.584000 Percent Server: 0.510983 Total: 3.099907 $9 = (struct Timing *) 0x17d9b4 (gdb) print [timer20 summary:stderrStream] Timer 10 : 4940 trials App: 1.312533 Server: 7.856000 Percent Server: 0.856844 Total: 9.168533 $10 = (struct Timing *) 0x17da64 (gdb) print [timer21 summary:stderrStream] Timer 11 : 4940 trials App: 0.875321 Server: 9.040000 Percent Server: 0.911720 Total: 9.915321 $11 = (struct Timing *) 0x17db14 (gdb) print [timer22 summary:stderrStream] Timer 12 : 4940 trials App: 1.297191 Server: 5.440000 Percent Server: 0.807458 Total: 6.737191 $12 = (struct Timing *) 0x17dbc4 Timer 10's contribution will become a negligible part of the total after a large number of oneStep's. */ return self; } - (BOOL)okPosition:(const NXPoint *)position :(NXCoord)size { /* Many Roaches will execute okPosition:: and positionExposed:: with * each invocation of oneStep. The relevant RoachesView instance * variables are therefore updated in oneStep. */ unsigned i, count, flag; flag = 0; count = [roachList count]; for (i = 0; i < count; ++i) { if ([[roachList objectAt:i] okPosition:position :size] == NO && ++flag > 1) return NO; } /* Flag ought to be precisely one now, but why check. */ return insideBounds(position, &bounds, size); } - (int)positionExposed:(const NXPoint *)newPosition :(NXCoord)size { windowRectangle *wRect; NXPoint newPositionInScreenCoordinates; set(&newPositionInScreenCoordinates, newPosition); translate(&newPositionInScreenCoordinates, &roachesViewOffset); wRect = windowRectangles; while ((wRect = wRect->next) && wRect->number != currentWindowNumber) { if (insideBounds(&newPositionInScreenCoordinates,&wRect->rectangle,size)) { return HIDDEN; } } return EXPOSED; /* Note that a Roach may actually be hidden by two windows, yet not be * hidden by either of them separately. In this case, positionExposed:: * would return EXPOSED, and the Roach would move along, until a single * window was by itself hiding it. */ } - drawSelf:(const NXRect *)rects :(int)rectCount { NXColor oldBColor; oldBColor = backgroundColor; /* Make background color user's background color, but still * black when window is on top. drawSelf:: gets used a lot. * I should figure out a time that is used less often to do * the following checks. On the other hand, the frequent * checks give immediate feedback if the user changes the * background preference. */ #ifdef TIMING [timer0 enter:PSTIME]; #endif { int saveNext; windowRectangle *saveLastWindowRectangle; saveNext = next; next = 0; saveLastWindowRectangle = lastWindowRectangle; lastWindowRectangle = windowRectangles; [self update]; next = saveNext; lastWindowRectangle = saveLastWindowRectangle; } if (currentWindowNumber == windowRectangles->next->number) { backgroundColor = NX_COLORBLACK; } else { /* Running as background (or as normal window). Background color is * user's background color. Reread user's default. */ [self readBackgroundColor]; } /* The following has to be done in every drawSelf::. If I just do it * in one, the grestore which follows the drawSelf:: loses it. This is * also why this sequence is duplicated in oneStep. */ PSsetrgbcolor(NXRedComponent(backgroundColor), NXGreenComponent(backgroundColor), NXBlueComponent(backgroundColor)); PSsetexposurecolor(); if (NXRedComponent(backgroundColor) != NXRedComponent(oldBColor) || NXGreenComponent(backgroundColor) != NXGreenComponent(oldBColor) || NXBlueComponent(backgroundColor) != NXBlueComponent(oldBColor)) { /* [self clearSharedImages]; */ /* No longer necessary now that Roach background color is clear. */ NXRectFill(&bounds); return self; } if (rectCount == 1) { NXRectFill(rects); } else { ++rects; rectCount = 2; NXRectFillList(rects, rectCount); } #ifdef TIMING [timer0 leave]; #endif return self; } - removeFromSuperview /* - windowChanged:newWindow */ /* This didn't work. */ { const NXColor black = NX_COLORBLACK; [self lockFocus]; PSsetrgbcolor(NXRedComponent(black), NXGreenComponent(black), NXBlueComponent(black)); PSsetexposurecolor(); [self unlockFocus]; return [super removeFromSuperview /* windowChanged:newWindow */ ]; } - sizeTo:(NXCoord)width :(NXCoord)height { sizeInvalid = YES; return [super sizeTo:width :height]; } - inspector:sender { char buf[MAXPATHLEN]; if (!inspector) { [NXBundle getPath:buf forResource:"RoachesView" ofType:"nib" inDirectory:[sender moduleDirectory:"Roaches"] withVersion:0]; [NXApp loadNibFile:buf owner:self withNames:NO]; } [colorWell setColor:roachesColor]; [numberSlider setIntValue:[roachList count]]; [numberField setIntValue:[roachList count]]; [speedSlider setIntValue:speed]; [speedField setIntValue:speed]; return inspector; } - (const char *)windowTitle { return WINDOW_TITLE; } - fillBoundsWithBlack { /* if ([self canDraw]) { [self lockFocus]; PSsetrgbcolor(NXRedComponent(backgroundColor), NXGreenComponent(backgroundColor), NXBlueComponent(backgroundColor)); NXRectFill(&bounds); [self unlockFocus]; } */ return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.