File:  [Isaki's NoNo m68k/m88k emulator] / nono / host / logger.cpp
Revision 1.1.1.3 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:06:02 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2025 nono project
// Licensed under nono-license.txt
//

//
// ログ
//

// Logger はモニタを持つため Object から派生しているが、(Object のもう一つの
// 機能である) ログ出力は出来ない。これは自明。

// また、ログと comdriver_stdio (特にデバッガの出力) が標準出力への出力を
// 共有するため標準出力側のみここで担当している。
// 概念としての動作はこんな感じ。
//
// 送信側スレッド   :   ログスレッド
//
// ログ/標準出力    :
//     |
//     +------->| queue |------+
//                             |     Yes
//                  :        (ログ?)-------------+
//                             |                 |
//                  :          | No      +------------------+
//                             |         | ログモニタに格納 |
//                  :          |         +------------------+
//                             |                 |
//                  :          |                 v
//                             |       (ログを標準出力に出力?)
//                  :          |          |           |
//                             |          | Yes    No |
//                  :          +<---------+           |
//                             |                      |
//                  :          v                      |
//                     +----------------+             |
//                  :  | 標準出力に出力 |             |
//                     +----------------+             |
//                  :          |                      |
//                             +-----------+----------+
//                  :                      |
//                                        END

#include "logger.h"
#include "monitor.h"
#include "mythread.h"
#include "sjis.h"
#include "textscreen.h"

// コンストラクタ
Logger::Logger()
	: inherited(OBJ_LOGGER)
{
	ClearAlias();

	monitor = gMonitorManager->Regist(ID_MONITOR_LOG, this);
	monitor->SetCallback(&Logger::MonitorScreen);
	monitor->SetSize(80, 40);	// 初期サイズは適当
}

// デストラクタ
Logger::~Logger()
{
	TerminateThread();
}

// UTF-8 -> Shift_JIS 変換関数を登録する。GUI が登録する。
void
Logger::SetConverter(std::string (*conv)(const std::string&))
{
	utf8_to_sjis_converter = conv;
}

// スレッドの開始。
bool
Logger::StartThread()
{
	if (ResizeCol(monitor->GetCol()) == false) {
		return false;
	}

	// スレッド起動。
	std::lock_guard<std::mutex> lock(thread_starter);
	try {
		thread.reset(new std::thread(&Logger::OnStart, this));
	} catch (...) { }
	if ((bool)thread == false) {
		warnx("Failed to initialize thread at %s", __method__);
		return false;
	}
	return true;
}

// 開始されたスレッドでのエントリポイント。
void
Logger::OnStart()
{
	// このスレッドにスレッド名を設定。
	static const char name[] = "Logger";
	PTHREAD_SETNAME(name);

	auto thman = gMainApp.GetThreadManager();
	thman->RegistThread(this, name);

	// 実行開始。
	std::lock_guard<std::mutex> lock_sub(this->thread_starter);
	ThreadRun();
}

// スレッドを終了させる
void
Logger::TerminateThread()
{
	if ((bool)thread) {
		auto thman = gMainApp.GetThreadManager();
		thman->UnregistThread(this);

		Terminate();

		if (thread->joinable()) {
			thread->join();
		}

		thread.reset();
	}
}

// スレッドの終了を指示
void
Logger::Terminate()
{
	std::lock_guard<std::mutex> lock(queue_mtx);
	request |= REQ_TERMINATE;
	cv.notify_one();
}

// 標準出力にも出力するかを設定。
void
Logger::UseStdout(bool val)
{
	use_stdout = val;
}

// ログ書き込み。
// 他スレッドから呼ばれる。
void
Logger::WriteLog(const char *str)
{
	std::lock_guard<std::mutex> lock(queue_mtx);
	queue.emplace_back(LogMsg::Type::Log, str);
	request |= REQ_QUEUE;
	cv.notify_one();
}

// 標準出力への出力。
// COMDriverStdio から呼ばれる。
void
Logger::WriteStdout(const char *str)
{
	std::lock_guard<std::mutex> lock(queue_mtx);
	queue.emplace_back(LogMsg::Type::Stdout, str);
	request |= REQ_QUEUE;
	cv.notify_one();
}

void
Logger::ThreadRun()
{
	SetThreadAffinityHint(AffinityClass::Light);

	for (;;) {
		LogMsg msg;

		// 何か起きるまで待つ。
		{
			std::unique_lock<std::mutex> lock(queue_mtx);
			cv.wait(lock, [&]{ return (request != 0); });

			if (__predict_false((request & REQ_TERMINATE))) {
				// 終了するので、他の条件は無視
				break;
			}
			// ここは (request == REQ_QUEUE) が成立しているはず。
			if (__predict_false(queue.empty())) {
				request = 0;
				continue;
			} else {
				msg = queue.front();
				queue.pop_front();
				if (queue.empty()) {
					request = 0;
				}
			}
		}

		if (msg.type == LogMsg::Type::Log) {
			DoLog(msg.text);
		} else {
			DoStdout(msg.text);
		}
	}
}

// 標準出力へ出力。
void
Logger::DoStdout(const std::string& text)
{
	fputs(text.c_str(), stdout);
	fflush(stdout);
}

// 着信処理。
void
Logger::DoLog(const std::string& utf8str)
{
	if (use_stdout) {
		// 標準出力にも出力。
		// 入力のログ文字列に終端の改行はなく、
		// puts() は stdout に改行を付けて出す。fflush() は念のためくらい。
		puts(utf8str.c_str());
		fflush(stdout);
	}

	// コンバータがなければここで終了 (CLI)。
	// GUI でも万が一 dispbuf がなければ、これ以上出来ることはない。
	if (__predict_false(utf8_to_sjis_converter == NULL)) {
		return;
	}
	if (__predict_false(dispbuf.empty())) {
		return;
	}

	// Shift_JIS に変換。
	std::string sjis_str = utf8_to_sjis_converter(utf8str);

	// 入力の1文字列が改行を含んでいれば行を分解。
	std::vector<std::string> lines = string_split(sjis_str, '\n');

	{
		std::lock_guard<std::mutex> lock(backend_mtx);

		// 1行ずつバックログに追加。
		for (const auto& str : lines) {
			AddLog(str);
		}
	}
}

// バックログに str を追加。
// str は Shift_JIS で改行を含まないこと。
void
Logger::AddLog(const std::string& str)
{
	// バックログに追加。埋まっていれば古いのを追い出す。
	if (__predict_true(logs.size() >= maxlines)) {
		logs.pop_front();
	}
	logs.emplace_back(str);

	// ディスプレイバッファにレンダリング。
	AddDisplay(str);
}

// ディスプレイバッファにレンダリングする。
// TAB 文字は展開されず 0x09 のグリフが表示される。
// backend_mtx 内で呼ぶこと。
void
Logger::AddDisplay(const std::string& str)
{
	assert(dispbuf.empty() == false);

	uint8 *d = &dispbuf[current * col];
	uint x = 0;
	for (const char *s = str.c_str(); ; ) {
		uint8 c = s[x];
		if (__predict_false(c == '\0')) {
			// 改行へ。
		} else if (SJIS::IsHankaku(c) || s[x + 1] == '\0') {
			// 半角。(半角でない判定でも次が終端文字なら半角扱い)
			x++;
			if (__predict_true(x < col)) {
				continue;
			}
		} else if (x < col - 1) {
			x += 2;
			if (__predict_true(x < col)) {
				continue;
			}
		} else {
			// 2バイト文字がはみ出すなら改行。
		}

		// s から d に x 文字出力して、改行。
		memcpy(d, s, x);
		if (x < col) {
			// 余った右側は空白で埋める。
			memset(d + x, ' ', col - x);
		}
		s += x;
		x = 0;

		// 行送り。
		current = (current + 1) % maxlines;
		d = &dispbuf[current * col];
		if (__predict_false(displines < maxlines)) {
			displines++;
		}

		// 行を出力したあとで、改めて s が終端なら終了。
		if (*s == '\0') {
			break;
		}
	}
}

// ログの表示桁数を変更(再設定)する。
// 0 なら表示用バッファなし。
bool
Logger::ResizeCol(int newcol)
{
	std::lock_guard<std::mutex> lock(backend_mtx);

	size_t buflen = maxlines * newcol;
	try {
		dispbuf.resize(buflen);
	} catch (...) {
		// もう出来ることはない…。
		warnx("%s: Failed to allocate %zu bytes", __method__, buflen);
		dispbuf.resize(0);
		return false;
	}

	col = newcol;

	if (col != 0) {
		// 今あるバッファを全部書き直す。
		current = 0;
		displines = 0;
		for (const auto& str : logs) {
			AddDisplay(str);
		}
	}

	return true;
}

// screen.userdata はログの最新から何行手前を screen の底の行にコピーするか
// を示す。
// userdata = 0 なら screen の底が最新行。
// userdata = 1 なら1行古いほうに遡った状態、となる。
// screen.userdata + screen の行数が maxlines を超えてはいけない。
void
Logger::MonitorScreen(Monitor *, TextScreen& screen)
{
	assertmsg(screen.userdata + screen.GetRow() <= maxlines,
		"userdata=%" PRIu64 " row=%d maxlines=%d",
		screen.userdata, screen.GetRow(), maxlines);
	assertmsg(screen.GetCol() == col,
		"GetCol=%d col=%d", screen.GetCol(), col);

	// ここのモニタは常に Ring モードで書き込む。
	// 本当はこれを呼び出す側が一度初期化しておくだけでいいのだが
	// ここでやるほうが簡単なので。
	screen.Mode = TextScreen::Ring;

	std::lock_guard<std::mutex> lock(backend_mtx);

	// 万が一 dispbuf が空なら出来ることはない。
	if (__predict_false(dispbuf.empty())) {
		return;
	}

	// nlines はコピーする行数。
	int nlines = std::min(displines, screen.GetRow());
	if (nlines < screen.GetRow()) {
		// ログが画面高さ分埋まってなければ、画面をクリア。
		screen.Clear();
	} else {
		// 全文字上書きするのでクリア不要。
		screen.Locate(0, 0);
	}

	// dispy は dispbuf のコピー開始位置 (行単位)。
	int dispy = current - (screen.userdata + nlines);
	dispy = (dispy + maxlines) % maxlines;

	// 1行ずつコピー。
	for (uint y = 0; y < nlines; y++) {
		uint8 *dp = &dispbuf[dispy * col];
		for (uint x = 0; x < col; x++) {
			screen.Putc(*dp++);
		}
		dispy++;
		if (__predict_false(dispy >= maxlines)) {
			dispy = 0;
		}
	}
}

unix.superglobalmegacorp.com

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