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