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.