|
|
1.1 root 1: /************************************************************************
2: *
3: * lfmsg.c
4: *
5: * Message handling subroutines for the LineFractal window class.
6: *
7: * Created by Microsoft Corp., 1988
8: ************************************************************************/
9:
10: #define INCL_WIN
11: #define INCL_GPI
12: #define INCL_DOSPROCESS
13: #define INCL_DOSSEMAPHORES
14: #include <os2.h>
15:
16: #include <stdio.h>
17: #include <stdlib.h>
18: #include <math.h>
19: #include "linefrac.h"
20:
21:
22:
23:
24: /************************************************************************
25: *
26: * Function prototypes.
27: *
28: * These add some error checking to function calls, and prevent
29: * forward references to functions of undefined return type.
30: *
31: ************************************************************************/
32:
33: VOID MySetWindowUShort(HWND, USHORT, USHORT);
34: VOID MySetWindowLong(HWND, USHORT, LONG);
35: USHORT MyGetWindowUShort(HWND, USHORT);
36: LONG MyGetWindowLong(HWND, USHORT);
37:
38:
39:
40:
41: /************************************************************************
42: *
43: * Global Variables
44: *
45: *
46: * hAB Anchor block handle. Needed only for some obscure mainframe
47: * compatability issue.
48: * hwndLineFrac
49: * Our main window handle.
50: * hwndLineFracFrame
51: * The handle to our window frame (to get at menus, etc.).
52: *
53: * LineFracPS Presentation Space for the background accumulation thread.
54: * LineFracRc Holds the dimensions of the bitmap we draw into.
55: * LineFracBM Handle to our bitmap. Needed only during creation and
56: * destruction.
57: * LineFracDC Handle to the DC we've selected our bitmap into. Needed
58: * only during creation and destruction.
59: * fHaveBitmap TRUE ==> a bitmap was successfully created for the second
60: * thread to draw into.
61: * fHaveAccumThread
62: * TRUE ==> the point accumulation thread was successfully
63: * created.
64: *
65: * tidAccumFrac
66: * Thread ID for the point accumulation thread.
67: * abAccumStack
68: * Stack for the ...
69: *
70: * usCurrentXform
71: * The menu command id of the currently selected transform.
72: * LineFracXform
73: * The current similarity transform used when drawing.
74: * usRecursion The currently selected depth of recursion.
75: * usPolygonSides
76: * Number of sides to the polygonal frame of "unit
77: * intervals". The fractal transformation is
78: * applied to each line segment of the frame.
79: *
80: * lSemAccumulateFractal
81: * Semaphore controlling the point accumulation thread.
82: * Clear it to start another fractal.
83: * fInterrupted
84: * TRUE ==> abort current fractal.
85: * fClearBetween
86: * TRUE ==> clear drawing surface (bitmap) between fractals.
87: * fAutoScale TRUE ==> resize our bitmap to match the client rectangle.
88: *
89: * xscale These transform each point accumulated, so
90: * yscale that the drawing fits into the window,
91: * xoff with the original unit interval at the center.
92: * yoff
93: *
94: * lColorBack Background color for fractal.
95: * lColor Line foreground color.
96: * usStyle Line style.
97: * usMixMode Line foreground mix mode.
98: *
99: * XformFuncs Array of built-in transform-defining functions.
100: *
101: ************************************************************************/
102:
103: #define CCHSTR 12 /* work buffer size for MyGetWindowLong, etc */
104:
105: HAB hAB;
106: HWND hwndLineFrac;
107: HWND hwndLineFracFrame;
108:
109: HPS LineFracPS = NULL;
110: RECTL LineFracRc = { 0L, 0L, 0L, 0L };
111: HBITMAP LineFracBM = NULL;
112: HDC LineFracDC = NULL;
113: BOOL fHaveBitmap = FALSE;
114: BOOL fHaveAccumThread = FALSE;
115:
116: #define SIZE_ACCUM_STACK 2048
117: TID tidAccumFrac;
118: CHAR abAccumStack[SIZE_ACCUM_STACK];
119:
120: USHORT usCurrentXform = IDMSAWTOOTH;
121: PLINEFRAC LineFracXform = NULL;
122: USHORT usRecursion = 1;
123: USHORT usPolygonSides = 3;
124: USHORT cptMax = MAX_POINT_COUNT;
125:
126: LONG lSemAccumulateFractal = NULL;
127: BOOL fInterrupted = FALSE;
128: BOOL fClearBetween = TRUE;
129: BOOL fAutoScale = TRUE;
130:
131: double xscale = 250.0;
132: double yscale = 220.0;
133: double xoff = 30.0;
134: double yoff = 30.0;
135:
136: LONG lColorBack = CLR_BLACK;
137: LONG lColor = CLR_BLUE;
138: USHORT usStyle = LINETYPE_SOLID;
139: USHORT usMixMode = FM_OVERPAINT;
140:
141: typedef PLINEFRAC (* PFPLF)(); /* "ptr to func returning ptr to LINEFRAC" */
142: PFPLF XformFuncs[] =
143: {
144: DefineSharkTooth,
145: DefineSawTooth,
146: DefineKochIsland,
147: DefineStovePipe,
148: DefineEsses
149: };
150:
151:
152:
153:
154: /************************************************************************
155: *
156: * LineFracInit
157: *
158: * Set up a bitmap for the second thread to draw into. Then set
159: * up the second thread itself. Set the flag so we don't do this
160: * twice. Lower the priority of the background thread to IDLETIME,
161: * so that it won't slow down user-interface response.
162: *
163: * Initialize the current transform. Post a message to cause the
164: * transform to actually be defined.
165: *
166: ************************************************************************/
167:
168: VOID
169: LineFracInit(hWnd)
170: HWND hWnd;
171: {
172: if (fHaveBitmap = ResizeBitmap(hWnd))
173: {
174: DosSemSet( &lSemAccumulateFractal );
175: if (fHaveAccumThread = !DosCreateThread( AccumulateLineFractal,
176: &tidAccumFrac,
177: abAccumStack + sizeof(abAccumStack)));
178: DosSetPrty(PRTYS_THREAD, PRTYC_IDLETIME, 0, tidAccumFrac);
179: WinPostMsg(hWnd, WM_COMMAND, (MPARAM)usCurrentXform, (MPARAM)0L);
180: }
181: }
182:
183:
184:
185:
186: /************************************************************************
187: *
188: * LineFracExit
189: *
190: * If we have a bitmap lying around, delete it.
191: *
192: ************************************************************************/
193:
194: VOID
195: LineFracExit()
196: {
197: if (fHaveBitmap)
198: {
199: GpiSetBitmap(LineFracPS, NULL);
200: GpiDeleteBitmap(LineFracBM);
201: GpiDestroyPS(LineFracPS);
202: DevCloseDC(LineFracDC);
203: }
204: }
205:
206:
207:
208:
209: /************************************************************************
210: *
211: * ResizeBitmap
212: *
213: * Destroy the current bitmap and create a new one the size of the
214: * current client rectangle.
215: *
216: ************************************************************************/
217:
218: BOOL
219: ResizeBitmap(hWnd)
220: HWND hWnd;
221: {
222: SIZEL size;
223: BITMAPINFOHEADER bminfo;
224:
225:
226: if (fHaveBitmap)
227: {
228: GpiSetBitmap(LineFracPS, NULL);
229: GpiDeleteBitmap(LineFracBM);
230: GpiDestroyPS(LineFracPS);
231: DevCloseDC(LineFracDC);
232: }
233:
234: LineFracDC = DevOpenDC(hAB, OD_MEMORY, "*", 0L, NULL, NULL);
235: if (!LineFracDC)
236: return FALSE;
237:
238: size.cx = 0L;
239: size.cy = 0L;
240: LineFracPS = GpiCreatePS(hAB, LineFracDC, &size,
241: PU_PELS|GPIT_MICRO|GPIA_ASSOC);
242: if (!LineFracPS)
243: {
244: DevCloseDC(LineFracDC);
245: return FALSE;
246: }
247:
248:
249: bminfo.cbFix = sizeof(BITMAPINFOHEADER);
250: WinQueryWindowRect(hWnd, &LineFracRc);
251: bminfo.cx = (USHORT) (LineFracRc.xRight - LineFracRc.xLeft);
252: bminfo.cy = (USHORT) (LineFracRc.yTop - LineFracRc.yBottom);
253: bminfo.cPlanes = 1;
254: bminfo.cBitCount = 4;
255: LineFracBM = GpiCreateBitmap(LineFracPS, &bminfo, 0L, 0L, 0L);
256: if (!LineFracBM)
257: {
258: GpiDestroyPS(LineFracPS);
259: DevCloseDC(LineFracDC);
260: return FALSE;
261: }
262:
263: GpiSetBitmap(LineFracPS, LineFracBM);
264: WinFillRect(LineFracPS, &LineFracRc, lColorBack);
265:
266:
267: /* Compute the scale and translation parameters from the
268: * current client rectangle.
269: */
270:
271: xoff = (LineFracRc.xRight - LineFracRc.xLeft) * 0.125;
272: yoff = (LineFracRc.yTop - LineFracRc.yBottom) * 0.5;
273: xscale = xoff * 6.0;
274: yscale = (LineFracRc.yTop - LineFracRc.yBottom) * 0.75;
275:
276: return TRUE;
277: }
278:
279:
280:
281:
282: /************************************************************************
283: *
284: * LineFracCommand
285: *
286: * Process menu commands related to line fractals.
287: *
288: ************************************************************************/
289:
290: VOID
291: LineFracCommand(hWnd, id)
292: HWND hWnd;
293: USHORT id;
294: {
295: switch (id)
296: {
297: case IDMABOUT:
298: WinDlgBox( HWND_DESKTOP, hWnd, (PFNWP)AboutDlg, NULL,
299: IDD_ABOUT, NULL );
300: break;
301:
302: case IDMFRACATTRS:
303: WinDlgBox( HWND_DESKTOP, hWnd, (PFNWP)LineFracDlg, NULL,
304: IDD_LINEFRAC, NULL );
305: break;
306:
307: case IDMCLEARBITMAP:
308: if (fHaveBitmap)
309: {
310: WinFillRect(LineFracPS, &LineFracRc, lColorBack);
311: WinInvalidateRect(hwndLineFrac, &LineFracRc, FALSE);
312: }
313: break;
314:
315: case IDMAUTOSCALE:
316: fAutoScale = !fAutoScale;
317: WinSendDlgItemMsg( hwndLineFracFrame, FID_MENU,
318: MM_SETITEMATTR, MPFROM2SHORT(IDMAUTOSCALE, TRUE),
319: MPFROM2SHORT(MIA_CHECKED, fAutoScale ? MIA_CHECKED : NULL));
320: break;
321:
322: case IDMSHARKTOOTH:
323: case IDMSAWTOOTH:
324: case IDMKOCH:
325: case IDMSTOVE:
326: case IDMESSES:
327: LineFracXform = (*(XformFuncs[id - IDMSHARKTOOTH]))();
328: WinSendDlgItemMsg( hwndLineFracFrame, FID_MENU,
329: MM_SETITEMATTR, MPFROM2SHORT(usCurrentXform, TRUE),
330: MPFROM2SHORT(MIA_CHECKED, NULL));
331: usCurrentXform = id;
332: WinSendDlgItemMsg( hwndLineFracFrame, FID_MENU,
333: MM_SETITEMATTR, MPFROM2SHORT(usCurrentXform, TRUE),
334: MPFROM2SHORT(MIA_CHECKED, MIA_CHECKED));
335:
336: case IDMREDRAW:
337: DosSemClear(&lSemAccumulateFractal);
338:
339: case IDMABORT:
340: fInterrupted = TRUE; /* suicide flag for pt accumulation */
341: break;
342: }
343: }
344:
345:
346:
347:
348: /************************************************************************
349: *
350: * LineFracPaint
351: *
352: * If we have a bitmap, blit it to the screen, no matter what state
353: * it's in.
354: *
355: ************************************************************************/
356:
357: VOID
358: LineFracPaint(hPS)
359: HPS hPS;
360: {
361: POINTL bbp[4];
362:
363: if (fHaveBitmap)
364: {
365: bbp[0].x = 0L; bbp[0].y = 0L;
366: bbp[1].x = LineFracRc.xRight; bbp[1].y = LineFracRc.yTop;
367: bbp[2].x = 0L; bbp[2].y = 0L;
368:
369: GpiBitBlt(hPS, LineFracPS, 3L, bbp, ROP_SRCCOPY, (LONG)NULL);
370: }
371: }
372:
373:
374:
375:
376: /************************************************************************
377: *
378: * DefineSharkTooth
379: *
380: * Set up the similarity transform for the following linefractal,
381: * which looks roughly like:
382: *
383: *
384: * *
385: * ===> * *
386: * * *
387: * *************** * *
388: *
389: ************************************************************************/
390:
391: PLINEFRAC
392: DefineSharkTooth()
393: {
394: double ang;
395: double len;
396: static LINEFRAC shark[3];
397:
398: ang = PI / 6.0;
399: len = 1.0 / sqrt(3.0);
400:
401:
402: shark[0].angle = ang;
403: shark[0].length = len;
404: shark[0].flip = FALSE;
405: shark[0].next = &(shark[1]);
406:
407: shark[1].angle = -ang * 2.0;
408: shark[1].length = len;
409: shark[1].flip = FALSE;
410: shark[1].next = EOLIST;
411:
412: return &(shark[0]);
413: }
414:
415:
416:
417:
418: /************************************************************************
419: *
420: * DefineSawTooth
421: *
422: * Set up the similarity transform for the following linefractal,
423: * which looks roughly like:
424: *
425: *
426: *
427: * ===> *
428: * * *
429: * *************** * * *
430: * * *
431: * *
432: *
433: ************************************************************************/
434:
435: PLINEFRAC
436: DefineSawTooth()
437: {
438: double ang;
439: double len;
440: static LINEFRAC saw[3];
441:
442: ang = PI / 4.0;
443: len = 1.0 / sqrt(2.0);
444:
445:
446: saw[0].angle = ang;
447: saw[0].length = len / 2.0;
448: saw[0].flip = FALSE;
449: saw[0].next = &(saw[1]);
450:
451: saw[1].angle = -ang * 2.0;
452: saw[1].length = len;
453: saw[1].flip = FALSE;
454: saw[1].next = &(saw[2]);
455:
456: saw[2].angle = ang * 2.0;
457: saw[2].length = len / 2.0;
458: saw[2].flip = FALSE;
459: saw[2].next = EOLIST;
460:
461: return &(saw[0]);
462: }
463:
464:
465:
466:
467: /************************************************************************
468: *
469: * DefineKochIsland
470: *
471: * Set up the similarity transform for the following linefractal,
472: * which looks roughly like:
473: *
474: * This is known as the Koch, or snowflake, transform.
475: *
476: *
477: * *
478: * ===> * *
479: * * *
480: * *************** ***** *****
481: *
482: ************************************************************************/
483:
484: PLINEFRAC
485: DefineKochIsland()
486: {
487: double ang;
488: double len;
489: static LINEFRAC koch[4];
490:
491: ang = PI / 3.0;
492: len = 1.0 / 3.0;
493:
494:
495: koch[0].angle = 0.0;
496: koch[0].length = len;
497: koch[0].flip = FALSE;
498: koch[0].next = &(koch[1]);
499:
500: koch[1].angle = ang;
501: koch[1].length = len;
502: koch[1].flip = FALSE;
503: koch[1].next = &(koch[2]);
504:
505: koch[2].angle = -2.0 * ang;
506: koch[2].length = len;
507: koch[2].flip = FALSE;
508: koch[2].next = &(koch[3]);
509:
510: koch[3].angle = ang;
511: koch[3].length = len;
512: koch[3].flip = FALSE;
513: koch[3].next = EOLIST;
514:
515: return &(koch[0]);
516: }
517:
518:
519:
520:
521: /************************************************************************
522: *
523: * DefineStovePipe
524: *
525: * Set up the similarity transform for the following linefractal,
526: * which looks roughly like:
527: *
528: * This is sometimes known as the stovepipe transform.
529: *
530: *
531: * ******
532: * * *
533: * ===> * *
534: * * *
535: * ************** ***** *****
536: *
537: ************************************************************************/
538:
539: PLINEFRAC
540: DefineStovePipe()
541: {
542: double ang;
543: double len;
544: static LINEFRAC Stove[5];
545:
546:
547: ang = PI / 2.0;
548: len = 1.0 / 3.0;
549:
550:
551: Stove[0].angle = 0.0;
552: Stove[0].length = len;
553: Stove[0].flip = FALSE;
554: Stove[0].next = &(Stove[1]);
555:
556: Stove[1].angle = ang;
557: Stove[1].length = len;
558: Stove[1].flip = FALSE;
559: Stove[1].next = &(Stove[2]);
560:
561: Stove[2].angle = -ang;
562: Stove[2].length = len;
563: Stove[2].flip = FALSE;
564: Stove[2].next = &(Stove[3]);
565:
566: Stove[3].angle = -ang;
567: Stove[3].length = len;
568: Stove[3].flip = FALSE;
569: Stove[3].next = &(Stove[4]);
570:
571: Stove[4].angle = ang;
572: Stove[4].length = len;
573: Stove[4].flip = FALSE;
574: Stove[4].next = EOLIST;
575:
576: return &(Stove[0]);
577: }
578:
579:
580:
581:
582:
583: /************************************************************************
584: *
585: * DefineEsses
586: *
587: * Set up the similarity transform for the following linefractal,
588: * which looks roughly like:
589: *
590: *
591: * ******
592: * * *
593: * ************** ===> * * *
594: * * *
595: * ******
596: *
597: ************************************************************************/
598:
599: PLINEFRAC
600: DefineEsses()
601: {
602: double ang;
603: static LINEFRAC Esses[5];
604:
605:
606: ang = PI / 2.0;
607:
608:
609: Esses[0].angle = ang;
610: Esses[0].length = 0.25;
611: Esses[0].flip = FALSE;
612: Esses[0].next = &(Esses[1]);
613:
614: Esses[1].angle = -ang;
615: Esses[1].length = 0.5;
616: Esses[1].flip = FALSE;
617: Esses[1].next = &(Esses[2]);
618:
619: Esses[2].angle = -ang;
620: Esses[2].length = 0.5;
621: Esses[2].flip = FALSE;
622: Esses[2].next = &(Esses[3]);
623:
624: Esses[3].angle = ang;
625: Esses[3].length = 0.5;
626: Esses[3].flip = FALSE;
627: Esses[3].next = &(Esses[4]);
628:
629: Esses[4].angle = ang;
630: Esses[4].length = 0.25;
631: Esses[4].flip = FALSE;
632: Esses[4].next = EOLIST;
633:
634: return &(Esses[0]);
635: }
636:
637:
638:
639:
640: /************************************************************************
641: *
642: * LineFracDlg
643: *
644: * Process messages for the fractal attributes dialog box.
645: *
646: ************************************************************************/
647:
648: ULONG FAR PASCAL
649: LineFracDlg( hWndDlg, message, mp1, mp2 )
650: HWND hWndDlg;
651: USHORT message;
652: MPARAM mp1;
653: MPARAM mp2;
654: {
655: switch (message)
656: {
657: case WM_INITDLG:
658: MySetWindowLong (hWndDlg, IDDCOLORBK, lColorBack);
659: MySetWindowLong (hWndDlg, IDDCOLOR, lColor);
660: MySetWindowUShort(hWndDlg, IDDSTYLE, usStyle);
661: MySetWindowUShort(hWndDlg, IDDMIX, usMixMode);
662: MySetWindowUShort(hWndDlg, IDDNUMSIDES, usPolygonSides);
663: MySetWindowUShort(hWndDlg, IDDCPTMAX, cptMax);
664: MySetWindowUShort(hWndDlg, IDDRECURSION, usRecursion);
665:
666: WinSendDlgItemMsg( hWndDlg, IDDCLEARBETWEEN, BM_SETCHECK,
667: MPFROM2SHORT(fClearBetween, 0), 0L );
668: return FALSE;
669:
670: case WM_COMMAND:
671: switch (SHORT1FROMMP(mp1))
672: {
673: case IDDOK:
674: lColorBack = MyGetWindowLong (hWndDlg, IDDCOLORBK);
675: lColor = MyGetWindowLong (hWndDlg, IDDCOLOR);
676: usStyle = MyGetWindowUShort(hWndDlg, IDDSTYLE);
677: usMixMode = MyGetWindowUShort(hWndDlg, IDDMIX);
678: usPolygonSides = MyGetWindowUShort(hWndDlg, IDDNUMSIDES);
679: cptMax = MyGetWindowUShort(hWndDlg, IDDCPTMAX);
680: usRecursion = MyGetWindowUShort(hWndDlg, IDDRECURSION);
681:
682: fClearBetween = (SHORT)WinSendDlgItemMsg( hWndDlg, IDDCLEARBETWEEN,
683: BM_QUERYCHECK, 0L, 0L );
684:
685: if (usPolygonSides == 0)
686: usPolygonSides = 2;
687:
688: if (cptMax == 0)
689: cptMax = 1;
690: else if (cptMax > MAX_POINT_COUNT)
691: cptMax = MAX_POINT_COUNT;
692:
693:
694: WinDismissDlg( hWndDlg, TRUE );
695: break;
696:
697: case IDDCANCEL:
698: WinDismissDlg( hWndDlg, TRUE );
699: break;
700:
701: default:
702: return FALSE;
703: }
704: break;
705:
706: default:
707: return (ULONG) WinDefDlgProc( hWndDlg, message, mp1, mp2 );
708: }
709: return FALSE;
710: }
711:
712:
713:
714:
715: /************************************************************************
716: *
717: * AboutDlg
718: *
719: * Process messages for the About box.
720: *
721: ************************************************************************/
722:
723: ULONG FAR PASCAL
724: AboutDlg( hWndDlg, message, mp1, mp2 )
725: HWND hWndDlg;
726: USHORT message;
727: MPARAM mp1;
728: MPARAM mp2;
729: {
730: switch (message)
731: {
732: case WM_COMMAND: /* the user has pressed a button */
733: switch (SHORT1FROMMP(mp1)) /* which button? */
734: {
735: case DID_OK:
736: case DID_CANCEL:
737: WinDismissDlg( hWndDlg, TRUE );
738: break;
739:
740: default:
741: return FALSE;
742: }
743: break;
744:
745: default:
746: return (ULONG) WinDefDlgProc( hWndDlg, message, mp1, mp2 );
747: }
748: return FALSE;
749: }
750:
751:
752:
753:
754: /************************************************************************
755: *
756: * MySetWindowUShort
757: *
758: * Sets the given control id to the value specified.
759: *
760: * Be careful! The call to sprintf only works because our
761: * stack segment is the same as our data segment.
762: *
763: ************************************************************************/
764:
765: VOID
766: MySetWindowUShort(hWnd, id, num)
767: HWND hWnd;
768: USHORT id;
769: USHORT num;
770: {
771: char szStr[CCHSTR];
772:
773: sprintf((NPCH)szStr, "%u", num);
774: WinSetWindowText(WinWindowFromID(hWnd, id), (PCH)szStr);
775: }
776:
777:
778:
779:
780: /************************************************************************
781: *
782: * MySetWindowLong
783: *
784: * Sets the given control id to the value specified.
785: *
786: * Be careful! The call to sprintf only works because our
787: * stack segment is the same as our data segment.
788: *
789: ************************************************************************/
790:
791: VOID
792: MySetWindowLong(hWnd, id, num)
793: HWND hWnd;
794: USHORT id;
795: LONG num;
796: {
797: char szStr[CCHSTR];
798:
799: sprintf((NPCH)szStr, "%ld", num);
800: WinSetWindowText(WinWindowFromID(hWnd, id), (PCH)szStr);
801: }
802:
803:
804:
805:
806: /************************************************************************
807: *
808: * MyGetWindowUShort
809: *
810: * Returns the value from the given control id.
811: *
812: * Be careful! The call to atoi only works because our
813: * stack segment is the same as our data segment.
814: *
815: ************************************************************************/
816:
817: USHORT
818: MyGetWindowUShort(hWnd, id)
819: HWND hWnd;
820: USHORT id;
821: {
822: char szStr[CCHSTR];
823:
824: WinQueryWindowText(WinWindowFromID(hWnd, id), CCHSTR, (PCH)szStr);
825: return atoi(szStr);
826: }
827:
828:
829:
830:
831: /************************************************************************
832: *
833: * MyGetWindowLong
834: *
835: * Returns the value from the given control id.
836: *
837: * Be careful! The call to atol only works because our
838: * stack segment is the same as our data segment.
839: *
840: ************************************************************************/
841:
842: LONG
843: MyGetWindowLong(hWnd, id)
844: HWND hWnd;
845: USHORT id;
846: {
847: char szStr[CCHSTR];
848:
849: WinQueryWindowText(WinWindowFromID(hWnd, id), CCHSTR, (PCH)szStr);
850: return atol(szStr);
851: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.