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

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

unix.superglobalmegacorp.com

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