From bf73356d393e761f749b26a1eb4ea97456398400 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Sat, 8 Jun 2024 19:37:15 +0200 Subject: [PATCH] utils: Initial Commit --- .clang-format | 65 ++++++ .github/workflows/arch.yml | 57 +++++ .gitignore | 4 + CMakeLists.txt | 63 ++++++ hyprutils.pc.in | 10 + include/hyprutils/memory/SharedPtr.hpp | 302 +++++++++++++++++++++++++ include/hyprutils/memory/WeakPtr.hpp | 192 ++++++++++++++++ include/hyprutils/signal/Listener.hpp | 44 ++++ include/hyprutils/signal/Signal.hpp | 28 +++ include/hyprutils/string/String.hpp | 10 + src/signal/Listener.cpp | 22 ++ src/signal/Signal.cpp | 58 +++++ src/string/String.cpp | 58 +++++ tests/memory.cpp | 29 +++ tests/shared.hpp | 20 ++ tests/signal.cpp | 30 +++ tests/string.cpp | 33 +++ 17 files changed, 1025 insertions(+) create mode 100644 .clang-format create mode 100644 .github/workflows/arch.yml create mode 100644 CMakeLists.txt create mode 100644 hyprutils.pc.in create mode 100644 include/hyprutils/memory/SharedPtr.hpp create mode 100644 include/hyprutils/memory/WeakPtr.hpp create mode 100644 include/hyprutils/signal/Listener.hpp create mode 100644 include/hyprutils/signal/Signal.hpp create mode 100644 include/hyprutils/string/String.hpp create mode 100644 src/signal/Listener.cpp create mode 100644 src/signal/Signal.cpp create mode 100644 src/string/String.cpp create mode 100644 tests/memory.cpp create mode 100644 tests/shared.hpp create mode 100644 tests/signal.cpp create mode 100644 tests/string.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..90314ef --- /dev/null +++ b/.clang-format @@ -0,0 +1,65 @@ +--- +Language: Cpp +BasedOnStyle: LLVM + +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: true +AlignConsecutiveAssignments: true +AlignEscapedNewlines: Right +AlignOperands: false +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: false +BreakConstructorInitializers: AfterColon +ColumnLimit: 180 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +IncludeBlocks: Preserve +IndentCaseLabels: true +IndentWidth: 4 +PointerAlignment: Left +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 4 +UseTab: Never + +AllowShortEnumsOnASingleLine: false + +BraceWrapping: + AfterEnum: false + +AlignConsecutiveDeclarations: AcrossEmptyLines + +NamespaceIndentation: All diff --git a/.github/workflows/arch.yml b/.github/workflows/arch.yml new file mode 100644 index 0000000..f8a4a0e --- /dev/null +++ b/.github/workflows/arch.yml @@ -0,0 +1,57 @@ +name: Build & Test (Arch) + +on: [push, pull_request, workflow_dispatch] +jobs: + gcc: + name: "Arch: Build and Test (gcc)" + runs-on: ubuntu-latest + container: + image: archlinux + steps: + - name: Checkout repository actions + uses: actions/checkout@v4 + with: + sparse-checkout: .github/actions + + - name: Get required pkgs + run: | + sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf + pacman --noconfirm --noprogressbar -Syyu + pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ + + - name: Build hyprutils with gcc + run: | + CC="/usr/bin/gcc" CXX="/usr/bin/g++" cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build + CC="/usr/bin/gcc" CXX="/usr/bin/g++" cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` + cmake --install ./build + + - name: Run tests + run: | + cd ./build && ctest --output-on-failure + + clang: + name: "Arch: Build and Test (clang)" + runs-on: ubuntu-latest + container: + image: archlinux + steps: + - name: Checkout repository actions + uses: actions/checkout@v4 + with: + sparse-checkout: .github/actions + + - name: Get required pkgs + run: | + sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf + pacman --noconfirm --noprogressbar -Syyu + pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ + + - name: Build hyprutils with clang + run: | + CC="/usr/bin/clang" CXX="/usr/bin/clang++" cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build + CC="/usr/bin/clang" CXX="/usr/bin/clang++" cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` + cmake --install ./build + + - name: Run tests + run: | + cd ./build && ctest --output-on-failure diff --git a/.gitignore b/.gitignore index 259148f..2c633ed 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,7 @@ *.exe *.out *.app + +build/ +.vscode/ +.cache/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..fddeb62 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,63 @@ +cmake_minimum_required(VERSION 3.19) + +set(HYPRUTILS_VERSION "0.1.0") +add_compile_definitions(HYPRUTILS_VERSION="${HYPRUTILS_VERSION}") + +project(hyprutils + VERSION ${HYPRUTILS_VERSION} + DESCRIPTION "A library and toolkit for the Hyprland cursor format" +) + +include(CTest) +include(GNUInstallDirs) + +set(PREFIX ${CMAKE_INSTALL_PREFIX}) +set(INCLUDE ${CMAKE_INSTALL_FULL_INCLUDEDIR}) +set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR}) + +configure_file(hyprutils.pc.in hyprutils.pc @ONLY) + +set(CMAKE_CXX_STANDARD 23) + +if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) + message(STATUS "Configuring hyprutils in Debug") + add_compile_definitions(HYPRLAND_DEBUG) +else() + add_compile_options(-O3) + message(STATUS "Configuring hyprutils in Release") +endif() + +file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp") + +add_library(hyprutils SHARED ${SRCFILES}) +target_include_directories( hyprutils + PUBLIC "./include" + PRIVATE "./src" +) +set_target_properties(hyprutils PROPERTIES + VERSION ${hyprutils_VERSION} + SOVERSION 0 + PUBLIC_HEADER include/hyprutils/hyprutils.hpp include/hyprutils/hyprutils.h include/hyprutils/shared.h +) + +# tests +add_custom_target(tests) + +add_executable(hyprutils_memory "tests/memory.cpp") +target_link_libraries(hyprutils_memory PRIVATE hyprutils) +add_test(NAME "Memory" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_memory "memory") +add_dependencies(tests hyprutils_memory) + +add_executable(hyprutils_string "tests/string.cpp") +target_link_libraries(hyprutils_string PRIVATE hyprutils) +add_test(NAME "String" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_string "string") +add_dependencies(tests hyprutils_string) + +add_executable(hyprutils_signal "tests/signal.cpp") +target_link_libraries(hyprutils_signal PRIVATE hyprutils) +add_test(NAME "Signal" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_signal "signal") +add_dependencies(tests hyprutils_signal) + +# Installation +install(TARGETS hyprutils) +install(DIRECTORY "include/hyprutils" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/hyprutils.pc.in b/hyprutils.pc.in new file mode 100644 index 0000000..9c52156 --- /dev/null +++ b/hyprutils.pc.in @@ -0,0 +1,10 @@ +prefix=@PREFIX@ +includedir=@INCLUDE@ +libdir=@LIBDIR@ + +Name: hyprutils +URL: https://github.com/hyprwm/hyprutils +Description: Hyprland utilities library used across the ecosystem +Version: @HYPRUTILS_VERSION@ +Cflags: -I${includedir} +Libs: -L${libdir} -lhyprutils diff --git a/include/hyprutils/memory/SharedPtr.hpp b/include/hyprutils/memory/SharedPtr.hpp new file mode 100644 index 0000000..ec9df70 --- /dev/null +++ b/include/hyprutils/memory/SharedPtr.hpp @@ -0,0 +1,302 @@ +#pragma once + +#include +#include +#include + +/* + This is a custom impl of std::shared_ptr. + It is not thread-safe like the STL one, + but Hyprland is single-threaded anyways. + + It differs a bit from how the STL one works, + namely in the fact that it keeps the T* inside the + control block, and that you can still make a CWeakPtr + or deref an existing one inside the destructor. +*/ + +namespace Hyprutils { + namespace Memory { + namespace CSharedPointer_ { + class impl_base { + public: + virtual ~impl_base(){}; + + virtual void inc() noexcept = 0; + virtual void dec() noexcept = 0; + virtual void incWeak() noexcept = 0; + virtual void decWeak() noexcept = 0; + virtual unsigned int ref() noexcept = 0; + virtual unsigned int wref() noexcept = 0; + virtual void destroy() noexcept = 0; + virtual bool destroying() noexcept = 0; + virtual bool dataNonNull() noexcept = 0; + }; + + template + class impl : public impl_base { + public: + impl(T* data) noexcept : _data(data) { + ; + } + + /* strong refcount */ + unsigned int _ref = 0; + /* weak refcount */ + unsigned int _weak = 0; + + T* _data = nullptr; + + friend void swap(impl*& a, impl*& b) { + impl* tmp = a; + a = b; + b = tmp; + } + + /* if the destructor was called, + creating shared_ptrs is no longer valid */ + bool _destroying = false; + + void _destroy() { + if (!_data || _destroying) + return; + + // first, we destroy the data, but keep the pointer. + // this way, weak pointers will still be able to + // reference and use, but no longer create shared ones. + _destroying = true; + __deleter(_data); + // now, we can reset the data and call it a day. + _data = nullptr; + _destroying = false; + } + + std::default_delete __deleter{}; + + // + virtual void inc() noexcept { + _ref++; + } + + virtual void dec() noexcept { + _ref--; + } + + virtual void incWeak() noexcept { + _weak++; + } + + virtual void decWeak() noexcept { + _weak--; + } + + virtual unsigned int ref() noexcept { + return _ref; + } + + virtual unsigned int wref() noexcept { + return _weak; + } + + virtual void destroy() noexcept { + _destroy(); + } + + virtual bool destroying() noexcept { + return _destroying; + } + + virtual bool dataNonNull() noexcept { + return _data; + } + + virtual ~impl() { + destroy(); + } + }; + }; + + template + class CSharedPointer { + public: + template + using validHierarchy = typename std::enable_if&, X>::value, CSharedPointer&>::type; + template + using isConstructible = typename std::enable_if::value>::type; + + /* creates a new shared pointer managing a resource + avoid calling. Could duplicate ownership. Prefer makeShared */ + explicit CSharedPointer(T* object) noexcept { + impl_ = new CSharedPointer_::impl(object); + increment(); + } + + /* creates a shared pointer from a reference */ + template > + CSharedPointer(const CSharedPointer& ref) noexcept { + impl_ = ref.impl_; + increment(); + } + + CSharedPointer(const CSharedPointer& ref) noexcept { + impl_ = ref.impl_; + increment(); + } + + template > + CSharedPointer(CSharedPointer&& ref) noexcept { + std::swap(impl_, ref.impl_); + } + + CSharedPointer(CSharedPointer&& ref) noexcept { + std::swap(impl_, ref.impl_); + } + + /* allows weakPointer to create from an impl */ + CSharedPointer(CSharedPointer_::impl_base* implementation) noexcept { + impl_ = implementation; + increment(); + } + + /* creates an empty shared pointer with no implementation */ + CSharedPointer() noexcept { + ; // empty + } + + /* creates an empty shared pointer with no implementation */ + CSharedPointer(std::nullptr_t) noexcept { + ; // empty + } + + ~CSharedPointer() { + // we do not decrement here, + // because we want to preserve the pointer + // in case this is the last owner. + if (impl_ && impl_->ref() == 1) + destroyImpl(); + else + decrement(); + } + + template + validHierarchy&> operator=(const CSharedPointer& rhs) { + if (impl_ == rhs.impl_) + return *this; + + decrement(); + impl_ = rhs.impl_; + increment(); + return *this; + } + + CSharedPointer& operator=(const CSharedPointer& rhs) { + if (impl_ == rhs.impl_) + return *this; + + decrement(); + impl_ = rhs.impl_; + increment(); + return *this; + } + + template + validHierarchy&> operator=(CSharedPointer&& rhs) { + std::swap(impl_, rhs.impl_); + return *this; + } + + CSharedPointer& operator=(CSharedPointer&& rhs) { + std::swap(impl_, rhs.impl_); + return *this; + } + + operator bool() const { + return impl_ && impl_->dataNonNull(); + } + + bool operator==(const CSharedPointer& rhs) const { + return impl_ == rhs.impl_; + } + + bool operator()(const CSharedPointer& lhs, const CSharedPointer& rhs) const { + return (uintptr_t)lhs.impl_ < (uintptr_t)rhs.impl_; + } + + bool operator<(const CSharedPointer& rhs) const { + return (uintptr_t)impl_ < (uintptr_t)rhs.impl_; + } + + T* operator->() const { + return get(); + } + + T& operator*() const { + return *get(); + } + + void reset() { + decrement(); + impl_ = nullptr; + } + + T* get() const { + return (T*)(impl_ ? static_cast*>(impl_)->_data : nullptr); + } + + unsigned int strongRef() const { + return impl_ ? impl_->ref() : 0; + } + + CSharedPointer_::impl_base* impl_ = nullptr; + + private: + /* + no-op if there is no impl_ + may delete the stored object if ref == 0 + may delete and reset impl_ if ref == 0 and weak == 0 + */ + void decrement() { + if (!impl_) + return; + + impl_->dec(); + + // if ref == 0, we can destroy impl + if (impl_->ref() == 0) + destroyImpl(); + } + /* no-op if there is no impl_ */ + void increment() { + if (!impl_) + return; + + impl_->inc(); + } + + /* destroy the pointed-to object + if able, will also destroy impl */ + void destroyImpl() { + // destroy the impl contents + impl_->destroy(); + + // check for weak refs, if zero, we can also delete impl_ + if (impl_->wref() == 0) { + delete impl_; + impl_ = nullptr; + } + } + }; + + template + static CSharedPointer makeShared(Args&&... args) { + return CSharedPointer(new U(std::forward(args)...)); + } + } +} + +template +struct std::hash> { + std::size_t operator()(const Hyprutils::Memory::CSharedPointer& p) const noexcept { + return std::hash{}(p.impl_); + } +}; \ No newline at end of file diff --git a/include/hyprutils/memory/WeakPtr.hpp b/include/hyprutils/memory/WeakPtr.hpp new file mode 100644 index 0000000..8c62fa4 --- /dev/null +++ b/include/hyprutils/memory/WeakPtr.hpp @@ -0,0 +1,192 @@ +#pragma once + +#include "./SharedPtr.hpp" + +/* + This is a Hyprland implementation of std::weak_ptr. + + See SharedPtr.hpp for more info on how it's different. +*/ + +namespace Hyprutils { + namespace Memory { + template + class CWeakPointer { + public: + template + using validHierarchy = typename std::enable_if&, X>::value, CWeakPointer&>::type; + template + using isConstructible = typename std::enable_if::value>::type; + + /* create a weak ptr from a reference */ + template > + CWeakPointer(const CSharedPointer& ref) noexcept { + if (!ref.impl_) + return; + + impl_ = ref.impl_; + incrementWeak(); + } + + /* create a weak ptr from another weak ptr */ + template > + CWeakPointer(const CWeakPointer& ref) noexcept { + if (!ref.impl_) + return; + + impl_ = ref.impl_; + incrementWeak(); + } + + CWeakPointer(const CWeakPointer& ref) noexcept { + if (!ref.impl_) + return; + + impl_ = ref.impl_; + incrementWeak(); + } + + template > + CWeakPointer(CWeakPointer&& ref) noexcept { + std::swap(impl_, ref.impl_); + } + + CWeakPointer(CWeakPointer&& ref) noexcept { + std::swap(impl_, ref.impl_); + } + + /* create a weak ptr from another weak ptr with assignment */ + template + validHierarchy&> operator=(const CWeakPointer& rhs) { + if (impl_ == rhs.impl_) + return *this; + + decrementWeak(); + impl_ = rhs.impl_; + incrementWeak(); + return *this; + } + + CWeakPointer& operator=(const CWeakPointer& rhs) { + if (impl_ == rhs.impl_) + return *this; + + decrementWeak(); + impl_ = rhs.impl_; + incrementWeak(); + return *this; + } + + /* create a weak ptr from a shared ptr with assignment */ + template + validHierarchy&> operator=(const CSharedPointer& rhs) { + if ((uintptr_t)impl_ == (uintptr_t)rhs.impl_) + return *this; + + decrementWeak(); + impl_ = rhs.impl_; + incrementWeak(); + return *this; + } + + /* create an empty weak ptr */ + CWeakPointer() { + ; + } + + ~CWeakPointer() { + decrementWeak(); + } + + /* expired MAY return true even if the pointer is still stored. + the situation would be e.g. self-weak pointer in a destructor. + for pointer validity, use valid() */ + bool expired() const { + return !impl_ || !impl_->dataNonNull() || impl_->destroying(); + } + + /* this means the pointed-to object is not yet deleted and can still be + referenced, but it might be in the process of being deleted. + check !expired() if you want to check whether it's valid and + assignable to a SP. */ + bool valid() const { + return impl_ && impl_->dataNonNull(); + } + + void reset() { + decrementWeak(); + impl_ = nullptr; + } + + CSharedPointer lock() const { + if (!impl_ || !impl_->dataNonNull() || impl_->destroying()) + return {}; + + return CSharedPointer(impl_); + } + + /* this returns valid() */ + operator bool() const { + return valid(); + } + + bool operator==(const CWeakPointer& rhs) const { + return impl_ == rhs.impl_; + } + + bool operator==(const CSharedPointer& rhs) const { + return impl_ == rhs.impl_; + } + + bool operator()(const CWeakPointer& lhs, const CWeakPointer& rhs) const { + return (uintptr_t)lhs.impl_ < (uintptr_t)rhs.impl_; + } + + bool operator<(const CWeakPointer& rhs) const { + return (uintptr_t)impl_ < (uintptr_t)rhs.impl_; + } + + T* get() const { + return (T*)(impl_ ? static_cast*>(impl_)->_data : nullptr); + } + + T* operator->() const { + return get(); + } + + CSharedPointer_::impl_base* impl_ = nullptr; + + private: + /* no-op if there is no impl_ */ + void decrementWeak() { + if (!impl_) + return; + + impl_->decWeak(); + + // we need to check for ->destroying, + // because otherwise we could destroy here + // and have a shared_ptr destroy the same thing + // later (in situations where we have a weak_ptr to self) + if (impl_->wref() == 0 && impl_->ref() == 0 && !impl_->destroying()) { + delete impl_; + impl_ = nullptr; + } + } + /* no-op if there is no impl_ */ + void incrementWeak() { + if (!impl_) + return; + + impl_->incWeak(); + } + }; + } +} + +template +struct std::hash> { + std::size_t operator()(const Hyprutils::Memory::CWeakPointer& p) const noexcept { + return std::hash{}(p.impl_); + } +}; diff --git a/include/hyprutils/signal/Listener.hpp b/include/hyprutils/signal/Listener.hpp new file mode 100644 index 0000000..a78ffc7 --- /dev/null +++ b/include/hyprutils/signal/Listener.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include + +namespace Hyprutils { + namespace Signal { + class CSignal; + + class CSignalListener { + public: + CSignalListener(std::function handler); + + CSignalListener(CSignalListener&&) = delete; + CSignalListener(CSignalListener&) = delete; + CSignalListener(const CSignalListener&) = delete; + CSignalListener(const CSignalListener&&) = delete; + + void emit(std::any data); + + private: + std::function m_fHandler; + }; + + typedef Hyprutils::Memory::CSharedPointer CHyprSignalListener; + + class CStaticSignalListener { + public: + CStaticSignalListener(std::function handler, void* owner); + + CStaticSignalListener(CStaticSignalListener&&) = delete; + CStaticSignalListener(CStaticSignalListener&) = delete; + CStaticSignalListener(const CStaticSignalListener&) = delete; + CStaticSignalListener(const CStaticSignalListener&&) = delete; + + void emit(std::any data); + + private: + void* m_pOwner = nullptr; + std::function m_fHandler; + }; + } +} diff --git a/include/hyprutils/signal/Signal.hpp b/include/hyprutils/signal/Signal.hpp new file mode 100644 index 0000000..f59b9df --- /dev/null +++ b/include/hyprutils/signal/Signal.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "./Listener.hpp" + +namespace Hyprutils { + namespace Signal { + class CSignal { + public: + void emit(std::any data = {}); + + // + [[nodiscard("Listener is unregistered when the ptr is lost")]] CHyprSignalListener registerListener(std::function handler); + + // this is for static listeners. They die with this signal. + // TODO: can we somehow rid of the void* data and make it a custom this? + void registerStaticListener(std::function handler, void* owner); + + private: + std::vector> m_vListeners; + std::vector> m_vStaticListeners; + }; + } +} \ No newline at end of file diff --git a/include/hyprutils/string/String.hpp b/include/hyprutils/string/String.hpp new file mode 100644 index 0000000..933e727 --- /dev/null +++ b/include/hyprutils/string/String.hpp @@ -0,0 +1,10 @@ +#pragma once +#include + +namespace Hyprutils { + namespace String { + // trims beginning and end of whitespace characters + std::string trim(const std::string& in); + bool isNumber(const std::string& str, bool allowfloat = false); + }; +}; \ No newline at end of file diff --git a/src/signal/Listener.cpp b/src/signal/Listener.cpp new file mode 100644 index 0000000..884af5a --- /dev/null +++ b/src/signal/Listener.cpp @@ -0,0 +1,22 @@ +#include + +using namespace Hyprutils::Signal; + +Hyprutils::Signal::CSignalListener::CSignalListener(std::function handler) : m_fHandler(handler) { + ; +} + +void Hyprutils::Signal::CSignalListener::emit(std::any data) { + if (!m_fHandler) + return; + + m_fHandler(data); +} + +Hyprutils::Signal::CStaticSignalListener::CStaticSignalListener(std::function handler, void* owner) : m_pOwner(owner), m_fHandler(handler) { + ; +} + +void Hyprutils::Signal::CStaticSignalListener::emit(std::any data) { + m_fHandler(m_pOwner, data); +} diff --git a/src/signal/Signal.cpp b/src/signal/Signal.cpp new file mode 100644 index 0000000..698d16a --- /dev/null +++ b/src/signal/Signal.cpp @@ -0,0 +1,58 @@ +#include +#include +#include + +using namespace Hyprutils::Signal; +using namespace Hyprutils::Memory; + +#define SP CSharedPointer +#define WP CWeakPointer + +void Hyprutils::Signal::CSignal::emit(std::any data) { + bool dirty = false; + + std::vector> listeners; + for (auto& l : m_vListeners) { + if (l.expired()) { + dirty = true; + continue; + } + + listeners.emplace_back(l.lock()); + } + + std::vector statics; + for (auto& l : m_vStaticListeners) { + statics.emplace_back(l.get()); + } + + for (auto& l : listeners) { + // if there is only one lock, it means the event is only held by the listeners + // vector and was removed during our iteration + if (l.strongRef() == 1) { + dirty = true; + continue; + } + l->emit(data); + } + + for (auto& l : statics) { + l->emit(data); + } + + // release SPs + listeners.clear(); + + if (dirty) + std::erase_if(m_vListeners, [](const auto& other) { return other.expired(); }); +} + +CHyprSignalListener Hyprutils::Signal::CSignal::registerListener(std::function handler) { + CHyprSignalListener listener = makeShared(handler); + m_vListeners.emplace_back(listener); + return listener; +} + +void Hyprutils::Signal::CSignal::registerStaticListener(std::function handler, void* owner) { + m_vStaticListeners.emplace_back(std::make_unique(handler, owner)); +} \ No newline at end of file diff --git a/src/string/String.cpp b/src/string/String.cpp new file mode 100644 index 0000000..24bb14f --- /dev/null +++ b/src/string/String.cpp @@ -0,0 +1,58 @@ +#include +#include + +using namespace Hyprutils::String; + +std::string Hyprutils::String::trim(const std::string& in) { + if (in.empty()) + return in; + + int countBefore = 0; + while (countBefore < in.length() && std::isspace(in.at(countBefore))) { + countBefore++; + } + + int countAfter = 0; + while (countAfter < in.length() - countBefore && std::isspace(in.at(in.length() - countAfter - 1))) { + countAfter++; + } + + std::string result = in.substr(countBefore, in.length() - countBefore - countAfter); + + return result; +} + +bool Hyprutils::String::isNumber(const std::string& str, bool allowfloat) { + if (str.empty()) + return false; + + for (size_t i = 0; i < str.length(); ++i) { + const char& c = str.at(i); + + if (i == 0 && str.at(i) == '-') { + // only place where we allow - + continue; + } + + if (!isdigit(c)) { + if (!allowfloat) + return false; + + if (c != '.') + return false; + + if (i == 0) + return false; + + if (str.at(0) == '-') + return false; + + continue; + } + } + + if (str.back() == '.') + return false; + + return true; +} diff --git a/tests/memory.cpp b/tests/memory.cpp new file mode 100644 index 0000000..805ef42 --- /dev/null +++ b/tests/memory.cpp @@ -0,0 +1,29 @@ +#include +#include "shared.hpp" + +using namespace Hyprutils::Memory; + +#define SP CSharedPointer +#define WP CWeakPointer + +int main(int argc, char** argv, char** envp) { + SP intPtr = makeShared(10); + + int ret = 0; + + EXPECT(*intPtr, 10); + EXPECT(intPtr.strongRef(), 1); + + WP weak = intPtr; + + EXPECT(*intPtr, 10); + EXPECT(intPtr.strongRef(), 1); + EXPECT(*weak.get(), 10); + EXPECT(weak.expired(), false); + + intPtr = {}; + + EXPECT(weak.expired(), true); + + return ret; +} \ No newline at end of file diff --git a/tests/shared.hpp b/tests/shared.hpp new file mode 100644 index 0000000..b267426 --- /dev/null +++ b/tests/shared.hpp @@ -0,0 +1,20 @@ +#pragma once +#include + +namespace Colors { + constexpr const char* RED = "\x1b[31m"; + constexpr const char* GREEN = "\x1b[32m"; + constexpr const char* YELLOW = "\x1b[33m"; + constexpr const char* BLUE = "\x1b[34m"; + constexpr const char* MAGENTA = "\x1b[35m"; + constexpr const char* CYAN = "\x1b[36m"; + constexpr const char* RESET = "\x1b[0m"; +}; + +#define EXPECT(expr, val) \ + if (const auto RESULT = expr; RESULT != (val)) { \ + std::cout << Colors::RED << "Failed: " << Colors::RESET << #expr << ", expected " << #val << " but got " << RESULT << "\n"; \ + ret = 1; \ + } else { \ + std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got " << val << "\n"; \ + } \ No newline at end of file diff --git a/tests/signal.cpp b/tests/signal.cpp new file mode 100644 index 0000000..e76e427 --- /dev/null +++ b/tests/signal.cpp @@ -0,0 +1,30 @@ +#include +#include +#include "shared.hpp" + +using namespace Hyprutils::Signal; +using namespace Hyprutils::Memory; + +int main(int argc, char** argv, char** envp) { + int ret = 0; + + CSignal signal; + int data = 0; + auto listener = signal.registerListener([&] (std::any d) { + data = 1; + }); + + signal.emit(); + + EXPECT(data, 1); + + data = 0; + + listener.reset(); + + signal.emit(); + + EXPECT(data, 0); + + return ret; +} \ No newline at end of file diff --git a/tests/string.cpp b/tests/string.cpp new file mode 100644 index 0000000..40a7ba7 --- /dev/null +++ b/tests/string.cpp @@ -0,0 +1,33 @@ +#include +#include "shared.hpp" + +using namespace Hyprutils::String; + +int main(int argc, char** argv, char** envp) { + int ret = 0; + + EXPECT(trim(" a "), "a"); + EXPECT(trim(" a a "), "a a"); + EXPECT(trim("a"), "a"); + EXPECT(trim(" "), ""); + + EXPECT(isNumber("99214123434"), true); + EXPECT(isNumber("-35252345234"), true); + EXPECT(isNumber("---3423--432"), false); + EXPECT(isNumber("s---3423--432"), false); + EXPECT(isNumber("---3423--432s"), false); + EXPECT(isNumber("1s"), false); + EXPECT(isNumber(""), false); + EXPECT(isNumber("--0"), false); + EXPECT(isNumber("abc"), false); + EXPECT(isNumber("0.0", true), true); + EXPECT(isNumber("0.2", true), true); + EXPECT(isNumber("0.", true), false); + EXPECT(isNumber(".0", true), false); + EXPECT(isNumber("", true), false); + EXPECT(isNumber("vvss", true), false); + EXPECT(isNumber("0.9999s", true), false); + EXPECT(isNumber("s0.9999", true), false); + + return ret; +} \ No newline at end of file