Annotation of nono/vm/renderer.cpp, revision 1.1.1.10

1.1       root        1: //
                      2: // nono
1.1.1.3   root        3: // Copyright (C) 2020 nono project
                      4: // Licensed under nono-license.txt
1.1       root        5: //
                      6: 
1.1.1.6   root        7: //
                      8: // レンダラ
                      9: //
                     10: 
1.1.1.4   root       11: #include "renderer.h"
1.1.1.9   root       12: #include "planevram.h"
1.1.1.10! root       13: #include "scheduler.h"
1.1.1.8   root       14: #include "syncer.h"
1.1.1.6   root       15: #include "uimessage.h"
1.1.1.9   root       16: #include "videoctlr.h"
1.1       root       17: 
1.1.1.10! root       18: #if 0
        !            19: #define PERFREND
        !            20: #include "stopwatch.h"
        !            21: static uint64 check_time;
        !            22: static uint check_count;
        !            23: static Stopwatch sw_notify;
        !            24: static uint64 notify_time;
        !            25: static uint notify_count;
        !            26: static uint64 render_time;
        !            27: static uint64 stretch_time;
        !            28: static uint64 convert_time;
        !            29: static uint convert_count;
        !            30: #endif
        !            31: 
        !            32: // 電源オフ時の画面色 (電源オンで黒なのとは区別したいのであえて灰色)
        !            33: static const Color ScreenOffColor(0x70, 0x70, 0x70);
        !            34: 
1.1       root       35: // コンストラクタ
                     36: Renderer::Renderer()
1.1.1.8   root       37:        : inherited(OBJ_RENDERER)
1.1       root       38: {
                     39: }
                     40: 
                     41: // デストラクタ
                     42: Renderer::~Renderer()
                     43: {
1.1.1.6   root       44:        TerminateThread();
1.1.1.8   root       45: }
                     46: 
1.1.1.10! root       47: // モニタを初期化する (継承先コンストラクタから必要なら呼ばれる)
        !            48: void
        !            49: Renderer::InitMonitor()
        !            50: {
        !            51:        monitor.func = ToMonitorCallback(&Renderer::MonitorUpdate);
        !            52:        monitor.SetSize(42, 16);
        !            53:        monitor.Regist(ID_MONITOR_RENDERER);
        !            54: }
        !            55: 
1.1.1.8   root       56: // 初期化
                     57: bool
                     58: Renderer::Init()
                     59: {
                     60:        if (inherited::Init() == false) {
                     61:                return false;
                     62:        }
                     63: 
1.1.1.10! root       64:        scheduler = GetScheduler();
1.1.1.8   root       65:        syncer = GetSyncer();
                     66: 
1.1.1.10! root       67:        // GUI ならレンダラ有効。
        !            68:        if (gMainApp.IsGUI()) {
        !            69:                enable = true;
        !            70:                bitmap.Create(width, height);
        !            71:                // 初期値 (どこでやる?)
        !            72:                bitmap.Fill(ScreenOffColor);
        !            73:        }
        !            74: 
1.1.1.8   root       75:        return true;
1.1       root       76: }
                     77: 
1.1.1.6   root       78: // リセット
                     79: void
                     80: Renderer::ResetHard(bool poweron)
1.1       root       81: {
1.1.1.8   root       82:        next_refresh_rtime = syncer->GetRealTime();
1.1       root       83: 
1.1.1.6   root       84:        // 統計情報をクリア
1.1.1.10! root       85:        uint64 now = scheduler->GetVirtTime();
        !            86:        stat_input_count = 0;
        !            87:        last_input_time = now;
        !            88:        ma_input_span.Clear();
        !            89:        stat_enqueue_count = 0;
        !            90:        stat_enqueue_full = 0;
        !            91:        stat_render_count = 0;
        !            92:        stat_noupdate_count = 0;
        !            93:        stat_nobitmap_count = 0;
        !            94:        stat_output_count = 0;
        !            95:        last_output_time = now;
        !            96:        ma_output_span.Clear();
        !            97:        last_mod_lines = 0;
1.1.1.6   root       98:        total_invalidate2 = 0;
1.1       root       99: }
                    100: 
1.1.1.6   root      101: // 電源オフ
                    102: void
1.1       root      103: Renderer::PowerOff()
                    104: {
1.1.1.6   root      105:        if (enable) {
1.1       root      106:                // レンダリングスレッドに指示
                    107:                std::unique_lock<std::mutex> lock(mtx);
1.1.1.10! root      108:                request |= REQ_SCREENOFF;
1.1       root      109:                cv.notify_one();
                    110:        }
                    111: }
                    112: 
1.1.1.6   root      113: // 画面合成処理 (VM スレッドで CRTC/CRTC2 から呼ばれる)。
1.1       root      114: // 今は一画面単位で合成を行なっている。ラスター云々は未対応。
                    115: void
1.1.1.10! root      116: Renderer::NotifyRender()
1.1       root      117: {
                    118:        // CLI 版なら何もしない。
1.1.1.6   root      119:        if (enable == false) {
1.1       root      120:                return;
                    121:        }
                    122: 
1.1.1.10! root      123:        // 統計情報
        !           124:        stat_input_count++;
        !           125:        uint64 now = scheduler->GetVirtTime();
        !           126:        ma_input_span.EnqueueForce(now - last_input_time);
        !           127:        last_input_time = now;
        !           128: 
1.1.1.6   root      129:        // 更新されていればレンダラに引き渡す
1.1.1.10! root      130: #if defined(PERFREND)
        !           131:        Stopwatch sw;
        !           132:        sw.Start();
        !           133: #endif
        !           134:        ModifyInfo mod;
        !           135:        bool dirty = GetModifyMD(&mod);
1.1.1.6   root      136:        if (__predict_false(invalidate2 == true)) {
                    137:                mod.invalidate2 = true;
                    138:        }
                    139: 
1.1.1.10! root      140:        if (dirty || mod.invalidate2) {
1.1.1.6   root      141:                if (modq.Enqueue(mod)) {
1.1.1.10! root      142: #if defined(PERFREND)
        !           143:                        // 更新チェックしてキューに入れるまでにかかる VM スレッド側時間
        !           144:                        sw.Stop();
        !           145:                        check_time += sw.Elapsed();
        !           146:                        check_count++;
        !           147: #endif
1.1.1.6   root      148:                        // 更新情報を引き取ったのでクリアする
1.1.1.10! root      149:                        ClearDirtyMD();
1.1.1.6   root      150:                        invalidate2 = false;
1.1.1.10! root      151: 
        !           152:                        stat_enqueue_count++;
        !           153: 
        !           154: #if defined(PERFREND)
        !           155:                        // スレッドをまたぐのにかかる時間
        !           156:                        sw_notify.Restart();
        !           157: #endif
        !           158:                        // 描画指示。
        !           159:                        std::unique_lock<std::mutex> lock(mtx);
        !           160:                        request |= REQ_RENDER;
        !           161:                        cv.notify_one();
1.1.1.6   root      162:                } else {
1.1.1.10! root      163:                        stat_enqueue_full++;
1.1.1.6   root      164:                }
                    165:        }
1.1       root      166: }
                    167: 
1.1.1.6   root      168: // 全画面更新の指示 (パレット変更やスクロールなど VRAM の変更を伴わないもの)。
1.1.1.4   root      169: // ここでは無効化だけ受け付けておいて、実際の処理はレンダリングの際に行う。
                    170: void
1.1.1.6   root      171: Renderer::Invalidate2()
1.1.1.4   root      172: {
1.1.1.6   root      173:        invalidate2 = true;
1.1       root      174: }
                    175: 
                    176: // レンダリングスレッド
                    177: void
                    178: Renderer::ThreadRun()
                    179: {
1.1.1.10! root      180:        if (enable) {
        !           181:                SetThreadAffinityHint(AffinityClass::Heavy);
        !           182:        }
        !           183: 
        !           184:        int64 delay = 0;
1.1       root      185:        for (;;) {
                    186:                uint32 req;
                    187: 
                    188:                // 何か起きるまで待つ
                    189:                {
                    190:                        std::unique_lock<std::mutex> lock(mtx);
1.1.1.10! root      191:                        if (delay == 0) {
        !           192:                                cv.wait(lock, [&] { return (request != 0); });
        !           193:                        } else {
        !           194:                                cv.wait_for(lock, std::chrono::nanoseconds(delay),
        !           195:                                        [&] { return (request != 0); });
        !           196:                                if (request == 0) {
        !           197:                                        request = REQ_POST;
        !           198:                                }
        !           199:                        }
1.1       root      200:                        req = request;
                    201:                        request = 0;
                    202:                }
                    203: 
                    204:                if ((req & REQ_TERMINATE)) {
                    205:                        // 終了するので、他のフラグは無視
                    206:                        break;
1.1.1.4   root      207:                }
1.1       root      208: 
1.1.1.10! root      209:                // req に基づいて処理。
        !           210:                int64 t = DoRender(req);
        !           211:                if (t == 0 || (req & REQ_POST)) {
        !           212:                        // UI に即通知。
        !           213: 
        !           214:                        // 統計情報
        !           215:                        stat_output_count++;
        !           216:                        uint64 now = syncer->GetRealTime();
        !           217:                        ma_output_span.EnqueueForce(now - last_output_time);
        !           218:                        last_output_time = now;
        !           219: 
1.1.1.6   root      220:                        UIMessage::Post(UIMessage::RENDER);
1.1.1.10! root      221:                        delay = 0;
        !           222:                } else if (t > 0) {
        !           223:                        // 指定時間後に UI に通知。
        !           224:                        delay = t;
1.1.1.4   root      225:                }
1.1.1.10! root      226:        }
        !           227: }
1.1       root      228: 
1.1.1.10! root      229: // req に基づいて描画する。
        !           230: // UI への描画通知を遅延させる時間を返す (即通知する場合は 0)。
        !           231: // UI に通知しない場合は -1 を返す。
        !           232: int64
        !           233: Renderer::DoRender(uint32 req)
        !           234: {
        !           235:        // 1. REND_NORMAL なら電源オン時の通常描画指示、
        !           236:        //    REND_POWEROFF なら電源オン→オフ遷移時の描画指示。
        !           237:        // 2. REND_RESIZE なら valid = false にして、表示ビットマップをリサイズ。
        !           238:        // 3. 必要なら bitmap を拡大縮小。
        !           239:        // 4. bitmap24 に描画して valid = true;
1.1.1.6   root      240: 
1.1.1.10! root      241: #if defined(PERFREND)
        !           242:        Stopwatch sw;
        !           243: #endif
        !           244:        bool updated = false;
        !           245: 
        !           246:        if ((req & REQ_RENDER)) {
        !           247:                // 通常描画
        !           248: 
        !           249: #if defined(PERFREND)
        !           250:                sw_notify.Stop();
        !           251:                notify_time += sw_notify.Elapsed();
        !           252:                notify_count++;
        !           253: #endif
        !           254: 
        !           255:                if (modq.Dequeue(&modify)) {
        !           256:                        // 更新ライン数を数える (統計用)
        !           257:                        last_mod_lines = modify.GetDirtyCount();
        !           258:                        if (__predict_false(modify.invalidate2)) {
        !           259:                                total_invalidate2++;
1.1       root      260:                        }
1.1.1.10! root      261: 
        !           262:                        // レンダラを駆動する。
        !           263: #if defined(PERFREND)
        !           264:                        sw.Restart();
        !           265: #endif
        !           266:                        updated = RenderMD();
        !           267: #if defined(PERFREND)
        !           268:                        sw.Stop();
        !           269:                        render_time += sw.Elapsed();
        !           270: #endif
        !           271:                        stat_render_count++;
1.1       root      272:                }
                    273:        }
1.1.1.10! root      274:        if ((req & REQ_SCREENOFF)) {
        !           275:                // 電源オン→オフ時の描画
        !           276:                bitmap.Fill(ScreenOffColor);
        !           277:                updated = true;
        !           278:        }
        !           279: 
        !           280:        if ((req & REQ_RESIZE)) {
        !           281:                // 表示ビットマップのリサイズ要求
        !           282: 
        !           283:                bitmap24_valid = false;
        !           284:                bitmap24.reset(new BitmapRGB(new_viewwidth, new_viewheight));
        !           285:                if ((bool)bitmap24 == false) {
        !           286:                        putlog(0, "Could not allocate BitmapRGB(%u, %u)",
        !           287:                                new_viewwidth, new_viewheight);
        !           288:                        return -1;
        !           289:                }
        !           290:                viewrect = Rect(0, 0, new_viewwidth, new_viewheight);
        !           291:                updated = true;
        !           292:        }
        !           293: 
        !           294:        // 更新箇所がなければここで終わり。
        !           295:        if (updated == false) {
        !           296:                stat_noupdate_count++;
        !           297:                return -1;
        !           298:        }
        !           299: 
        !           300:        // 出力ビットマップが用意できてなければここで終わり。
        !           301:        if (__predict_false((bool)bitmap24 == false)) {
        !           302:                stat_nobitmap_count++;
        !           303:                return -1;
        !           304:        }
        !           305: 
        !           306:        // 等倍表示でなければ拡大縮小。
        !           307: #if defined(PERFREND)
        !           308:        sw.Restart();
        !           309: #endif
        !           310:        BitmapRGBX scaled_bitmap;
        !           311:        BitmapRGBX *src_bitmap;
        !           312:        if (bitmap.GetWidth() == bitmap24->GetWidth() &&
        !           313:                bitmap.GetHeight() == bitmap24->GetHeight())
        !           314:        {
        !           315:                src_bitmap = &bitmap;
        !           316:        } else {
        !           317:                scaled_bitmap.Create(viewrect.w, viewrect.h);
        !           318:                scaled_bitmap.DrawBitmapStretch(viewrect, bitmap);
        !           319:                src_bitmap = &scaled_bitmap;
        !           320:        }
        !           321: #if defined(PERFREND)
        !           322:        sw.Stop();
        !           323:        stretch_time += sw.Elapsed();
        !           324: #endif
        !           325: 
        !           326:        // wxWidgets 用の RGBX→RGB 変換。
        !           327: #if defined(PERFREND)
        !           328:        sw.Restart();
        !           329: #endif
        !           330:        src_bitmap->ConvertToRGB(*bitmap24);
        !           331:        bitmap24_valid = true;
        !           332: #if defined(PERFREND)
        !           333:        sw.Stop();
        !           334:        convert_time += sw.Elapsed();
        !           335: #endif
        !           336: 
        !           337: #if defined(PERFREND)
        !           338:        if (++convert_count % 100 == 0) {
        !           339:                printf("Check %4.2f, Notify %u, Render %u, Stretch %u, Convert %u"
        !           340:                        " [usec]\n",
        !           341:                        (double)check_time / check_count / 1000,
        !           342:                        (uint)(notify_time / notify_count / 1000U),
        !           343:                        (uint)(render_time / stat_render_count / 1000U),
        !           344:                        (uint)(stretch_time / convert_count / 1000U),
        !           345:                        (uint)(convert_time / convert_count / 1000U));
        !           346:        }
        !           347: #endif
        !           348: 
        !           349:        if ((req & (REQ_SCREENOFF | REQ_RESIZE))) {
        !           350:                // 電源オン→オフ遷移かリサイズ要求時は即通知。
        !           351:                return 0;
        !           352:        }
        !           353: 
        !           354:        // 通常描画のみならここで通知を律速する。
        !           355:        uint64 rtime = syncer->GetRealTime();
        !           356:        if (rtime < next_refresh_rtime) {
        !           357:                return next_refresh_rtime - rtime;
        !           358:        }
        !           359:        next_refresh_rtime = rtime + refresh_interval;
        !           360:        return 0;
1.1       root      361: }
                    362: 
1.1.1.6   root      363: // スレッドの終了を指示
1.1.1.2   root      364: void
                    365: Renderer::Terminate()
                    366: {
                    367:        std::unique_lock<std::mutex> lock(mtx);
                    368:        request |= REQ_TERMINATE;
                    369:        cv.notify_one();
                    370: }
                    371: 
1.1.1.10! root      372: // リサイズ指示
        !           373: void
        !           374: Renderer::ResizeView(uint width_, uint height_)
        !           375: {
        !           376:        assert(enable);
        !           377: 
        !           378:        new_viewwidth = width_;
        !           379:        new_viewheight = height_;
        !           380: 
        !           381:        std::unique_lock<std::mutex> lock(mtx);
        !           382:        request |= REQ_RESIZE;
        !           383:        cv.notify_one();
        !           384: }
        !           385: 
1.1.1.6   root      386: // 描画更新(通知)間隔を設定
                    387: void
                    388: Renderer::SetRefreshInterval(uint64 t)
                    389: {
                    390:        refresh_interval = t;
                    391: }
                    392: 
                    393: // モニタ更新
                    394: void
                    395: Renderer::MonitorUpdate(Monitor *, TextScreen& screen)
                    396: {
                    397:        int x;
                    398:        int y;
                    399: 
                    400:        screen.Clear();
                    401: 
1.1.1.10! root      402:        x = 16;
1.1.1.6   root      403:        y = 0;
                    404: 
1.1.1.10! root      405:        screen.Puts(0, y++, "<Statistics>");
1.1.1.6   root      406: 
1.1.1.10! root      407:        screen.Puts(0, y, "Input Count");
        !           408:        screen.Print(x, y++, "%26s", format_number(stat_input_count).c_str());
        !           409:        uint count = ma_input_span.Length();
        !           410:        uint64 ma = 0;
        !           411:        for (uint i = 0; i < count; i++) {
        !           412:                ma += ma_input_span.Peek(i);
        !           413:        }
        !           414:        if (count != 0) {
        !           415:                ma /= count;
        !           416:        }
        !           417:        screen.Puts(0, y, "Input Frequency(VT)");
        !           418:        screen.Print(x + 18, y, "%5.1f Hz",
        !           419:                ma == 0 ? 0.0 : (double)1000'000'000 / ma);
1.1.1.6   root      420:        y++;
                    421: 
1.1.1.10! root      422:        y++;
        !           423:        screen.Puts(0, y, "Enqueue Count");
        !           424:        screen.Print(x, y++, "%26s", format_number(stat_enqueue_count).c_str());
        !           425:        screen.Puts(0, y, "Enqueue Full");
        !           426:        screen.Print(x, y++, "%26s", format_number(stat_enqueue_full).c_str());
1.1.1.9   root      427: 
1.1.1.10! root      428:        y++;
        !           429:        screen.Puts(0, y, "RenderMD Count");
        !           430:        screen.Print(x, y++, "%26s", format_number(stat_render_count).c_str());
        !           431:        screen.Puts(0, y, "NoUpdate Count");
        !           432:        screen.Print(x, y++, "%26s", format_number(stat_noupdate_count).c_str());
        !           433:        screen.Puts(0, y, "NoBitmap Count");
        !           434:        screen.Print(x, y++, "%26s", format_number(stat_nobitmap_count).c_str());
1.1.1.9   root      435: 
1.1.1.10! root      436:        y++;
        !           437:        screen.Puts(0, y, "Output Count");
        !           438:        screen.Print(x, y++, "%26s", format_number(stat_output_count).c_str());
        !           439:        count = ma_output_span.Length();
        !           440:        ma = 0;
        !           441:        for (uint i = 0; i < count; i++) {
        !           442:                ma += ma_output_span.Peek(i);
        !           443:        }
        !           444:        if (count != 0) {
        !           445:                ma /= count;
        !           446:        }
        !           447:        screen.Puts(0, y, "Output Frequency(RT)");
        !           448:        screen.Print(x + 18, y, "%5.1f Hz",
        !           449:                ma == 0 ? 0.0 : (double)1000'000'000 / ma);
        !           450:        y++;
        !           451: 
        !           452:        y++;
        !           453:        screen.Puts(0, y, "Last Mod Lines");
        !           454:        screen.Print(x, y++, "%26u", last_mod_lines);
        !           455:        screen.Puts(0, y, "Invalidate2");
        !           456:        screen.Print(x, y++, "%26s", format_number(total_invalidate2).c_str());
1.1.1.9   root      457: }
                    458: 
1.1.1.6   root      459: 
1.1       root      460: //
                    461: // X68030 レンダラ
                    462: //
                    463: 
                    464: // コンストラクタ
                    465: X68030Renderer::X68030Renderer()
                    466: {
1.1.1.10! root      467:        InitMonitor();
        !           468: 
1.1       root      469:        // XXX とりあえず
                    470:        width = 768;
                    471:        height = 512;
1.1.1.9   root      472: 
1.1.1.10! root      473:        bitmap.Create(width, height);
1.1       root      474: }
                    475: 
                    476: // デストラクタ
                    477: X68030Renderer::~X68030Renderer()
                    478: {
                    479: }
                    480: 
1.1.1.9   root      481: // 初期化
                    482: bool
                    483: X68030Renderer::Init()
                    484: {
                    485:        if (inherited::Init() == false) {
                    486:                return false;
                    487:        }
                    488: 
                    489:        planevram = GetPlaneVRAMDevice();
                    490:        videoctlr = GetVideoCtlrDevice();
                    491: 
                    492:        return true;
                    493: }
                    494: 
1.1.1.10! root      495: // 更新チェック
        !           496: bool
        !           497: X68030Renderer::GetModifyMD(ModifyInfo *mod) const
        !           498: {
        !           499:        // 今のところテキスト VRAM しかない
        !           500:        // TODO: videoctlr に移動
        !           501:        return planevram->GetModify(mod);
        !           502: }
        !           503: 
        !           504: // 更新情報クリア
        !           505: void
        !           506: X68030Renderer::ClearDirtyMD()
        !           507: {
        !           508:        // 今のところテキスト VRAM しかない
        !           509:        planevram->ClearDirty();
        !           510: }
        !           511: 
1.1       root      512: // 画面合成
                    513: bool
1.1.1.10! root      514: X68030Renderer::RenderMD()
1.1       root      515: {
1.1.1.10! root      516:        bool updated = videoctlr->Render(bitmap, modify);
1.1.1.9   root      517:        return updated;
1.1       root      518: }
                    519: 
1.1.1.6   root      520: 
1.1       root      521: //
                    522: // LUNA レンダラ
                    523: //
                    524: 
                    525: // コンストラクタ
                    526: LunaRenderer::LunaRenderer()
                    527: {
1.1.1.10! root      528:        InitMonitor();
        !           529: 
1.1       root      530:        width = 1280;
                    531:        height = 1024;
1.1.1.9   root      532: 
1.1.1.10! root      533:        bitmap.Create(width, height);
1.1       root      534: }
                    535: 
                    536: // デストラクタ
                    537: LunaRenderer::~LunaRenderer()
                    538: {
                    539: }
                    540: 
1.1.1.9   root      541: // 初期化
                    542: bool
                    543: LunaRenderer::Init()
                    544: {
                    545:        if (inherited::Init() == false) {
                    546:                return false;
                    547:        }
                    548: 
                    549:        planevram = GetPlaneVRAMDevice();
                    550: 
                    551:        return true;
                    552: }
                    553: 
1.1.1.10! root      554: // 更新チェック
        !           555: bool
        !           556: LunaRenderer::GetModifyMD(ModifyInfo *mod) const
        !           557: {
        !           558:        return planevram->GetModify(mod);
        !           559: }
        !           560: 
        !           561: // 更新情報クリア
        !           562: void
        !           563: LunaRenderer::ClearDirtyMD()
        !           564: {
        !           565:        planevram->ClearDirty();
        !           566: }
        !           567: 
1.1       root      568: // 画面合成
                    569: bool
1.1.1.10! root      570: LunaRenderer::RenderMD()
1.1       root      571: {
1.1.1.10! root      572:        bool updated = planevram->Render(bitmap, modify);
1.1.1.9   root      573:        return updated;
1.1.1.8   root      574: }
                    575: 
                    576: 
                    577: //
1.1.1.9   root      578: // 何もしないレンダラ
1.1.1.8   root      579: //
                    580: 
                    581: // コンストラクタ
1.1.1.9   root      582: NopRenderer::NopRenderer()
1.1.1.8   root      583: {
                    584:        // XXX サポートしてない
                    585:        width = 640;
                    586:        height = 480;
                    587: }
                    588: 
                    589: // デストラクタ
1.1.1.9   root      590: NopRenderer::~NopRenderer()
1.1.1.8   root      591: {
                    592: }
                    593: 
1.1.1.10! root      594: // 更新チェック
        !           595: bool
        !           596: NopRenderer::GetModifyMD(ModifyInfo *) const
        !           597: {
        !           598:        return false;
        !           599: }
        !           600: 
        !           601: // 更新情報クリア
        !           602: void
        !           603: NopRenderer::ClearDirtyMD()
        !           604: {
        !           605: }
        !           606: 
1.1.1.8   root      607: // 画面合成
                    608: bool
1.1.1.10! root      609: NopRenderer::RenderMD()
1.1.1.8   root      610: {
                    611:        return false;
1.1.1.6   root      612: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.