mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-15 15:30:29 +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.
173 lines
4.6 KiB
C++
173 lines
4.6 KiB
C++
#pragma once
|
|
|
|
// I18N = I....18..dots.....N = INTERNATIONALIZATION
|
|
|
|
// Super simple I18N library.
|
|
// Just enough to be useful and usable.
|
|
// Spits out easy-to-edit utf-8 .INI files.
|
|
|
|
// As usual, everything is UTF-8. Nothing else allowed.
|
|
|
|
#include <map>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
#include <memory>
|
|
|
|
#include <retro_atomic.h>
|
|
#include <queues/mpsc_stack.h>
|
|
|
|
#include "Common/Common.h"
|
|
#include "Common/File/Path.h"
|
|
|
|
// Lookups may run on any thread while the language is reloaded: a category is immutable once
|
|
// published, and a replaced one is kept until the repo goes away.
|
|
|
|
class I18NRepo;
|
|
class IniFile;
|
|
class Section;
|
|
|
|
// Don't forget to update the string array in the cpp file if you change this.
|
|
enum class I18NCat : uint8_t {
|
|
AUDIO = 0,
|
|
CONTROLS,
|
|
CWCHEATS,
|
|
DESKTOPUI,
|
|
DEVELOPER,
|
|
DIALOG,
|
|
ERRORS, // Can't name it ERROR, clashes with many defines.
|
|
GAME,
|
|
GRAPHICS,
|
|
INSTALLZIP,
|
|
KEYMAPPING,
|
|
MAINMENU,
|
|
MAINSETTINGS,
|
|
MAPPABLECONTROLS,
|
|
NETWORKING,
|
|
PAUSE,
|
|
POSTSHADERS,
|
|
PSPCREDITS,
|
|
MEMSTICK,
|
|
REMOTEISO,
|
|
REPORTING,
|
|
SAVEDATA,
|
|
SCREEN,
|
|
SEARCH,
|
|
STORE,
|
|
SYSINFO,
|
|
SYSTEM,
|
|
TEXTURESHADERS,
|
|
THEMES,
|
|
UI_ELEMENTS,
|
|
VR,
|
|
ACHIEVEMENTS,
|
|
PSPSETTINGS,
|
|
CATEGORY_COUNT,
|
|
NONE = CATEGORY_COUNT,
|
|
};
|
|
|
|
struct I18NEntry {
|
|
I18NEntry(std::string_view t) : text(t), readFlag(false) {}
|
|
I18NEntry() : readFlag(false) {}
|
|
std::string text;
|
|
bool readFlag;
|
|
};
|
|
|
|
class I18NCategory {
|
|
public:
|
|
I18NCategory();
|
|
explicit I18NCategory(const Section §ion);
|
|
~I18NCategory();
|
|
|
|
// Faster since the string lengths don't need to be recomputed.
|
|
std::string_view T(std::string_view key, std::string_view def = "");
|
|
|
|
// Try to avoid this. Still useful in snprintf.
|
|
const char *T_cstr(const char *key, const char *def = nullptr);
|
|
|
|
// Safe alongside lookups. ClearMissed() must not run alongside Missed().
|
|
std::map<std::string, std::string, std::less<>> Missed() const;
|
|
|
|
const std::map<std::string, I18NEntry, std::less<>> &GetMap() { return map_; }
|
|
void ClearMissed();
|
|
|
|
private:
|
|
void SetMap(const std::map<std::string, std::string> &m);
|
|
void NoteMissed(std::string_view key, std::string_view def);
|
|
|
|
// std::less<> is needed to be able to look up string_views in a string-keyed map.
|
|
std::map<std::string, I18NEntry, std::less<>> map_;
|
|
|
|
// Missed keys: a lock-free set of key hashes so each is recorded once, and the
|
|
// records themselves on an MPSC stack that is only ever walked, never popped.
|
|
struct MissedKey;
|
|
struct MissedLink {
|
|
mpsc_stack_node_t node; // first, so a node pointer is a link pointer
|
|
MissedKey *self;
|
|
};
|
|
struct MissedKey {
|
|
MissedLink link;
|
|
std::string key;
|
|
std::string def;
|
|
};
|
|
enum { MISSED_SLOTS = 1024 };
|
|
retro_atomic_ptr_t missedHashes_[MISSED_SLOTS]; // hashes, as pointer-sized words
|
|
mpsc_stack_t missed_;
|
|
|
|
std::string name_;
|
|
// Noone else can create these.
|
|
friend class I18NRepo;
|
|
};
|
|
|
|
class I18NRepo {
|
|
public:
|
|
I18NRepo();
|
|
bool IniExists(const std::string &languageID) const;
|
|
bool LoadIni(const std::string &languageID, const Path &overridePath = Path()); // NOT the filename!
|
|
|
|
std::string LanguageID();
|
|
|
|
I18NCategory *GetCategory(I18NCat category);
|
|
|
|
// Translate the string, by looking up "key" in the file, and falling back to either def or key, in that order, if the lookup fails.
|
|
// def can (and usually is) set to nullptr.
|
|
std::string_view T(I18NCat category, std::string_view key, std::string_view def = "") {
|
|
if (category == I18NCat::NONE)
|
|
return !def.empty() ? def : key;
|
|
return Cat(category)->T(key, def);
|
|
}
|
|
const char *T_cstr(I18NCat category, const char *key, const char *def = nullptr) {
|
|
if (category == I18NCat::NONE)
|
|
return def ? def : key;
|
|
return Cat(category)->T_cstr(key, def);
|
|
}
|
|
void LogMissingKeys() const;
|
|
|
|
private:
|
|
Path GetIniPath(const std::string &languageID) const;
|
|
void Clear();
|
|
void Publish(size_t index, I18NCategory *cat);
|
|
I18NCategory *Cat(I18NCat category) const {
|
|
return (I18NCategory *)retro_atomic_load_acquire_ptr(const_cast<retro_atomic_ptr_t *>(&cats_[(size_t)category]));
|
|
}
|
|
|
|
retro_atomic_ptr_t cats_[(size_t)I18NCat::CATEGORY_COUNT];
|
|
// Every category ever published, since a lookup may still hold an old one.
|
|
std::vector<std::unique_ptr<I18NCategory>> owned_;
|
|
std::string languageID_;
|
|
};
|
|
|
|
extern I18NRepo g_i18nrepo;
|
|
|
|
// These are simply talking to the one global instance of I18NRepo.
|
|
|
|
I18NCategory *GetI18NCategory(I18NCat cat);
|
|
|
|
inline std::string_view T(I18NCat category, std::string_view key, std::string_view def = "") {
|
|
return g_i18nrepo.T(category, key, def);
|
|
}
|
|
|
|
inline const char *T_cstr(I18NCat category, const char *key, const char *def = "") {
|
|
return g_i18nrepo.T_cstr(category, key, def);
|
|
}
|