OXYGENHUB
◆ main openmsx / src / events / GlobalCliComm.cc
3.3 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "GlobalCliComm.hh"
2
3
#include "CliConnection.hh"
4
#include "CliListener.hh"
5
6
#include "Thread.hh"
7
8
#include "ScopedAssign.hh"
9
#include "stl.hh"
10
11
#include <cassert>
12
#include <iostream>
13
#include <utility>
14
15
namespace openmsx {
16
17
GlobalCliComm::~GlobalCliComm()
18
{
19
	assert(Thread::isMainThread());
20
	assert(!delivering);
21
}
22
23
CliListener* GlobalCliComm::addListener(std::unique_ptr<CliListener> listener)
24
{
25
	// can be called from any thread
26
	std::scoped_lock lock(mutex);
27
28
	// first cleanup stale SocketConnection objects
29
	std::erase_if(listeners, [](auto& l) {
30
		auto* s = dynamic_cast<SocketConnection*>(l.get());
31
		return s && s->isClosed(); // closed from the client side
32
	});
33
34
	auto* p = listener.get();
35
	listeners.push_back(std::move(listener));
36
	if (allowExternalCommands) {
37
		if (auto* conn = dynamic_cast<CliConnection*>(p)) {
38
			conn->start();
39
		}
40
	}
41
	return p;
42
}
43
44
std::unique_ptr<CliListener> GlobalCliComm::removeListener(CliListener& listener)
45
{
46
	// can be called from any thread
47
	std::scoped_lock lock(mutex);
48
	auto it = rfind_unguarded(listeners, &listener,
49
		[](const auto& ptr) { return ptr.get(); });
50
	auto result = std::move(*it);
51
	move_pop_back(listeners, it);
52
	return result;
53
}
54
55
void GlobalCliComm::setAllowExternalCommands()
56
{
57
	assert(!allowExternalCommands); // should only be called once
58
	allowExternalCommands = true;
59
	for (const auto& listener : listeners) {
60
		if (auto* conn = dynamic_cast<CliConnection*>(listener.get())) {
61
			conn->start();
62
		}
63
	}
64
}
65
66
void GlobalCliComm::log(LogLevel level, std::string_view message, float fraction)
67
{
68
	assert(Thread::isMainThread());
69
70
	if (delivering) {
71
		// Don't allow recursive calls, this would hang while trying to
72
		// acquire the mutex below. But also when we would change
73
		// this to a recursive-mutex, this could result in an infinite
74
		// loop.
75
		// One example of a recursive invocation is when something goes
76
		// wrong in the Tcl proc attached to message_callback (e.g. the
77
		// font used to display the message could not be loaded).
78
		std::cerr << "Recursive cliComm message: " << message << '\n';
79
		return;
80
	}
81
	ScopedAssign sa(delivering, true);
82
83
	std::scoped_lock lock(mutex);
84
	if (!listeners.empty()) {
85
		for (const auto& l : listeners) {
86
			l->log(level, message, fraction);
87
		}
88
	} else {
89
		// don't let the message get lost
90
		std::cerr << message;
91
		if (level == LogLevel::PROGRESS && fraction >= 0.0f) {
92
			std::cerr << "... " << int(100.0f * fraction) << '%';
93
		}
94
		std::cerr << '\n';
95
	}
96
}
97
98
void GlobalCliComm::update(UpdateType type, std::string_view name, std::string_view value)
99
{
100
	updateHelper(type, {}, name, value);
101
}
102
103
void GlobalCliComm::updateFiltered(UpdateType type, std::string_view name, std::string_view value)
104
{
105
	if (auto [it, inserted] = prevValues[type].try_emplace(name, value);
106
	    !inserted) { // was already present ..
107
		if (it->second == value) {
108
			return; // .. with the same value
109
		} else {
110
			it->second = value; // .. but with a different value
111
		}
112
	}
113
	updateHelper(type, {}, name, value);
114
}
115
116
void GlobalCliComm::updateHelper(UpdateType type, std::string_view machine,
117
                                 std::string_view name, std::string_view value)
118
{
119
	assert(Thread::isMainThread());
120
	std::scoped_lock lock(mutex);
121
	for (const auto& l : listeners) {
122
		l->update(type, machine, name, value);
123
	}
124
}
125
126
} // namespace openmsx