mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-16 07:50:28 +02:00
The core and the headless tools no longer use mutexes, condition variables, spinlocks, std::atomic, std::thread or volatile flags. Threads hand off through libretro-common's retro_atomic and rthreads plus a few small pieces on top of them in Common/Thread: Thread, MpscQueue, EventCounter, ParkingLot waits and ReaderGate. Every wait blocks until it's woken; nothing polls on a timer. State that only one thread touches just loses its lock. The PSP filesystem, sceIo and the savedata dialog now belong to the emulation thread: async reads and writes run when they're queued and complete later in emulated time, and the IO and savedata threads are gone. The friend finder only talks to the adhoc server and hands what it gets to the emulation thread. GPU init always runs on the thread that starts the game. The Vulkan render and compile queues, pipeline and render pass caches, delete list and SPIR-V cache, the GL render queue, the software renderer's jit caches, the file loader caches, the web debugger, UPnP, input, savestates, logging and the GE debugger all hand off through queues and atomics. The IR jit's spinning profiler thread goes. A CPU thread with nothing to run now sleeps until work is queued instead of spinning. Headless with --debugger waits for a client while stopped, and ends the run once every client that connected has left. Web server shutdown keeps servicing the CPU queue until the server has finished.
421 lines
12 KiB
C++
421 lines
12 KiB
C++
#include <cstring>
|
|
#include <algorithm>
|
|
// for std::min
|
|
|
|
#include "Common/System/OSD.h"
|
|
#include "Common/TimeUtil.h"
|
|
#include "Common/Log.h"
|
|
#include "Common/Math/math_util.h"
|
|
|
|
OnScreenDisplay g_OSD;
|
|
|
|
// Effectively forever.
|
|
constexpr double forever_s = 10000000000.0;
|
|
|
|
namespace {
|
|
struct OSDCommand;
|
|
struct OSDLink {
|
|
mpsc_stack_node_t node; // first, so a node pointer is a link pointer
|
|
OSDCommand *self;
|
|
};
|
|
struct OSDCommand {
|
|
OSDLink link;
|
|
std::function<void(OnScreenDisplay &)> fn;
|
|
};
|
|
}
|
|
|
|
OnScreenDisplay::OnScreenDisplay() {
|
|
mpsc_stack_init(&pending_);
|
|
}
|
|
|
|
OnScreenDisplay::~OnScreenDisplay() {
|
|
mpsc_stack_node_t *node = mpsc_stack_drain(&pending_);
|
|
while (node) {
|
|
mpsc_stack_node_t *next = node->next;
|
|
delete ((OSDLink *)node)->self;
|
|
node = next;
|
|
}
|
|
}
|
|
|
|
void OnScreenDisplay::Post(std::function<void(OnScreenDisplay &)> fn) {
|
|
OSDCommand *cmd = new OSDCommand{ {}, std::move(fn) };
|
|
cmd->link.self = cmd;
|
|
mpsc_stack_push(&pending_, &cmd->link.node);
|
|
}
|
|
|
|
void OnScreenDisplay::Update() {
|
|
// Apply commands oldest first.
|
|
mpsc_stack_node_t *node = mpsc_stack_reverse(mpsc_stack_drain(&pending_));
|
|
while (node) {
|
|
mpsc_stack_node_t *next = node->next;
|
|
OSDCommand *cmd = ((OSDLink *)node)->self;
|
|
cmd->fn(*this);
|
|
delete cmd;
|
|
node = next;
|
|
}
|
|
|
|
double now = time_now_d();
|
|
for (auto iter = entries_.begin(); iter != entries_.end(); ) {
|
|
if (now >= iter->endTime) {
|
|
iter = entries_.erase(iter);
|
|
} else {
|
|
iter++;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<OnScreenDisplay::Entry> OnScreenDisplay::Entries() {
|
|
return entries_; // makes a copy.
|
|
}
|
|
|
|
void OnScreenDisplay::Show(OSDType type, std::string_view text, std::string_view text2, std::string_view icon, float duration_s, const char *id) {
|
|
if (text.empty() && type != OSDType::STATUS_ICON) {
|
|
// The user hacked the translation files to get rid of the message. Let's reward the dedication
|
|
// by skipping it entirely.
|
|
return;
|
|
}
|
|
|
|
INFO_LOG(Log::UI, "OSD: %.*s (%.*s)", STR_VIEW(text), STR_VIEW(text2));
|
|
|
|
const bool hasId = id != nullptr;
|
|
Post([type, t = std::string(text), t2 = std::string(text2), ic = std::string(icon), duration_s, hasId, i = std::string(hasId ? id : "")](OnScreenDisplay &osd) {
|
|
osd.ShowNow(type, t, t2, ic, duration_s, hasId ? i.c_str() : nullptr);
|
|
});
|
|
}
|
|
|
|
void OnScreenDisplay::CancelById(std::string_view id) {
|
|
Post([i = std::string(id)](OnScreenDisplay &osd) { osd.CancelByIdNow(i); });
|
|
}
|
|
|
|
void OnScreenDisplay::ShowAchievementUnlocked(int achievementID) {
|
|
Post([=](OnScreenDisplay &osd) { osd.ShowAchievementUnlockedNow(achievementID); });
|
|
}
|
|
|
|
void OnScreenDisplay::ShowAchievementProgress(int achievementID, bool show) {
|
|
Post([=](OnScreenDisplay &osd) { osd.ShowAchievementProgressNow(achievementID, show); });
|
|
}
|
|
|
|
void OnScreenDisplay::ShowChallengeIndicator(int achievementID, bool show) {
|
|
Post([=](OnScreenDisplay &osd) { osd.ShowChallengeIndicatorNow(achievementID, show); });
|
|
}
|
|
|
|
void OnScreenDisplay::ShowLeaderboardTracker(int leaderboardTrackerID, std::string_view trackerText, bool show) {
|
|
Post([=, t = std::string(trackerText)](OnScreenDisplay &osd) { osd.ShowLeaderboardTrackerNow(leaderboardTrackerID, t, show); });
|
|
}
|
|
|
|
void OnScreenDisplay::SetProgressBar(std::string_view id, std::string_view message, float minValue, float maxValue, float progress, float delay_s) {
|
|
Post([=, i = std::string(id), m = std::string(message)](OnScreenDisplay &osd) { osd.SetProgressBarNow(i, m, minValue, maxValue, progress, delay_s); });
|
|
}
|
|
|
|
void OnScreenDisplay::RemoveProgressBar(std::string_view id, bool success, float delay_s) {
|
|
Post([=, i = std::string(id)](OnScreenDisplay &osd) { osd.RemoveProgressBarNow(i, success, delay_s); });
|
|
}
|
|
|
|
void OnScreenDisplay::ClearAchievementStuff() {
|
|
Post([](OnScreenDisplay &osd) { osd.ClearAchievementStuffNow(); });
|
|
}
|
|
|
|
void OnScreenDisplay::SetClickCallback(std::string_view id, std::function<void()> callback) {
|
|
Post([i = std::string(id), cb = std::move(callback)](OnScreenDisplay &osd) { osd.SetClickCallbackNow(i, cb); });
|
|
}
|
|
|
|
void OnScreenDisplay::SetFlags(std::string_view id, OSDMessageFlags flags) {
|
|
Post([=, i = std::string(id)](OnScreenDisplay &osd) { osd.SetFlagsNow(i, flags); });
|
|
}
|
|
|
|
void OnScreenDisplay::NudgeIngameNotifications() {
|
|
sideBarShowTime_ = time_now_d();
|
|
}
|
|
|
|
float OnScreenDisplay::IngameAlpha() const {
|
|
double timeSinceNudge = time_now_d() - sideBarShowTime_;
|
|
|
|
// Fade out in 1/4 second, 0.1s after the last nudge.
|
|
return saturatef(1.0f - ((float)timeSinceNudge - 0.1f) * 4.0f);
|
|
}
|
|
|
|
void OnScreenDisplay::ClickEntry(size_t index, double now) {
|
|
if (index < entries_.size() && entries_[index].type != OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR) {
|
|
if (entries_[index].clickCallback) {
|
|
entries_[index].clickCallback();
|
|
} else {
|
|
entries_[index].endTime = std::min(now + FadeoutTime(), entries_[index].endTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnScreenDisplay::ShowNow(OSDType type, std::string_view text, std::string_view text2, std::string_view icon, float duration_s, const char *id) {
|
|
// Automatic duration based on type.
|
|
if (duration_s <= 0.0f) {
|
|
switch (type) {
|
|
case OSDType::MESSAGE_ERROR:
|
|
case OSDType::MESSAGE_WARNING:
|
|
duration_s = 4.0f;
|
|
break;
|
|
case OSDType::MESSAGE_FILE_LINK:
|
|
duration_s = 5.0f;
|
|
break;
|
|
case OSDType::MESSAGE_INFO:
|
|
duration_s = 3.0f;
|
|
break;
|
|
case OSDType::MESSAGE_SUCCESS:
|
|
duration_s = 2.0f;
|
|
break;
|
|
default:
|
|
duration_s = 1.5f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
double now = time_now_d();
|
|
if (id) {
|
|
for (auto iter = entries_.begin(); iter != entries_.end(); ++iter) {
|
|
if (iter->id == id) {
|
|
Entry msg = *iter;
|
|
msg.endTime = now + duration_s;
|
|
msg.text = text;
|
|
msg.text2 = text2;
|
|
msg.type = type;
|
|
msg.iconName = icon;
|
|
// Move to top (should we? maybe not?)
|
|
entries_.erase(iter);
|
|
entries_.insert(entries_.begin(), msg);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
Entry msg{};
|
|
msg.text = text;
|
|
msg.text2 = text2;
|
|
msg.iconName = icon;
|
|
msg.startTime = now;
|
|
msg.endTime = now + duration_s;
|
|
msg.type = type;
|
|
if (id) {
|
|
msg.id = id;
|
|
}
|
|
entries_.insert(entries_.begin(), msg);
|
|
}
|
|
|
|
void OnScreenDisplay::CancelByIdNow(std::string_view id) {
|
|
for (auto iter = entries_.begin(); iter != entries_.end();) {
|
|
if (iter->id == id) {
|
|
iter = entries_.erase(iter);
|
|
} else {
|
|
iter++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnScreenDisplay::ShowOnOff(std::string_view message, bool on, float duration_s) {
|
|
std::string msg(message);
|
|
msg += ": ";
|
|
msg += on ? "on" : "off";
|
|
// TODO: translate "on" and "off"? Or just get rid of this whole thing?
|
|
Show(OSDType::MESSAGE_INFO, msg, duration_s);
|
|
}
|
|
|
|
void OnScreenDisplay::ShowAchievementUnlockedNow(int achievementID) {
|
|
double now = time_now_d();
|
|
|
|
double duration_s = 5.0;
|
|
|
|
Entry msg{};
|
|
msg.numericID = achievementID;
|
|
msg.type = OSDType::ACHIEVEMENT_UNLOCKED;
|
|
msg.startTime = now;
|
|
msg.endTime = now + duration_s;
|
|
entries_.insert(entries_.begin(), msg);
|
|
}
|
|
|
|
void OnScreenDisplay::ShowAchievementProgressNow(int achievementID, bool show) {
|
|
double now = time_now_d();
|
|
|
|
// There can only be one of these at a time.
|
|
for (auto &entry : entries_) {
|
|
if (entry.type == OSDType::ACHIEVEMENT_PROGRESS) {
|
|
if (!show) {
|
|
// Hide and eventually delete it.
|
|
entry.endTime = now + (double)FadeoutTime();
|
|
// Found it, we're done.
|
|
return;
|
|
}
|
|
// Else update it.
|
|
entry.numericID = achievementID;
|
|
entry.endTime = now + forever_s;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!show) {
|
|
// Sanity check
|
|
return;
|
|
}
|
|
|
|
// OK, let's make a new side-entry.
|
|
Entry entry{};
|
|
entry.numericID = achievementID;
|
|
entry.type = OSDType::ACHIEVEMENT_PROGRESS;
|
|
entry.startTime = now;
|
|
entry.endTime = now + forever_s;
|
|
entries_.insert(entries_.begin(), entry);
|
|
}
|
|
|
|
void OnScreenDisplay::ShowChallengeIndicatorNow(int achievementID, bool show) {
|
|
double now = time_now_d();
|
|
|
|
for (auto &entry : entries_) {
|
|
if (entry.numericID == achievementID && entry.type == OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR && !show) {
|
|
// Hide and eventually delete it.
|
|
entry.endTime = now + (double)FadeoutTime();
|
|
// Found it, we're done.
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!show) {
|
|
// Sanity check
|
|
return;
|
|
}
|
|
|
|
// OK, let's make a new side-entry.
|
|
Entry entry{};
|
|
entry.numericID = achievementID;
|
|
entry.type = OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR;
|
|
entry.startTime = now;
|
|
entry.endTime = now + forever_s;
|
|
entries_.insert(entries_.begin(), entry);
|
|
}
|
|
|
|
void OnScreenDisplay::ShowLeaderboardTrackerNow(int leaderboardTrackerID, std::string_view trackerText, bool show) { // show=true is used both for create and update.
|
|
double now = time_now_d();
|
|
|
|
for (auto &entry : entries_) {
|
|
if (entry.numericID == leaderboardTrackerID && entry.type == OSDType::LEADERBOARD_TRACKER) {
|
|
if (show) {
|
|
// Just an update.
|
|
entry.text = trackerText;
|
|
// Bump the end-time, in case it was fading out.
|
|
entry.endTime = now + forever_s;
|
|
} else {
|
|
// Keep the current text, hide and eventually delete it.
|
|
entry.endTime = now + (double)FadeoutTime();
|
|
}
|
|
// Found it, we're done.
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!show) {
|
|
// Sanity check
|
|
return;
|
|
}
|
|
|
|
// OK, let's make a new side-entry.
|
|
Entry entry{};
|
|
entry.numericID = leaderboardTrackerID;
|
|
entry.type = OSDType::LEADERBOARD_TRACKER;
|
|
entry.startTime = now;
|
|
entry.endTime = now + forever_s;
|
|
entry.text = trackerText;
|
|
entries_.insert(entries_.begin(), entry);
|
|
}
|
|
|
|
void OnScreenDisplay::ShowLeaderboardStartEnd(std::string_view title, std::string_view description, bool started) {
|
|
g_OSD.Show(OSDType::LEADERBOARD_STARTED_FAILED, title, description, 3.0f);
|
|
}
|
|
|
|
void OnScreenDisplay::ShowLeaderboardSubmitted(std::string_view title, std::string_view value) {
|
|
g_OSD.Show(OSDType::LEADERBOARD_SUBMITTED, title, value, 3.0f);
|
|
}
|
|
|
|
void OnScreenDisplay::SetProgressBarNow(std::string_view id, std::string_view message, float minValue, float maxValue, float progress, float delay) {
|
|
_dbg_assert_(!my_isnanorinf(progress));
|
|
_dbg_assert_(!my_isnanorinf(minValue));
|
|
_dbg_assert_(!my_isnanorinf(maxValue));
|
|
|
|
double now = time_now_d();
|
|
bool found = false;
|
|
|
|
for (auto &bar : entries_) {
|
|
if (bar.type == OSDType::PROGRESS_BAR && bar.id == id) {
|
|
bar.minValue = minValue;
|
|
bar.maxValue = maxValue;
|
|
bar.progress = progress;
|
|
bar.text = message;
|
|
bar.endTime = now + 60.0; // Nudge the progress bar to keep it shown.
|
|
return;
|
|
}
|
|
}
|
|
|
|
Entry bar{};
|
|
bar.id = id;
|
|
bar.type = OSDType::PROGRESS_BAR;
|
|
bar.text = message;
|
|
bar.minValue = minValue;
|
|
bar.maxValue = maxValue;
|
|
bar.progress = progress;
|
|
bar.startTime = now + delay;
|
|
bar.endTime = now + 60.0; // Show the progress bar for 60 seconds, then fade it out.
|
|
entries_.push_back(bar);
|
|
}
|
|
|
|
void OnScreenDisplay::RemoveProgressBarNow(std::string_view id, bool success, float delay_s) {
|
|
for (auto &ent : entries_) {
|
|
if (ent.type == OSDType::PROGRESS_BAR && ent.id == id) {
|
|
if (success) {
|
|
// Quickly shoot up to max, if we weren't there.
|
|
if (ent.maxValue != 0.0f) {
|
|
ent.progress = ent.maxValue;
|
|
} else {
|
|
// Fake a full progress
|
|
ent.minValue = 0;
|
|
ent.maxValue = 1;
|
|
ent.progress = 1;
|
|
}
|
|
}
|
|
ent.endTime = time_now_d() + delay_s + FadeoutTime();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fades out everything related to achievements. Should be used on game shutdown.
|
|
void OnScreenDisplay::ClearAchievementStuffNow() {
|
|
double now = time_now_d();
|
|
for (auto &iter : entries_) {
|
|
switch (iter.type) {
|
|
case OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR:
|
|
case OSDType::ACHIEVEMENT_UNLOCKED:
|
|
case OSDType::ACHIEVEMENT_PROGRESS:
|
|
case OSDType::LEADERBOARD_TRACKER:
|
|
case OSDType::LEADERBOARD_STARTED_FAILED:
|
|
case OSDType::LEADERBOARD_SUBMITTED:
|
|
iter.endTime = now;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnScreenDisplay::SetClickCallbackNow(std::string_view id, std::function<void()> callback) {
|
|
_dbg_assert_(callback != nullptr);
|
|
for (auto &ent : entries_) {
|
|
// protect against dupes.
|
|
if (ent.id == id) {
|
|
ent.clickCallback = callback;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnScreenDisplay::SetFlagsNow(std::string_view id, OSDMessageFlags flags) {
|
|
for (auto &ent : entries_) {
|
|
// protect against dupes.
|
|
if (ent.id == id) {
|
|
ent.flags = flags;
|
|
}
|
|
}
|
|
}
|