Annotation of nono/host/logger.cpp, revision 1.1.1.3

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: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.