ui: scan games in a background thread. hide unknown zips and naomi chds

This commit is contained in:
Flyinghead 2020-04-09 11:44:19 +02:00
parent 786c8e7744
commit 381f0f0f95
4 changed files with 245 additions and 159 deletions

View File

@ -1,9 +1,6 @@
#pragma once
#include "types.h"
#include <algorithm>
#include <cctype>
void os_SetWindowText(const char* text);
double os_GetSeconds();
@ -28,13 +25,3 @@ u32 static INLINE bitscanrev(u32 v)
}
void os_DebugBreak();
static inline std::string get_file_extension(const std::string& s)
{
size_t dot = s.find_last_of('.');
if (dot >= s.length())
return "";
std::string ext = s.substr(dot + 1, s.length() - dot - 1);
std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c){ return std::tolower(c); });
return ext;
}

171
core/rend/game_scanner.h Normal file
View File

@ -0,0 +1,171 @@
/*
Copyright 2020 flyinghead
This file is part of flycast.
flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <mutex>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <dirent.h>
#include <sys/stat.h>
#include "types.h"
#include "stdclass.h"
#include "hw/naomi/naomi_roms.h"
struct GameMedia {
std::string name;
std::string path;
};
static bool operator<(const GameMedia &left, const GameMedia &right)
{
return left.name < right.name;
}
class GameScanner
{
std::vector<GameMedia> game_list;
std::mutex mutex;
std::unique_ptr<std::thread> scan_thread;
bool scan_done = false;
bool running = false;
std::unordered_map<std::string, const Game*> arcade_games;
std::unordered_set<std::string> arcade_gdroms;
void insert_game(const GameMedia& game)
{
std::lock_guard<std::mutex> guard(mutex);
game_list.insert(std::upper_bound(game_list.begin(), game_list.end(), game), game);
}
void add_game_directory(const std::string& path)
{
//printf("Exploring %s\n", path.c_str());
DIR *dir = opendir(path.c_str());
if (dir == NULL)
return;
while (running)
{
struct dirent *entry = readdir(dir);
if (entry == NULL)
break;
std::string name(entry->d_name);
if (name == "." || name == "..")
continue;
std::string child_path = path + "/" + name;
bool is_dir = false;
#ifndef _WIN32
if (entry->d_type == DT_DIR)
is_dir = true;
if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK)
#endif
{
struct stat st;
if (stat(child_path.c_str(), &st) != 0)
continue;
if (S_ISDIR(st.st_mode))
is_dir = true;
}
if (is_dir)
{
add_game_directory(child_path);
}
else
{
std::string extension = get_file_extension(name);
if (extension == "zip" || extension == "7z")
{
std::string basename = get_file_basename(name);
string_tolower(basename);
auto it = arcade_games.find(basename);
if (it == arcade_games.end())
continue;
name = name + " (" + std::string(it->second->description) + ")";
}
else if (extension == "chd" || extension == "gdi")
{
// Hide arcade gdroms
std::string basename = get_file_basename(name);
string_tolower(basename);
if (arcade_gdroms.count(basename) != 0)
continue;
}
else if ((settings.dreamcast.HideLegacyNaomiRoms
|| (extension != "bin" && extension != "lst" && extension != "dat"))
&& extension != "cdi" && extension != "cue")
continue;
insert_game(GameMedia{ name, child_path });
}
}
closedir(dir);
}
public:
~GameScanner()
{
stop();
}
void refresh()
{
stop();
scan_done = false;
}
void stop()
{
running = false;
if (scan_thread && scan_thread->joinable())
scan_thread->join();
}
void fetch_game_list()
{
if (scan_done || running)
return;
running = true;
scan_thread = std::unique_ptr<std::thread>(
new std::thread([this]()
{
if (arcade_games.empty())
for (int gameid = 0; Games[gameid].name != nullptr; gameid++)
{
const Game *game = &Games[gameid];
arcade_games[game->name] = game;
if (game->gdrom_name != nullptr)
arcade_gdroms.insert(game->gdrom_name);
}
{
std::lock_guard<std::mutex> guard(mutex);
game_list.clear();
}
for (const auto& path : settings.dreamcast.ContentPath)
{
add_game_directory(path);
if (!running)
break;
}
if (running)
scan_done = true;
running = false;
}));
}
std::mutex& get_mutex() { return mutex; }
const std::vector<GameMedia>& get_game_list() { return game_list; }
};

View File

@ -16,18 +16,12 @@
You should have received a copy of the GNU General Public License
along with reicast. If not, see <https://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <cmath>
#include <dirent.h>
#include <sys/stat.h>
#include "gui.h"
#include "oslib/oslib.h"
#include "cfg/cfg.h"
#include "hw/maple/maple_if.h"
#include "hw/maple/maple_devs.h"
#include "hw/naomi/naomi_cart.h"
#include "hw/naomi/naomi_roms.h"
#include "imgui/imgui.h"
#include "gles/imgui_impl_opengl3.h"
#include "imgui/roboto_medium.h"
@ -36,6 +30,7 @@
#include "input/keyboard_device.h"
#include "gui_util.h"
#include "gui_android.h"
#include "game_scanner.h"
#include "version.h"
#include "oslib/audiostream.h"
#include "imgread/common.h"
@ -44,9 +39,7 @@
extern void dc_loadstate();
extern void dc_savestate();
extern void dc_stop();
extern void dc_reset(bool manual);
extern void dc_resume();
extern void dc_term();
extern void dc_start_game(const char *path);
extern void UpdateInputState(u32 port);
extern bool game_started;
@ -70,6 +63,8 @@ static void display_vmus();
static void reset_vmus();
static void term_vmus();
GameScanner scanner;
float gui_get_scaling()
{
return scaling;
@ -248,6 +243,7 @@ void ImGui_Impl_NewFrame()
}
#if 0
#include "oslib/timeseries.h"
TimeSeries renderTimes;
TimeSeries vblankTimes;
@ -292,6 +288,7 @@ void gui_open_settings()
static void gui_start_game(const std::string& path)
{
scanner.stop();
try {
dc_start_game(path.empty() ? NULL : path.c_str());
} catch (ReicastException& ex) {
@ -646,14 +643,12 @@ static void error_popup()
}
}
static bool game_list_done; // Set to false to refresh the game list
void directory_selected_callback(bool cancelled, std::string selection)
{
if (!cancelled)
{
settings.dreamcast.ContentPath.push_back(selection);
game_list_done = false;
scanner.refresh();
}
}
@ -815,7 +810,7 @@ static void gui_display_settings()
if (to_delete >= 0)
{
settings.dreamcast.ContentPath.erase(settings.dreamcast.ContentPath.begin() + to_delete);
game_list_done = false;
scanner.refresh();
}
}
ImGui::SameLine();
@ -833,9 +828,9 @@ static void gui_display_settings()
ImGui::ListBoxFooter();
}
ImGui::SameLine();
ShowHelpMarker("The directory where reicast saves configuration files and VMUs. BIOS files should be in the data subdirectory");
ShowHelpMarker("The directory where reicast saves configuration files and VMUs. BIOS files should be in a subfolder named \"data\"");
if (ImGui::Checkbox("Hide Legacy Naomi Roms", &settings.dreamcast.HideLegacyNaomiRoms))
game_list_done = false;
scanner.refresh();
ImGui::SameLine();
ShowHelpMarker("Hide .bin, .dat and .lst files from the content browser");
@ -1126,7 +1121,7 @@ static void gui_display_settings()
{
ImGui::SliderInt("Texture Upscaling", (int *)&settings.rend.TextureUpscale, 1, 8);
ImGui::SameLine();
ShowHelpMarker("Upscale textures with the xBRZ algorithm. Only on fast platforms and for certain games");
ShowHelpMarker("Upscale textures with the xBRZ algorithm. Only on fast platforms and for certain 2D games");
ImGui::SliderInt("Upscaled Texture Max Size", (int *)&settings.rend.MaxFilteredTextureSize, 8, 1024);
ImGui::SameLine();
ShowHelpMarker("Textures larger than this dimension squared will not be upscaled");
@ -1148,7 +1143,7 @@ static void gui_display_settings()
ShowHelpMarker("Disable the emulator sound output");
ImGui::Checkbox("Enable DSP", &settings.aica.DSPEnabled);
ImGui::SameLine();
ShowHelpMarker("Enable the Dreamcast Digital Sound Processor. Only recommended on fast and arm64 platforms");
ShowHelpMarker("Enable the Dreamcast Digital Sound Processor. Only recommended on fast platforms");
ImGui::Checkbox("Limit Emulator Speed", &settings.aica.LimitFPS);
ImGui::SameLine();
ShowHelpMarker("Whether to limit the emulator speed using the audio output. Recommended");
@ -1291,10 +1286,10 @@ static void gui_display_settings()
{
ImGui::Checkbox("HLE BIOS", &settings.bios.UseReios);
ImGui::SameLine();
ShowHelpMarker("Use high-level BIOS emulation if BIOS files are not found");
ShowHelpMarker("Force high-level BIOS emulation");
ImGui::Checkbox("Force Windows CE", &settings.dreamcast.ForceWindowsCE);
ImGui::SameLine();
ShowHelpMarker("Enable full MMU emulation and other Windows CE settings");
ShowHelpMarker("Enable full MMU emulation and other Windows CE settings. Do not enable unless necessary");
#ifndef __ANDROID
ImGui::Checkbox("Serial Console", &settings.debug.SerialConsole);
ImGui::SameLine();
@ -1421,79 +1416,6 @@ static void gui_display_settings()
settings.dynarec.Enable = (bool)dynarec_enabled;
}
struct GameMedia {
std::string name;
std::string path;
};
static bool operator<(const GameMedia &left, const GameMedia &right)
{
return left.name < right.name;
}
static void add_game_directory(const std::string& path, std::vector<GameMedia>& game_list)
{
//printf("Exploring %s\n", path.c_str());
DIR *dir = opendir(path.c_str());
if (dir == NULL)
return;
while (true)
{
struct dirent *entry = readdir(dir);
if (entry == NULL)
break;
std::string name(entry->d_name);
if (name == "." || name == "..")
continue;
std::string child_path = path + "/" + name;
bool is_dir = false;
#ifndef _WIN32
if (entry->d_type == DT_DIR)
is_dir = true;
if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK)
#endif
{
struct stat st;
if (stat(child_path.c_str(), &st) != 0)
continue;
if (S_ISDIR(st.st_mode))
is_dir = true;
}
if (is_dir)
{
add_game_directory(child_path, game_list);
}
else
{
std::string::size_type dotpos = name.find_last_of('.');
if (dotpos == std::string::npos || dotpos == name.size() - 1)
continue;
std::string extension = name.substr(dotpos);
if (stricmp(extension.c_str(), ".zip") && stricmp(extension.c_str(), ".7z")
&& (settings.dreamcast.HideLegacyNaomiRoms
|| (stricmp(extension.c_str(), ".bin") && stricmp(extension.c_str(), ".lst")
&& stricmp(extension.c_str(), ".dat")))
&& stricmp(extension.c_str(), ".cdi") && stricmp(extension.c_str(), ".gdi")
&& stricmp(extension.c_str(), ".chd") && stricmp(extension.c_str(), ".cue"))
continue;
game_list.push_back({ name, child_path });
}
}
closedir(dir);
}
static std::vector<GameMedia> game_list;
static void fetch_game_list()
{
if (game_list_done)
return;
game_list.clear();
for (auto& path : settings.dreamcast.ContentPath)
add_game_directory(path, game_list);
std::stable_sort(game_list.begin(), game_list.end());
game_list_done = true;
}
static void gui_display_demo()
{
@ -1505,22 +1427,6 @@ static void gui_display_demo()
ImGui_impl_RenderDrawData(ImGui::GetDrawData(), false);
}
static std::string get_game_description(std::string name)
{
size_t dot = name.find_last_of('.');
if (dot != std::string::npos && dot != name.size() - 1)
name = name.substr(0, dot);
int gameid = 0;
for (; Games[gameid].name != NULL; gameid++)
if (!stricmp(Games[gameid].name, name.c_str()))
break;
if (Games[gameid].name == NULL)
return std::string();
return std::string(Games[gameid].description);
}
static void gui_display_content()
{
ImGui_Impl_NewFrame();
@ -1550,7 +1456,7 @@ static void gui_display_content()
}
ImGui::PopStyleVar();
fetch_game_list();
scanner.fetch_game_list();
// Only if Filter and Settings aren't focused... ImGui::SetNextWindowFocus();
ImGui::BeginChild(ImGui::GetID("library"), ImVec2(0, 0), true);
@ -1566,29 +1472,22 @@ static void gui_display_content()
}
ImGui::PopID();
for (const auto& game : game_list)
{
scanner.get_mutex().lock();
for (const auto& game : scanner.get_game_list())
{
if (gui_state == SelectDisk)
{
std::string::size_type dotpos = game.name.find_last_of('.');
if (dotpos == std::string::npos || dotpos == game.name.size() - 1)
continue;
std::string extension = game.name.substr(dotpos);
if (stricmp(extension.c_str(), ".gdi") && stricmp(extension.c_str(), ".chd")
&& stricmp(extension.c_str(), ".cdi") && stricmp(extension.c_str(), ".cue"))
std::string extension = get_file_extension(game.path);
if (extension != "gdi" && extension != "chd"
&& extension != "cdi" && extension != "cue")
// Only dreamcast disks
continue;
}
std::string description,selection;
description = get_game_description(game.name);
if (description.empty())
selection = game.name;
else
selection = game.name + " (" + description + ")";
if (filter.PassFilter(selection.c_str()))
if (filter.PassFilter(game.name.c_str()))
{
ImGui::PushID(game.path.c_str());
if (ImGui::Selectable(selection.c_str()))
if (ImGui::Selectable(game.name.c_str()))
{
if (gui_state == SelectDisk)
{
@ -1598,13 +1497,17 @@ static void gui_display_content()
}
else
{
scanner.get_mutex().unlock();
gui_state = ClosedNoResume;
gui_start_game(game.path);
scanner.get_mutex().lock();
}
}
ImGui::PopID();
}
}
scanner.get_mutex().unlock();
}
ImGui::PopStyleVar();
}
ImGui::EndChild();
@ -1812,7 +1715,7 @@ extern bool subfolders_read;
void gui_refresh_files()
{
game_list_done = false;
scanner.refresh();
subfolders_read = false;
}

View File

@ -3,6 +3,8 @@
#include <condition_variable>
#include <mutex>
#include <algorithm>
#include <cctype>
#ifndef _WIN32
#include <pthread.h>
@ -111,3 +113,26 @@ public:
return data[i];
}
};
static inline void string_tolower(std::string& s)
{
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c){ return std::tolower(c); });
}
static inline std::string get_file_extension(const std::string& s)
{
size_t dot = s.find_last_of('.');
if (dot == std::string::npos)
return "";
std::string ext = s.substr(dot + 1, s.length() - dot - 1);
string_tolower(ext);
return ext;
}
static inline std::string get_file_basename(const std::string& s)
{
size_t dot = s.find_last_of('.');
if (dot == std::string::npos)
return s;
return s.substr(0, dot);
}