async marker loading !

This commit is contained in:
Stepland 2023-07-14 01:57:09 +02:00
parent 0446e717ec
commit f190201582
8 changed files with 271 additions and 87 deletions

View File

@ -36,6 +36,7 @@
- The editable time range of a chart now grows in a way that should interfere less with editing - The editable time range of a chart now grows in a way that should interfere less with editing
- Support for the jujube marker format - Support for the jujube marker format
- Sound parameters are saved - Sound parameters are saved
- Markers and Marker previews are loaded in the background, which should avoid long boot up times + UI freezes
## 🚧 Changes 🚧 ## 🚧 Changes 🚧
- Force using the asset folder next to the executable - Force using the asset folder next to the executable

View File

@ -24,8 +24,12 @@
#include <SFML/System/Vector2.hpp> #include <SFML/System/Vector2.hpp>
#include <tinyfiledialogs.h> #include <tinyfiledialogs.h>
#include "custom_sfml_audio/synced_sound_streams.hpp"
#include "widgets/linear_view.hpp"
#include "better_metadata.hpp"
#include "better_note.hpp" #include "better_note.hpp"
#include "better_song.hpp" #include "better_song.hpp"
#include "better_timing.hpp"
#include "chart_state.hpp" #include "chart_state.hpp"
#include "compile_time_info.hpp" #include "compile_time_info.hpp"
#include "file_dialogs.hpp" #include "file_dialogs.hpp"
@ -36,14 +40,10 @@
#include "long_note_dummy.hpp" #include "long_note_dummy.hpp"
#include "notifications_queue.hpp" #include "notifications_queue.hpp"
#include "special_numeric_types.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 "utf8_sfml_redefinitions.hpp"
#include "variant_visitor.hpp"
#include "utf8_strings.hpp" #include "utf8_strings.hpp"
#include "variant_visitor.hpp"
#include "waveform.hpp" #include "waveform.hpp"
#include "widgets/linear_view.hpp"
EditorState::EditorState(const std::filesystem::path& assets_, config::Config& config_) : EditorState::EditorState(const std::filesystem::path& assets_, config::Config& config_) :
config(config_), config(config_),
@ -434,7 +434,7 @@ Fraction EditorState::get_snap_step() const {
return Fraction{1, snap}; return Fraction{1, snap};
}; };
void EditorState::display_playfield(const std::optional<std::shared_ptr<Marker>>& opt_marker, Judgement markerEndingState) { void EditorState::display_playfield(const Markers::marker_type& opt_marker, Judgement markerEndingState) {
ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_Once); ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_Once);
ImGui::SetNextWindowSizeConstraints( ImGui::SetNextWindowSizeConstraints(
ImVec2(0, 0), ImVec2(0, 0),

View File

@ -28,6 +28,7 @@
#include "generic_interval.hpp" #include "generic_interval.hpp"
#include "history.hpp" #include "history.hpp"
#include "marker.hpp" #include "marker.hpp"
#include "markers.hpp"
#include "clipboard.hpp" #include "clipboard.hpp"
#include "notifications_queue.hpp" #include "notifications_queue.hpp"
#include "playfield.hpp" #include "playfield.hpp"
@ -142,7 +143,7 @@ public:
Fraction get_snap_step() const; Fraction get_snap_step() const;
bool show_playfield = true; bool show_playfield = true;
void display_playfield(const std::optional<std::shared_ptr<Marker>>& marker, Judgement markerEndingState); void display_playfield(const Markers::marker_type& marker, Judgement markerEndingState);
bool show_file_properties = false; bool show_file_properties = false;
void display_file_properties(); void display_file_properties();

View File

@ -4,6 +4,7 @@
#include <SFML/Graphics/Texture.hpp> #include <SFML/Graphics/Texture.hpp>
#include <SFML/System/Time.hpp> #include <SFML/System/Time.hpp>
#include <SFML/Window/Keyboard.hpp> #include <SFML/Window/Keyboard.hpp>
#include <algorithm>
#include <chrono> #include <chrono>
#include <exception> #include <exception>
#include <filesystem> #include <filesystem>
@ -28,6 +29,7 @@
#include "history_item.hpp" #include "history_item.hpp"
#include "imgui_extras.hpp" #include "imgui_extras.hpp"
#include "marker.hpp" #include "marker.hpp"
#include "markers.hpp"
#include "mp3_reader.hpp" #include "mp3_reader.hpp"
#include "notifications_queue.hpp" #include "notifications_queue.hpp"
#include "src/custom_sfml_audio/synced_sound_streams.hpp" #include "src/custom_sfml_audio/synced_sound_streams.hpp"
@ -36,8 +38,6 @@
#include "widgets/blank_screen.hpp" #include "widgets/blank_screen.hpp"
int main() { int main() {
// TODO : Make the playfield not appear when there's no chart selected
// extend SFML to be able to read mp3's // extend SFML to be able to read mp3's
sf::SoundFileFactory::registerReader<sf::priv::SoundFileReaderMp3>(); sf::SoundFileFactory::registerReader<sf::priv::SoundFileReaderMp3>();
@ -46,7 +46,7 @@ int main() {
const auto settings_folder = executable_folder / "settings"; const auto settings_folder = executable_folder / "settings";
const auto markers_folder = assets_folder / "textures" / "markers"; 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"); sf::RenderWindow window(sf::VideoMode(800, 600), "FEIS");
window.setVerticalSyncEnabled(true); window.setVerticalSyncEnabled(true);
@ -78,49 +78,7 @@ int main() {
IO.ConfigWindowsMoveFromTitleBarOnly = true; IO.ConfigWindowsMoveFromTitleBarOnly = true;
// Loading markers preview Markers markers{markers_folder, config};
std::map<std::filesystem::path, std::future<std::optional<feis::Texture>>> marker_previews_loaders;
std::map<std::filesystem::path, std::optional<feis::Texture>> 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<feis::Texture> {
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::filesystem::path, std::optional<std::shared_ptr<Marker>>>;
std::future<MarkerTuple> 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> marker = [&](const std::optional<std::filesystem::path>& 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);
if (not config.marker.ending_state) { if (not config.marker.ending_state) {
config.marker.ending_state = Judgement::Perfect; config.marker.ending_state = Judgement::Perfect;
} }
@ -490,7 +448,7 @@ int main() {
editor_state->display_history(); editor_state->display_history();
} }
if (editor_state->chart_state and editor_state->show_playfield) { 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) { if (editor_state->show_linear_view) {
editor_state->display_linear_view(); editor_state->display_linear_view();
@ -781,33 +739,44 @@ int main() {
} }
if (ImGui::BeginMenu("Marker")) { if (ImGui::BeginMenu("Marker")) {
int i = 0; int i = 0;
for (const auto& [path, opt_preview] : marker_previews) { std::for_each(
markers.cbegin(),
markers.cend(),
[&](const auto& it){
const auto& [path, opt_preview] = it;
ImGui::PushID(path.c_str()); ImGui::PushID(path.c_str());
bool clicked = false;
if (opt_preview) { if (opt_preview) {
if (ImGui::ImageButton(*opt_preview, {100, 100})) { clicked = ImGui::ImageButton(*opt_preview, {100, 100});
} else {
marker_loader = std::async clicked = ImGui::Button(path_to_utf8_encoded_string(path).c_str(), {100, 100});
marker = marker_ptr;
config.marker.folder = path;
} }
if (clicked) {
markers.load_marker(path);
} }
ImGui::PopID(); ImGui::PopID();
i++; i++;
if (i % 4 != 0) { if (i % 4 != 0) {
ImGui::SameLine(); ImGui::SameLine();
} }
} }
);
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (ImGui::BeginMenu("Marker Ending State")) { if (ImGui::BeginMenu("Marker Ending State")) {
for (const auto& [judgement, name] : judgement_to_name) { for (const auto& [judgement, name] : judgement_to_name) {
const auto preview = marker->judgement_preview(judgement); if (const auto& marker = markers.get_chosen_marker()) {
const auto preview = (*marker)->judgement_preview(judgement);
if (ImGui::ImageButton(preview, {100, 100})) { if (ImGui::ImageButton(preview, {100, 100})) {
markerEndingState = judgement; markerEndingState = judgement;
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::TextUnformatted(name.c_str()); ImGui::TextUnformatted(name.c_str());
} else {
if (ImGui::Button((name+"##Marker Ending State").c_str())) {
markerEndingState = judgement;
}
}
} }
ImGui::EndMenu(); ImGui::EndMenu();
} }
@ -824,19 +793,9 @@ int main() {
} }
} }
ImGui::EndMainMenuBar(); ImGui::EndMainMenuBar();
ImGui::SFML::Render(window); ImGui::SFML::Render(window);
window.display(); window.display();
markers.update();
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();
}
} }
ImGui::SFML::Shutdown(); ImGui::SFML::Shutdown();

View File

@ -196,8 +196,8 @@ std::shared_ptr<Marker> load_marker_from(const std::filesystem::path& folder) {
return std::make_shared<JujubeMarker>(std::move(JujubeMarker::load_from_folder(folder))); return std::make_shared<JujubeMarker>(std::move(JujubeMarker::load_from_folder(folder)));
} }
std::shared_ptr<Marker> first_available_marker_in(const std::filesystem::path& assets_folder) { std::shared_ptr<Marker> first_available_marker_in(const std::filesystem::path& markers_folder) {
for (auto& folder : std::filesystem::directory_iterator(assets_folder / "textures" / "markers")) { for (auto& folder : std::filesystem::directory_iterator(markers_folder)) {
try { try {
return load_marker_from(folder); return load_marker_from(folder);
} catch (const std::exception&) {} } catch (const std::exception&) {}

164
src/markers.cpp Normal file
View File

@ -0,0 +1,164 @@
#include "markers.hpp"
#include <future>
#include <ranges>
#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<void>& 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<std::filesystem::path>& 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<marker_type>&& 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 {};
}

58
src/markers.hpp Normal file
View File

@ -0,0 +1,58 @@
#pragma once
#include <filesystem>
#include <future>
#include <map>
#include <optional>
#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<std::shared_ptr<Marker>>;
using marker_loader_type = std::future<marker_type>;
using preview_type = std::optional<feis::Texture>;
using previews_type = std::map<std::filesystem::path, preview_type>;
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<std::filesystem::path, std::future<marker_type>> marker_loader;
static marker_type load_default_marker(
const std::optional<std::filesystem::path>& 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<std::future<void>> discarded_markers;
static void discard_marker(std::future<marker_type>&& future);
previews_type previews;
std::map<std::filesystem::path, std::future<preview_type>> preview_loaders;
static preview_type load_marker_preview(const std::filesystem::path& folder);
};

View File

@ -27,6 +27,7 @@ sources += files(
'long_note_dummy.cpp', 'long_note_dummy.cpp',
'main.cpp', 'main.cpp',
'marker.cpp', 'marker.cpp',
'markers.cpp',
'mp3_reader.cpp', 'mp3_reader.cpp',
'note.cpp', 'note.cpp',
'notification.cpp', 'notification.cpp',