Merge branch 'v0.22.x'
This commit is contained in:
		
							
								
								
									
										6
									
								
								NEWS
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								NEWS
									
									
									
									
									
								
							| @@ -7,8 +7,14 @@ ver 0.23 (not yet released) | ||||
|   - snapcast: new plugin | ||||
|  | ||||
| ver 0.22.7 (not yet released) | ||||
| * protocol | ||||
|   - don't use glibc extension to parse time stamps | ||||
| * decoder | ||||
|   - ffmpeg: fix build problem with FFmpeg 3.4 | ||||
| * storage | ||||
|   - curl: don't use glibc extension | ||||
| * output | ||||
|   - wasapi: add algorithm for finding usable audio format | ||||
|  | ||||
| ver 0.22.6 (2021/02/16) | ||||
| * fix missing tags on songs in queue | ||||
|   | ||||
							
								
								
									
										14
									
								
								doc/user.rst
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								doc/user.rst
									
									
									
									
									
								
							| @@ -141,6 +141,15 @@ Basically, there are two ways to compile :program:`MPD` for Windows: | ||||
|  | ||||
| This section is about the latter. | ||||
|  | ||||
| You need: | ||||
|  | ||||
| * `mingw-w64 <http://mingw-w64.org/doku.php>`__ | ||||
| * `Meson 0.49.0 <http://mesonbuild.com/>`__ and `Ninja | ||||
|   <https://ninja-build.org/>`__ | ||||
| * cmake | ||||
| * pkg-config | ||||
| * quilt | ||||
|  | ||||
| Just like with the native build, unpack the :program:`MPD` source | ||||
| tarball and change into the directory.  Then, instead of | ||||
| :program:`meson`, type: | ||||
| @@ -168,6 +177,11 @@ You need: | ||||
|  | ||||
| * Android SDK | ||||
| * `Android NDK r22 <https://developer.android.com/ndk/downloads>`_ | ||||
| * `Meson 0.49.0 <http://mesonbuild.com/>`__ and `Ninja | ||||
|   <https://ninja-build.org/>`__ | ||||
| * cmake | ||||
| * pkg-config | ||||
| * quilt | ||||
|  | ||||
| Just like with the native build, unpack the :program:`MPD` source | ||||
| tarball and change into the directory.  Then, instead of | ||||
|   | ||||
| @@ -261,7 +261,6 @@ sources = [ | ||||
|   'src/LogInit.cxx', | ||||
|   'src/ls.cxx', | ||||
|   'src/Instance.cxx', | ||||
|   'src/win32/Win32Main.cxx', | ||||
|   'src/MusicBuffer.cxx', | ||||
|   'src/MusicPipe.cxx', | ||||
|   'src/MusicChunk.cxx', | ||||
| @@ -309,6 +308,13 @@ sources = [ | ||||
|   'src/PlaylistFile.cxx', | ||||
| ] | ||||
|  | ||||
| if is_windows | ||||
|   sources += [ | ||||
|     'src/win32/Win32Main.cxx', | ||||
|     'src/win32/ComWorker.cxx', | ||||
|   ] | ||||
| endif | ||||
|  | ||||
| if not is_android | ||||
|   sources += [ | ||||
|     'src/CommandLine.cxx', | ||||
|   | ||||
| @@ -19,7 +19,7 @@ | ||||
|  | ||||
| #include "mixer/MixerInternal.hxx" | ||||
| #include "output/plugins/WasapiOutputPlugin.hxx" | ||||
| #include "win32/Com.hxx" | ||||
| #include "win32/ComWorker.hxx" | ||||
| #include "win32/HResult.hxx" | ||||
|  | ||||
| #include <cmath> | ||||
| @@ -28,92 +28,103 @@ | ||||
|  | ||||
| class WasapiMixer final : public Mixer { | ||||
| 	WasapiOutput &output; | ||||
| 	std::optional<COM> com; | ||||
|  | ||||
| public: | ||||
| 	WasapiMixer(WasapiOutput &_output, MixerListener &_listener) | ||||
| 	: Mixer(wasapi_mixer_plugin, _listener), output(_output) {} | ||||
|  | ||||
| 	void Open() override { com.emplace(); } | ||||
| 	void Open() override {} | ||||
|  | ||||
| 	void Close() noexcept override { com.reset(); } | ||||
| 	void Close() noexcept override {} | ||||
|  | ||||
| 	int GetVolume() override { | ||||
| 		HRESULT result; | ||||
| 		float volume_level; | ||||
| 		auto future = COMWorker::Async([&]() -> int { | ||||
| 			HRESULT result; | ||||
| 			float volume_level; | ||||
|  | ||||
| 		if (wasapi_is_exclusive(output)) { | ||||
| 			ComPtr<IAudioEndpointVolume> endpoint_volume; | ||||
| 			result = wasapi_output_get_device(output)->Activate( | ||||
| 				__uuidof(IAudioEndpointVolume), CLSCTX_ALL, nullptr, | ||||
| 				endpoint_volume.AddressCast()); | ||||
| 			if (FAILED(result)) { | ||||
| 				throw FormatHResultError( | ||||
| 					result, "Unable to get device endpoint volume"); | ||||
| 			if (wasapi_is_exclusive(output)) { | ||||
| 				ComPtr<IAudioEndpointVolume> endpoint_volume; | ||||
| 				result = wasapi_output_get_device(output)->Activate( | ||||
| 					__uuidof(IAudioEndpointVolume), CLSCTX_ALL, | ||||
| 					nullptr, endpoint_volume.AddressCast()); | ||||
| 				if (FAILED(result)) { | ||||
| 					throw FormatHResultError(result, | ||||
| 								 "Unable to get device " | ||||
| 								 "endpoint volume"); | ||||
| 				} | ||||
|  | ||||
| 				result = endpoint_volume->GetMasterVolumeLevelScalar( | ||||
| 					&volume_level); | ||||
| 				if (FAILED(result)) { | ||||
| 					throw FormatHResultError(result, | ||||
| 								 "Unable to get master " | ||||
| 								 "volume level"); | ||||
| 				} | ||||
| 			} else { | ||||
| 				ComPtr<ISimpleAudioVolume> session_volume; | ||||
| 				result = wasapi_output_get_client(output)->GetService( | ||||
| 					__uuidof(ISimpleAudioVolume), | ||||
| 					session_volume.AddressCast<void>()); | ||||
| 				if (FAILED(result)) { | ||||
| 					throw FormatHResultError(result, | ||||
| 								 "Unable to get client " | ||||
| 								 "session volume"); | ||||
| 				} | ||||
|  | ||||
| 				result = session_volume->GetMasterVolume(&volume_level); | ||||
| 				if (FAILED(result)) { | ||||
| 					throw FormatHResultError( | ||||
| 						result, "Unable to get master volume"); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			result = endpoint_volume->GetMasterVolumeLevelScalar( | ||||
| 				&volume_level); | ||||
| 			if (FAILED(result)) { | ||||
| 				throw FormatHResultError( | ||||
| 					result, "Unable to get master volume level"); | ||||
| 			} | ||||
| 		} else { | ||||
| 			ComPtr<ISimpleAudioVolume> session_volume; | ||||
| 			result = wasapi_output_get_client(output)->GetService( | ||||
| 				__uuidof(ISimpleAudioVolume), | ||||
| 				session_volume.AddressCast<void>()); | ||||
| 			if (FAILED(result)) { | ||||
| 				throw FormatHResultError( | ||||
| 					result, "Unable to get client session volume"); | ||||
| 			} | ||||
|  | ||||
| 			result = session_volume->GetMasterVolume(&volume_level); | ||||
| 			if (FAILED(result)) { | ||||
| 				throw FormatHResultError(result, | ||||
| 							 "Unable to get master volume"); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return std::lround(volume_level * 100.0f); | ||||
| 			return std::lround(volume_level * 100.0f); | ||||
| 		}); | ||||
| 		return future.get(); | ||||
| 	} | ||||
|  | ||||
| 	void SetVolume(unsigned volume) override { | ||||
| 		HRESULT result; | ||||
| 		const float volume_level = volume / 100.0f; | ||||
| 		COMWorker::Async([&]() { | ||||
| 			HRESULT result; | ||||
| 			const float volume_level = volume / 100.0f; | ||||
|  | ||||
| 		if (wasapi_is_exclusive(output)) { | ||||
| 			ComPtr<IAudioEndpointVolume> endpoint_volume; | ||||
| 			result = wasapi_output_get_device(output)->Activate( | ||||
| 				__uuidof(IAudioEndpointVolume), CLSCTX_ALL, nullptr, | ||||
| 				endpoint_volume.AddressCast()); | ||||
| 			if (FAILED(result)) { | ||||
| 				throw FormatHResultError( | ||||
| 					result, "Unable to get device endpoint volume"); | ||||
| 			} | ||||
| 			if (wasapi_is_exclusive(output)) { | ||||
| 				ComPtr<IAudioEndpointVolume> endpoint_volume; | ||||
| 				result = wasapi_output_get_device(output)->Activate( | ||||
| 					__uuidof(IAudioEndpointVolume), CLSCTX_ALL, | ||||
| 					nullptr, endpoint_volume.AddressCast()); | ||||
| 				if (FAILED(result)) { | ||||
| 					throw FormatHResultError( | ||||
| 						result, | ||||
| 						"Unable to get device endpoint volume"); | ||||
| 				} | ||||
|  | ||||
| 			result = endpoint_volume->SetMasterVolumeLevelScalar(volume_level, | ||||
| 									     nullptr); | ||||
| 			if (FAILED(result)) { | ||||
| 				throw FormatHResultError( | ||||
| 					result, "Unable to set master volume level"); | ||||
| 			} | ||||
| 		} else { | ||||
| 			ComPtr<ISimpleAudioVolume> session_volume; | ||||
| 			result = wasapi_output_get_client(output)->GetService( | ||||
| 				__uuidof(ISimpleAudioVolume), | ||||
| 				session_volume.AddressCast<void>()); | ||||
| 			if (FAILED(result)) { | ||||
| 				throw FormatHResultError( | ||||
| 					result, "Unable to get client session volume"); | ||||
| 			} | ||||
| 				result = endpoint_volume->SetMasterVolumeLevelScalar( | ||||
| 					volume_level, nullptr); | ||||
| 				if (FAILED(result)) { | ||||
| 					throw FormatHResultError( | ||||
| 						result, | ||||
| 						"Unable to set master volume level"); | ||||
| 				} | ||||
| 			} else { | ||||
| 				ComPtr<ISimpleAudioVolume> session_volume; | ||||
| 				result = wasapi_output_get_client(output)->GetService( | ||||
| 					__uuidof(ISimpleAudioVolume), | ||||
| 					session_volume.AddressCast<void>()); | ||||
| 				if (FAILED(result)) { | ||||
| 					throw FormatHResultError( | ||||
| 						result, | ||||
| 						"Unable to get client session volume"); | ||||
| 				} | ||||
|  | ||||
| 			result = session_volume->SetMasterVolume(volume_level, nullptr); | ||||
| 			if (FAILED(result)) { | ||||
| 				throw FormatHResultError(result, | ||||
| 							 "Unable to set master volume"); | ||||
| 				result = session_volume->SetMasterVolume(volume_level, | ||||
| 									 nullptr); | ||||
| 				if (FAILED(result)) { | ||||
| 					throw FormatHResultError( | ||||
| 						result, "Unable to set master volume"); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		}).get(); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -193,7 +193,7 @@ ParseTimeStamp(const char *s) | ||||
| { | ||||
| 	try { | ||||
| 		// TODO: make this more robust | ||||
| 		return ParseTimePoint(s, "%a, %d %b %Y %T %Z"); | ||||
| 		return ParseTimePoint(s, "%a, %d %b %Y %T"); | ||||
| 	} catch (...) { | ||||
| 		return std::chrono::system_clock::time_point::min(); | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										43
									
								
								src/thread/Future.hxx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/thread/Future.hxx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| /* | ||||
|  * Copyright 2020-2021 The Music Player Daemon Project | ||||
|  * http://www.musicpd.org | ||||
|  * | ||||
|  * This program 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. | ||||
|  * | ||||
|  * This program 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 this program; if not, write to the Free Software Foundation, Inc., | ||||
|  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||
|  */ | ||||
|  | ||||
| #ifndef THREAD_FUTURE_HXX | ||||
| #define THREAD_FUTURE_HXX | ||||
|  | ||||
| #ifdef _WIN32 | ||||
|  | ||||
| #include "WindowsFuture.hxx" | ||||
|  | ||||
| template <typename R> | ||||
| using Future = WinFuture<R>; | ||||
| template <typename R> | ||||
| using Promise = WinPromise<R>; | ||||
|  | ||||
| #else | ||||
|  | ||||
| #include <future> | ||||
|  | ||||
| template <typename R> | ||||
| using Future = std::future<R>; | ||||
| template <typename R> | ||||
| using Promise = std::promise<R>; | ||||
|  | ||||
| #endif | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										394
									
								
								src/thread/WindowsFuture.hxx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										394
									
								
								src/thread/WindowsFuture.hxx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,394 @@ | ||||
| /* | ||||
|  * Copyright 2020-2021 The Music Player Daemon Project | ||||
|  * http://www.musicpd.org | ||||
|  * | ||||
|  * This program 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. | ||||
|  * | ||||
|  * This program 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 this program; if not, write to the Free Software Foundation, Inc., | ||||
|  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||
|  */ | ||||
|  | ||||
| #ifndef THREAD_WINDOWS_FUTURE_HXX | ||||
| #define THREAD_WINDOWS_FUTURE_HXX | ||||
|  | ||||
| #include "CriticalSection.hxx" | ||||
| #include "WindowsCond.hxx" | ||||
| #include <atomic> | ||||
| #include <memory> | ||||
| #include <variant> | ||||
|  | ||||
| enum class WinFutureErrc : int { | ||||
| 	future_already_retrieved = 1, | ||||
| 	promise_already_satisfied, | ||||
| 	no_state, | ||||
| 	broken_promise, | ||||
| }; | ||||
|  | ||||
| enum class WinFutureStatus { ready, timeout, deferred }; | ||||
|  | ||||
| static inline const std::error_category &win_future_category() noexcept; | ||||
| class WinFutureCategory : public std::error_category { | ||||
| public: | ||||
| 	const char *name() const noexcept override { return "win_future"; } | ||||
| 	std::string message(int Errcode) const override { | ||||
| 		using namespace std::literals; | ||||
| 		switch (static_cast<WinFutureErrc>(Errcode)) { | ||||
| 		case WinFutureErrc::broken_promise: | ||||
| 			return "Broken promise"s; | ||||
| 		case WinFutureErrc::future_already_retrieved: | ||||
| 			return "Future already retrieved"s; | ||||
| 		case WinFutureErrc::promise_already_satisfied: | ||||
| 			return "Promise already satisfied"s; | ||||
| 		case WinFutureErrc::no_state: | ||||
| 			return "No associated state"s; | ||||
| 		default: | ||||
| 			return "Unknown error"s; | ||||
| 		} | ||||
| 	} | ||||
| 	std::error_condition default_error_condition(int code) const noexcept override { | ||||
| 		return std::error_condition(code, win_future_category()); | ||||
| 	} | ||||
| }; | ||||
| static inline const std::error_category &win_future_category() noexcept { | ||||
| 	static const WinFutureCategory win_future_category_instance{}; | ||||
| 	return win_future_category_instance; | ||||
| } | ||||
|  | ||||
| class WinFutureError : public std::logic_error { | ||||
| public: | ||||
| 	WinFutureError(WinFutureErrc errcode) | ||||
| 	: WinFutureError( | ||||
| 		  std::error_code(static_cast<int>(errcode), win_future_category())) {} | ||||
|  | ||||
| private: | ||||
| 	explicit WinFutureError(std::error_code errcode) | ||||
| 	: std::logic_error("WinFutureError: " + errcode.message()), code(errcode) {} | ||||
| 	std::error_code code; | ||||
| }; | ||||
|  | ||||
| template <typename T> | ||||
| class WinFutureState { | ||||
| private: | ||||
| 	mutable CriticalSection mutex; | ||||
| 	WindowsCond condition; | ||||
| 	std::variant<std::monostate, T, std::exception_ptr> result; | ||||
| 	bool retrieved = false; | ||||
| 	bool ready = false; | ||||
|  | ||||
| public: | ||||
| 	bool is_ready() const noexcept { | ||||
| 		std::unique_lock<CriticalSection> lock(mutex); | ||||
| 		return ready; | ||||
| 	} | ||||
|  | ||||
| 	bool already_retrieved() const noexcept { | ||||
| 		std::unique_lock<CriticalSection> lock(mutex); | ||||
| 		return retrieved; | ||||
| 	} | ||||
|  | ||||
| 	void wait() { | ||||
| 		std::unique_lock<CriticalSection> lock(mutex); | ||||
| 		condition.wait(lock, [this]() { return ready; }); | ||||
| 	} | ||||
|  | ||||
| 	template <class Rep, class Period> | ||||
| 	WinFutureStatus | ||||
| 	wait_for(const std::chrono::duration<Rep, Period> &timeout_duration) const { | ||||
| 		std::unique_lock<CriticalSection> lock(mutex); | ||||
| 		// deferred function not support yet | ||||
| 		if (condition.wait_for(lock, timeout_duration, | ||||
| 				       [this]() { return ready; })) { | ||||
| 			return WinFutureStatus::ready; | ||||
| 		} | ||||
| 		return WinFutureStatus::timeout; | ||||
| 	} | ||||
|  | ||||
| 	virtual T &get_value() { | ||||
| 		std::unique_lock<CriticalSection> lock(mutex); | ||||
| 		if (retrieved) { | ||||
| 			throw WinFutureError(WinFutureErrc::future_already_retrieved); | ||||
| 		} | ||||
| 		if (auto eptr = std::get_if<std::exception_ptr>(&result)) { | ||||
| 			std::rethrow_exception(*eptr); | ||||
| 		} | ||||
| 		retrieved = true; | ||||
| 		condition.wait(lock, [this]() { return ready; }); | ||||
| 		if (auto eptr = std::get_if<std::exception_ptr>(&result)) { | ||||
| 			std::rethrow_exception(*eptr); | ||||
| 		} | ||||
| 		return *std::get_if<T>(&result); | ||||
| 	} | ||||
|  | ||||
| 	void set_value(const T &value) { | ||||
| 		std::unique_lock<CriticalSection> lock(mutex); | ||||
| 		if (!std::holds_alternative<std::monostate>(&result)) { | ||||
| 			throw WinFutureError(WinFutureErrc::promise_already_satisfied); | ||||
| 		} | ||||
| 		result.template emplace<T>(value); | ||||
| 		ready = true; | ||||
| 		condition.notify_all(); | ||||
| 	} | ||||
|  | ||||
| 	void set_value(T &&value) { | ||||
| 		std::unique_lock<CriticalSection> lock(mutex); | ||||
| 		if (!std::holds_alternative<std::monostate>(result)) { | ||||
| 			throw WinFutureError(WinFutureErrc::promise_already_satisfied); | ||||
| 		} | ||||
| 		result.template emplace<T>(std::move(value)); | ||||
| 		ready = true; | ||||
| 		condition.notify_all(); | ||||
| 	} | ||||
|  | ||||
| 	void set_exception(std::exception_ptr eptr) { | ||||
| 		std::unique_lock<CriticalSection> lock(mutex); | ||||
| 		if (!std::holds_alternative<std::monostate>(result)) { | ||||
| 			throw WinFutureError(WinFutureErrc::promise_already_satisfied); | ||||
| 		} | ||||
| 		result.template emplace<std::exception_ptr>(eptr); | ||||
| 		ready = true; | ||||
| 		condition.notify_all(); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| template <typename T> | ||||
| class WinFutureStateManager { | ||||
| public: | ||||
| 	WinFutureStateManager() = default; | ||||
| 	WinFutureStateManager(std::shared_ptr<WinFutureState<T>> new_state) | ||||
| 	: state(std::move(new_state)) {} | ||||
| 	WinFutureStateManager(const WinFutureStateManager &) = default; | ||||
| 	WinFutureStateManager &operator=(const WinFutureStateManager &) = default; | ||||
| 	WinFutureStateManager(WinFutureStateManager &&) = default; | ||||
| 	WinFutureStateManager &operator=(WinFutureStateManager &&) = default; | ||||
|  | ||||
| 	[[nodiscard]] bool valid() const noexcept { return static_cast<bool>(state); } | ||||
|  | ||||
| 	void wait() const { | ||||
| 		if (!valid()) { | ||||
| 			throw WinFutureError(WinFutureErrc::no_state); | ||||
| 		} | ||||
| 		state->wait(); | ||||
| 	} | ||||
|  | ||||
| 	template <class Rep, class Period> | ||||
| 	WinFutureStatus | ||||
| 	wait_for(const std::chrono::duration<Rep, Period> &timeout_duration) const { | ||||
| 		if (!valid()) { | ||||
| 			throw WinFutureError(WinFutureErrc::no_state); | ||||
| 		} | ||||
| 		return state->wait_for(timeout_duration); | ||||
| 	} | ||||
|  | ||||
| 	T &get_value() const { | ||||
| 		if (!valid()) { | ||||
| 			throw WinFutureError(WinFutureErrc::no_state); | ||||
| 		} | ||||
| 		return state->get_value(); | ||||
| 	} | ||||
|  | ||||
| 	void set_value(const T &value) { | ||||
| 		if (!valid()) { | ||||
| 			throw WinFutureError(WinFutureErrc::no_state); | ||||
| 		} | ||||
| 		state->set_value(value); | ||||
| 	} | ||||
|  | ||||
| 	void set_value(T &&value) { | ||||
| 		if (!valid()) { | ||||
| 			throw WinFutureError(WinFutureErrc::no_state); | ||||
| 		} | ||||
| 		state->set_value(std::move(value)); | ||||
| 	} | ||||
|  | ||||
| 	void set_exception(std::exception_ptr eptr) { | ||||
| 		if (!valid()) { | ||||
| 			throw WinFutureError(WinFutureErrc::no_state); | ||||
| 		} | ||||
| 		state->set_exception(eptr); | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	std::shared_ptr<WinFutureState<T>> state; | ||||
| }; | ||||
|  | ||||
| template <typename T> | ||||
| class WinFuture : public WinFutureStateManager<T> { | ||||
| 	using Base = WinFutureStateManager<T>; | ||||
| 	static_assert(!std::is_array_v<T> && std::is_object_v<T> && | ||||
| 			      std::is_destructible_v<T>, | ||||
| 		      "T in future<T> must meet the Cpp17Destructible requirements " | ||||
| 		      "(N4878 [futures.unique.future]/4)."); | ||||
|  | ||||
| public: | ||||
| 	WinFuture() noexcept = default; | ||||
| 	WinFuture(WinFuture &&) noexcept = default; | ||||
| 	WinFuture &operator=(WinFuture &&) noexcept = default; | ||||
| 	WinFuture(const WinFuture &) noexcept = delete; | ||||
| 	WinFuture &operator=(const WinFuture &) noexcept = delete; | ||||
|  | ||||
| 	WinFuture(const Base &base, std::monostate) : Base(base) {} | ||||
| 	~WinFuture() noexcept = default; | ||||
| 	T get() { | ||||
| 		WinFuture local(std::move(*this)); | ||||
| 		return std::move(local.get_value()); | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	using Base::get_value; | ||||
| 	using Base::set_exception; | ||||
| 	using Base::set_value; | ||||
| }; | ||||
|  | ||||
| template <typename T> | ||||
| class WinFuture<T &> : public WinFutureStateManager<T *> { | ||||
| 	using Base = WinFutureStateManager<T *>; | ||||
|  | ||||
| public: | ||||
| 	WinFuture() noexcept = default; | ||||
| 	WinFuture(WinFuture &&) noexcept = default; | ||||
| 	WinFuture &operator=(WinFuture &&) noexcept = default; | ||||
| 	WinFuture(const WinFuture &) noexcept = delete; | ||||
| 	WinFuture &operator=(const WinFuture &) noexcept = delete; | ||||
|  | ||||
| 	WinFuture(const Base &base, std::monostate) : Base(base) {} | ||||
| 	~WinFuture() noexcept = default; | ||||
| 	T &get() { | ||||
| 		WinFuture local(std::move(*this)); | ||||
| 		return *local.get_value(); | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	using Base::get_value; | ||||
| 	using Base::set_exception; | ||||
| 	using Base::set_value; | ||||
| }; | ||||
|  | ||||
| template <> | ||||
| class WinFuture<void> : public WinFutureStateManager<int> { | ||||
| 	using Base = WinFutureStateManager<int>; | ||||
|  | ||||
| public: | ||||
| 	WinFuture() noexcept = default; | ||||
| 	WinFuture(WinFuture &&) noexcept = default; | ||||
| 	WinFuture &operator=(WinFuture &&) noexcept = default; | ||||
| 	WinFuture(const WinFuture &) noexcept = delete; | ||||
| 	WinFuture &operator=(const WinFuture &) noexcept = delete; | ||||
|  | ||||
| 	WinFuture(const Base &base, std::monostate) : Base(base) {} | ||||
| 	~WinFuture() noexcept = default; | ||||
| 	void get() { | ||||
| 		WinFuture local(std::move(*this)); | ||||
| 		local.get_value(); | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	using Base::get_value; | ||||
| 	using Base::set_exception; | ||||
| 	using Base::set_value; | ||||
| }; | ||||
|  | ||||
| template <typename T> | ||||
| class WinPromiseBase { | ||||
| public: | ||||
| 	WinPromiseBase(std::shared_ptr<WinFutureState<T>> new_state) | ||||
| 	: state(std::move(new_state)) {} | ||||
| 	WinPromiseBase(WinPromiseBase &&) = default; | ||||
| 	WinPromiseBase &operator=(WinPromiseBase &&) = default; | ||||
| 	WinPromiseBase(const WinPromiseBase &) = delete; | ||||
| 	WinPromiseBase &operator=(const WinPromiseBase &) = delete; | ||||
|  | ||||
| 	WinFutureStateManager<T> &get_state_for_set() { | ||||
| 		if (!state.valid()) { | ||||
| 			throw WinFutureError(WinFutureErrc::no_state); | ||||
| 		} | ||||
| 		return state; | ||||
| 	} | ||||
|  | ||||
| 	WinFutureStateManager<T> &get_state_for_future() { | ||||
| 		if (!state.valid()) { | ||||
| 			throw WinFutureError(WinFutureErrc::no_state); | ||||
| 		} | ||||
| 		if (future_retrieved) { | ||||
| 			throw WinFutureError(WinFutureErrc::future_already_retrieved); | ||||
| 		} | ||||
| 		future_retrieved = true; | ||||
| 		return state; | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	WinFutureStateManager<T> state; | ||||
| 	bool future_retrieved = false; | ||||
| }; | ||||
|  | ||||
| template <typename T> | ||||
| class WinPromise { | ||||
| public: | ||||
| 	WinPromise() : base(std::make_shared<WinFutureState<T>>()) {} | ||||
| 	WinPromise(WinPromise &&) = default; | ||||
| 	WinPromise(const WinPromise &) = delete; | ||||
| 	~WinPromise() noexcept {} | ||||
| 	[[nodiscard]] WinFuture<T> get_future() { | ||||
| 		return WinFuture<T>(base.get_state_for_future(), std::monostate()); | ||||
| 	} | ||||
| 	void set_value(const T &value) { base.get_state_for_set().set_value(value); } | ||||
| 	void set_value(T &&value) { | ||||
| 		base.get_state_for_set().set_value(std::forward<T>(value)); | ||||
| 	} | ||||
| 	void set_exception(std::exception_ptr eptr) { | ||||
| 		base.get_state_for_set().set_exception(eptr); | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	WinPromiseBase<T> base; | ||||
| }; | ||||
|  | ||||
| template <typename T> | ||||
| class WinPromise<T &> { | ||||
| public: | ||||
| 	WinPromise() : base(std::make_shared<WinFutureState<T *>>()) {} | ||||
| 	WinPromise(WinPromise &&) = default; | ||||
| 	WinPromise(const WinPromise &) = delete; | ||||
| 	~WinPromise() noexcept {} | ||||
| 	[[nodiscard]] WinFuture<T &> get_future() { | ||||
| 		return WinFuture<T>(base.get_state_for_future(), std::monostate()); | ||||
| 	} | ||||
| 	void set_value(T &value) { | ||||
| 		base.get_state_for_set().set_value(std::addressof(value)); | ||||
| 	} | ||||
| 	void set_exception(std::exception_ptr eptr) { | ||||
| 		base.get_state_for_set().set_exception(eptr); | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	WinPromiseBase<T *> base; | ||||
| }; | ||||
|  | ||||
| template <> | ||||
| class WinPromise<void> { | ||||
| public: | ||||
| 	WinPromise() : base(std::make_shared<WinFutureState<int>>()) {} | ||||
| 	WinPromise(WinPromise &&) = default; | ||||
| 	WinPromise(const WinPromise &) = delete; | ||||
| 	~WinPromise() noexcept {} | ||||
| 	[[nodiscard]] WinFuture<void> get_future() { | ||||
| 		return WinFuture<void>(base.get_state_for_future(), std::monostate()); | ||||
| 	} | ||||
| 	void set_value() { base.get_state_for_set().set_value(0); } | ||||
| 	void set_exception(std::exception_ptr eptr) { | ||||
| 		base.get_state_for_set().set_exception(eptr); | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	WinPromiseBase<int> base; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
| @@ -202,7 +202,7 @@ ParseISO8601(const char *s) | ||||
| 	} | ||||
|  | ||||
| 	/* parse the date */ | ||||
| 	const char *end = strptime(s, "%F", &tm); | ||||
| 	const char *end = strptime(s, "%Y-%m-%d", &tm); | ||||
| 	if (end == nullptr) { | ||||
| 		/* try without field separators */ | ||||
| 		end = strptime(s, "%Y%m%d", &tm); | ||||
|   | ||||
| @@ -29,9 +29,19 @@ | ||||
| class COM { | ||||
| public: | ||||
| 	COM() { | ||||
| 		HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); | ||||
| 		if (FAILED(result)) { | ||||
| 			throw FormatHResultError(result, "Unable to initialize COM"); | ||||
| 		if (HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); | ||||
| 		    FAILED(result)) { | ||||
| 			throw FormatHResultError( | ||||
| 				result, | ||||
| 				"Unable to initialize COM with COINIT_MULTITHREADED"); | ||||
| 		} | ||||
| 	} | ||||
| 	COM(bool) { | ||||
| 		if (HRESULT result = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); | ||||
| 		    FAILED(result)) { | ||||
| 			throw FormatHResultError( | ||||
| 				result, | ||||
| 				"Unable to initialize COM with COINIT_APARTMENTTHREADED"); | ||||
| 		} | ||||
| 	} | ||||
| 	~COM() noexcept { CoUninitialize(); } | ||||
|   | ||||
							
								
								
									
										49
									
								
								src/win32/ComWorker.cxx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/win32/ComWorker.cxx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| /* | ||||
|  * Copyright 2020 The Music Player Daemon Project | ||||
|  * http://www.musicpd.org | ||||
|  * | ||||
|  * This program 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. | ||||
|  * | ||||
|  * This program 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 this program; if not, write to the Free Software Foundation, Inc., | ||||
|  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||
|  */ | ||||
| #include "ComWorker.hxx" | ||||
| #include "Log.hxx" | ||||
| #include "thread/Name.hxx" | ||||
| #include "util/Domain.hxx" | ||||
| #include "win32/Com.hxx" | ||||
|  | ||||
| namespace { | ||||
| static constexpr Domain com_worker_domain("com_worker"); | ||||
| } | ||||
|  | ||||
| Mutex COMWorker::mutex; | ||||
| unsigned int COMWorker::reference_count = 0; | ||||
| std::optional<COMWorker::COMWorkerThread> COMWorker::thread; | ||||
|  | ||||
| void COMWorker::COMWorkerThread::Work() noexcept { | ||||
| 	FormatDebug(com_worker_domain, "Working thread started"); | ||||
| 	SetThreadName("COM Worker"); | ||||
| 	COM com{true}; | ||||
| 	while (true) { | ||||
| 		if (!running_flag.test_and_set()) { | ||||
| 			FormatDebug(com_worker_domain, "Working thread ended"); | ||||
| 			return; | ||||
| 		} | ||||
| 		while (!spsc_buffer.empty()) { | ||||
| 			std::function<void()> function; | ||||
| 			spsc_buffer.pop(function); | ||||
| 			function(); | ||||
| 		} | ||||
| 		event.Wait(200); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										110
									
								
								src/win32/ComWorker.hxx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								src/win32/ComWorker.hxx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| /* | ||||
|  * Copyright 2020 The Music Player Daemon Project | ||||
|  * http://www.musicpd.org | ||||
|  * | ||||
|  * This program 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. | ||||
|  * | ||||
|  * This program 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 this program; if not, write to the Free Software Foundation, Inc., | ||||
|  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||
|  */ | ||||
|  | ||||
| #ifndef MPD_WIN32_COM_WORKER_HXX | ||||
| #define MPD_WIN32_COM_WORKER_HXX | ||||
|  | ||||
| #include <boost/lockfree/spsc_queue.hpp> | ||||
| #include <condition_variable> | ||||
| #include <mutex> | ||||
| #include <optional> | ||||
|  | ||||
| #include "thread/Future.hxx" | ||||
| #include "thread/Mutex.hxx" | ||||
| #include "thread/Thread.hxx" | ||||
| #include "win32/WinEvent.hxx" | ||||
| #include <objbase.h> | ||||
| #include <windows.h> | ||||
|  | ||||
| // Worker thread for all COM operation | ||||
| class COMWorker { | ||||
| private: | ||||
| 	class COMWorkerThread : public Thread { | ||||
| 	public: | ||||
| 		COMWorkerThread() : Thread{BIND_THIS_METHOD(Work)} {} | ||||
|  | ||||
| 	private: | ||||
| 		friend class COMWorker; | ||||
| 		void Work() noexcept; | ||||
| 		void Finish() noexcept { | ||||
| 			running_flag.clear(); | ||||
| 			event.Set(); | ||||
| 		} | ||||
| 		void Push(const std::function<void()> &function) { | ||||
| 			spsc_buffer.push(function); | ||||
| 			event.Set(); | ||||
| 		} | ||||
|  | ||||
| 		boost::lockfree::spsc_queue<std::function<void()>> spsc_buffer{32}; | ||||
| 		std::atomic_flag running_flag = true; | ||||
| 		WinEvent event{}; | ||||
| 	}; | ||||
|  | ||||
| public: | ||||
| 	static void Aquire() { | ||||
| 		std::unique_lock locker(mutex); | ||||
| 		if (reference_count == 0) { | ||||
| 			thread.emplace(); | ||||
| 			thread->Start(); | ||||
| 		} | ||||
| 		++reference_count; | ||||
| 	} | ||||
| 	static void Release() noexcept { | ||||
| 		std::unique_lock locker(mutex); | ||||
| 		--reference_count; | ||||
| 		if (reference_count == 0) { | ||||
| 			thread->Finish(); | ||||
| 			thread->Join(); | ||||
| 			thread.reset(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	template <typename Function, typename... Args> | ||||
| 	static auto Async(Function &&function, Args &&...args) { | ||||
| 		using R = std::invoke_result_t<std::decay_t<Function>, | ||||
| 					       std::decay_t<Args>...>; | ||||
| 		auto promise = std::make_shared<Promise<R>>(); | ||||
| 		auto future = promise->get_future(); | ||||
| 		thread->Push([function = std::forward<Function>(function), | ||||
| 			      args = std::make_tuple(std::forward<Args>(args)...), | ||||
| 			      promise = std::move(promise)]() mutable { | ||||
| 			try { | ||||
| 				if constexpr (std::is_void_v<R>) { | ||||
| 					std::apply(std::forward<Function>(function), | ||||
| 						   std::move(args)); | ||||
| 					promise->set_value(); | ||||
| 				} else { | ||||
| 					promise->set_value(std::apply( | ||||
| 						std::forward<Function>(function), | ||||
| 						std::move(args))); | ||||
| 				} | ||||
| 			} catch (...) { | ||||
| 				promise->set_exception(std::current_exception()); | ||||
| 			} | ||||
| 		}); | ||||
| 		return future; | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	static Mutex mutex; | ||||
| 	static unsigned int reference_count; | ||||
| 	static std::optional<COMWorkerThread> thread; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
| @@ -18,9 +18,6 @@ | ||||
|  */ | ||||
|  | ||||
| #include "Main.hxx" | ||||
|  | ||||
| #ifdef _WIN32 | ||||
|  | ||||
| #include "util/Compiler.h" | ||||
| #include "Instance.hxx" | ||||
| #include "system/FatalError.hxx" | ||||
| @@ -155,5 +152,3 @@ void win32_app_stopping() | ||||
| 	else | ||||
| 		running.store(false); | ||||
| } | ||||
|  | ||||
| #endif | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Max Kellermann
					Max Kellermann