Annotation of nono/vm/scheduler.cpp, revision 1.1.1.14

1.1       root        1: //
                      2: // nono
1.1.1.4   root        3: // Copyright (C) 2020 nono project
                      4: // Licensed under nono-license.txt
1.1       root        5: //
                      6: 
1.1.1.14! root        7: //
        !             8: // スケジューラ
        !             9: //
1.1       root       10: 
                     11: // VM スケジューラは、仮想時間とイベントを管理する。
1.1.1.14! root       12: // 仮想時間はすべて nsec で表し、StartTime() から単調増加する。
        !            13: // VM 界からは仮想時間は gScheduler->GetVirtTime() で取得する。
1.1       root       14: //
1.1.1.14! root       15: // 実時間は Sync が管理し、こちらも nsec で表す。
        !            16: // このストップウォッチも StartTime() から単調増加する。
        !            17: // 実時間、仮想時間とも電源オフ中でも単調増加することに注意。
1.1.1.9   root       18: //
1.1.1.14! root       19: // 「実経過時間」は gRealtime.Elapsed() で取得でき
        !            20: // o (たぶん) Scheduler スレッド開始時から、常に増加。
1.1.1.9   root       21: // o (たぶん)ホストの時刻変更やサスペンドの影響を受けず、常に増加。
                     22: // o デバッガプロンプトで停止中は進行が停止する。
                     23: // という性質を持つ。
                     24: //
                     25: // 同期走行モードにおいては rtime_epoch, vtime_epoch をそれぞれの基準点とし
                     26: // これらと現在の実経過時間、仮想経過時間の差が同期するよう調整する。
                     27: // 高速走行モードにおいてはこれらは必要ないので特に参照しない。
                     28: //
                     29: // 一定間隔で行うパフォーマンス測定は、前回計測時の実および仮想経過時間を
                     30: // last_perf_rtime, last_perf_vtime として持っているので、これと現在の
                     31: // 経過時間との比で求める。同期走行/高速走行の影響は受けない。
1.1       root       32: 
1.1.1.14! root       33: #include "scheduler.h"
        !            34: #include "mpu.h"
        !            35: #include "rtc.h"
        !            36: #include "sync.h"
1.1       root       37: 
1.1.1.14! root       38: //#define CT_ON
1.1.1.9   root       39: 
1.1.1.14! root       40: #ifdef CT_ON
        !            41: static uint64 ct_get1;
        !            42: static uint64 ct_get2;
        !            43: static uint64 ct_start;
        !            44: static uint64 ct_start1;
        !            45: static uint64 ct_start2;
        !            46: static uint64 ct_start3;
        !            47: static uint64 ct_start4;
        !            48: static uint64 ct_stop1;
        !            49: static uint64 ct_stop2;
        !            50: static uint64 ct_eslow;
        !            51: static uint64 ct_sslow;
        !            52: static uint64 ct_pslow;
        !            53: #define CT(name) ct_##name += 1
        !            54: #else
        !            55: #define CT(name)
        !            56: #endif
1.1.1.9   root       57: 
1.1.1.14! root       58: // グローバル参照用
        !            59: Scheduler *gScheduler;
1.1.1.9   root       60: 
                     61: #define FORMAT_FULL 0
                     62: #define FORMAT_SEC 1
                     63: 
                     64: // 経過時間 t を文字列にして返す。
1.1.1.10  root       65: // 文字列長は、t の大きさ(とフォーマット指定)によって以下の通り。
                     66: //
                     67: //  0         1         2
                     68: //  01234567890123456789012345
                     69: // "0.mmm'uuu'nnn"              10秒未満、または FORMAT_SEC なら13桁
                     70: // "59.mmm'uuu'nnn"             1分未満なら14桁
                     71: // " 9:59.mmm'uuu'nnn"          1時間未満なら17桁
                     72: // " 9:59:59.mmm'uuu'nnn"       24時間未満なら20桁
                     73: // "999d 23:59:59.mmm'uuu'nnn"  1日以上なら25桁。これが最大幅。
                     74: //
                     75: // FORMAT_SEC は1桁秒以下の場合 (実際には1秒未満の場合) に用いる。
                     76: // FORMAT_FULL は 1000日経過すると桁がずれるけど、それはもういいだろう。
                     77: //
                     78: // 10秒未満の場合だけ %2d ではなく %d で1桁切り詰めているが、これは
                     79: // FORMAT_SEC との互換性のため。その必要のない10分未満と10時間未満は
                     80: // どちらも %2d で表記し桁数を維持することに努める。
1.1.1.9   root       81: static const std::string
                     82: TimeToStrF(uint64 t, int format)
                     83: {
                     84:        char buf[32];
                     85:        char *p;
                     86:        size_t len;
                     87:        int n;
                     88: 
                     89:        uint ns = t % 1000;
                     90:        t /= 1000;
                     91:        uint us = t % 1000;
                     92:        t /= 1000;
                     93:        uint ms = t % 1000;
                     94:        t /= 1000;
                     95: 
                     96:        uint s, m, h, d;
                     97:        if (format == FORMAT_FULL) {
                     98:                s = t % 60;
                     99:                t /= 60;
                    100:                m = t % 60;
                    101:                t /= 60;
                    102:                h = t % 24;
                    103:                t /= 24;
                    104:                d = t;
                    105:        } else {
                    106:                s = t;
                    107:                m = 0;
                    108:                h = 0;
                    109:                d = 0;
                    110:        }
                    111: 
                    112:        p = buf;
                    113:        len = sizeof(buf);
                    114:        if (d) {
                    115:                n = snprintf(p, len, "%3dd %02u:%02u:%02u", d, h, m, s);
                    116:                p += n;
                    117:                len -= n;
                    118:        } else if (h) {
                    119:                n = snprintf(p, len, "%2u:%02u:%02u", h, m, s);
                    120:                p += n;
                    121:                len -= n;
                    122:        } else if (m) {
                    123:                n = snprintf(p, len, "%2u:%02u", m, s);
                    124:                p += n;
                    125:                len -= n;
                    126:        } else {
                    127:                n = snprintf(p, len, "%u", s);
                    128:                p += n;
                    129:                len -= n;
                    130:        }
                    131:        n = snprintf(p, len, ".%03u'%03u'%03u", ms, us, ns);
                    132:        p += n;
                    133:        len -= n;
                    134: 
                    135:        return std::string(buf, p - buf);
                    136: }
                    137: 
1.1.1.10  root      138: const std::string
1.1.1.9   root      139: TimeToStr(uint64 t)
                    140: {
                    141:        return TimeToStrF(t, FORMAT_FULL);
                    142: }
                    143: 
1.1.1.14! root      144: const std::string
1.1.1.9   root      145: SecToStr(uint64 t)
                    146: {
                    147:        return TimeToStrF(t, FORMAT_SEC);
                    148: }
                    149: 
                    150: //
                    151: // スケジューラ
                    152: //
                    153: 
1.1       root      154: // コンストラクタ
                    155: Scheduler::Scheduler()
1.1.1.10  root      156:        : inherited("Scheduler")
1.1       root      157: {
1.1.1.14! root      158:        // オブジェクト名は Scheduler だがここのスレッド名は VM くらいのほうが
        !           159:        // 通りがよさそうだ。
        !           160:        SetThreadName("VM");
1.1.1.9   root      161: 
1.1.1.14! root      162:        slow_top = -1;
        !           163:        slow_top_vtime = UINT64_MAX;
1.1.1.10  root      164: 
1.1.1.14! root      165:        monitor.func = ToMonitorCallback(&Scheduler::MonitorUpdate);
1.1.1.10  root      166:        // サイズは Init で決まる
                    167:        monitor.Regist(ID_MONITOR_SCHEDULER);
1.1       root      168: }
                    169: 
                    170: // デストラクタ
                    171: Scheduler::~Scheduler()
                    172: {
1.1.1.14! root      173:        TerminateThread();
        !           174:        gScheduler = NULL;
1.1       root      175: }
                    176: 
                    177: // 初期化
                    178: bool
                    179: Scheduler::Init()
                    180: {
1.1.1.10  root      181:        // この時点でイベントの個数は確定しているはず(してなければならない)
1.1.1.14! root      182:        monitor.SetSize(80, 9 + all_events.size());
1.1       root      183: 
                    184:        return true;
                    185: }
                    186: 
                    187: // スレッドエントリ関数
                    188: void
                    189: Scheduler::ThreadRun()
                    190: {
1.1.1.14! root      191:        // 電源オンオフに関わらず、ここが rtime, vtime の基準点
        !           192:        StartTime();
1.1       root      193: 
1.1.1.3   root      194:        for (;;) {
1.1.1.14! root      195:                // 条件変数の正しい使い方としては request の参照にも mtx ロックが必要
        !           196:                // だが、ここでロック取らずに参照したことによって request が立った
        !           197:                // ことを見落としても、ループ一回回ってもう一度ここに来るだけなので
        !           198:                // たぶん致命的なことはないはず。
        !           199:                if (__predict_false(request)) {
        !           200:                        uint32 req;
        !           201:                        {
        !           202:                                std::lock_guard<std::mutex> lock(mtx);
        !           203:                                req = request;
        !           204:                                request = 0;
1.1.1.9   root      205:                        }
1.1.1.14! root      206:                        if ((req & REQUEST_EXIT)) {
        !           207:                                break;
1.1.1.9   root      208:                        }
1.1.1.14! root      209:                        if ((req & REQUEST_MESSAGE)) {
        !           210:                                DispatchMessage();
1.1.1.9   root      211:                        }
1.1.1.14! root      212:                }
1.1.1.9   root      213: 
1.1.1.14! root      214:                // 先頭のイベントを取得して停止
        !           215:                Event *ev;
        !           216:                if (__predict_true(fast != NULL)) {
        !           217:                        CT(get1);
        !           218:                        ev = fast;
        !           219:                        fast = NULL;
        !           220:                } else {
        !           221:                        CT(get2);
        !           222:                        ev = slow[slow_top--];
        !           223:                        slow_top_vtime = slow[slow_top]->vtime;
1.1.1.9   root      224:                }
1.1.1.14! root      225:                ev->active = false;
1.1.1.9   root      226: 
1.1.1.14! root      227:                // 時刻更新
        !           228:                vtime = ev->vtime;
        !           229: 
        !           230:                // コールバック
        !           231:                ev->count++;
        !           232:                ((ev->dev)->*(ev->func))(*ev);
1.1       root      233:        }
1.1.1.9   root      234: }
1.1       root      235: 
1.1.1.14! root      236: // 時間の始まり
        !           237: void
        !           238: Scheduler::StartTime()
1.1.1.9   root      239: {
1.1.1.14! root      240:        // イベントキューを空にする
        !           241:        for (auto ev : all_events) {
        !           242:                if (ev->active) {
        !           243:                        StopEvent(*ev);
1.1.1.9   root      244:                }
1.1.1.14! root      245:                ev->count = 0;
1.1.1.9   root      246:        }
1.1.1.14! root      247: 
        !           248:        // 時間をリセットする。
        !           249:        // 呼び出しの依存関係に注意。
        !           250:        vtime = 0;                                      // required by Sync
        !           251:        gSync->StartTime();                     // required by RTC
        !           252:        gRTC->StartTime();
1.1.1.9   root      253: }
1.1       root      254: 
1.1.1.14! root      255: // スレッド終了指示
        !           256: // (当然他スレッドから呼ぶことになる)
1.1.1.9   root      257: void
1.1.1.14! root      258: Scheduler::Terminate()
1.1.1.9   root      259: {
1.1.1.14! root      260:        std::unique_lock<std::mutex> lock(mtx);
        !           261:        request |= REQUEST_EXIT;
        !           262:        cv.notify_one();
1.1.1.9   root      263: }
1.1       root      264: 
1.1.1.14! root      265: // イベントを登録する。
        !           266: // 登録時点では実は type だけ確定していればよい。
        !           267: //
        !           268: // ここで管理してるのはポインタだけで、実体は呼び出し元クラスのメンバ
        !           269: // インスタンスのはずなので、登録しっぱなしにする。
        !           270: void
        !           271: Scheduler::RegistEvent(Event& ev)
1.1.1.9   root      272: {
1.1.1.14! root      273:        // イベントリストに追加
        !           274:        all_events.push_back(&ev);
1.1.1.9   root      275: }
                    276: 
1.1.1.14! root      277: // 指定のイベントを開始する。
        !           278: // すでに同イベントが登録されている場合は古いイベントを削除してから
        !           279: // 新しいイベントを再登録となる。
        !           280: // イベントはワンショットのみ。
1.1.1.9   root      281: void
1.1.1.14! root      282: Scheduler::RestartEvent(Event& ev)
1.1.1.9   root      283: {
1.1.1.14! root      284:        if (ev.IsRunning()) {
        !           285:                StopEvent(ev);
1.1.1.9   root      286:        }
1.1.1.14! root      287: 
        !           288:        StartEvent(ev);
1.1       root      289: }
                    290: 
1.1.1.14! root      291: // 指定のイベントを開始する。
        !           292: // このイベントが登録されてないことが確定できる場合のみこちらが使える。
        !           293: // イベントはワンショットのみ。
1.1       root      294: void
1.1.1.14! root      295: Scheduler::StartEvent(Event& ev)
1.1       root      296: {
1.1.1.14! root      297:        CT(start);
        !           298: #ifdef CT_ON
        !           299:        if (ct_start % 100'000'000 == 0) {
        !           300:                printf("get*=%lu [%lu, %lu]\n",
        !           301:                        (ct_get1 + ct_get2), ct_get1, ct_get2);
        !           302:                printf("start*=%lu [%lu, %lu, %lu, %lu]\n",
        !           303:                        ct_start, ct_start1, ct_start2, ct_start3, ct_start4);
        !           304:                printf("stop*=%lu [%lu, %lu]\n",
        !           305:                        (ct_stop1 + ct_stop2), ct_stop1, ct_stop2);
        !           306:                printf("eslow=%lu sslow=%lu pslow=%lu\n",
        !           307:                        ct_eslow, ct_sslow, ct_pslow);
        !           308:                printf("slow_top=%d vtime=%lu\n", slow_top, slow_top_vtime);
        !           309:        }
        !           310: #endif
        !           311: 
        !           312:        // この assert を有効にすると 10 ポイント性能が低下する(502%->492%)
        !           313:        //assertmsg(ev.IsRunning() == false, "%s", ev.GetName().c_str());
        !           314: 
        !           315:        // vtime は仮想絶対時刻 [nsec]、time は仮想相対時間 [nsec]
        !           316:        ev.vtime = vtime + ev.time;
        !           317: 
        !           318:        ev.active = true;
        !           319: 
        !           320:        if (__predict_true(ev.vtime <= slow_top_vtime)) {
        !           321:                if (__predict_true(fast == NULL)) {
        !           322:                        CT(start1);
        !           323:                        fast = &ev;
        !           324:                } else {
        !           325:                        if (__predict_false(ev.vtime <= fast->vtime)) {
        !           326:                                CT(start2);
        !           327:                                PushSlow(*fast);
        !           328:                                fast = &ev;
        !           329:                        } else {
        !           330:                                CT(start3);
        !           331:                                PushSlow(ev);
        !           332:                        }
        !           333:                }
        !           334:        } else {
        !           335:                CT(start4);
        !           336:                EnqueueSlow(ev);
1.1.1.10  root      337:        }
                    338: }
                    339: 
1.1.1.14! root      340: // 実時間間隔を指定してイベントを開始する。
        !           341: // rt_now はイベント発行者の実時間での現在時刻で、
        !           342: // rt_period は次回のイベントまでの実時間間隔。
        !           343: void
        !           344: Scheduler::StartRealtimeEvent(Event& ev, uint64 rt_now, uint64 rt_period)
        !           345: {
        !           346:        // 実時間をもとに仮想時間軸上の推定を行う。
        !           347: 
        !           348:        uint64 rtime = gSync->GetRealTime();
        !           349: 
        !           350:        if (rtime > rt_now + rt_period) {
        !           351:                // 実時間がさらに 1 period 以上進んでいる。
        !           352:                // 実時間に追いつかないといけないので、イベントの
        !           353:                // 周期を短くして回復運転させる。
        !           354:                // 周期を短くしすぎるとゲストが処理できなくなるので、
        !           355:                // 10% カットに留める。
        !           356:                ev.time = rt_period - rt_period / 10;
        !           357:        } else if (rtime < rt_now - rt_period) {
        !           358:                // イベントがさらに 1 period 以上進んでいる。
        !           359:                // 実時間を追い越してしまっているので、イベントの
        !           360:                // 周期を長くして待つ。長くする方はいくらでも長くしていいので、
        !           361:                // 連続した場合はどんどん長くしていく。
        !           362:                ev.time += rt_period;
        !           363:        } else {
        !           364:                // おおむね同期している
        !           365:                ev.time = rt_period;
        !           366:        }
        !           367: 
        !           368:        RestartEvent(ev);
        !           369: }
        !           370: 
        !           371: // ev は slow_top には来ない前提
1.1.1.10  root      372: void
1.1.1.14! root      373: Scheduler::EnqueueSlow(Event& ev)
1.1.1.10  root      374: {
1.1.1.14! root      375:        CT(eslow);
        !           376: 
        !           377:        int s = slow_top;
        !           378:        slow[++slow_top] = slow[s];
        !           379: 
        !           380:        for (; --s >= 0; ) {
        !           381:                if (ev.vtime <= slow[s]->vtime) {
        !           382:                        slow[s + 1] = &ev;
        !           383:                        return;
        !           384:                } else {
        !           385:                        slow[s + 1] = slow[s];
        !           386:                }
1.1.1.3   root      387:        }
1.1.1.14! root      388:        slow[0] = &ev;
1.1       root      389: }
                    390: 
1.1.1.8   root      391: void
1.1.1.14! root      392: Scheduler::PushSlow(Event& ev)
1.1.1.8   root      393: {
1.1.1.14! root      394:        CT(pslow);
        !           395: 
        !           396:        slow[++slow_top] = &ev;
        !           397:        slow_top_vtime = ev.vtime;
1.1.1.8   root      398: }
                    399: 
1.1.1.14! root      400: // 指定のイベントを停止する。
        !           401: // 指定されたイベントが登録されていなければ何もしない。
1.1       root      402: void
1.1.1.14! root      403: Scheduler::StopEvent(Event& ev)
1.1       root      404: {
1.1.1.14! root      405:        ev.active = false;
        !           406: 
        !           407:        if (__predict_false(&ev == fast)) {
        !           408:                CT(stop1);
        !           409:                fast = NULL;
1.1       root      410:        } else {
1.1.1.14! root      411:                CT(stop2);
        !           412:                StopSlowEvent(ev);
1.1       root      413:        }
                    414: }
                    415: 
1.1.1.9   root      416: void
1.1.1.14! root      417: Scheduler::StopSlowEvent(Event& ev)
1.1.1.9   root      418: {
1.1.1.14! root      419:        CT(sslow);
        !           420: 
        !           421:        int s = slow_top;
        !           422: 
        !           423:        for (; s >= 0; s--) {
        !           424:                if (&ev == slow[s]) {
        !           425:                        for (; s < slow_top; s++) {
        !           426:                                slow[s] = slow[s + 1];
        !           427:                        }
        !           428:                        slow_top--;
        !           429:                        if (slow_top < 0) {
        !           430:                                slow_top_vtime = UINT64_MAX;
        !           431:                        } else {
        !           432:                                slow_top_vtime = slow[slow_top]->vtime;
        !           433:                        }
        !           434:                        break;
        !           435:                }
1.1.1.9   root      436:        }
                    437: }
                    438: 
1.1.1.14! root      439: // メッセージハンドラを登録する。
1.1.1.9   root      440: void
1.1.1.14! root      441: Scheduler::ConnectMessage(MessageID msgid, Device *dev, MessageCallback_t func)
1.1.1.9   root      442: {
1.1.1.14! root      443:        auto& mh = message_handlers[msgid];
        !           444:        assert(mh.dev == NULL);
        !           445:        mh.dev = dev;
        !           446:        mh.func = func;
1.1.1.9   root      447: }
                    448: 
1.1.1.14! root      449: // メッセージをディスパッチして、登録されていればハンドラを呼び出す。
1.1.1.9   root      450: void
1.1.1.14! root      451: Scheduler::DispatchMessage()
1.1.1.9   root      452: {
1.1.1.14! root      453:        uint64 msg;
1.1.1.9   root      454: 
1.1.1.14! root      455:        while (msgq.Dequeue(&msg)) {
        !           456:                MessageID msgid = (MessageID)(msg & 0xff);
        !           457:                uint32 arg = (msg >> 32);
1.1.1.9   root      458: 
1.1.1.14! root      459:                assert(0 <= msgid && msgid < MessageID::MAX);
1.1.1.9   root      460: 
1.1.1.14! root      461:                auto& mh = message_handlers[msgid];
        !           462:                if (mh.dev != NULL) {
        !           463:                        ((mh.dev)->*(mh.func))(msgid, arg);
        !           464:                }
1.1.1.9   root      465:        }
                    466: }
                    467: 
1.1.1.14! root      468: // メッセージを送る。
        !           469: // VM スレッド以外から呼び出しても良い。
1.1       root      470: void
1.1.1.14! root      471: Scheduler::SendMessage(MessageID msgid, uint32 arg)
1.1       root      472: {
1.1.1.14! root      473:        std::unique_lock<std::mutex> lock(mtx);
1.1       root      474: 
1.1.1.14! root      475:        uint64 msg = (uint64)msgid | (((uint64)arg) << 32);
        !           476:        if (msgq.Enqueue(msg) == false) {
        !           477:                putlog(0, "Message queue exhausted !!");
1.1       root      478:        }
1.1.1.14! root      479:        request |= REQUEST_MESSAGE;
        !           480:        cv.notify_one();
1.1       root      481: }
                    482: 
1.1.1.14! root      483: // 指定時間が経過するか、リクエストが起きるまでスリープ
1.1       root      484: void
1.1.1.14! root      485: Scheduler::Sleep(uint64 time)
1.1       root      486: {
1.1.1.14! root      487:        std::unique_lock<std::mutex> lock(mtx);
        !           488:        cv.wait_for(lock, std::chrono::nanoseconds(time), [&] {
        !           489:                return (request != 0);
        !           490:        });
        !           491:        // リクエストフラグが立ってもここでは何もしない
1.1       root      492: }
                    493: 
1.1.1.5   root      494: void
1.1.1.10  root      495: Scheduler::MonitorUpdate(Monitor *, TextScreen& screen)
1.1       root      496: {
1.1.1.14! root      497:        int y;
1.1       root      498: 
1.1.1.10  root      499:        screen.Clear();
1.1       root      500: 
1.1.1.14! root      501:        y = gSync->MonitorUpdateSub(screen, vtime);
1.1.1.7   root      502: 
1.1.1.14! root      503:        int clock_khz = gMPU->GetClockSpeed();
1.1.1.10  root      504:        screen.Print(0, y++, "MPU Speed: %d.%03dMHz",
1.1.1.7   root      505:                (clock_khz / 1000), (clock_khz % 1000));
1.1.1.9   root      506: 
1.1.1.14! root      507:        // 0         1         2         3
        !           508:        // 012345678901234567890123456789
        !           509:        // Set Time       Remain Time
        !           510:        // 3.123'456'789  3.123'456'789
        !           511:        //
        !           512:        // 3         4         5         6         7
        !           513:        // 01234567890123456789012345678901234567890123456789
        !           514:        // Description                                  Count
        !           515:        // 012345678901234567890123456789 999,999,999,999,999
        !           516:        //                         18,446,744,073,709,551,615
        !           517:        y++;
        !           518:        screen.Puts(0, y, "Event Time");
        !           519:        screen.Puts(15, y, "Remain Time");
        !           520:        screen.Puts(30, y, "Description");
        !           521:        screen.Puts(75, y, "Count");
1.1       root      522:        y++;
1.1.1.6   root      523: 
1.1.1.14! root      524:        for (const auto ev : all_events) {
1.1       root      525:                uint64 rem;
1.1.1.4   root      526:                TA attr;
1.1.1.14! root      527:                if (ev->IsRunning()) {
        !           528:                        if (ev->vtime > vtime) {
        !           529:                                rem = ev->vtime - vtime;
1.1.1.6   root      530:                        } else {
                    531:                                // XXX 実際には起きないけど、この表示をするにあたって
                    532:                                // スケジューラスレッドと一切調停していないので、仮想時刻が
1.1.1.14! root      533:                                // ev->vtime (イベント発生時刻) をすぎていることがある。
1.1.1.6   root      534:                                // すぎてるので残り時間 0 と表示しておく。
                    535:                                rem = 0;
                    536:                        }
1.1       root      537:                } else {
                    538:                        rem = 0;
                    539:                }
1.1.1.14! root      540: 
        !           541:                if (ev->count != ev->last_count) {
        !           542:                        attr = TA::Normal;
        !           543:                        ev->last_count = ev->count;
        !           544:                } else {
        !           545:                        attr = TA::Disable;
        !           546:                }
        !           547: 
        !           548:                // 先にイベント回数を表示。
        !           549:                // 21文字以上になったら上位桁が長い Description で消されても
        !           550:                // 大勢に影響はないだろう。
        !           551:                std::string countstr = format_number(ev->count);
        !           552:                screen.Print(54, y, attr, "%26s", countstr.c_str());
        !           553:                screen.Print(0, y, attr, "%13s  %13s  %s ",
        !           554:                        SecToStr(ev->time).c_str(),
1.1.1.9   root      555:                        SecToStr(rem).c_str(),
1.1.1.14! root      556:                        ev->GetName().c_str());
        !           557:                y++;
1.1       root      558:        }
                    559: }

unix.superglobalmegacorp.com

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