diff --git a/examples/sound/Sound.cpp b/examples/sound/Sound.cpp index a74c47d37..38780dae6 100644 --- a/examples/sound/Sound.cpp +++ b/examples/sound/Sound.cpp @@ -4,7 +4,6 @@ #include #include -#include //////////////////////////////////////////////////////////// @@ -22,7 +21,7 @@ void playSound() std::cout << "killdeer.wav:" << '\n' << " " << buffer.getDuration().asSeconds() << " seconds" << '\n' << " " << buffer.getSampleRate() << " samples / sec" << '\n' - << " " << buffer.getChannelCount() << " channels" << std::endl; + << " " << buffer.getChannelCount() << " channels" << '\n'; // Create a sound instance and play it sf::Sound sound(buffer); @@ -35,10 +34,10 @@ void playSound() sf::sleep(sf::milliseconds(100)); // Display the playing position - std::cout << "\rPlaying... " << sound.getPlayingOffset().asSeconds() << " sec "; - std::cout << std::flush; + std::cout << "\rPlaying... " << sound.getPlayingOffset().asSeconds() << " sec " << std::flush; } - std::cout << std::endl << std::endl; + + std::cout << '\n' << std::endl; } @@ -57,7 +56,7 @@ void playMusic(const std::filesystem::path& filename) std::cout << filename << ":" << '\n' << " " << music.getDuration().asSeconds() << " seconds" << '\n' << " " << music.getSampleRate() << " samples / sec" << '\n' - << " " << music.getChannelCount() << " channels" << std::endl; + << " " << music.getChannelCount() << " channels" << '\n'; // Play it music.play(); @@ -69,9 +68,9 @@ void playMusic(const std::filesystem::path& filename) sf::sleep(sf::milliseconds(100)); // Display the playing position - std::cout << "\rPlaying... " << music.getPlayingOffset().asSeconds() << " sec "; - std::cout << std::flush; + std::cout << "\rPlaying... " << music.getPlayingOffset().asSeconds() << " sec " << std::flush; } + std::cout << '\n' << std::endl; } diff --git a/include/SFML/Audio/SoundFileFactory.hpp b/include/SFML/Audio/SoundFileFactory.hpp index 8942ca6ad..899c7c134 100644 --- a/include/SFML/Audio/SoundFileFactory.hpp +++ b/include/SFML/Audio/SoundFileFactory.hpp @@ -31,7 +31,7 @@ #include #include -#include +#include #include @@ -67,6 +67,13 @@ public: template static void unregisterReader(); + //////////////////////////////////////////////////////////// + /// \brief Check if a reader is registered + /// + //////////////////////////////////////////////////////////// + template + static bool isReaderRegistered(); + //////////////////////////////////////////////////////////// /// \brief Register a new writer /// @@ -85,6 +92,13 @@ public: template static void unregisterWriter(); + //////////////////////////////////////////////////////////// + /// \brief Check if a writer is registered + /// + //////////////////////////////////////////////////////////// + template + static bool isWriterRegistered(); + //////////////////////////////////////////////////////////// /// \brief Instantiate the right reader for the given file on disk /// @@ -136,27 +150,20 @@ private: //////////////////////////////////////////////////////////// // Types //////////////////////////////////////////////////////////// - struct ReaderFactory - { - bool (*check)(InputStream&); - std::unique_ptr (*create)(); - }; - using ReaderFactoryArray = std::vector; + template + using CreateFnPtr = std::unique_ptr (*)(); - struct WriterFactory - { - bool (*check)(const std::filesystem::path&); - std::unique_ptr (*create)(); - }; - using WriterFactoryArray = std::vector; + using ReaderCheckFnPtr = bool (*)(InputStream&); + using WriterCheckFnPtr = bool (*)(const std::filesystem::path&); + + using ReaderFactoryMap = std::unordered_map, ReaderCheckFnPtr>; + using WriterFactoryMap = std::unordered_map, WriterCheckFnPtr>; //////////////////////////////////////////////////////////// - // Static member data + // Static member functions //////////////////////////////////////////////////////////// - // NOLINTBEGIN(readability-identifier-naming) - static inline ReaderFactoryArray s_readers; //!< List of all registered readers - static inline WriterFactoryArray s_writers; //!< List of all registered writers - // NOLINTEND(readability-identifier-naming) + static ReaderFactoryMap& getReaderFactoryMap(); + static WriterFactoryMap& getWriterFactoryMap(); }; } // namespace sf @@ -182,7 +189,10 @@ private: /// Usage example: /// \code /// sf::SoundFileFactory::registerReader(); +/// assert(sf::SoundFileFactory::isReaderRegistered()); +/// /// sf::SoundFileFactory::registerWriter(); +/// assert(sf::SoundFileFactory::isWriterRegistered()); /// \endcode /// /// \see sf::InputSoundFile, sf::OutputSoundFile, sf::SoundFileReader, sf::SoundFileWriter diff --git a/include/SFML/Audio/SoundFileFactory.inl b/include/SFML/Audio/SoundFileFactory.inl index 3e565bbbe..569466d63 100644 --- a/include/SFML/Audio/SoundFileFactory.inl +++ b/include/SFML/Audio/SoundFileFactory.inl @@ -46,18 +46,12 @@ std::unique_ptr createWriter() } } // namespace priv + //////////////////////////////////////////////////////////// template void SoundFileFactory::registerReader() { - // Make sure the same class won't be registered twice - unregisterReader(); - - // Create a new factory with the functions provided by the class - const ReaderFactory factory{&T::check, &priv::createReader}; - - // Add it - s_readers.push_back(factory); + getReaderFactoryMap()[&priv::createReader] = &T::check; } @@ -65,26 +59,23 @@ void SoundFileFactory::registerReader() template void SoundFileFactory::unregisterReader() { - // Remove the instance(s) of the reader from the array of factories - s_readers.erase(std::remove_if(s_readers.begin(), - s_readers.end(), - [](const ReaderFactory& readerFactory) - { return readerFactory.create == &priv::createReader; }), - s_readers.end()); + getReaderFactoryMap().erase(&priv::createReader); } + +//////////////////////////////////////////////////////////// +template +bool SoundFileFactory::isReaderRegistered() +{ + return getReaderFactoryMap().count(&priv::createReader) == 1; +} + + //////////////////////////////////////////////////////////// template void SoundFileFactory::registerWriter() { - // Make sure the same class won't be registered twice - unregisterWriter(); - - // Create a new factory with the functions provided by the class - const WriterFactory factory{&T::check, &priv::createWriter}; - - // Add it - s_writers.push_back(factory); + getWriterFactoryMap()[&priv::createWriter] = &T::check; } @@ -92,12 +83,15 @@ void SoundFileFactory::registerWriter() template void SoundFileFactory::unregisterWriter() { - // Remove the instance(s) of the writer from the array of factories - s_writers.erase(std::remove_if(s_writers.begin(), - s_writers.end(), - [](const WriterFactory& writerFactory) - { return writerFactory.create == &priv::createWriter; }), - s_writers.end()); + getWriterFactoryMap().erase(&priv::createWriter); +} + + +//////////////////////////////////////////////////////////// +template +bool SoundFileFactory::isWriterRegistered() +{ + return getWriterFactoryMap().count(&priv::createWriter) == 1; } } // namespace sf diff --git a/src/SFML/Audio/SoundFileFactory.cpp b/src/SFML/Audio/SoundFileFactory.cpp index 041d5132f..b8ef7ac41 100644 --- a/src/SFML/Audio/SoundFileFactory.cpp +++ b/src/SFML/Audio/SoundFileFactory.cpp @@ -42,34 +42,11 @@ #include -namespace -{ -// Register all the built-in readers and writers if not already done -void ensureDefaultReadersWritersRegistered() -{ - static bool registered = false; - if (!registered) - { - sf::SoundFileFactory::registerReader(); - sf::SoundFileFactory::registerWriter(); - sf::SoundFileFactory::registerReader(); - sf::SoundFileFactory::registerReader(); - sf::SoundFileFactory::registerWriter(); - sf::SoundFileFactory::registerReader(); - sf::SoundFileFactory::registerWriter(); - registered = true; - } -} -} // namespace - namespace sf { //////////////////////////////////////////////////////////// std::unique_ptr SoundFileFactory::createReaderFromFilename(const std::filesystem::path& filename) { - // Register the built-in readers/writers on first call - ensureDefaultReadersWritersRegistered(); - // Wrap the input file into a file stream FileInputStream stream; if (!stream.open(filename)) @@ -79,7 +56,7 @@ std::unique_ptr SoundFileFactory::createReaderFromFilename(cons } // Test the filename in all the registered factories - for (const ReaderFactory& readerFactory : s_readers) + for (const auto& [fpCreate, fpCheck] : getReaderFactoryMap()) { if (stream.seek(0) == -1) { @@ -87,8 +64,8 @@ std::unique_ptr SoundFileFactory::createReaderFromFilename(cons return nullptr; } - if (readerFactory.check(stream)) - return readerFactory.create(); + if (fpCheck(stream)) + return fpCreate(); } // No suitable reader found @@ -100,15 +77,12 @@ std::unique_ptr SoundFileFactory::createReaderFromFilename(cons //////////////////////////////////////////////////////////// std::unique_ptr SoundFileFactory::createReaderFromMemory(const void* data, std::size_t sizeInBytes) { - // Register the built-in readers/writers on first call - ensureDefaultReadersWritersRegistered(); - // Wrap the memory file into a file stream MemoryInputStream stream; stream.open(data, sizeInBytes); // Test the stream for all the registered factories - for (const ReaderFactory& readerFactory : s_readers) + for (const auto& [fpCreate, fpCheck] : getReaderFactoryMap()) { if (stream.seek(0) == -1) { @@ -116,8 +90,8 @@ std::unique_ptr SoundFileFactory::createReaderFromMemory(const return nullptr; } - if (readerFactory.check(stream)) - return readerFactory.create(); + if (fpCheck(stream)) + return fpCreate(); } // No suitable reader found @@ -129,11 +103,8 @@ std::unique_ptr SoundFileFactory::createReaderFromMemory(const //////////////////////////////////////////////////////////// std::unique_ptr SoundFileFactory::createReaderFromStream(InputStream& stream) { - // Register the built-in readers/writers on first call - ensureDefaultReadersWritersRegistered(); - // Test the stream for all the registered factories - for (const ReaderFactory& readerFactory : s_readers) + for (const auto& [fpCreate, fpCheck] : getReaderFactoryMap()) { if (stream.seek(0) == -1) { @@ -141,8 +112,8 @@ std::unique_ptr SoundFileFactory::createReaderFromStream(InputS return nullptr; } - if (readerFactory.check(stream)) - return readerFactory.create(); + if (fpCheck(stream)) + return fpCreate(); } // No suitable reader found @@ -154,14 +125,11 @@ std::unique_ptr SoundFileFactory::createReaderFromStream(InputS //////////////////////////////////////////////////////////// std::unique_ptr SoundFileFactory::createWriterFromFilename(const std::filesystem::path& filename) { - // Register the built-in readers/writers on first call - ensureDefaultReadersWritersRegistered(); - // Test the filename in all the registered factories - for (const WriterFactory& writerFactory : s_writers) + for (const auto& [fpCreate, fpCheck] : getWriterFactoryMap()) { - if (writerFactory.check(filename)) - return writerFactory.create(); + if (fpCheck(filename)) + return fpCreate(); } // No suitable writer found @@ -169,4 +137,29 @@ std::unique_ptr SoundFileFactory::createWriterFromFilename(cons return nullptr; } + +//////////////////////////////////////////////////////////// +SoundFileFactory::ReaderFactoryMap& SoundFileFactory::getReaderFactoryMap() +{ + // The map is pre-populated with default readers on construction + static ReaderFactoryMap result{{&priv::createReader, &priv::SoundFileReaderFlac::check}, + {&priv::createReader, &priv::SoundFileReaderMp3::check}, + {&priv::createReader, &priv::SoundFileReaderOgg::check}, + {&priv::createReader, &priv::SoundFileReaderWav::check}}; + + return result; +} + + +//////////////////////////////////////////////////////////// +SoundFileFactory::WriterFactoryMap& SoundFileFactory::getWriterFactoryMap() +{ + // The map is pre-populated with default writers on construction + static WriterFactoryMap result{{&priv::createWriter, &priv::SoundFileWriterFlac::check}, + {&priv::createWriter, &priv::SoundFileWriterOgg::check}, + {&priv::createWriter, &priv::SoundFileWriterWav::check}}; + + return result; +} + } // namespace sf diff --git a/test/Audio/SoundFileFactory.test.cpp b/test/Audio/SoundFileFactory.test.cpp index ab688682c..2238dc27e 100644 --- a/test/Audio/SoundFileFactory.test.cpp +++ b/test/Audio/SoundFileFactory.test.cpp @@ -5,11 +5,60 @@ #include #include +#include #include +#include +#include #include +#include + +namespace +{ + +struct NoopSoundFileReader : sf::SoundFileReader +{ + static bool check(sf::InputStream&) + { + return false; + } + + std::optional open(sf::InputStream&) override + { + return {}; + } + + void seek(std::uint64_t) override + { + } + + std::uint64_t read(std::int16_t*, std::uint64_t) override + { + return 0; + } +}; + +struct NoopSoundFileWriter : sf::SoundFileWriter +{ + static bool check(const std::filesystem::path&) + { + return false; + } + + bool open(const std::filesystem::path&, unsigned int, unsigned int) override + { + return false; + } + + void write(const std::int16_t*, std::uint64_t) override + { + } +}; + +} // namespace + TEST_CASE("[Audio] sf::SoundFileFactory") { SECTION("Type traits") @@ -20,6 +69,28 @@ TEST_CASE("[Audio] sf::SoundFileFactory") STATIC_CHECK(std::is_nothrow_move_assignable_v); } + SECTION("isReaderRegistered()") + { + CHECK(!sf::SoundFileFactory::isReaderRegistered()); + + sf::SoundFileFactory::registerReader(); + CHECK(sf::SoundFileFactory::isReaderRegistered()); + + sf::SoundFileFactory::unregisterReader(); + CHECK(!sf::SoundFileFactory::isReaderRegistered()); + } + + SECTION("isWriterRegistered()") + { + CHECK(!sf::SoundFileFactory::isWriterRegistered()); + + sf::SoundFileFactory::registerWriter(); + CHECK(sf::SoundFileFactory::isWriterRegistered()); + + sf::SoundFileFactory::unregisterWriter(); + CHECK(!sf::SoundFileFactory::isWriterRegistered()); + } + SECTION("createReaderFromFilename()") { SECTION("Missing file")