diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b08c3ba20..546fb017e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -218,7 +218,6 @@ jobs: name: msvc-${{ matrix.arch }} runs-on: windows-2019 env: - qt-version: '5.15.2' CCACHE_MAXSIZE: 0 CCACHE_NOCOMPRESS: 1 steps: @@ -246,22 +245,21 @@ jobs: path: ~\AppData\Local\ccache - name: Install tools run: choco install ccache - - name: Install 64-bit Qt - if: matrix.arch == 'x64' + - name: Install Qt uses: jurplel/install-qt-action@b3ea5275e37b734d027040e2c7fe7a10ea2ef946 with: - version: ${{ env.qt-version }} - arch: win64_msvc2019_64 + version: '5.15.2' + arch: |- + ${{ + fromJSON(' + { + "x86": "win32_msvc2019", + "x64": "win64_msvc2019_64" + } + ')[matrix.arch] + }} archives: qtbase qtsvg qttools cache: true - - name: Install 32-bit Qt - uses: jurplel/install-qt-action@b3ea5275e37b734d027040e2c7fe7a10ea2ef946 - with: - version: ${{ env.qt-version }} - arch: win32_msvc2019 - archives: qtbase qtsvg qttools - cache: true - set-env: ${{ matrix.arch == 'x86' }} - name: Set up build environment uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 with: diff --git a/include/RemotePluginBase.h b/include/RemotePluginBase.h index 357be1bea..5214b6f92 100644 --- a/include/RemotePluginBase.h +++ b/include/RemotePluginBase.h @@ -41,10 +41,6 @@ #ifdef LMMS_HAVE_PROCESS_H #include #endif - -#include -#include -#include #else // !(LMMS_HAVE_SYS_IPC_H && LMMS_HAVE_SEMAPHORE_H) #ifdef LMMS_HAVE_UNISTD_H #include @@ -75,6 +71,7 @@ #include #include #include +#include #ifndef SYNC_WITH_SHM_FIFO #include @@ -85,6 +82,7 @@ #ifdef SYNC_WITH_SHM_FIFO #include "SharedMemory.h" +#include "SystemSemaphore.h" #endif namespace lmms @@ -120,12 +118,11 @@ class shmFifo } ; public: +#ifndef BUILD_REMOTE_PLUGIN_CLIENT // constructor for master-side shmFifo() : m_invalid( false ), m_master( true ), - m_dataSem( QString() ), - m_messageSem( QString() ), m_lockDepth( 0 ) { m_data.create(QUuid::createUuid().toString().toStdString()); @@ -133,26 +130,21 @@ public: static int k = 0; m_data->dataSem.semKey = ( getpid()<<10 ) + ++k; m_data->messageSem.semKey = ( getpid()<<10 ) + ++k; - m_dataSem.setKey( QString::number( m_data->dataSem.semKey ), - 1, QSystemSemaphore::Create ); - m_messageSem.setKey( QString::number( - m_data->messageSem.semKey ), - 0, QSystemSemaphore::Create ); + m_dataSem = SystemSemaphore{std::to_string(m_data->dataSem.semKey), 1u}; + m_messageSem = SystemSemaphore{std::to_string(m_data->messageSem.semKey), 0u}; } +#endif // constructor for remote-/client-side - use _shm_key for making up // the connection to master shmFifo(const std::string& shmKey) : m_invalid( false ), m_master( false ), - m_dataSem( QString() ), - m_messageSem( QString() ), m_lockDepth( 0 ) { m_data.attach(shmKey); - m_dataSem.setKey( QString::number( m_data->dataSem.semKey ) ); - m_messageSem.setKey( QString::number( - m_data->messageSem.semKey ) ); + m_dataSem = SystemSemaphore{std::to_string(m_data->dataSem.semKey)}; + m_messageSem = SystemSemaphore{std::to_string(m_data->messageSem.semKey)}; } inline bool isInvalid() const @@ -336,11 +328,10 @@ private: volatile bool m_invalid; bool m_master; SharedMemory m_data; - QSystemSemaphore m_dataSem; - QSystemSemaphore m_messageSem; + SystemSemaphore m_dataSem; + SystemSemaphore m_messageSem; std::atomic_int m_lockDepth; - -} ; +}; #endif // SYNC_WITH_SHM_FIFO diff --git a/include/SystemSemaphore.h b/include/SystemSemaphore.h new file mode 100644 index 000000000..931c472bb --- /dev/null +++ b/include/SystemSemaphore.h @@ -0,0 +1,61 @@ +/* + * SystemSemaphore.h + * + * Copyright (c) 2024 Dominic Clark + * + * This file is part of LMMS - https://lmms.io + * + * 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 (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + */ + +#ifndef LMMS_SYSTEM_SEMAPHORE_H +#define LMMS_SYSTEM_SEMAPHORE_H + +#include +#include + +namespace lmms { + +namespace detail { + +class SystemSemaphoreImpl; + +} // namespace detail + +class SystemSemaphore +{ +public: + SystemSemaphore() noexcept; + SystemSemaphore(std::string key, unsigned int value); + explicit SystemSemaphore(std::string key); + ~SystemSemaphore(); + + SystemSemaphore(SystemSemaphore&& other) noexcept; + auto operator=(SystemSemaphore&& other) noexcept -> SystemSemaphore&; + + auto acquire() noexcept -> bool; + auto release() noexcept -> bool; + + auto key() const noexcept -> const std::string& { return m_key; } + +private: + std::string m_key; + std::unique_ptr m_impl; +}; + +} // namespace lmms + +#endif // LMMS_SYSTEM_SEMAPHORE_H diff --git a/plugins/VstBase/RemoteVstPlugin/CMakeLists.txt b/plugins/VstBase/RemoteVstPlugin/CMakeLists.txt index 3356ae596..c75b95ab7 100644 --- a/plugins/VstBase/RemoteVstPlugin/CMakeLists.txt +++ b/plugins/VstBase/RemoteVstPlugin/CMakeLists.txt @@ -22,6 +22,9 @@ FOREACH( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} ) SET("CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") ENDFOREACH() +# Import of windows.h breaks min()/max() +add_definitions(-DNOMINMAX) + ADD_SUBDIRECTORY("${LMMS_SOURCE_DIR}/src/common" common) if(NOT IS_WIN) @@ -64,12 +67,6 @@ if(MSVC) ) endif() - -if(WIN32) - find_package(Qt5Core REQUIRED) - target_link_libraries(${EXE_NAME} Qt5::Core) -endif() - if(IS_MINGW) SET(CMAKE_REQUIRED_FLAGS "-std=c++17") @@ -96,7 +93,9 @@ if(LMMS_BUILD_WIN32) set(NOOP_COMMAND "${CMAKE_COMMAND}" "-E" "echo") endif() if(STRIP) - set(STRIP_COMMAND "$,${NOOP_COMMAND},${STRIP}>") + # TODO CMake 3.19: Now that CONFIG generator expressions support testing for + # multiple configurations, combine the OR into a single CONFIG expression. + set(STRIP_COMMAND "$,$>,${NOOP_COMMAND},${STRIP}>") else() set(STRIP_COMMAND "${NOOP_COMMAND}") endif() diff --git a/plugins/VstBase/RemoteVstPlugin32.cmake b/plugins/VstBase/RemoteVstPlugin32.cmake index 0f98d34e0..5afb7db81 100644 --- a/plugins/VstBase/RemoteVstPlugin32.cmake +++ b/plugins/VstBase/RemoteVstPlugin32.cmake @@ -17,23 +17,6 @@ ENDMACRO() IF(LMMS_BUILD_WIN32 AND NOT LMMS_BUILD_WIN64) ADD_SUBDIRECTORY(RemoteVstPlugin) ELSEIF(LMMS_BUILD_WIN64 AND MSVC) - IF(NOT QT_32_PREFIX) - SET(LMMS_MSVC_YEAR_FOR_QT ${LMMS_MSVC_YEAR}) - - if(LMMS_MSVC_YEAR_FOR_QT EQUAL 2019 AND Qt5_VERSION VERSION_LESS "5.15") - SET(LMMS_MSVC_YEAR_FOR_QT 2017) # Qt only provides binaries for MSVC 2017, but 2019 is binary compatible - endif() - - GET_FILENAME_COMPONENT(QT_BIN_DIR ${QT_QMAKE_EXECUTABLE} DIRECTORY) - SET(QT_32_PREFIX "${QT_BIN_DIR}/../../msvc${LMMS_MSVC_YEAR_FOR_QT}") - ENDIF() - - #TODO: qt5 installed using vcpkg: I don't know how to detect if the user built the x86 version of qt5 from here. At least not cleanly. - #So for the moment, we'll allow the built. - IF(NOT (IS_DIRECTORY ${QT_32_PREFIX} AND EXISTS ${QT_32_PREFIX}/bin/qmake.exe)) - MESSAGE(WARNING "No Qt 32 bit installation found at ${QT_32_PREFIX}. If you're using VCPKG you can ignore this message if you've built x86-windows version of qt5") - ENDIF() - ExternalProject_Add(RemoteVstPlugin32 "${EXTERNALPROJECT_ARGS}" CMAKE_GENERATOR "${LMMS_MSVC_GENERATOR}" @@ -42,7 +25,6 @@ ELSEIF(LMMS_BUILD_WIN64 AND MSVC) CMAKE_ARGS "${EXTERNALPROJECT_CMAKE_ARGS}" "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" - "-DCMAKE_PREFIX_PATH=${QT_32_PREFIX}" ) INSTALL_EXTERNAL_PROJECT(RemoteVstPlugin32) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 877bea3fc..9f349f091 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,6 +1,7 @@ set(COMMON_SRCS RemotePluginBase.cpp SharedMemory.cpp + SystemSemaphore.cpp ) foreach(SRC ${COMMON_SRCS}) diff --git a/src/common/SharedMemory.cpp b/src/common/SharedMemory.cpp index 005e726ed..470016530 100644 --- a/src/common/SharedMemory.cpp +++ b/src/common/SharedMemory.cpp @@ -23,45 +23,44 @@ #include "SharedMemory.h" +#include +#include + #include "lmmsconfig.h" +#include "RaiiHelpers.h" #ifdef LMMS_HAVE_UNISTD_H # include #endif -#if _POSIX_SHARED_MEMORY_OBJECTS > 0 -# include -# +#if _POSIX_SHARED_MEMORY_OBJECTS > 0 || defined(LMMS_BUILD_APPLE) # include # include # include -# -# include "RaiiHelpers.h" +#elif defined(LMMS_BUILD_WIN32) +# include #else -# include -# -# include -# include +# error "No shared memory implementation available" #endif - namespace lmms::detail { -#if _POSIX_SHARED_MEMORY_OBJECTS > 0 - +#if _POSIX_SHARED_MEMORY_OBJECTS > 0 || defined(LMMS_BUILD_APPLE) namespace { +[[noreturn]] void throwSystemError(const char* message) +{ + throw std::system_error{errno, std::generic_category(), message}; +} + template int retryWhileInterrupted(F&& function) noexcept(std::is_nothrow_invocable_v) { - int result; - do - { - result = function(); + while (true) { + const auto result = function(); + if (result != -1 || errno != EINTR) { return result; } } - while (result == -1 && errno == EINTR); - return result; } void deleteFileDescriptor(int fd) noexcept { retryWhileInterrupted([fd]() noexcept { return close(fd); }); } @@ -82,22 +81,15 @@ public: const auto fd = FileDescriptor{ retryWhileInterrupted([&]() noexcept { return shm_open(m_key.c_str(), openFlags, 0); }) }; - if (!fd) - { - throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: shm_open() failed"}; - } + if (!fd) { throwSystemError("SharedMemoryImpl: shm_open() failed"); } + auto stat = (struct stat){}; - if (fstat(fd.get(), &stat) == -1) - { - throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: fstat() failed"}; - } + if (fstat(fd.get(), &stat) == -1) { throwSystemError("SharedMemoryImpl: fstat() failed"); } m_size = stat.st_size; + const auto mappingProtection = readOnly ? PROT_READ : PROT_READ | PROT_WRITE; m_mapping = mmap(nullptr, m_size, mappingProtection, MAP_SHARED, fd.get(), 0); - if (m_mapping == MAP_FAILED) - { - throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: mmap() failed"}; - } + if (m_mapping == MAP_FAILED) { throwSystemError("SharedMemoryImpl: mmap() failed"); } } SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly) : @@ -107,21 +99,16 @@ public: const auto fd = FileDescriptor{ retryWhileInterrupted([&]() noexcept { return shm_open(m_key.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600); }) }; - if (fd.get() == -1) - { - throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: shm_open() failed"}; - } + if (fd.get() == -1) { throwSystemError("SharedMemoryImpl: shm_open() failed"); } m_object.reset(m_key.c_str()); - if (retryWhileInterrupted([&]() noexcept { return ftruncate(fd.get(), m_size); }) == -1) - { - throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: ftruncate() failed"}; + + if (retryWhileInterrupted([&]() noexcept { return ftruncate(fd.get(), m_size); }) == -1) { + throwSystemError("SharedMemoryImpl: ftruncate() failed"); } + const auto mappingProtection = readOnly ? PROT_READ : PROT_READ | PROT_WRITE; m_mapping = mmap(nullptr, m_size, mappingProtection, MAP_SHARED, fd.get(), 0); - if (m_mapping == MAP_FAILED) - { - throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: mmap() failed"}; - } + if (m_mapping == MAP_FAILED) { throwSystemError("SharedMemoryImpl: mmap() failed"); } } SharedMemoryImpl(const SharedMemoryImpl&) = delete; @@ -132,7 +119,7 @@ public: munmap(m_mapping, m_size); } - void* get() { return m_mapping; } + auto get() const noexcept -> void* { return m_mapping; } private: std::string m_key; @@ -141,38 +128,67 @@ private: ShmObject m_object; }; -#else +#elif defined(LMMS_BUILD_WIN32) + +namespace { + +auto sizeToHighAndLow(std::size_t size) -> std::pair +{ + if constexpr(sizeof(std::size_t) <= sizeof(DWORD)) { + return {0, size}; + } else { + return {static_cast(size >> 32), static_cast(size)}; + } +} + +[[noreturn]] void throwLastError(const char* message) +{ + throw std::system_error{static_cast(GetLastError()), std::system_category(), message}; +} + +using UniqueHandle = UniqueNullableResource; +using FileView = UniqueNullableResource; + +} // namespace class SharedMemoryImpl { public: - SharedMemoryImpl(const std::string& key, bool readOnly) : - m_shm{QString::fromStdString(key)} + SharedMemoryImpl(const std::string& key, bool readOnly) { - const auto mode = readOnly ? QSharedMemory::ReadOnly : QSharedMemory::ReadWrite; - if (!m_shm.attach(mode)) - { - throw std::runtime_error{"SharedMemoryImpl: QSharedMemory::attach() failed"}; - } + const auto access = readOnly ? FILE_MAP_READ : FILE_MAP_WRITE; + m_mapping.reset(OpenFileMappingA(access, false, key.c_str())); + if (!m_mapping) { throwLastError("SharedMemoryImpl: OpenFileMappingA() failed"); } + + m_view.reset(MapViewOfFile(m_mapping.get(), access, 0, 0, 0)); + if (!m_view) { throwLastError("SharedMemoryImpl: MapViewOfFile() failed"); } } - SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly) : - m_shm{QString::fromStdString(key)} + SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly) { - const auto mode = readOnly ? QSharedMemory::ReadOnly : QSharedMemory::ReadWrite; - if (!m_shm.create(size, mode)) - { - throw std::runtime_error{"SharedMemoryImpl: QSharedMemory::create() failed"}; + const auto [high, low] = sizeToHighAndLow(size); + m_mapping.reset(CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, high, low, key.c_str())); + // This constructor is supposed to create a new shared memory object, + // but passing the name of an existing object causes CreateFileMappingA + // to succeed and return a handle to that object. Thus we have to check + // GetLastError() too. + if (!m_mapping || GetLastError() == ERROR_ALREADY_EXISTS) { + throwLastError("SharedMemoryImpl: CreateFileMappingA() failed"); } + + const auto access = readOnly ? FILE_MAP_READ : FILE_MAP_WRITE; + m_view.reset(MapViewOfFile(m_mapping.get(), access, 0, 0, 0)); + if (!m_view) { throwLastError("SharedMemoryImpl: MapViewOfFile() failed"); } } SharedMemoryImpl(const SharedMemoryImpl&) = delete; - SharedMemoryImpl& operator=(const SharedMemoryImpl&) = delete; + auto operator=(const SharedMemoryImpl&) -> SharedMemoryImpl& = delete; - void* get() { return m_shm.data(); } + auto get() const noexcept -> void* { return m_view.get(); } private: - QSharedMemory m_shm; + UniqueHandle m_mapping; + FileView m_view; }; #endif @@ -196,7 +212,7 @@ SharedMemoryData::~SharedMemoryData() = default; SharedMemoryData::SharedMemoryData(SharedMemoryData&& other) noexcept : m_key{std::move(other.m_key)}, m_impl{std::move(other.m_impl)}, - m_ptr{other.m_ptr} + m_ptr{std::exchange(other.m_ptr, nullptr)} { } } // namespace lmms::detail diff --git a/src/common/SystemSemaphore.cpp b/src/common/SystemSemaphore.cpp new file mode 100644 index 000000000..02c9a2888 --- /dev/null +++ b/src/common/SystemSemaphore.cpp @@ -0,0 +1,181 @@ +/* + * SystemSemaphore.cpp + * + * Copyright (c) 2024 Dominic Clark + * + * This file is part of LMMS - https://lmms.io + * + * 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 (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + */ + +#include "SystemSemaphore.h" + +#include +#include +#include +#include + +#include "lmmsconfig.h" +#include "RaiiHelpers.h" + +#ifdef LMMS_HAVE_UNISTD_H +# include +#endif + +#if (_POSIX_SEMAPHORES > 0 && !defined(__MINGW32__)) || defined(LMMS_BUILD_APPLE) +# include +# include +#elif defined(LMMS_BUILD_WIN32) +# include +#else +# error "No system semaphore implementation available" +#endif + +namespace lmms { + +namespace detail { + +#if (_POSIX_SEMAPHORES > 0 && !defined(__MINGW32__)) || defined(LMMS_BUILD_APPLE) + +namespace { + +[[noreturn]] void throwSystemError(const char* message) +{ + throw std::system_error{errno, std::generic_category(), message}; +} + +template +auto retryWhileInterrupted(F&& function, std::invoke_result_t error = -1) + noexcept(std::is_nothrow_invocable_v) -> auto +{ + while (true) + { + const auto result = function(); + if (result != error || errno != EINTR) { return result; } + } +} + +using UniqueSemaphore = UniqueNullableResource; + +} // namespace + +class SystemSemaphoreImpl +{ +public: + SystemSemaphoreImpl(const std::string& key, unsigned int value) : + m_key{"/" + key}, + m_sem{retryWhileInterrupted([&]() noexcept { + return sem_open(m_key.c_str(), O_CREAT | O_EXCL, 0600, value); + }, SEM_FAILED)} + { + if (m_sem == SEM_FAILED) { throwSystemError("SystemSemaphoreImpl: sem_open() failed"); } + m_ownedSemaphore.reset(m_key.c_str()); + } + + explicit SystemSemaphoreImpl(const std::string& key) : + m_key{"/" + key}, + m_sem{retryWhileInterrupted([&]() noexcept { + return sem_open(m_key.c_str(), 0); + }, SEM_FAILED)} + { + if (m_sem == SEM_FAILED) { throwSystemError("SystemSemaphoreImpl: sem_open() failed"); } + } + + ~SystemSemaphoreImpl() + { + // We can't use `UniqueNullableResource` for `m_sem`, as the null value + // (`SEM_FAILED`) is not a constant expression on macOS (it's defined as + // `(sem_t*) -1`), so can't be used as a template parameter. + sem_close(m_sem); + } + + auto acquire() noexcept -> bool + { + return retryWhileInterrupted([&]() noexcept { + return sem_wait(m_sem); + }) == 0; + } + + auto release() noexcept -> bool { return sem_post(m_sem) == 0; } + +private: + std::string m_key; + sem_t* m_sem; + UniqueSemaphore m_ownedSemaphore; +}; + +#elif defined(LMMS_BUILD_WIN32) + +namespace { + +[[noreturn]] void throwSystemError(const char* message) +{ + throw std::system_error{static_cast(GetLastError()), std::system_category(), message}; +} + +using UniqueHandle = UniqueNullableResource; + +} // namespace + +class SystemSemaphoreImpl +{ +public: + SystemSemaphoreImpl(const std::string& key, unsigned int value) : + m_sem{CreateSemaphoreA(nullptr, value, std::numeric_limits::max(), key.c_str())} + { + if (!m_sem || GetLastError() == ERROR_ALREADY_EXISTS) { + throwSystemError("SystemSemaphoreImpl: CreateSemaphoreA failed"); + } + } + + explicit SystemSemaphoreImpl(const std::string& key) : + m_sem{OpenSemaphoreA(SEMAPHORE_MODIFY_STATE | SYNCHRONIZE, false, key.c_str())} + { + if (!m_sem) { throwSystemError("SystemSemaphoreImpl: OpenSemaphoreA failed"); } + } + + auto acquire() noexcept -> bool { return WaitForSingleObject(m_sem.get(), INFINITE) == WAIT_OBJECT_0; } + auto release() noexcept -> bool { return ReleaseSemaphore(m_sem.get(), 1, nullptr); } + +private: + UniqueHandle m_sem; +}; + +#endif + +} // namespace detail + +SystemSemaphore::SystemSemaphore() noexcept = default; + +SystemSemaphore::SystemSemaphore(std::string key, unsigned int value) : + m_key{std::move(key)}, + m_impl{std::make_unique(m_key, value)} +{} + +SystemSemaphore::SystemSemaphore(std::string key) : + m_key{std::move(key)}, + m_impl{std::make_unique(m_key)} +{} + +SystemSemaphore::~SystemSemaphore() = default; + +SystemSemaphore::SystemSemaphore(SystemSemaphore&& other) noexcept = default; +auto SystemSemaphore::operator=(SystemSemaphore&& other) noexcept -> SystemSemaphore& = default; + +auto SystemSemaphore::acquire() noexcept -> bool { return m_impl->acquire(); } +auto SystemSemaphore::release() noexcept -> bool { return m_impl->release(); } + +} // namespace lmms