Merge pull request #19676 from hrydgard/file-system-perf-part-4

File system perf part 4, fix save deletion regression
This commit is contained in:
Henrik Rydgård
2024-11-30 03:18:37 +01:00
committed by GitHub
27 changed files with 177 additions and 130 deletions

View File

@@ -162,7 +162,7 @@ static std::string EscapeHash(std::string_view value) {
return result;
}
void ParsedIniLine::ParseFrom(std::string_view line) {
ParsedIniLine::ParsedIniLine(std::string_view line) {
line = StripSpaces(line);
if (line.empty()) {
key.clear();
@@ -534,7 +534,7 @@ bool IniFile::Load(std::istream &in) {
while (!(in.eof() || in.fail()))
{
in.getline(templine, MAX_BYTES);
std::string line = templine;
std::string_view line = templine;
// Remove UTF-8 byte order marks.
if (line.substr(0, 3) == "\xEF\xBB\xBF") {
@@ -543,8 +543,8 @@ bool IniFile::Load(std::istream &in) {
#ifndef _WIN32
// Check for CRLF eol and convert it to LF
if (!line.empty() && line.at(line.size()-1) == '\r') {
line.erase(line.size()-1);
if (!line.empty() && line.at(line.size() - 1) == '\r') {
line = line.substr(line.size() - 1);
}
#endif
@@ -556,7 +556,7 @@ bool IniFile::Load(std::istream &in) {
if (sectionNameEnd != std::string::npos) {
// New section!
std::string sub = line.substr(1, sectionNameEnd - 1);
std::string_view sub = line.substr(1, sectionNameEnd - 1);
sections.push_back(std::make_unique<Section>(sub));
if (sectionNameEnd + 1 < line.size()) {
@@ -566,9 +566,7 @@ bool IniFile::Load(std::istream &in) {
if (sections.empty()) {
sections.push_back(std::make_unique<Section>(""));
}
ParsedIniLine parsedLine;
parsedLine.ParseFrom(line);
sections.back()->lines_.push_back(parsedLine);
sections.back()->lines_.emplace_back(line);
}
}
}

View File

@@ -18,7 +18,8 @@ class VFSInterface;
class ParsedIniLine {
public:
ParsedIniLine() {}
explicit ParsedIniLine(std::string_view line);
ParsedIniLine(std::string_view key, std::string_view value) {
this->key = key;
this->value = value;
@@ -32,8 +33,6 @@ public:
return ParsedIniLine(std::string_view(), std::string_view(), comment);
}
// Comments only come from "ParseFrom".
void ParseFrom(std::string_view line);
void Reconstruct(std::string *output) const;
// Having these as views allows a more efficient internal representation, like one joint string.

View File

@@ -131,9 +131,7 @@ Path I18NRepo::GetIniPath(const std::string &languageID) const {
bool I18NRepo::IniExists(const std::string &languageID) const {
File::FileInfo info;
if (!g_VFS.GetFileInfo(GetIniPath(languageID).ToString().c_str(), &info))
return false;
if (!info.exists)
if (!g_VFS.Exists(GetIniPath(languageID).ToString().c_str()))
return false;
return true;
}

View File

@@ -34,7 +34,7 @@ void Android_StorageSetNativeActivity(jobject nativeActivity) {
void Android_RegisterStorageCallbacks(JNIEnv * env, jobject obj) {
openContentUri = env->GetMethodID(env->GetObjectClass(obj), "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I");
_dbg_assert_(openContentUri);
listContentUriDir = env->GetMethodID(env->GetObjectClass(obj), "listContentUriDir", "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;");
listContentUriDir = env->GetMethodID(env->GetObjectClass(obj), "listContentUriDir", "(Ljava/lang/String;)[Ljava/lang/String;");
_dbg_assert_(listContentUriDir);
contentUriCreateDirectory = env->GetMethodID(env->GetObjectClass(obj), "contentUriCreateDirectory", "(Ljava/lang/String;Ljava/lang/String;)I");
_dbg_assert_(contentUriCreateDirectory);
@@ -233,8 +233,7 @@ std::vector<File::FileInfo> Android_ListContentUri(const std::string &uri, const
double start = time_now_d();
jstring param = env->NewStringUTF(uri.c_str());
jstring filter = prefix.empty() ? nullptr : env->NewStringUTF(prefix.c_str());
jobject retval = env->CallObjectMethod(g_nativeActivity, listContentUriDir, param, filter);
jobject retval = env->CallObjectMethod(g_nativeActivity, listContentUriDir, param);
jobjectArray fileList = (jobjectArray)retval;
std::vector<File::FileInfo> items;
@@ -251,6 +250,7 @@ std::vector<File::FileInfo> Android_ListContentUri(const std::string &uri, const
} else if (ParseFileInfo(line, &info)) {
// We can just reconstruct the URI.
info.fullName = Path(uri) / info.name;
INFO_LOG(Log::FileSystem, "%s", info.name.c_str());
items.push_back(info);
}
}

View File

@@ -63,7 +63,7 @@ static uint64_t FiletimeToStatTime(FILETIME ft) {
bool GetFileInfo(const Path &path, FileInfo * fileInfo) {
if (LOG_IO) {
INFO_LOG(Log::System, "GetFileInfo %s", path.c_str());
INFO_LOG(Log::System, "GetFileInfo %s", path.ToVisualString().c_str());
}
if (SIMULATE_SLOW_IO) {
sleep_ms(300, "slow-io-sim");
@@ -178,12 +178,14 @@ std::vector<File::FileInfo> ApplyFilter(std::vector<File::FileInfo> files, const
}
auto pred = [&](const File::FileInfo &info) {
// WARNING: Keep in mind that if we return true here, the files is REMOVED from the list.
// It's not retain_if.
if (!startsWith(info.name, prefix)) {
return true;
}
if (info.isDirectory || !extensionFilter)
return false;
std::string ext = info.fullName.GetFileExtension();
if (!startsWith(info.name, prefix)) {
return false;
}
return filters.find(ext) == filters.end();
};
files.erase(std::remove_if(files.begin(), files.end(), pred), files.end());
@@ -192,7 +194,7 @@ std::vector<File::FileInfo> ApplyFilter(std::vector<File::FileInfo> files, const
bool GetFilesInDir(const Path &directory, std::vector<FileInfo> *files, const char *filter, int flags, std::string_view prefix) {
if (LOG_IO) {
INFO_LOG(Log::System, "GetFilesInDir %s (ext %s, prefix %.*s)", directory.c_str(), filter, (int)prefix.size(), prefix.data());
INFO_LOG(Log::System, "GetFilesInDir '%s' (ext %s, prefix %.*s)", directory.ToVisualString().c_str(), filter, (int)prefix.size(), prefix.data());
}
if (SIMULATE_SLOW_IO) {
sleep_ms(300, "slow-io-sim");
@@ -202,8 +204,10 @@ bool GetFilesInDir(const Path &directory, std::vector<FileInfo> *files, const ch
bool exists = false;
// TODO: Move prefix filtering over to the Java side for more speed.
std::vector<File::FileInfo> fileList = Android_ListContentUri(directory.ToString(), std::string(prefix), &exists);
*files = ApplyFilter(fileList, filter, "");
int beforeFilter = (int)fileList.size();
*files = ApplyFilter(fileList, filter, prefix);
std::sort(files->begin(), files->end());
DEBUG_LOG(Log::System, "GetFilesInDir: Found %d entries (%d before filter)", (int)files->size(), beforeFilter);
return exists;
}
@@ -276,12 +280,9 @@ bool GetFilesInDir(const Path &directory, std::vector<FileInfo> *files, const ch
continue;
}
/*
if (SIMULATE_SLOW_IO) {
INFO_LOG(Log::System, "GetFilesInDir item %s", virtualName.c_str());
sleep_ms(50, "slow-io-sim");
if (LOG_IO) {
// INFO_LOG(Log::System, "GetFilesInDir item %s", virtualName.c_str());
}
*/
FileInfo info;
info.name = virtualName;

View File

@@ -420,7 +420,7 @@ bool ExistsInDir(const Path &path, const std::string &filename) {
bool Exists(const Path &path) {
if (LOG_IO) {
INFO_LOG(Log::System, "Exists %s", path.c_str());
INFO_LOG(Log::System, "Exists %s", path.ToVisualString().c_str());
}
if (SIMULATE_SLOW_IO) {
sleep_ms(200, "slow-io-sim");
@@ -631,7 +631,7 @@ bool CreateDir(const Path &path) {
// Creates the full path of fullPath returns true on success
bool CreateFullPath(const Path &path) {
if (File::Exists(path)) {
DEBUG_LOG(Log::Common, "CreateFullPath: path exists %s", path.c_str());
DEBUG_LOG(Log::Common, "CreateFullPath: path exists %s", path.ToVisualString().c_str());
return true;
}
@@ -640,7 +640,7 @@ bool CreateFullPath(const Path &path) {
case PathType::CONTENT_URI:
break; // OK
default:
ERROR_LOG(Log::Common, "CreateFullPath(%s): Not yet supported", path.c_str());
ERROR_LOG(Log::Common, "CreateFullPath(%s): Not yet supported", path.ToVisualString().c_str());
return false;
}
@@ -665,9 +665,7 @@ bool CreateFullPath(const Path &path) {
Path curPath = root;
for (auto part : parts) {
curPath /= part;
if (!File::Exists(curPath)) {
File::CreateDir(curPath);
}
File::CreateDir(curPath);
}
return true;

View File

@@ -32,6 +32,11 @@ bool DirectoryReader::GetFileInfo(const char *path, File::FileInfo *info) {
return File::GetFileInfo(new_path, info);
}
bool DirectoryReader::Exists(const char *path) {
Path new_path = Path(path).StartsWith(path_) ? Path(path) : path_ / path;
return File::Exists(new_path);
}
class DirectoryReaderFileReference : public VFSFileReference {
public:
Path path;

View File

@@ -21,6 +21,7 @@ public:
bool GetFileListing(const char *path, std::vector<File::FileInfo> *listing, const char *filter) override;
bool GetFileInfo(const char *path, File::FileInfo *info) override;
bool Exists(const char *path) override;
std::string toString() const override {
return path_.ToString();
}

View File

@@ -118,3 +118,29 @@ bool VFS::GetFileInfo(const char *path, File::FileInfo *info) {
} // Otherwise, the file was just missing. No need to log.
return false;
}
bool VFS::Exists(const char *path) {
if (IsLocalAbsolutePath(path)) {
// Local path, not VFS.
// INFO_LOG(Log::IO, "Not a VFS path: %s . Getting local file info.", path);
return File::Exists(Path(std::string(path)));
}
bool fileSystemFound = false;
int fn_len = (int)strlen(path);
for (const auto &entry : entries_) {
int prefix_len = (int)strlen(entry.prefix);
if (prefix_len >= fn_len) continue;
if (0 == memcmp(path, entry.prefix, prefix_len)) {
fileSystemFound = true;
if (entry.reader->Exists(path + prefix_len))
return true;
else
continue;
}
}
if (!fileSystemFound) {
ERROR_LOG(Log::IO, "Missing filesystem for '%s'", path);
} // Otherwise, the file was just missing. No need to log.
return false;
}

View File

@@ -56,6 +56,10 @@ public:
// Filter support is optional but nice to have
virtual bool GetFileInfo(const char *path, File::FileInfo *info) = 0;
virtual bool Exists(const char *path) {
File::FileInfo info{};
return GetFileInfo(path, &info) && info.exists;
}
virtual std::string toString() const = 0;
};
@@ -70,14 +74,10 @@ public:
// Always allocates an extra zero byte at the end, so that it
// can be used for text like shader sources.
uint8_t *ReadFile(const char *filename, size_t *size) override;
bool GetFileInfo(const char *filename, File::FileInfo *fileInfo);
bool GetFileListing(const char *path, std::vector<File::FileInfo> *listing, const char *filter = nullptr) override;
// Shortcut for cleaner code
bool Exists(const char *path) {
File::FileInfo info{};
return GetFileInfo(path, &info);
}
bool GetFileInfo(const char *filename, File::FileInfo *fileInfo);
bool Exists(const char *path);
private:
struct VFSEntry {

View File

@@ -44,7 +44,7 @@ ZipFileReader *ZipFileReader::Create(const Path &zipFile, const char *inZipPath,
if (!path.empty() && path.back() != '/') {
path.push_back('/');
}
return new ZipFileReader(zip_file, path);
return new ZipFileReader(zip_file, zipFile, path);
}
ZipFileReader::~ZipFileReader() {

View File

@@ -36,15 +36,21 @@ public:
bool GetFileListing(const char *path, std::vector<File::FileInfo> *listing, const char *filter) override;
bool GetFileInfo(const char *path, File::FileInfo *info) override;
std::string toString() const override {
return inZipPath_;
std::string retval = zipPath_.ToVisualString();
if (!inZipPath_.empty()) {
retval += ": ";
retval += inZipPath_;
}
return retval;
}
private:
ZipFileReader(zip *zip_file, const std::string &inZipPath) : zip_file_(zip_file), inZipPath_(inZipPath) {}
ZipFileReader(zip *zip_file, const Path &zipPath, const std::string &inZipPath) : zip_file_(zip_file), zipPath_(zipPath), inZipPath_(inZipPath) {}
// Path has to be either an empty string, or a string ending with a /.
bool GetZipListings(const std::string &path, std::set<std::string> &files, std::set<std::string> &directories);
zip *zip_file_ = nullptr;
std::mutex lock_;
std::string inZipPath_;
Path zipPath_;
};

View File

@@ -258,7 +258,7 @@ void LogManager::LogLine(LogLevel level, Log type, const char *file, int line, c
const char *threadName;
#if PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(MAC)
const char *hostThreadName = GetCurrentThreadName();
if (strcmp(hostThreadName, "EmuThread") != 0 || !hleCurrentThreadName) {
if (hostThreadName && strcmp(hostThreadName, "EmuThread") != 0 || !hleCurrentThreadName) {
// Use the host thread name.
threadName = hostThreadName;
} else {

View File

@@ -1090,10 +1090,11 @@ void Config::Reload() {
void Config::UpdateIniLocation(const char *iniFileName, const char *controllerIniFilename) {
const bool useIniFilename = iniFileName != nullptr && strlen(iniFileName) > 0;
const char *ppssppIniFilename = IsVREnabled() ? "ppssppvr.ini" : "ppsspp.ini";
iniFilename_ = FindConfigFile(useIniFilename ? iniFileName : ppssppIniFilename);
bool exists;
iniFilename_ = FindConfigFile(useIniFilename ? iniFileName : ppssppIniFilename, &exists);
const bool useControllerIniFilename = controllerIniFilename != nullptr && strlen(controllerIniFilename) > 0;
const char *controlsIniFilename = IsVREnabled() ? "controlsvr.ini" : "controls.ini";
controllerIniFilename_ = FindConfigFile(useControllerIniFilename ? controllerIniFilename : controlsIniFilename);
controllerIniFilename_ = FindConfigFile(useControllerIniFilename ? controllerIniFilename : controlsIniFilename, &exists);
}
bool Config::LoadAppendedConfig() {
@@ -1660,22 +1661,27 @@ void Config::SetSearchPath(const Path &searchPath) {
searchPath_ = searchPath;
}
const Path Config::FindConfigFile(const std::string &baseFilename) {
const Path Config::FindConfigFile(const std::string &baseFilename, bool *exists) {
// Don't search for an absolute path.
if (baseFilename.size() > 1 && baseFilename[0] == '/') {
return Path(baseFilename);
Path path(baseFilename);
*exists = File::Exists(path);
return path;
}
#ifdef _WIN32
if (baseFilename.size() > 3 && baseFilename[1] == ':' && (baseFilename[2] == '/' || baseFilename[2] == '\\')) {
return Path(baseFilename);
Path path(baseFilename);
*exists = File::Exists(path);
return path;
}
#endif
Path filename = searchPath_ / baseFilename;
if (File::Exists(filename)) {
*exists = true;
return filename;
}
*exists = false;
// Make sure at least the directory it's supposed to be in exists.
Path parent = filename.NavigateUp();
@@ -1711,8 +1717,9 @@ void Config::RestoreDefaults(RestoreSettingsBits whatToRestore) {
}
bool Config::hasGameConfig(const std::string &pGameId) {
Path fullIniFilePath = getGameConfigFile(pGameId);
return File::Exists(fullIniFilePath);
bool exists = false;
Path fullIniFilePath = getGameConfigFile(pGameId, &exists);
return exists;
}
void Config::changeGameSpecific(const std::string &pGameId, const std::string &title) {
@@ -1724,9 +1731,11 @@ void Config::changeGameSpecific(const std::string &pGameId, const std::string &t
}
bool Config::createGameConfig(const std::string &pGameId) {
Path fullIniFilePath = getGameConfigFile(pGameId);
bool exists;
Path fullIniFilePath = getGameConfigFile(pGameId, &exists);
if (hasGameConfig(pGameId)) {
if (exists) {
INFO_LOG(Log::System, "Game config already exists");
return false;
}
@@ -1735,16 +1744,19 @@ bool Config::createGameConfig(const std::string &pGameId) {
}
bool Config::deleteGameConfig(const std::string& pGameId) {
Path fullIniFilePath = Path(getGameConfigFile(pGameId));
bool exists;
Path fullIniFilePath = Path(getGameConfigFile(pGameId, &exists));
File::Delete(fullIniFilePath);
if (exists) {
File::Delete(fullIniFilePath);
}
return true;
}
Path Config::getGameConfigFile(const std::string &pGameId) {
Path Config::getGameConfigFile(const std::string &pGameId, bool *exists) {
const char *ppssppIniFilename = IsVREnabled() ? "_ppssppvr.ini" : "_ppsspp.ini";
std::string iniFileName = pGameId + ppssppIniFilename;
Path iniFileNameFull = FindConfigFile(iniFileName);
Path iniFileNameFull = FindConfigFile(iniFileName, exists);
return iniFileNameFull;
}
@@ -1754,7 +1766,8 @@ bool Config::saveGameConfig(const std::string &pGameId, const std::string &title
return false;
}
Path fullIniFilePath = getGameConfigFile(pGameId);
bool exists;
Path fullIniFilePath = getGameConfigFile(pGameId, &exists);
IniFile iniFile;
@@ -1791,9 +1804,9 @@ bool Config::saveGameConfig(const std::string &pGameId, const std::string &title
}
bool Config::loadGameConfig(const std::string &pGameId, const std::string &title) {
Path iniFileNameFull = getGameConfigFile(pGameId);
if (!hasGameConfig(pGameId)) {
bool exists;
Path iniFileNameFull = getGameConfigFile(pGameId, &exists);
if (!exists) {
DEBUG_LOG(Log::Loader, "No game-specific settings found in %s. Using global defaults.", iniFileNameFull.c_str());
return false;
}

View File

@@ -585,11 +585,11 @@ public:
bool loadGameConfig(const std::string &game_id, const std::string &title);
bool saveGameConfig(const std::string &pGameId, const std::string &title);
void unloadGameConfig();
Path getGameConfigFile(const std::string &gameId);
Path getGameConfigFile(const std::string &gameId, bool *exists);
bool hasGameConfig(const std::string &game_id);
void SetSearchPath(const Path &path);
const Path FindConfigFile(const std::string &baseFilename);
const Path FindConfigFile(const std::string &baseFilename, bool *exists);
void UpdateIniLocation(const char *iniFileName = nullptr, const char *controllerIniFilename = nullptr);

View File

@@ -1558,12 +1558,20 @@ int SavedataParam::SetPspParam(SceUtilitySavedataParam *param)
// get and stock file info for each file
int realCount = 0;
// TODO: Filter away non-directories directly?
std::vector<PSPFileInfo> allSaves = pspFileSystem.GetDirListing(savePath);
bool fetchedAllSaves = false;
std::string gameName = GetGameName(param);
for (int i = 0; i < saveDataListCount; i++) {
// "<>" means saveName can be anything...
if (strncmp(saveNameListData[i], "<>", ARRAY_SIZE(saveNameListData[i])) == 0) {
// TODO:Maybe we need a way to reorder the files?
auto allSaves = pspFileSystem.GetDirListing(savePath);
std::string gameName = GetGameName(param);
if (!fetchedAllSaves) {
fetchedAllSaves = true;
}
for (auto it = allSaves.begin(); it != allSaves.end(); ++it) {
if (it->name.compare(0, gameName.length(), gameName) == 0) {
std::string saveName = it->name.substr(gameName.length());
@@ -1590,13 +1598,30 @@ int SavedataParam::SetPspParam(SceUtilitySavedataParam *param)
const std::string thisSaveName = FixedToString(saveNameListData[i], ARRAY_SIZE(saveNameListData[i]));
std::string fileDataDir = savePath + GetGameName(param) + thisSaveName;
PSPFileInfo info = GetSaveInfo(fileDataDir);
if (info.exists) {
SetFileInfo(realCount, info, thisSaveName);
INFO_LOG(Log::sceUtility, "Save data exists: %s = %s", thisSaveName.c_str(), fileDataDir.c_str());
realCount++;
} else {
const std::string folderName = gameName + thisSaveName;
// Check if thisSaveName is in the list before processing.
// This is hopefully faster than doing file I/O.
bool found = false;
for (int i = 0; i < allSaves.size(); i++) {
if (allSaves[i].name == folderName) {
found = true;
}
}
const std::string fileDataDir = savePath + gameName + thisSaveName;
if (found) {
PSPFileInfo info = GetSaveInfo(fileDataDir);
if (info.exists) {
SetFileInfo(realCount, info, thisSaveName);
INFO_LOG(Log::sceUtility, "Save data exists: %s = %s", thisSaveName.c_str(), fileDataDir.c_str());
realCount++;
} else {
found = false;
}
}
if (!found) { // NOTE: May be changed above, can't merge with the expression
if (listEmptyFile) {
ClearFileInfo(saveDataList[realCount], thisSaveName);
INFO_LOG(Log::sceUtility, "Listing missing save data: %s = %s", thisSaveName.c_str(), fileDataDir.c_str());
@@ -2002,7 +2027,7 @@ int SavedataParam::GetSaveCryptMode(const SceUtilitySavedataParam *param, const
bool SavedataParam::IsInSaveDataList(const std::string &saveName, int count) {
for(int i = 0; i < count; ++i) {
if(strcmp(saveDataList[i].saveName.c_str(),saveName.c_str()) == 0)
if (saveDataList[i].saveName == saveName)
return true;
}
return false;

View File

@@ -155,12 +155,7 @@ bool LocalFileLoader::Exists() {
return true;
#endif
File::FileInfo info;
if (File::GetFileInfo(filename_, &info)) {
return info.exists;
} else {
return false;
}
return File::Exists(filename_);
}
bool LocalFileLoader::IsDirectory() {

View File

@@ -2466,6 +2466,7 @@ static u32 sceIoDopen(const char *path) {
"TEXTURES",
"DUMP",
"SHADERS",
"DRIVERS",
};
std::vector<PSPFileInfo> filtered;
for (const auto &entry : dir->listing) {

View File

@@ -801,26 +801,24 @@ namespace SaveState
std::string GetSlotDateAsString(const Path &gameFilename, int slot) {
Path fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION);
if (File::Exists(fn)) {
tm time;
if (File::GetModifTime(fn, time)) {
char buf[256];
// TODO: Use local time format? Americans and some others might not like ISO standard :)
switch (g_Config.iDateFormat) {
case PSP_SYSTEMPARAM_DATE_FORMAT_YYYYMMDD:
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &time);
break;
case PSP_SYSTEMPARAM_DATE_FORMAT_MMDDYYYY:
strftime(buf, sizeof(buf), "%m-%d-%Y %H:%M:%S", &time);
break;
case PSP_SYSTEMPARAM_DATE_FORMAT_DDMMYYYY:
strftime(buf, sizeof(buf), "%d-%m-%Y %H:%M:%S", &time);
break;
default: // Should never happen
return "";
}
return std::string(buf);
tm time;
if (File::GetModifTime(fn, time)) {
char buf[256];
// TODO: Use local time format? Americans and some others might not like ISO standard :)
switch (g_Config.iDateFormat) {
case PSP_SYSTEMPARAM_DATE_FORMAT_YYYYMMDD:
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &time);
break;
case PSP_SYSTEMPARAM_DATE_FORMAT_MMDDYYYY:
strftime(buf, sizeof(buf), "%m-%d-%Y %H:%M:%S", &time);
break;
case PSP_SYSTEMPARAM_DATE_FORMAT_DDMMYYYY:
strftime(buf, sizeof(buf), "%d-%m-%Y %H:%M:%S", &time);
break;
default: // Should never happen
return "";
}
return std::string(buf);
}
return "";
}

View File

@@ -101,7 +101,6 @@ ReplacedTexture::~ReplacedTexture() {
if (threadWaitable_) {
SetState(ReplacementState::CANCEL_INIT);
std::unique_lock<std::mutex> lock(lock_);
threadWaitable_->WaitAndRelease();
threadWaitable_ = nullptr;
}
@@ -225,7 +224,7 @@ void ReplacedTexture::Prepare(VFSBackend *vfs) {
VFSFileReference *fileRef = vfs_->GetFile(desc_.filenames[i].c_str());
if (!fileRef) {
if (i == 0) {
INFO_LOG(Log::G3D, "Texture replacement file '%s' not found", desc_.filenames[i].c_str());
INFO_LOG(Log::G3D, "Texture replacement file '%s' not found in %s", desc_.filenames[i].c_str(), vfs_->toString().c_str());
// No file at all. Mark as NOT_FOUND.
SetState(ReplacementState::NOT_FOUND);
return;

Some files were not shown because too many files have changed in this diff Show More