|
|
1.1 ! root 1: // ! 2: // nono ! 3: // Copyright (C) 2025 nono project ! 4: // Licensed under nono-license.txt ! 5: // ! 6: ! 7: // ! 8: // ログ ! 9: // ! 10: ! 11: #include "logger.h" ! 12: #include "monitor.h" ! 13: #include "mythread.h" ! 14: #include "sjis.h" ! 15: #include "textscreen.h" ! 16: ! 17: // コンストラクタ ! 18: Logger::Logger() ! 19: : inherited(OBJ_LOGGER) ! 20: { ! 21: monitor = gMonitorManager->Regist(ID_MONITOR_LOG, this); ! 22: monitor->func = ToMonitorCallback(&Logger::MonitorUpdate); ! 23: monitor->SetSize(80, 40); // 初期サイズは適当 ! 24: } ! 25: ! 26: // デストラクタ ! 27: Logger::~Logger() ! 28: { ! 29: TerminateThread(); ! 30: } ! 31: ! 32: // UTF-8 -> Shift_JIS 変換関数を登録する。GUI が登録する。 ! 33: void ! 34: Logger::SetConverter(std::string (*conv)(const std::string&)) ! 35: { ! 36: utf8_to_sjis_converter = conv; ! 37: } ! 38: ! 39: // スレッドの開始。 ! 40: bool ! 41: Logger::StartThread() ! 42: { ! 43: auto func = [this]() { ! 44: PTHREAD_SETNAME("Logger"); ! 45: std::lock_guard<std::mutex> lock_sub(this->thread_starter); ! 46: this->ThreadRun(); ! 47: }; ! 48: ! 49: if (ResizeCol(monitor->GetCol()) == false) { ! 50: return false; ! 51: } ! 52: ! 53: // スレッド起動 ! 54: std::lock_guard<std::mutex> lock(thread_starter); ! 55: ! 56: try { ! 57: thread.reset(new std::thread(func)); ! 58: } catch (...) { } ! 59: if ((bool)thread == false) { ! 60: warnx("Failed to initialize thread at %s", __method__); ! 61: return false; ! 62: } ! 63: return true; ! 64: } ! 65: ! 66: // スレッドを終了させる ! 67: void ! 68: Logger::TerminateThread() ! 69: { ! 70: if ((bool)thread) { ! 71: Terminate(); ! 72: ! 73: if (thread->joinable()) { ! 74: thread->join(); ! 75: } ! 76: ! 77: thread.reset(); ! 78: } ! 79: } ! 80: ! 81: // スレッドの終了を指示 ! 82: void ! 83: Logger::Terminate() ! 84: { ! 85: std::lock_guard<std::mutex> lock(queue_mtx); ! 86: request |= REQ_TERMINATE; ! 87: cv.notify_one(); ! 88: } ! 89: ! 90: // ログ書き込み。 ! 91: // 他スレッドから呼ばれる。 ! 92: void ! 93: Logger::Write(const char *str) ! 94: { ! 95: std::lock_guard<std::mutex> lock(queue_mtx); ! 96: queue.emplace_back(str); ! 97: request |= REQ_QUEUE; ! 98: cv.notify_one(); ! 99: } ! 100: ! 101: void ! 102: Logger::ThreadRun() ! 103: { ! 104: SetThreadAffinityHint(AffinityClass::Light); ! 105: ! 106: for (;;) { ! 107: std::string utf8str; ! 108: ! 109: // 何か起きるまで待つ。 ! 110: { ! 111: std::unique_lock<std::mutex> lock(queue_mtx); ! 112: cv.wait(lock, [&]{ return (request != 0); }); ! 113: ! 114: if (__predict_false((request & REQ_TERMINATE))) { ! 115: // 終了するので、他の条件は無視 ! 116: break; ! 117: } ! 118: // ここは (request == REQ_QUEUE) が成立しているはず。 ! 119: if (__predict_false(queue.empty())) { ! 120: request = 0; ! 121: continue; ! 122: } else { ! 123: utf8str = queue.front(); ! 124: queue.pop_front(); ! 125: if (queue.empty()) { ! 126: request = 0; ! 127: } ! 128: } ! 129: } ! 130: ! 131: if (__predict_true(utf8str.empty() == false)) { ! 132: DoLog(utf8str); ! 133: } ! 134: } ! 135: } ! 136: ! 137: // 着信処理。 ! 138: void ! 139: Logger::DoLog(const std::string& utf8str) ! 140: { ! 141: // 必要なら標準出力にも出力 ! 142: if (use_stdout) { ! 143: puts(utf8str.c_str()); ! 144: } ! 145: ! 146: // コンバータがなければここで終了 (CLI)。 ! 147: // GUI でも万が一 dispbuf がなければ、これ以上出来ることはない。 ! 148: if (__predict_false(utf8_to_sjis_converter == NULL)) { ! 149: return; ! 150: } ! 151: if (__predict_false(dispbuf.empty())) { ! 152: return; ! 153: } ! 154: ! 155: // Shift_JIS に変換。 ! 156: std::string sjis_str = utf8_to_sjis_converter(utf8str); ! 157: ! 158: // 入力の1文字列が改行を含んでいれば行を分解。 ! 159: std::vector<std::string> lines = string_split(sjis_str, '\n'); ! 160: ! 161: { ! 162: std::lock_guard<std::mutex> lock(backend_mtx); ! 163: ! 164: // 1行ずつバックログに追加。 ! 165: for (const auto& str : lines) { ! 166: AddLog(str); ! 167: } ! 168: } ! 169: } ! 170: ! 171: // バックログに str を追加。 ! 172: // str は Shift_JIS で改行を含まないこと。 ! 173: void ! 174: Logger::AddLog(const std::string& str) ! 175: { ! 176: // バックログに追加。埋まっていれば古いのを追い出す。 ! 177: if (__predict_true(logs.size() >= maxlines)) { ! 178: logs.pop_front(); ! 179: } ! 180: logs.emplace_back(str); ! 181: ! 182: // ディスプレイバッファにレンダリング。 ! 183: AddDisplay(str); ! 184: } ! 185: ! 186: // ディスプレイバッファにレンダリングする。 ! 187: // TAB 文字は展開されず 0x09 のグリフが表示される。 ! 188: // backend_mtx 内で呼ぶこと。 ! 189: void ! 190: Logger::AddDisplay(const std::string& str) ! 191: { ! 192: assert(dispbuf.empty() == false); ! 193: ! 194: uint8 *d = &dispbuf[current * col]; ! 195: uint x = 0; ! 196: for (const char *s = str.c_str(); ; ) { ! 197: uint8 c = s[x]; ! 198: if (__predict_false(c == '\0')) { ! 199: // 改行へ。 ! 200: } else if (SJIS::IsHankaku(c) || s[x + 1] == '\0') { ! 201: // 半角。(半角でない判定でも次が終端文字なら半角扱い) ! 202: x++; ! 203: if (__predict_true(x < col)) { ! 204: continue; ! 205: } ! 206: } else if (x < col - 1) { ! 207: x += 2; ! 208: if (__predict_true(x < col)) { ! 209: continue; ! 210: } ! 211: } else { ! 212: // 2バイト文字がはみ出すなら改行。 ! 213: } ! 214: ! 215: // s から d に x 文字出力して、改行。 ! 216: memcpy(d, s, x); ! 217: if (x < col) { ! 218: // 余った右側は空白で埋める。 ! 219: memset(d + x, ' ', col - x); ! 220: } ! 221: s += x; ! 222: x = 0; ! 223: ! 224: // 行送り。 ! 225: current = (current + 1) % maxlines; ! 226: d = &dispbuf[current * col]; ! 227: if (__predict_false(displines < maxlines)) { ! 228: displines++; ! 229: } ! 230: ! 231: // 行を出力したあとで、改めて s が終端なら終了。 ! 232: if (*s == '\0') { ! 233: break; ! 234: } ! 235: } ! 236: } ! 237: ! 238: // ログの表示桁数を変更(再設定)する。 ! 239: // 0 なら表示用バッファなし。 ! 240: bool ! 241: Logger::ResizeCol(int newcol) ! 242: { ! 243: std::lock_guard<std::mutex> lock(backend_mtx); ! 244: ! 245: size_t buflen = maxlines * newcol; ! 246: try { ! 247: dispbuf.resize(buflen); ! 248: } catch (...) { ! 249: // もう出来ることはない…。 ! 250: warnx("%s: Failed to allocate %zu bytes", __method__, buflen); ! 251: dispbuf.resize(0); ! 252: return false; ! 253: } ! 254: ! 255: col = newcol; ! 256: ! 257: if (col != 0) { ! 258: // 今あるバッファを全部書き直す。 ! 259: current = 0; ! 260: displines = 0; ! 261: for (const auto& str : logs) { ! 262: AddDisplay(str); ! 263: } ! 264: } ! 265: ! 266: return true; ! 267: } ! 268: ! 269: // screen.userdata はログの最新から何行手前を screen の底の行にコピーするか ! 270: // を示す。 ! 271: // userdata = 0 なら screen の底が最新行。 ! 272: // userdata = 1 なら1行古いほうに遡った状態、となる。 ! 273: // screen.userdata + screen の行数が maxlines を超えてはいけない。 ! 274: void ! 275: Logger::MonitorUpdate(Monitor *, TextScreen& screen) ! 276: { ! 277: assertmsg(screen.userdata + screen.GetRow() <= maxlines, ! 278: "userdata=%" PRIu64 " row=%d maxlines=%d", ! 279: screen.userdata, screen.GetRow(), maxlines); ! 280: assertmsg(screen.GetCol() == col, ! 281: "GetCol=%d col=%d", screen.GetCol(), col); ! 282: ! 283: // ここのモニタは常に Ring モードで書き込む。 ! 284: // 本当はこれを呼び出す側が一度初期化しておくだけでいいのだが ! 285: // ここでやるほうが簡単なので。 ! 286: screen.Mode = TextScreen::Ring; ! 287: ! 288: std::lock_guard<std::mutex> lock(backend_mtx); ! 289: ! 290: // 万が一 dispbuf が空なら出来ることはない。 ! 291: if (__predict_false(dispbuf.empty())) { ! 292: return; ! 293: } ! 294: ! 295: // nlines はコピーする行数。 ! 296: int nlines = std::min(displines, screen.GetRow()); ! 297: if (nlines < screen.GetRow()) { ! 298: // ログが画面高さ分埋まってなければ、画面をクリア。 ! 299: screen.Clear(); ! 300: } else { ! 301: // 全文字上書きするのでクリア不要。 ! 302: screen.Locate(0, 0); ! 303: } ! 304: ! 305: // dispy は dispbuf のコピー開始位置 (行単位)。 ! 306: int dispy = current - (screen.userdata + nlines); ! 307: dispy = (dispy + maxlines) % maxlines; ! 308: ! 309: // 1行ずつコピー。 ! 310: for (uint y = 0; y < nlines; y++) { ! 311: uint8 *dp = &dispbuf[dispy * col]; ! 312: for (uint x = 0; x < col; x++) { ! 313: screen.Putc(*dp++); ! 314: } ! 315: dispy++; ! 316: if (__predict_false(dispy >= maxlines)) { ! 317: dispy = 0; ! 318: } ! 319: } ! 320: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.