mirror of https://github.com/stenzek/duckstation
Misc: Split core and util Host
parent
779e78ae61
commit
e23c9875d5
@ -0,0 +1,147 @@
|
||||
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#include "host.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/heterogeneous_containers.h"
|
||||
#include "common/log.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <shared_mutex>
|
||||
|
||||
Log_SetChannel(Host);
|
||||
|
||||
namespace Host {
|
||||
static std::pair<const char*, u32> LookupTranslationString(const std::string_view& context,
|
||||
const std::string_view& msg);
|
||||
|
||||
static constexpr u32 TRANSLATION_STRING_CACHE_SIZE = 4 * 1024 * 1024;
|
||||
using TranslationStringMap = UnorderedStringMap<std::pair<u32, u32>>;
|
||||
using TranslationStringContextMap = UnorderedStringMap<TranslationStringMap>;
|
||||
static std::shared_mutex s_translation_string_mutex;
|
||||
static TranslationStringContextMap s_translation_string_map;
|
||||
static std::vector<char> s_translation_string_cache;
|
||||
static u32 s_translation_string_cache_pos;
|
||||
} // namespace Host
|
||||
|
||||
std::pair<const char*, u32> Host::LookupTranslationString(const std::string_view& context, const std::string_view& msg)
|
||||
{
|
||||
// TODO: TranslatableString, compile-time hashing.
|
||||
|
||||
TranslationStringContextMap::iterator ctx_it;
|
||||
TranslationStringMap::iterator msg_it;
|
||||
std::pair<const char*, u32> ret;
|
||||
s32 len;
|
||||
|
||||
// Shouldn't happen, but just in case someone tries to translate an empty string.
|
||||
if (UNLIKELY(msg.empty()))
|
||||
{
|
||||
ret.first = &s_translation_string_cache[0];
|
||||
ret.second = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
s_translation_string_mutex.lock_shared();
|
||||
ctx_it = UnorderedStringMapFind(s_translation_string_map, context);
|
||||
|
||||
if (UNLIKELY(ctx_it == s_translation_string_map.end()))
|
||||
goto add_string;
|
||||
|
||||
msg_it = UnorderedStringMapFind(ctx_it->second, msg);
|
||||
if (UNLIKELY(msg_it == ctx_it->second.end()))
|
||||
goto add_string;
|
||||
|
||||
ret.first = &s_translation_string_cache[msg_it->second.first];
|
||||
ret.second = msg_it->second.second;
|
||||
s_translation_string_mutex.unlock_shared();
|
||||
return ret;
|
||||
|
||||
add_string:
|
||||
s_translation_string_mutex.unlock_shared();
|
||||
s_translation_string_mutex.lock();
|
||||
|
||||
if (UNLIKELY(s_translation_string_cache.empty()))
|
||||
{
|
||||
// First element is always an empty string.
|
||||
s_translation_string_cache.resize(TRANSLATION_STRING_CACHE_SIZE);
|
||||
s_translation_string_cache[0] = '\0';
|
||||
s_translation_string_cache_pos = 0;
|
||||
}
|
||||
|
||||
if ((len =
|
||||
Internal::GetTranslatedStringImpl(context, msg, &s_translation_string_cache[s_translation_string_cache_pos],
|
||||
TRANSLATION_STRING_CACHE_SIZE - 1 - s_translation_string_cache_pos)) < 0)
|
||||
{
|
||||
Log_ErrorPrint("WARNING: Clearing translation string cache, it might need to be larger.");
|
||||
s_translation_string_cache_pos = 0;
|
||||
if ((len =
|
||||
Internal::GetTranslatedStringImpl(context, msg, &s_translation_string_cache[s_translation_string_cache_pos],
|
||||
TRANSLATION_STRING_CACHE_SIZE - 1 - s_translation_string_cache_pos)) < 0)
|
||||
{
|
||||
Panic("Failed to get translated string after clearing cache.");
|
||||
len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// New context?
|
||||
if (ctx_it == s_translation_string_map.end())
|
||||
ctx_it = s_translation_string_map.emplace(context, TranslationStringMap()).first;
|
||||
|
||||
// Impl doesn't null terminate, we need that for C strings.
|
||||
// TODO: do we want to consider aligning the buffer?
|
||||
const u32 insert_pos = s_translation_string_cache_pos;
|
||||
s_translation_string_cache[insert_pos + static_cast<u32>(len)] = 0;
|
||||
|
||||
ctx_it->second.emplace(msg, std::pair<u32, u32>(insert_pos, static_cast<u32>(len)));
|
||||
s_translation_string_cache_pos = insert_pos + static_cast<u32>(len) + 1;
|
||||
|
||||
ret.first = &s_translation_string_cache[insert_pos];
|
||||
ret.second = static_cast<u32>(len);
|
||||
s_translation_string_mutex.unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char* Host::TranslateToCString(const std::string_view& context, const std::string_view& msg)
|
||||
{
|
||||
return LookupTranslationString(context, msg).first;
|
||||
}
|
||||
|
||||
std::string_view Host::TranslateToStringView(const std::string_view& context, const std::string_view& msg)
|
||||
{
|
||||
const auto mp = LookupTranslationString(context, msg);
|
||||
return std::string_view(mp.first, mp.second);
|
||||
}
|
||||
|
||||
std::string Host::TranslateToString(const std::string_view& context, const std::string_view& msg)
|
||||
{
|
||||
return std::string(TranslateToStringView(context, msg));
|
||||
}
|
||||
|
||||
void Host::ClearTranslationCache()
|
||||
{
|
||||
s_translation_string_mutex.lock();
|
||||
s_translation_string_map.clear();
|
||||
s_translation_string_cache_pos = 0;
|
||||
s_translation_string_mutex.unlock();
|
||||
}
|
||||
|
||||
void Host::ReportFormattedErrorAsync(const std::string_view& title, const char* format, ...)
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, format);
|
||||
std::string message(StringUtil::StdStringFromFormatV(format, ap));
|
||||
va_end(ap);
|
||||
ReportErrorAsync(title, message);
|
||||
}
|
||||
|
||||
bool Host::ConfirmFormattedMessage(const std::string_view& title, const char* format, ...)
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, format);
|
||||
std::string message = StringUtil::StdStringFromFormatV(format, ap);
|
||||
va_end(ap);
|
||||
|
||||
return ConfirmMessage(title, message);
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace Host {
|
||||
/// Reads a file from the resources directory of the application.
|
||||
/// This may be outside of the "normal" filesystem on platforms such as Mac.
|
||||
std::optional<std::vector<u8>> ReadResourceFile(const char* filename);
|
||||
|
||||
/// Reads a resource file file from the resources directory as a string.
|
||||
std::optional<std::string> ReadResourceFileToString(const char* filename);
|
||||
|
||||
/// Returns the modified time of a resource.
|
||||
std::optional<std::time_t> GetResourceFileTimestamp(const char* filename);
|
||||
|
||||
/// Displays an asynchronous error on the UI thread, i.e. doesn't block the caller.
|
||||
void ReportErrorAsync(const std::string_view& title, const std::string_view& message);
|
||||
void ReportFormattedErrorAsync(const std::string_view& title, const char* format, ...);
|
||||
|
||||
/// Displays a synchronous confirmation on the UI thread, i.e. blocks the caller.
|
||||
bool ConfirmMessage(const std::string_view& title, const std::string_view& message);
|
||||
bool ConfirmFormattedMessage(const std::string_view& title, const char* format, ...);
|
||||
|
||||
/// Opens a URL, using the default application.
|
||||
void OpenURL(const std::string_view& url);
|
||||
|
||||
/// Copies the provided text to the host's clipboard, if present.
|
||||
bool CopyTextToClipboard(const std::string_view& text);
|
||||
|
||||
/// Returns a localized version of the specified string within the specified context.
|
||||
/// The pointer is guaranteed to be valid until the next language change.
|
||||
const char* TranslateToCString(const std::string_view& context, const std::string_view& msg);
|
||||
|
||||
/// Returns a localized version of the specified string within the specified context.
|
||||
/// The view is guaranteed to be valid until the next language change.
|
||||
/// NOTE: When passing this to fmt, positional arguments should be used in the base string, as
|
||||
/// not all locales follow the same word ordering.
|
||||
std::string_view TranslateToStringView(const std::string_view& context, const std::string_view& msg);
|
||||
|
||||
/// Returns a localized version of the specified string within the specified context.
|
||||
std::string TranslateToString(const std::string_view& context, const std::string_view& msg);
|
||||
|
||||
/// Clears the translation cache. All previously used strings should be considered invalid.
|
||||
void ClearTranslationCache();
|
||||
|
||||
namespace Internal {
|
||||
/// Implementation to retrieve a translated string.
|
||||
s32 GetTranslatedStringImpl(const std::string_view& context, const std::string_view& msg, char* tbuf,
|
||||
size_t tbuf_space);
|
||||
} // namespace Internal
|
||||
} // namespace Host
|
||||
|
||||
// Helper macros for retrieving translated strings.
|
||||
#define TRANSLATE(context, msg) Host::TranslateToCString(context, msg)
|
||||
#define TRANSLATE_SV(context, msg) Host::TranslateToStringView(context, msg)
|
||||
#define TRANSLATE_STR(context, msg) Host::TranslateToString(context, msg)
|
||||
#define TRANSLATE_FS(context, msg) fmt::runtime(Host::TranslateToStringView(context, msg))
|
||||
|
||||
// Does not translate the string at runtime, but allows the UI to in its own way.
|
||||
#define TRANSLATE_NOOP(context, msg) msg
|
||||
Loading…
Reference in New Issue