--- nono/vm/scheduler.cpp 2026/04/29 17:04:50 1.1.1.8 +++ nono/vm/scheduler.cpp 2026/04/29 17:05:51 1.1.1.21 @@ -4,508 +4,469 @@ // Licensed under nono-license.txt // -#include "scheduler.h" -#include "config.h" -#include "mainapp.h" -#include "mfp.h" -#include "mpu680x0.h" -#include "mpu88xx0.h" -#include "mythread.h" -#include "rtc.h" -#include "vm.h" +// +// スケジューラ +// // VM スケジューラは、仮想時間とイベントを管理する。 +// 仮想時間はすべて nsec で表し、StartTime() から単調増加する。 +// VM 界からは仮想時間は gScheduler->GetVirtTime() で取得する。 +// +// 実時間は Syncer が管理し、こちらも nsec で表す。 +// このストップウォッチも StartTime() から単調増加する。 +// 実時間、仮想時間とも電源オフ中でも単調増加することに注意。 // -// 仮想時間は起動時からの nsec で表し、MPU デバイス (実際はその向こうの -// CPU コア) が管理している。VM 界からは gMPU->GetVirtTime() で取得する。 +// 「実経過時間」は gRealtime.Elapsed() で取得でき +// o (たぶん) Scheduler スレッド開始時から、常に増加。 +// o (たぶん)ホストの時刻変更やサスペンドの影響を受けず、常に増加。 +// o デバッガプロンプトで停止中は進行が停止する。 +// という性質を持つ。 +// +// 同期走行モードにおいては rtime_epoch, vtime_epoch をそれぞれの基準点とし +// これらと現在の実経過時間、仮想経過時間の差が同期するよう調整する。 +// 高速走行モードにおいてはこれらは必要ないので特に参照しない。 +// +// 一定間隔で行うパフォーマンス測定は、前回計測時の実および仮想経過時間を +// last_perf_rtime, last_perf_vtime として持っているので、これと現在の +// 経過時間との比で求める。同期走行/高速走行の影響は受けない。 + +#include "scheduler.h" +#include "event.h" +#include "monitor.h" +#include "rtc.h" +#include "syncer.h" -static void *scheduler_run(void *arg); +//#define CT_ON -std::unique_ptr gScheduler; +#ifdef CT_ON +static uint64 ct_get1; +static uint64 ct_get2; +static uint64 ct_start; +static uint64 ct_start1; +static uint64 ct_start2; +static uint64 ct_start3; +static uint64 ct_start4; +static uint64 ct_stop1; +static uint64 ct_stop2; +static uint64 ct_eslow; +static uint64 ct_sslow; +static uint64 ct_pslow; +#define CT(name) ct_##name += 1 +#else +#define CT(name) +#endif + +// +// スケジューラ +// // コンストラクタ Scheduler::Scheduler() + : inherited(OBJ_SCHEDULER) { - logname = "scheduler"; - devname = "Scheduler"; - - eventlist.clear(); + // オブジェクト名は Scheduler だがここのスレッド名は VM くらいのほうが + // 通りがよさそうだ。 + SetThreadName("VM"); + + slow_top = -1; + slow_top_vtime = UINT64_MAX; + + monitor = gMonitorManager->Regist(ID_MONITOR_SCHEDULER, this); + monitor->func = ToMonitorCallback(&Scheduler::MonitorUpdate); + // サイズは最終的に Init2() で決まる。 + monitor->SetSize(80, 9); } // デストラクタ Scheduler::~Scheduler() { - Terminate(); + TerminateThread(); } // 初期化 bool Scheduler::Init() { - // 起動時引数指定の高速モード - SetFullSpeed(gMainApp.fast_mode); - - // スケジューラ(VM)スレッド起動 - pthread_create(&thread, NULL, scheduler_run, NULL); - thread_created = true; + evman = GetEventManager(); + syncer = GetSyncer(); return true; } -// スケジューラを開始する -void -Scheduler::Run() -{ - std::unique_lock lock(cvmtx); - atomic_reqflag |= REQ_POWER_ON; - cv.notify_one(); -} - -// スレッドエントリ関数 -void * -scheduler_run(void *arg) +// 初期化(VM から呼ばれる) +bool +Scheduler::Init2() { - PTHREAD_SETNAME("VM"); + // イベントの個数が確定した(してなければならない) + monitor->AddHeight(evman->all.size()); - gScheduler->ThreadRun(); - return NULL; + return true; } -// スレッドエントリ関数(の実体) +// スレッドエントリ関数 void Scheduler::ThreadRun() { - uint32 req; + SetThreadAffinityHint(AffinityClass::Heavy); - // 最初の電源オンを待つ - for (;;) { - std::unique_lock lock(cvmtx); - cv.wait(lock, [&] { return (atomic_reqflag != 0); }); - req = atomic_reqflag.load(); - // 終了 - if ((req & REQ_EXIT)) { - return; - } - // REQ_POWER_ON ならこれだけ下げて抜ける。 - // 他のフラグはメインループで処理するため維持。 - if ((req & REQ_POWER_ON)) { - req &= ~REQ_POWER_ON; - atomic_reqflag.store(req); - break; - } - } - // ここからは電源オン - - // RTC 時刻だけは最初から必要。 - rtimestart = GetRealTime(); - rtc_last_clock = rtimestart; + // 電源オンオフに関わらず、ここが rtime, vtime の基準点 + StartTime(); - mode = 0; for (;;) { - req = atomic_reqflag.exchange(0); - if (__predict_false(req != 0)) { - uint32 newmode = mode; - - // 動作モード変更要因を一つずつ調べる - if ((req & REQ_CPU_MODE)) { - // CPU 状態が new_cpumode に変更になった - newmode &= ~SCHED_CPU_MASK; - newmode |= new_cpumode; - req &= ~REQ_CPU_MODE; + // 条件変数の正しい使い方としては request の参照にも mtx ロックが必要 + // だが、ここでロック取らずに参照したことによって request が立った + // ことを見落としても、ループ一回回ってもう一度ここに来るだけなので + // たぶん致命的なことはないはず。 + if (__predict_false(request)) { + uint32 req; + { + std::lock_guard lock(mtx); + req = request; + request = 0; } - if ((req & REQ_USER_MODE)) { - // ユーザからの高速/同期モード指示が変更になった - newmode &= ~SCHED_SYNC; - newmode |= new_usermode; - req &= ~REQ_USER_MODE; + if ((req & REQUEST_EXIT)) { + break; } - // 動作モード変更要因を全部調べた結果、変更が必要か。 - // mode、newmode どちらもゼロ(高速モード)か非ゼロ(通常モード)か - // なので、等値比較ではなく二値にしてから比較。 - if ((bool)newmode != (bool)mode) { - // 動作モード変更 (ここは全ビットを維持して代入) - mode = newmode; - - if (mode == 0) { - // 高速モードに - putlog(1, "高速モード"); - // 高速モードでは RAM は直接アクセス - ::direct_ram_size = gRAM->GetSize(); + for (; req; req &= req - 1) { + uint id = __builtin_ctz(req); + if (id < MessageID::MAX_REQUEST) { + InvokeMessage(id, 0); } else { - // 同期モードに - putlog(1, "同期モード"); - - // 同期モードでは RAM アクセスはデバイス経由 - ::direct_ram_size = 0; + DispatchMessage(); } - - // 基準時刻をリセット - rtimebase = ::GetRealTime(); - vtimebase = gMPU->GetVirtTime(); } - - if ((req & REQ_POWER_OFF)) { - // 電源オフ要求の場合 - - // 全デバイス電源オフ - gVM->DevicePowerOff(); - - req &= ~REQ_POWER_OFF; - } - - if ((req & REQ_RESET)) { - // リセット要求の場合 - - // 全デバイスをリセット - gVM->ResetHard(); - - req &= ~REQ_RESET; - } - - if ((req & REQ_EXIT)) { - // 終了要求 - // この for ブロックをいきなり抜けるだけなので - // もうフラグの値も関係ないのだが一応。 - req &= ~REQ_EXIT; - break; - } - - // 電源オン要求は来ないはず - assertmsg(req == 0, "req=$%x", req); } - // RTC は常にホスト時刻で動いており - // 32Hz = 31.25msec ごとにパルスを入れる。 - while (::GetRealTime() > rtc_last_clock + 31.25_msec) { - gRTC->ClockIn(); - rtc_last_clock += 31.25_msec; + // 先頭のイベントを取得して停止 + Event *ev; + if (__predict_true(fast != NULL)) { + CT(get1); + ev = fast; + fast = NULL; + } else { + CT(get2); + ev = slow[slow_top--]; + slow_top_vtime = slow[slow_top]->vtime; } + ev->active = false; - // イベントリストを調べ現時点から最も早く発生するイベントまでの必要 - // 時間を求めて、この時間分だけ MPU を駆動する。 - // MPU 実行中にデバイスアクセスなどによってスケジューラにイベントが - // 登録されると、その命令の終了とともに処理を打ち切って戻ってくる。 - // また、電源オン(リセット)時には MPU デバイスが 0nsec で MPU の - // リセット例外イベントを起こすようになっている。 - - // delta_vtime が今回進める仮想時間。 - // とりあえず初期値を仮置きしとくけど、イベントは通常 20msec 以内には - // 必ず起きるので (例えば LUNA の sysclk は 10 or 16 msec 周期)、 - // この初期値は実質上限値。20msec 程度より大きければいくらでもよくて - // 100msec とかでいいんだけど、二進数で置数しやすいよう - // 0x200'0000 (約33.5msec) にしておく。 - uint64 delta_vtime = 0x200'0000; - - // イベント - uint64 vtime = gMPU->GetVirtTime(); - for (;;) { - Event *e; - // ロック区間 - { - std::lock_guard lock(evcs); + // 時刻更新 + vtime = ev->vtime; - if (eventlist.empty()) { - break; - } - e = eventlist.front(); - if (e->vtime > vtime) { - // まだ時刻に到達していない - delta_vtime = e->vtime - vtime; - break; - } + // コールバック + ev->count++; + ((ev->dev)->*(ev->func))(ev); + } +} - // 到達したのでこのイベントをリストから削除 - e->active = false; - eventlist.pop_front(); - } +// 時間の始まり +void +Scheduler::StartTime() +{ + // イベントキューを空にする + for (auto *ev : evman->all) { + if (ev->active) { + StopEvent(ev); + } + ev->count = 0; + } - // コールバック - e->dev->putlogf(3, lstr("イベント '%s' 時刻到達", - e->GetName().c_str())); - ((e->dev)->*(e->func))(*e); + // 時間をリセットする。 + // 呼び出しの依存関係に注意。 + vtime = 0; // required by Sync + syncer->StartTime(); // required by RTC + GetRTCDevice()->StartTime(); +} - // 同時刻のイベントがあるかも知れないのでなくなるまで調べる - } +// スレッド終了指示 +// (当然他スレッドから呼ぶことになる) +void +Scheduler::Terminate() +{ + std::lock_guard lock(mtx); + request |= REQUEST_EXIT; + cv.notify_one(); +} - // CPU を駆動 - new_cpumode = gMPU->Run(delta_vtime); +// 指定のイベントを開始する。 +// すでに同イベントが登録されている場合は古いイベントを削除してから +// 新しいイベントを再登録となる。 +// イベントはワンショットのみ。 +void +Scheduler::RestartEvent(Event *ev) +{ + if (ev->IsRunning()) { + StopEvent(ev); + } - // CPU 駆動によって進んだ仮想時刻を再取得 - vtime = gMPU->GetVirtTime(); + StartEvent(ev); +} - // 同期モードなら実時間調整。 - // あるいは高速モードであってもストップ状態なら実時間駆動する。 - if (mode != 0) { - // 通常モード - uint64 vspan; - uint64 rspan; - - // 基準時からの経過時間 - vspan = vtime - vtimebase; - rspan = GetRealTime() - rtimebase; - - if (vspan > rspan) { - // 仮想時間のほうが進んでいれば、スリープして待つ - uint64 diff = vspan - rspan; - struct timespec ts; - ts.tv_sec = diff / (1000 * 1000 * 1000); - ts.tv_nsec = diff % (1000 * 1000 * 1000); - nanosleep(&ts, NULL); +// 指定のイベントを開始する。 +// このイベントが登録されてないことが確定できる場合のみこちらが使える。 +// イベントはワンショットのみ。 +void +Scheduler::StartEvent(Event *ev) +{ + CT(start); +#ifdef CT_ON + if (ct_start % 100'000'000 == 0) { + printf("get*=%" PRIu64 " [%" PRIu64 ", %" PRIu64 "]\n", + (ct_get1 + ct_get2), ct_get1, ct_get2); + printf("start*=%" PRIu64 + " [%" PRIu64 ", %" PRIu64 ", %" PRIu64 ", %" PRIu64 "]\n", + ct_start, ct_start1, ct_start2, ct_start3, ct_start4); + printf("stop*=%" PRIu64 " [%" PRIu64 ", %" PRIu64 "]\n", + (ct_stop1 + ct_stop2), ct_stop1, ct_stop2); + printf("eslow=%" PRIu64 " sslow=%" PRIu64 " pslow=%" PRIu64 "\n", + ct_eslow, ct_sslow, ct_pslow); + printf("slow_top=%d vtime=%" PRIu64 "\n", slow_top, slow_top_vtime); + } +#endif + + // この assert を有効にすると 10 ポイント性能が低下する(502%->492%) + //assertmsg(ev->IsRunning() == false, "%s", ev->GetName().c_str()); + + // vtime は仮想絶対時刻 [nsec]、time は仮想相対時間 [nsec] + ev->vtime = vtime + ev->time; + + ev->active = true; + + if (__predict_true(ev->vtime <= slow_top_vtime)) { + if (__predict_true(fast == NULL)) { + CT(start1); + fast = ev; + } else { + if (__predict_false(ev->vtime <= fast->vtime)) { + CT(start2); + PushSlow(fast); + fast = ev; } else { - // 実時間のほうが進んでいれば、間に合ってない - - // XXX ここで間引き運転とか + CT(start3); + PushSlow(ev); } } + } else { + CT(start4); + EnqueueSlow(ev); + } +} - // CPU 側の走行状態が変わったら、このループに通知 - if (__predict_false(((mode ^ new_cpumode) & SCHED_CPU_MASK))) { - atomic_reqflag |= REQ_CPU_MODE; - } +// 実時間間隔を指定してイベントを開始する。 +// rt_now はイベント発行者の実時間での現在時刻で、 +// rt_period は次回のイベントまでの実時間間隔。 +void +Scheduler::StartRealtimeEvent(Event *ev, uint64 rt_now, uint64 rt_period) +{ + // 実時間をもとに仮想時間軸上の推定を行う。 + + uint64 rtime = syncer->GetRealTime(); + + if (rtime > rt_now + rt_period) { + // 実時間がさらに 1 period 以上進んでいる。 + // 実時間に追いつかないといけないので、イベントの + // 周期を短くして回復運転させる。 + // 周期を短くしすぎるとゲストが処理できなくなるので、 + // 10% カットに留める。 + ev->time = rt_period - rt_period / 10; + } else if (rtime < rt_now - rt_period) { + // イベントがさらに 1 period 以上進んでいる。 + // 実時間を追い越してしまっているので、イベントの + // 周期を長くして待つ。長くする方はいくらでも長くしていいので、 + // 連続した場合はどんどん長くしていく。 + ev->time += rt_period; + } else { + // おおむね同期している + ev->time = rt_period; } + + RestartEvent(ev); } -// 必要ならスレッドの終了を指示して完了まで待つ。 +// ev は slow_top には来ない前提 void -Scheduler::Terminate() +Scheduler::EnqueueSlow(Event *ev) { - if (thread_created) { - RequestExit(); - pthread_join(thread, NULL); - thread_created = false; + CT(eslow); + + int s = slow_top; + slow[++slow_top] = slow[s]; + + for (; --s >= 0; ) { + if (ev->vtime <= slow[s]->vtime) { + slow[s + 1] = ev; + return; + } else { + slow[s + 1] = slow[s]; + } } + slow[0] = ev; } -// スレッド終了を指示する。 void -Scheduler::RequestExit() +Scheduler::PushSlow(Event *ev) { - std::unique_lock lock(cvmtx); - atomic_reqflag |= REQ_EXIT; - cv.notify_one(); + CT(pslow); + + slow[++slow_top] = ev; + slow_top_vtime = ev->vtime; } -// 動作モードを設定する。 -// true なら高速モード、false なら同期モード。 +// 指定のイベントを停止する。 +// 指定されたイベントが登録されていなければ何もしない。 void -Scheduler::SetFullSpeed(bool enable) +Scheduler::StopEvent(Event *ev) { - if (enable) { - new_usermode = 0; + ev->active = false; + + if (__predict_false(ev == fast)) { + CT(stop1); + fast = NULL; } else { - new_usermode = SCHED_SYNC; + CT(stop2); + StopSlowEvent(ev); } - atomic_reqflag |= REQ_USER_MODE; } -// 指定のイベントを開始する。 -// すでに同イベントが登録されている場合は古いイベントを削除してから -// 新しいイベントを再登録となる。 -// イベントはワンショットのみ。 void -Scheduler::StartEvent(Event *newev) +Scheduler::StopSlowEvent(Event *ev) { - bool inserted = false; - bool updated = false; + CT(sslow); - // time は仮想相対時間 [nsec]、vtime は仮想絶対時刻 [nsec] - newev->vtime = gMPU->GetVirtTime() + newev->time; + int s = slow_top; - evcs.lock(); - // すでにあれば削除 - if (newev->active) { - for (auto it = eventlist.begin(); it != eventlist.end(); ++it) { - Event *e = *it; - if (e == newev) { - eventlist.erase(it); - updated = true; - break; + for (; s >= 0; s--) { + if (ev == slow[s]) { + for (; s < slow_top; s++) { + slow[s] = slow[s + 1]; + } + slow_top--; + if (slow_top < 0) { + slow_top_vtime = UINT64_MAX; + } else { + slow_top_vtime = slow[slow_top]->vtime; } - } - } - // ソートされているところに自身を挿入 - for (auto it = eventlist.begin(); it != eventlist.end(); ++it) { - Event *e = *it; - if (newev->vtime <= e->vtime) { - // このイベントの前に入れる - eventlist.insert(it, newev); - inserted = true; break; } } - if (inserted == false) { - // 最後に追加 - eventlist.push_back(newev); - } - newev->active = true; - evcs.unlock(); - - gMPU->Release(); - - newev->dev->putlogf(3, lstr("イベント '%s' %s %d.%03d usec 後", - newev->GetName().c_str(), - updated ? "更新" : "開始", - (int)(newev->time / 1000), - (int)(newev->time % 1000))); } -// 指定のイベントを停止する。 -// 指定されたイベントが登録されていなければ何もしない。 +// メッセージハンドラを登録する。 void -Scheduler::StopEvent(Event *event) +Scheduler::ConnectMessage(MessageID msgid, Device *dev, MessageCallback_t func) { - bool found = false; + assert(msgid < MessageID::MAX); - evcs.lock(); - for (auto it = eventlist.begin(); it != eventlist.end(); ++it) { - Event *e = *it; - if (e == event) { - e->active = false; - eventlist.erase(it); - found = true; - break; + auto& mh = message_handlers[msgid]; + assertmsg(mh.dev == NULL, + "%s: msgid=%d connected again", __func__, (int)msgid); + mh.dev = dev; + mh.func = func; +} + +// メッセージをディスパッチして、登録されていればハンドラを呼び出す。 +void +Scheduler::DispatchMessage() +{ + uint64 msg; + + while (msgq.Dequeue(&msg)) { + MessageID msgid = (MessageID)(msg & 0xff); + uint32 arg = (msg >> 32); + + // MessageID::MPU_TRACE_ALL だけ特別対応。 + // MPU_TRACE メッセージの宛先は(最大)2つあるが、 + // スレッドを越える時は1メッセージにしておきたいので、 + // MPU_TRACE_ALL が来たらここで分岐。 + if (msgid == MessageID::MPU_TRACE_ALL) { + InvokeMessage(MessageID::MPU_TRACE_MAIN, arg); + InvokeMessage(MessageID::MPU_TRACE_XP, arg); + } else { + assert(msgid < MessageID::MAX); + InvokeMessage(msgid, arg); } } - evcs.unlock(); +} - // イベントを停止した場合は MPU の実行中断はしなくてよい。 - // 1msec 後にイベントを追加した後、やっぱりそのイベントを取り消した - // 場合 (LUNA の電源オフとか) はここで MPU 処理を中断するよりも - // そのまま 1msec 走って問題ない。 - - if (found) { - event->dev->putlogf(3, lstr("イベント '%s' 停止", - event->GetName().c_str())); +// メッセージハンドラを呼び出す。登録されてなければ何もしない。 +void +Scheduler::InvokeMessage(MessageID msgid, uint32 arg) +{ + auto h = message_handlers[msgid]; + if (h.dev != NULL) { + (h.dev->*(h.func))(msgid, arg); } } -// 経過時間 t を文字列にして返す。 -static const std::string -TimeToStr(uint64 t) -{ - char buf[32]; - char *p; - size_t len; - int n; - - uint ns = t % 1000; - t /= 1000; - uint us = t % 1000; - t /= 1000; - uint ms = t % 1000; - t /= 1000; - uint s = t % 60; - t /= 60; - uint m = t % 60; - t /= 60; - uint h = t; - - p = buf; - len = sizeof(buf); - if (h) { - n = snprintf(p, len, "%d:%02d:%02d", h, m, s); - p += n; - len -= n; - } else if (m) { - n = snprintf(p, len, "%d:%02d", m, s); - p += n; - len -= n; +// メッセージを送る。 +// VM スレッド以外から呼び出しても良い。 +void +Scheduler::SendMessage(MessageID msgid, uint32 arg) +{ + std::lock_guard lock(mtx); + + if (msgid < MessageID::MAX_REQUEST) { + request |= 1U << (uint)msgid; } else { - n = snprintf(p, len, "%d", s); - p += n; - len -= n; + uint64 msg = (uint64)msgid | (((uint64)arg) << 32); + if (msgq.Enqueue(msg) == false) { + putlog(0, "Message queue exhausted !! msgid=%d", (int)msgid); + } + request |= REQUEST_MESSAGE; } - n = snprintf(p, len, ".%03d'%03d'%03d", ms, us, ns); - p += n; - len -= n; - - return std::string(buf, p - buf); + cv.notify_one(); } -// モニターサイズ取得 -nnSize -Scheduler::GetMonitorSize() +// 指定時間が経過するか、リクエストが起きるまでスリープ +void +Scheduler::Sleep(uint64 time) { - // 本当は Init() 完了時点でイベント個数は確定しているはずだけど、 - // そのタイミングで一度だけ呼ばれるフックが今の所ない。 - return nnSize(68, 11 + gEvents.size()); + std::unique_lock lock(mtx); + cv.wait_for(lock, std::chrono::nanoseconds(time), [&] { + return (request != 0); + }); + // リクエストフラグが立ってもここでは何もしない } void -Scheduler::MonitorUpdate(TextScreen& monitor) +Scheduler::MonitorUpdate(Monitor *, TextScreen& screen) { - int x, y; + int y; - monitor.Clear(); - x = 0; - y = 0; + screen.Clear(); - int clock_khz = gMPU->GetClockSpeed(); + y = syncer->MonitorUpdateSub(screen, vtime); - monitor.Print(0, y, "Mode: 0x%04x (", mode); - if ((mode & SCHED_SYNC)) { - monitor.Puts(14, y, TA::On, "Sync"); - } else { - monitor.Puts(14, y, "Full"); - } - switch (mode & SCHED_CPU_MASK) { - case SCHED_CPU_NORMAL: - monitor.Puts(19, y, "Run"); - break; - case SCHED_CPU_STOP: - monitor.Puts(19, y, TA::On, "STOP"); - break; - case SCHED_CPU_HALT: - monitor.Puts(19, y, TA::On, "HALT"); - break; - } - monitor.Puts(23, y, ")"); + // 0 1 2 3 + // 012345678901234567890123456789 + // Event Time Remain Time + // 3.123'456'789 3.123'456'789 + // + // 3 4 5 6 7 + // 01234567890123456789012345678901234567890123456789 + // Description Count + // 012345678901234567890123456789 999,999,999,999,999 + // 18,446,744,073,709,551,615 y++; - monitor.Print(0, y++, "Req: %08x", (uint32)atomic_reqflag); - monitor.Print(0, y++, "MPU Speed: %d.%03dMHz", - (clock_khz / 1000), (clock_khz % 1000)); - uint64 vtime = gMPU->GetVirtTime(); - uint64 rtime = GetRealTime(); - uint64 vspan = vtime - vtimebase; - uint64 rspan = rtime - rtimebase; - // RealTime は実時間なのでスケジューラ開始からの時間に変換 - rtime -= rtimestart; - monitor.Print(0, y++, "Total Real Time: %18s", TimeToStr(rtime).c_str()); - monitor.Print(0, y++, " Virtual Time: %18s", TimeToStr(vtime).c_str()); - double ratio = (double)vtime / rtime * 100; - monitor.Print(0, y++, " Ratio : %3d.%01d%%", - (int)ratio, ((int)(ratio * 10) % 10)); - monitor.Print(0, y++, "Moment Real Time: %18s", TimeToStr(rspan).c_str()); - monitor.Print(0, y++, " Virtual Time: %18s", TimeToStr(vspan).c_str()); - // XXX 移動平均をとる - double relratio = (double)vspan / rspan * 100; - monitor.Print(0, y++, " Ratio : %3d.%01d%%", - (int)relratio, ((int)(relratio * 10) % 10)); - - // 0 1 2 3 4 5 6 - // 0123456789012345678901234567890123456789012345678901234567890123456 - // Set Time Remain Time Description Code - // 3.123'456'789 3.123'456'789 0123456789012345678901234 $01234567 - x = 0; - y++; - monitor.Puts(x, y, "Set Time"); - monitor.Puts(x + 16, y, "Remain Time"); - monitor.Puts(x + 32, y, "Description"); - monitor.Puts(x + 58, y, "Code"); + screen.Puts(0, y, "Event Time"); + screen.Puts(15, y, "Remain Time"); + screen.Puts(30, y, "Description"); + screen.Puts(75, y, "Count"); y++; - for (const auto& e : gEvents) { + for (auto *ev : evman->all) { uint64 rem; TA attr; - if (e->active) { + + if (ev->IsRunning()) { attr = TA::Normal; - if (e->vtime > vtime) { - rem = e->vtime - vtime; + if (ev->vtime > vtime) { + rem = ev->vtime - vtime; } else { // XXX 実際には起きないけど、この表示をするにあたって // スケジューラスレッドと一切調停していないので、仮想時刻が - // e->cycle (イベント発生時刻) をすぎていることがある。 + // ev->vtime (イベント発生時刻) をすぎていることがある。 // すぎてるので残り時間 0 と表示しておく。 rem = 0; } @@ -513,17 +474,21 @@ Scheduler::MonitorUpdate(TextScreen& mon attr = TA::Disable; rem = 0; } - monitor.Print(x, y++, attr, - "%3u.%03u'%03u'%03u %3u.%03u'%03u'%03u %-25s $%08x", - (uint)(e->time / (1000 * 1000 * 1000)), - (uint)((e->time / 1000 / 1000) % 1000), - (uint)((e->time / 1000) % 1000), - (uint)(e->time % 1000), - (uint)(rem / (1000 * 1000 * 1000)), - (uint)((rem / 1000 / 1000) % 1000), - (uint)((rem / 1000) % 1000), - (uint)(rem % 1000), - e->GetName().c_str(), - e->code); + + if (ev->count != ev->last_count) { + attr = TA::Normal; + ev->last_count = ev->count; + } + + // 先にイベント回数を表示。 + // 21文字以上になったら上位桁が長い Description で消されても + // 大勢に影響はないだろう。 + std::string countstr = format_number(ev->count); + screen.Print(54, y, attr, "%26s", countstr.c_str()); + screen.Print(0, y, attr, "%14s %14s %s ", + SecToStr(ev->time).c_str(), + SecToStr(rem).c_str(), + ev->GetName().c_str()); + y++; } }