|
|
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.