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