From f1902015824032426d1dea314e104314968fdbb8 Mon Sep 17 00:00:00 2001 From: Stepland <10530295-Buggyroom@users.noreply.gitlab.com> Date: Fri, 14 Jul 2023 01:57:09 +0200 Subject: [PATCH] async marker loading ! --- CHANGELOG.md | 1 + src/editor_state.cpp | 12 ++-- src/editor_state.hpp | 3 +- src/main.cpp | 115 ++++++++++-------------------- src/marker.cpp | 4 +- src/markers.cpp | 164 +++++++++++++++++++++++++++++++++++++++++++ src/markers.hpp | 58 +++++++++++++++ src/meson.build | 1 + 8 files changed, 271 insertions(+), 87 deletions(-) create mode 100644 src/markers.cpp create mode 100644 src/markers.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 18d02ab..1a9b2ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - The editable time range of a chart now grows in a way that should interfere less with editing - Support for the jujube marker format - Sound parameters are saved +- Markers and Marker previews are loaded in the background, which should avoid long boot up times + UI freezes ## 🚧 Changes 🚧 - Force using the asset folder next to the executable diff --git a/src/editor_state.cpp b/src/editor_state.cpp index 9191213..f120d1a 100644 --- a/src/editor_state.cpp +++ b/src/editor_state.cpp @@ -24,8 +24,12 @@ #include #include +#include "custom_sfml_audio/synced_sound_streams.hpp" +#include "widgets/linear_view.hpp" +#include "better_metadata.hpp" #include "better_note.hpp" #include "better_song.hpp" +#include "better_timing.hpp" #include "chart_state.hpp" #include "compile_time_info.hpp" #include "file_dialogs.hpp" @@ -36,14 +40,10 @@ #include "long_note_dummy.hpp" #include "notifications_queue.hpp" #include "special_numeric_types.hpp" -#include "better_metadata.hpp" -#include "better_timing.hpp" -#include "custom_sfml_audio/synced_sound_streams.hpp" #include "utf8_sfml_redefinitions.hpp" -#include "variant_visitor.hpp" #include "utf8_strings.hpp" +#include "variant_visitor.hpp" #include "waveform.hpp" -#include "widgets/linear_view.hpp" EditorState::EditorState(const std::filesystem::path& assets_, config::Config& config_) : config(config_), @@ -434,7 +434,7 @@ Fraction EditorState::get_snap_step() const { return Fraction{1, snap}; }; -void EditorState::display_playfield(const std::optional>& opt_marker, Judgement markerEndingState) { +void EditorState::display_playfield(const Markers::marker_type& opt_marker, Judgement markerEndingState) { ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_Once); ImGui::SetNextWindowSizeConstraints( ImVec2(0, 0), diff --git a/src/editor_state.hpp b/src/editor_state.hpp index 8e9f2c1..208d818 100644 --- a/src/editor_state.hpp +++ b/src/editor_state.hpp @@ -28,6 +28,7 @@ #include "generic_interval.hpp" #include "history.hpp" #include "marker.hpp" +#include "markers.hpp" #include "clipboard.hpp" #include "notifications_queue.hpp" #include "playfield.hpp" @@ -142,7 +143,7 @@ public: Fraction get_snap_step() const; bool show_playfield = true; - void display_playfield(const std::optional>& marker, Judgement markerEndingState); + void display_playfield(const Markers::marker_type& marker, Judgement markerEndingState); bool show_file_properties = false; void display_file_properties(); diff --git a/src/main.cpp b/src/main.cpp index cdd5f23..b1314d8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,7 @@ #include "history_item.hpp" #include "imgui_extras.hpp" #include "marker.hpp" +#include "markers.hpp" #include "mp3_reader.hpp" #include "notifications_queue.hpp" #include "src/custom_sfml_audio/synced_sound_streams.hpp" @@ -36,8 +38,6 @@ #include "widgets/blank_screen.hpp" int main() { - // TODO : Make the playfield not appear when there's no chart selected - // extend SFML to be able to read mp3's sf::SoundFileFactory::registerReader(); @@ -46,7 +46,7 @@ int main() { const auto settings_folder = executable_folder / "settings"; const auto markers_folder = assets_folder / "textures" / "markers"; - config::Config config {settings_folder}; + config::Config config{settings_folder}; sf::RenderWindow window(sf::VideoMode(800, 600), "FEIS"); window.setVerticalSyncEnabled(true); @@ -78,49 +78,7 @@ int main() { IO.ConfigWindowsMoveFromTitleBarOnly = true; - // Loading markers preview - std::map>> marker_previews_loaders; - std::map> marker_previews; - for (const auto& dir_entry : std::filesystem::directory_iterator(markers_folder)) { - const auto folder = std::filesystem::relative(dir_entry, markers_folder); - marker_previews_loaders[folder] = std::async( - std::launch::async, - [](const std::filesystem::path& folder) -> std::optional { - for (const auto& file : {"preview.png", "ma15.png"}) { - const auto preview_path = folder / file; - if (std::filesystem::exists(preview_path)) { - feis::Texture preview; - if (preview.load_from_path(preview_path)) { - return preview; - } - } - } - return {}; - }, - dir_entry.path() - ); - } - - using MarkerTuple = std::tuple>>; - std::future marker_loader; - const auto load_marker_async = [&](const std::filesystem::path& folder) -> MarkerTuple { - try { - return {folder, load_marker_from(markers_folder / folder)}; - } catch (const std::exception& e) { - return {folder, {}}; - } - }; - std::shared_ptr marker = [&](const std::optional& folder){ - if (folder and folder->is_relative()) { - try { - return load_marker_from(markers_folder / *folder); - } catch (const std::exception& e) { - fmt::print("Failed to load marker from preferences"); - } - } - return first_available_marker_in(markers_folder); - }(config.marker.folder); - + Markers markers{markers_folder, config}; if (not config.marker.ending_state) { config.marker.ending_state = Judgement::Perfect; } @@ -490,7 +448,7 @@ int main() { editor_state->display_history(); } if (editor_state->chart_state and editor_state->show_playfield) { - editor_state->display_playfield(marker, markerEndingState); + editor_state->display_playfield(markers.get_chosen_marker(), markerEndingState); } if (editor_state->show_linear_view) { editor_state->display_linear_view(); @@ -781,33 +739,44 @@ int main() { } if (ImGui::BeginMenu("Marker")) { int i = 0; - for (const auto& [path, opt_preview] : marker_previews) { - ImGui::PushID(path.c_str()); - if (opt_preview) { - if (ImGui::ImageButton(*opt_preview, {100, 100})) { - - marker_loader = std::async - marker = marker_ptr; - config.marker.folder = path; + std::for_each( + markers.cbegin(), + markers.cend(), + [&](const auto& it){ + const auto& [path, opt_preview] = it; + ImGui::PushID(path.c_str()); + bool clicked = false; + if (opt_preview) { + clicked = ImGui::ImageButton(*opt_preview, {100, 100}); + } else { + clicked = ImGui::Button(path_to_utf8_encoded_string(path).c_str(), {100, 100}); + } + if (clicked) { + markers.load_marker(path); + } + ImGui::PopID(); + i++; + if (i % 4 != 0) { + ImGui::SameLine(); } } - - ImGui::PopID(); - i++; - if (i % 4 != 0) { - ImGui::SameLine(); - } - } + ); ImGui::EndMenu(); } if (ImGui::BeginMenu("Marker Ending State")) { for (const auto& [judgement, name] : judgement_to_name) { - const auto preview = marker->judgement_preview(judgement); - if (ImGui::ImageButton(preview, {100, 100})) { - markerEndingState = judgement; + if (const auto& marker = markers.get_chosen_marker()) { + const auto preview = (*marker)->judgement_preview(judgement); + if (ImGui::ImageButton(preview, {100, 100})) { + markerEndingState = judgement; + } + ImGui::SameLine(); + ImGui::TextUnformatted(name.c_str()); + } else { + if (ImGui::Button((name+"##Marker Ending State").c_str())) { + markerEndingState = judgement; + } } - ImGui::SameLine(); - ImGui::TextUnformatted(name.c_str()); } ImGui::EndMenu(); } @@ -824,19 +793,9 @@ int main() { } } ImGui::EndMainMenuBar(); - ImGui::SFML::Render(window); window.display(); - - for (auto& [path, future] : marker_previews_loaders) { - if (future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { - marker_previews[path] = future.get(); - } - } - for (auto& future : ) - if (marker_loader.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { - marker = marker_loader.get(); - } + markers.update(); } ImGui::SFML::Shutdown(); diff --git a/src/marker.cpp b/src/marker.cpp index c42e85c..f155c8c 100644 --- a/src/marker.cpp +++ b/src/marker.cpp @@ -196,8 +196,8 @@ std::shared_ptr load_marker_from(const std::filesystem::path& folder) { return std::make_shared(std::move(JujubeMarker::load_from_folder(folder))); } -std::shared_ptr first_available_marker_in(const std::filesystem::path& assets_folder) { - for (auto& folder : std::filesystem::directory_iterator(assets_folder / "textures" / "markers")) { +std::shared_ptr first_available_marker_in(const std::filesystem::path& markers_folder) { + for (auto& folder : std::filesystem::directory_iterator(markers_folder)) { try { return load_marker_from(folder); } catch (const std::exception&) {} diff --git a/src/markers.cpp b/src/markers.cpp new file mode 100644 index 0000000..c8aaef5 --- /dev/null +++ b/src/markers.cpp @@ -0,0 +1,164 @@ +#include "markers.hpp" + +#include +#include + +#include "fmt/core.h" +#include "imgui.h" + +#include "marker.hpp" +#include "utf8_strings.hpp" + +Markers::Markers( + const std::filesystem::path& markers_folder_, + config::Config& config_ +) : + markers_folder(markers_folder_), + config(config_) +{ + + auto contents = std::filesystem::directory_iterator(markers_folder); + auto subdirs = std::views::filter(contents, [](const auto& dir_it){return dir_it.is_directory();}); + + for (const auto& dir_it : subdirs) { + const auto folder = std::filesystem::relative(dir_it, markers_folder); + preview_loaders[folder] = std::async(std::launch::async, load_marker_preview, dir_it.path()); + } + + marker_loader = std::make_tuple( + std::filesystem::path{}, + std::async(std::launch::async, load_default_marker, std::cref(config.marker.folder), std::cref(markers_folder)) + ); +} + +void Markers::load_marker(const std::filesystem::path& chosen) { + auto& [path, future] = marker_loader; + if (future.valid()) { + discarded_markers.emplace_back(std::async(std::launch::async, discard_marker, std::move(future))); + } + marker_loader = std::make_tuple( + chosen, + std::async(std::launch::async, load_marker_async, std::cref(chosen), std::cref(markers_folder)) + ); +} + +const Markers::marker_type& Markers::get_chosen_marker() const { + return chosen_marker; +} + +Markers::previews_type::const_iterator Markers::cbegin() const { + return previews.cbegin(); +} + +Markers::previews_type::const_iterator Markers::cend() const { + return previews.cend(); +} + +void Markers::update() { + std::erase_if(preview_loaders, [](const auto& it){ + return not it.second.valid(); + }); + for (auto& [path, future] : preview_loaders) { + if (future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + previews[path] = future.get(); + } + } + { + auto& [path, future] = marker_loader; + if (future.valid()) { + if (future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + const auto loaded_marker = future.get(); + if (loaded_marker) { + chosen_marker = loaded_marker; + config.marker.folder = path; + } else { + previews.erase(path); + } + } + } + } + + std::erase_if(discarded_markers, [](const std::future& f){ + return not f.valid() or f.wait_for(std::chrono::seconds(0)) == std::future_status::ready; + }); +} + +void Markers::display_debug() { + if (ImGui::Begin("Markers Debug")) { + ImGui::TextUnformatted("chosen_marker :"); + ImGui::SameLine(); + if (not chosen_marker) { + ImGui::TextDisabled("(empty)"); + } else { + ImGui::TextUnformatted(path_to_utf8_encoded_string((*chosen_marker)->get_folder()).c_str()); + } + ImGui::TextUnformatted("marker_loader :"); + ImGui::SameLine(); + if (not std::get<1>(marker_loader).valid()) { + ImGui::TextDisabled("(empty)"); + } else { + ImGui::TextUnformatted(path_to_utf8_encoded_string(std::get<0>(marker_loader)).c_str()); + } + if (ImGui::TreeNodeEx("discarded_markers", ImGuiTreeNodeFlags_DefaultOpen)) { + for (const auto& future : discarded_markers) { + ImGui::BulletText("%s", fmt::format("{}", fmt::ptr(&future)).c_str()); + } + ImGui::TreePop(); + } + if (ImGui::TreeNodeEx("previews", ImGuiTreeNodeFlags_DefaultOpen)) { + for (const auto& [path, _] : previews) { + ImGui::BulletText("%s", fmt::format("{}", path_to_utf8_encoded_string(path)).c_str()); + } + ImGui::TreePop(); + } + if (ImGui::TreeNodeEx("preview_loaders", ImGuiTreeNodeFlags_DefaultOpen)) { + for (const auto& [path, _] : preview_loaders) { + ImGui::BulletText("%s", fmt::format("{}", path_to_utf8_encoded_string(path)).c_str()); + } + ImGui::TreePop(); + } + } + ImGui::End(); +} + +Markers::marker_type Markers::load_default_marker( + const std::optional& chosen_from_config, + const std::filesystem::path& markers +) { + if (chosen_from_config and chosen_from_config->is_relative()) { + try { + return load_marker_from(markers / *chosen_from_config); + } catch (const std::exception& e) { + fmt::print("Failed to load marker from preferences"); + } + } + return first_available_marker_in(markers); +} + +Markers::marker_type Markers::load_marker_async(const std::filesystem::path& chosen, const std::filesystem::path& markers) { + try { + return load_marker_from(markers / chosen); + } catch (const std::exception& e) { + return {}; + } +} + +void Markers::discard_marker(std::future&& future) { + auto other_future = std::move(future); + if (other_future.valid()) { + other_future.get(); + } +} + +Markers::preview_type Markers::load_marker_preview(const std::filesystem::path& chosen) { + for (const auto& file : {"preview.png", "ma15.png"}) { + const auto preview_path = chosen / file; + if (std::filesystem::exists(preview_path)) { + feis::Texture preview; + if (preview.load_from_path(preview_path)) { + return preview; + } + } + } + return {}; +} \ No newline at end of file diff --git a/src/markers.hpp b/src/markers.hpp new file mode 100644 index 0000000..a84f5ab --- /dev/null +++ b/src/markers.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include +#include + +#include "config.hpp" +#include "marker.hpp" +#include "utf8_sfml_redefinitions.hpp" + +/* Deals with loading markers and marker previews asynchronously */ +class Markers { +public: + + using marker_type = std::optional>; + using marker_loader_type = std::future; + using preview_type = std::optional; + using previews_type = std::map; + + Markers( + const std::filesystem::path& markers_folder, + config::Config& config + ); + + void load_marker(const std::filesystem::path& chosen); + + const marker_type& get_chosen_marker() const; + + previews_type::const_iterator cbegin() const; + previews_type::const_iterator cend() const; + + void update(); + + void display_debug(); +private: + std::filesystem::path markers_folder; + config::Config& config; + + // Markers + marker_type chosen_marker; + std::tuple> marker_loader; + static marker_type load_default_marker( + const std::optional& marker_path_from_config, + const std::filesystem::path& markers_folder + ); + static marker_type load_marker_async( + const std::filesystem::path& chosen, + const std::filesystem::path& markers + ); + std::vector> discarded_markers; + static void discard_marker(std::future&& future); + + previews_type previews; + std::map> preview_loaders; + + static preview_type load_marker_preview(const std::filesystem::path& folder); +}; \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index 7510e14..d88d1f7 100644 --- a/src/meson.build +++ b/src/meson.build @@ -27,6 +27,7 @@ sources += files( 'long_note_dummy.cpp', 'main.cpp', 'marker.cpp', + 'markers.cpp', 'mp3_reader.cpp', 'note.cpp', 'notification.cpp',