diff --git a/include/eepp/core/memorymanager.hpp b/include/eepp/core/memorymanager.hpp index f97a57bc6..9a9e2e8fc 100644 --- a/include/eepp/core/memorymanager.hpp +++ b/include/eepp/core/memorymanager.hpp @@ -6,20 +6,23 @@ #include #include #include +#include #include +#include namespace EE { class EE_API AllocatedPointer { public: AllocatedPointer( void* data, const std::string& File, int Line, size_t memory, - bool track = false ); + bool track = false, bool globalAllocation = false ); std::string mFile; int mLine; size_t mMemory; void* mData; bool mTrack; + bool mGlobalAllocation; }; typedef std::unordered_map AllocatedPointerMap; @@ -39,14 +42,19 @@ class EE_API MemoryManager { static bool removePointer( void* data, const char* file, const size_t& line ); + /** Removes a pointer when it is tracked, without diagnosing foreign allocator bookkeeping. */ + static bool removePointerIfTracked( void* data ); + static void showResults(); - template static T* deletePtr( T* data ) { + template static T* deletePtr( T* data, const char* file, size_t line ) { + removePointer( data, file, line ); delete data; return data; } - template static T* deleteArrayPtr( T* data ) { + template static T* deleteArrayPtr( T* data, const char* file, size_t line ) { + removePointer( data, file, line ); delete[] data; return data; } @@ -60,25 +68,46 @@ class EE_API MemoryManager { static void* reallocate( void* ptr, size_t size ); + /** Allocation entry points used by the debug global new/delete overrides. */ + static void* allocateGlobal( size_t size, size_t alignment ); + + static void freeGlobal( void* ptr ) noexcept; + + template + static auto create( Factory&& factory, const char* file, int line ) -> decltype( factory() ) { + auto* pointer = std::forward( factory )(); + using Type = typename std::remove_pointer::type; + return static_cast( + addPointer( AllocatedPointer( pointer, file, line, sizeof( Type ) ) ) ); + } + static size_t getPeakMemoryUsage(); static size_t getTotalMemoryUsage(); static AllocatedPointer getBiggestAllocation(); + + static AllocatedPointer getBiggestNonAnonymousAllocation(); }; #if defined( __GNUC__ ) && __GNUC__ >= 12 #pragma GCC diagnostic pop #endif #ifdef EE_MEMORY_MANAGER +#define eeNewExpression( constructor ) \ + EE::MemoryManager::create( [&]() { return new constructor; }, __FILE__, __LINE__ ) + +#define eeNewLegacy( classType, constructor ) \ + EE::MemoryManager::create( [&]() { return new classType constructor; }, __FILE__, __LINE__ ) + +#define eeNewSelect( _1, _2, NAME, ... ) NAME + +#define eeNew( ... ) eeNewSelect( __VA_ARGS__, eeNewLegacy, eeNewExpression )( __VA_ARGS__ ) + #define eeNewTracked( classType, constructor ) \ (classType*)EE::MemoryManager::addPointer( EE::AllocatedPointer( \ new classType constructor, __FILE__, __LINE__, sizeof( classType ), true ) ) -#define eeNew( classType, constructor ) \ - (classType*)EE::MemoryManager::addPointer( EE::AllocatedPointer( \ - new classType constructor, __FILE__, __LINE__, sizeof( classType ) ) ) - #define eeNewInPlace( place, classType, constructor ) \ (classType*)EE::MemoryManager::addPointerInPlace( \ place, EE::AllocatedPointer( new place classType constructor, __FILE__, __LINE__, \ @@ -104,12 +133,7 @@ class EE_API MemoryManager { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wuse-after-free" #endif -#define eeDelete( data ) \ - { \ - if ( EE::MemoryManager::removePointer( EE::MemoryManager::deletePtr( data ), __FILE__, \ - __LINE__ ) == false ) \ - printf( "Deleting at '%s' %d\n", __FILE__, __LINE__ ); \ - } +#define eeDelete( data ) EE::MemoryManager::deletePtr( data, __FILE__, __LINE__ ) #if defined( __GNUC__ ) && __GNUC__ >= 12 #pragma GCC diagnostic pop @@ -117,12 +141,7 @@ class EE_API MemoryManager { #pragma GCC diagnostic ignored "-Wuse-after-free" #endif -#define eeDeleteArray( data ) \ - { \ - if ( EE::MemoryManager::removePointer( EE::MemoryManager::deleteArrayPtr( data ), \ - __FILE__, __LINE__ ) == false ) \ - printf( "Deleting at '%s' %d\n", __FILE__, __LINE__ ); \ - } +#define eeDeleteArray( data ) EE::MemoryManager::deleteArrayPtr( data, __FILE__, __LINE__ ) #if defined( __GNUC__ ) && __GNUC__ >= 12 #pragma GCC diagnostic pop @@ -143,7 +162,13 @@ class EE_API MemoryManager { #define eeNewTracked( classType, constructor ) new classType constructor -#define eeNew( classType, constructor ) new classType constructor +#define eeNewExpression( constructor ) new constructor + +#define eeNewLegacy( classType, constructor ) new classType constructor + +#define eeNewSelect( _1, _2, NAME, ... ) NAME + +#define eeNew( ... ) eeNewSelect( __VA_ARGS__, eeNewLegacy, eeNewExpression )( __VA_ARGS__ ) #define eeNewInPlace( place, classType, constructor ) new place classType constructor diff --git a/include/eepp/graphics/text.hpp b/include/eepp/graphics/text.hpp index a99cbd13a..5900d90d8 100644 --- a/include/eepp/graphics/text.hpp +++ b/include/eepp/graphics/text.hpp @@ -422,6 +422,7 @@ class EE_API Text { std::vector mVertices; std::vector mColors; + std::vector mShadowColors; std::vector mRenderModes; std::vector mOutlineVertices; std::vector mOutlineColors; diff --git a/include/eepp/ui/css/propertyidset.hpp b/include/eepp/ui/css/propertyidset.hpp index 38c7ade74..68064a1ff 100644 --- a/include/eepp/ui/css/propertyidset.hpp +++ b/include/eepp/ui/css/propertyidset.hpp @@ -25,9 +25,9 @@ class EE_API PropertyIdSet { size_t size() const { return mIds.size(); } - bool operator==( const PropertyIdSet& other ) { return mIds == other.mIds; } + bool operator==( const PropertyIdSet& other ) const { return mIds == other.mIds; } - bool operator!=( const PropertyIdSet& other ) { return mIds != other.mIds; } + bool operator!=( const PropertyIdSet& other ) const { return mIds != other.mIds; } // Union with another set PropertyIdSet& operator|=( const PropertyIdSet& other ) { @@ -43,7 +43,7 @@ class EE_API PropertyIdSet { // Intersection with another set PropertyIdSet& operator&=( const PropertyIdSet& other ) { - if ( mIds.size() > 0 && other.mIds.size() > 0 ) { + if ( !mIds.empty() && !other.mIds.empty() ) { for ( auto it = mIds.begin(); it != mIds.end(); ) if ( other.mIds.count( *it ) == 0 ) it = mIds.erase( it ); @@ -57,7 +57,7 @@ class EE_API PropertyIdSet { PropertyIdSet operator&( const PropertyIdSet& other ) const { PropertyIdSet result; - if ( mIds.size() > 0 && other.mIds.size() > 0 ) { + if ( !mIds.empty() && !other.mIds.empty() ) { for ( Uint32 id : mIds ) if ( other.mIds.count( id ) == 1 ) result.mIds.insert( id ); diff --git a/include/eepp/ui/uistyle.hpp b/include/eepp/ui/uistyle.hpp index 948df747b..d64678985 100644 --- a/include/eepp/ui/uistyle.hpp +++ b/include/eepp/ui/uistyle.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -103,6 +104,7 @@ class EE_API UIStyle : public UIState { UnorderedSet mRelatedWidgets; UnorderedSet mSubscribedWidgets; UnorderedSet mStructurallyVolatileChildren; + std::optional mChangedProperties; Uint32 mStateDepthCounter{ 0 }; Uint64 mLoadedVersion{ 0 }; const CSS::StyleSheet* mLoadedStyleSheet{ nullptr }; diff --git a/premake4.lua b/premake4.lua index f5e54e3b1..4b36635d0 100644 --- a/premake4.lua +++ b/premake4.lua @@ -509,10 +509,15 @@ function popen( executable_path ) end function build_link_configuration( package_name, use_ee_icon ) + configuration {} includedirs { "include" } local extension = ""; + if package_name ~= "eepp" and package_name ~= "eepp-static" then + files { "src/eepp/core/memorymanagerglobal.cpp" } + end + if package_name == "eepp" then defines { "EE_EXPORTS" } elseif package_name == "eepp-static" then diff --git a/premake5.lua b/premake5.lua index 016f1c9a6..b697f22d8 100644 --- a/premake5.lua +++ b/premake5.lua @@ -459,9 +459,14 @@ function get_architecture() end function build_link_configuration( package_name, use_ee_icon ) + filter {} incdirs { "include" } local extension = ""; + if package_name ~= "eepp" and package_name ~= "eepp-static" then + files { "src/eepp/core/memorymanagerglobal.cpp" } + end + if os.istarget("emscripten") and package_name ~= "eepp" and package_name ~= "eepp-static" then local without_assets = { ["eepp-empty-window"] = true, diff --git a/src/eepp/core/memorymanager.cpp b/src/eepp/core/memorymanager.cpp index c0a205d2a..9bb4d67a3 100644 --- a/src/eepp/core/memorymanager.cpp +++ b/src/eepp/core/memorymanager.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -5,42 +6,47 @@ #include #include #include +#include +#include + +#if EE_PLATFORM == EE_PLATFORM_WIN +#include +#endif using namespace EE::System; using namespace EE::Window; -// #ifdef EE_DEBUG -// #define EE_OVERRIDE_NEW_DELETE -// #endif - -#ifdef EE_OVERRIDE_NEW_DELETE -void* operator new( std::size_t n ) { - return malloc( n ); -} - -void operator delete( void* p ) throw() { - free( p ); -} -#endif - namespace EE { AllocatedPointer::AllocatedPointer( void* data, const std::string& file, int line, size_t memory, - bool track ) { + bool track, bool globalAllocation ) { mData = data; mFile = file; mLine = line; mMemory = memory; mTrack = track; + mGlobalAllocation = globalAllocation; } namespace { +thread_local bool insideMemoryManager = false; + +class MemoryManagerScope { + public: + MemoryManagerScope() : mWasInside( insideMemoryManager ) { insideMemoryManager = true; } + ~MemoryManagerScope() { insideMemoryManager = mWasInside; } + + private: + bool mWasInside; +}; + struct MemoryManagerState { AllocatedPointerMap pointers; size_t totalMemoryUsage{ 0 }; size_t peakMemoryUsage{ 0 }; AllocatedPointer biggestAllocation{ NULL, "", 0, 0 }; + AllocatedPointer biggestNonAnonymousAllocation{ NULL, "", 0, 0 }; Mutex allocationMutex; }; @@ -63,6 +69,7 @@ void* MemoryManager::reallocate( void* ptr, size_t size ) { } void* MemoryManager::addPointerInPlace( void* place, const AllocatedPointer& aAllocatedPointer ) { + MemoryManagerScope scope; auto& state = getMemoryManagerState(); Lock lock( state.allocationMutex ); AllocatedPointerMapIt it = state.pointers.find( place ); @@ -75,11 +82,16 @@ void* MemoryManager::addPointerInPlace( void* place, const AllocatedPointer& aAl } void* MemoryManager::addPointer( const AllocatedPointer& aAllocatedPointer ) { + MemoryManagerScope scope; auto& state = getMemoryManagerState(); Lock lock( state.allocationMutex ); - state.pointers.insert( + auto result = state.pointers.insert( AllocatedPointerMap::value_type( aAllocatedPointer.mData, aAllocatedPointer ) ); + if ( !result.second ) { + state.totalMemoryUsage -= result.first->second.mMemory; + result.first->second = aAllocatedPointer; + } state.totalMemoryUsage += aAllocatedPointer.mMemory; @@ -91,6 +103,11 @@ void* MemoryManager::addPointer( const AllocatedPointer& aAllocatedPointer ) { state.biggestAllocation = aAllocatedPointer; } + if ( !aAllocatedPointer.mGlobalAllocation && + aAllocatedPointer.mMemory > state.biggestNonAnonymousAllocation.mMemory ) { + state.biggestNonAnonymousAllocation = aAllocatedPointer; + } + if ( aAllocatedPointer.mTrack ) eePRINTL( "Allocating pointer %p at '%s' %d", aAllocatedPointer.mData, aAllocatedPointer.mFile.c_str(), aAllocatedPointer.mLine ); @@ -99,6 +116,7 @@ void* MemoryManager::addPointer( const AllocatedPointer& aAllocatedPointer ) { } void* MemoryManager::reallocPointer( void* data, const AllocatedPointer& aAllocatedPointer ) { + MemoryManagerScope scope; auto& state = getMemoryManagerState(); Lock lock( state.allocationMutex ); @@ -134,12 +152,18 @@ void* MemoryManager::reallocPointer( void* data, const AllocatedPointer& aAlloca if ( aAllocatedPointer.mMemory > state.biggestAllocation.mMemory ) { state.biggestAllocation = aAllocatedPointer; } + + if ( !aAllocatedPointer.mGlobalAllocation && + aAllocatedPointer.mMemory > state.biggestNonAnonymousAllocation.mMemory ) { + state.biggestNonAnonymousAllocation = aAllocatedPointer; + } } return aAllocatedPointer.mData; } bool MemoryManager::removePointer( void* data, const char* file, const size_t& line ) { + MemoryManagerScope scope; auto& state = getMemoryManagerState(); Lock lock( state.allocationMutex ); @@ -161,24 +185,88 @@ bool MemoryManager::removePointer( void* data, const char* file, const size_t& l return true; } +bool MemoryManager::removePointerIfTracked( void* data ) { + MemoryManagerScope scope; + auto& state = getMemoryManagerState(); + Lock lock( state.allocationMutex ); + auto it = state.pointers.find( data ); + if ( it == state.pointers.end() ) + return false; + state.totalMemoryUsage -= it->second.mMemory; + state.pointers.erase( it ); + return true; +} + +void* MemoryManager::allocateGlobal( size_t size, size_t alignment ) { + size = size == 0 ? 1 : size; + void* ptr = nullptr; +#if EE_PLATFORM == EE_PLATFORM_WIN + ptr = _aligned_malloc( size, alignment ); +#else + if ( alignment <= alignof( std::max_align_t ) ) { + ptr = malloc( size ); + } else if ( posix_memalign( &ptr, alignment, size ) != 0 ) { + ptr = nullptr; + } +#endif + if ( ptr == nullptr ) + throw std::bad_alloc(); + if ( !insideMemoryManager ) { + MemoryManagerScope scope; + try { + addPointer( AllocatedPointer( ptr, "", 0, size, false, true ) ); + } catch ( ... ) { +#if EE_PLATFORM == EE_PLATFORM_WIN + _aligned_free( ptr ); +#else + free( ptr ); +#endif + throw; + } + } + return ptr; +} + +void MemoryManager::freeGlobal( void* ptr ) noexcept { + if ( ptr == nullptr ) + return; + if ( !insideMemoryManager ) + removePointerIfTracked( ptr ); +#if EE_PLATFORM == EE_PLATFORM_WIN + _aligned_free( ptr ); +#else + free( ptr ); +#endif +} + size_t MemoryManager::getPeakMemoryUsage() { + MemoryManagerScope scope; auto& state = getMemoryManagerState(); Lock lock( state.allocationMutex ); return state.peakMemoryUsage; } size_t MemoryManager::getTotalMemoryUsage() { + MemoryManagerScope scope; auto& state = getMemoryManagerState(); Lock lock( state.allocationMutex ); return state.totalMemoryUsage; } AllocatedPointer MemoryManager::getBiggestAllocation() { + MemoryManagerScope scope; auto& state = getMemoryManagerState(); Lock lock( state.allocationMutex ); return state.biggestAllocation; } +AllocatedPointer MemoryManager::getBiggestNonAnonymousAllocation() { + MemoryManagerScope scope; + auto& state = getMemoryManagerState(); + Lock lock( state.allocationMutex ); + return state.biggestNonAnonymousAllocation; +} + void MemoryManager::showResults() { #ifdef EE_MEMORY_MANAGER @@ -188,63 +276,74 @@ void MemoryManager::showResults() { } Engine::destroySingleton(); + MemoryManagerScope scope; auto& state = getMemoryManagerState(); Lock lock( state.allocationMutex ); - - eePRINTL( "\n|--Memory Manager Report-------------------------------------|" ); - eePRINTL( "|" ); - - if ( state.pointers.empty() ) { - eePRINTL( "| No memory leaks detected." ); - } else { - eePRINTL( "| Memory leaks detected: " ); - eePRINTL( "|" ); - eePRINTL( "| address\t file" ); - - // Get max length of file name - int lMax = 0; - AllocatedPointerMapIt it = state.pointers.begin(); - - for ( ; it != state.pointers.end(); ++it ) { - AllocatedPointer& ap = it->second; - - if ( (int)ap.mFile.length() > lMax ) - lMax = (int)ap.mFile.length(); - } - - lMax += 5; - - for ( int i = 0; i < lMax - 4; ++i ) - eePRINT( " " ); - - eePRINTL( "line\t\t memory usage\t " ); - - eePRINTL( "|-----------------------------------------------------------|" ); - - it = state.pointers.begin(); - - for ( ; it != state.pointers.end(); ++it ) { - AllocatedPointer& ap = it->second; - - eePRINT( "| %p\t %s", ap.mData, ap.mFile.c_str() ); - - for ( int i = 0; i < lMax - (int)ap.mFile.length(); ++i ) - eePRINT( " " ); - - eePRINTL( "%d\t\t %d\t", ap.mLine, ap.mMemory ); + size_t globalAllocationCount = 0; + size_t globalAllocationMemory = 0; + size_t leakCount = 0; + for ( const auto& pointer : state.pointers ) { + if ( pointer.second.mGlobalAllocation ) { + ++globalAllocationCount; + globalAllocationMemory += pointer.second.mMemory; + } else { + ++leakCount; } } - eePRINTL( "|" ); - eePRINTL( "| Memory left: %s", - FileSystem::sizeToString( static_cast( state.totalMemoryUsage ) ).c_str() ); - eePRINTL( "| Biggest allocation:" ); - eePRINTL( "| %s in file: %s at line: %d", - FileSystem::sizeToString( state.biggestAllocation.mMemory ).c_str(), - state.biggestAllocation.mFile.c_str(), state.biggestAllocation.mLine ); - eePRINTL( "| Peak Memory Usage: %s", - FileSystem::sizeToString( state.peakMemoryUsage ).c_str() ); - eePRINTL( "|------------------------------------------------------------|\n" ); + eePRINTL( "\nMemory Manager Report" ); + eePRINTL( "=====================" ); + + if ( leakCount == 0 ) { + eePRINTL( "\nNo actionable memory leaks detected." ); + } else { + eePRINTL( "\nActionable memory leaks: %zu\n", leakCount ); + tabulate::Table leaks; + leaks.add_row( { "Address", "File", "Line", "Memory usage" } ); + for ( size_t column = 0; column < 4; ++column ) + leaks[0][column].format().font_style( { tabulate::FontStyle::bold } ); + + for ( const auto& pointer : state.pointers ) { + const AllocatedPointer& ap = pointer.second; + if ( ap.mGlobalAllocation ) + continue; + + char address[2 + sizeof( void* ) * 2 + 1]; + snprintf( address, sizeof( address ), "%p", ap.mData ); + leaks.add_row( { address, ap.mFile, std::to_string( ap.mLine ), + FileSystem::sizeToString( static_cast( ap.mMemory ) ) } ); + } + + eePRINTL( "%s", leaks.str().c_str() ); + } + + auto allocationLocation = []( const AllocatedPointer& allocation ) { + return allocation.mData == nullptr + ? std::string( "-" ) + : allocation.mFile + ":" + std::to_string( allocation.mLine ); + }; + + tabulate::Table summary; + summary.add_row( { "Metric", "Value", "Details" } ); + for ( size_t column = 0; column < 3; ++column ) + summary[0][column].format().font_style( { tabulate::FontStyle::bold } ); + summary.add_row( { "Anonymous allocations still live", + std::to_string( globalAllocationCount ) + " / " + + FileSystem::sizeToString( static_cast( globalAllocationMemory ) ), + "Snapshot only; includes process-static and shared-library allocations" } ); + summary.add_row( { "Total memory still live", + FileSystem::sizeToString( static_cast( state.totalMemoryUsage ) ), + "All tracked allocations" } ); + summary.add_row( { "Biggest non-anonymous allocation", + FileSystem::sizeToString( state.biggestNonAnonymousAllocation.mMemory ), + allocationLocation( state.biggestNonAnonymousAllocation ) } ); + summary.add_row( { "Biggest allocation overall", + FileSystem::sizeToString( state.biggestAllocation.mMemory ), + allocationLocation( state.biggestAllocation ) } ); + summary.add_row( + { "Peak memory usage", FileSystem::sizeToString( state.peakMemoryUsage ), "-" } ); + + eePRINTL( "\nSummary\n-------\n%s\n", summary.str().c_str() ); #endif } diff --git a/src/eepp/core/memorymanagerglobal.cpp b/src/eepp/core/memorymanagerglobal.cpp new file mode 100644 index 000000000..72b0ac1cb --- /dev/null +++ b/src/eepp/core/memorymanagerglobal.cpp @@ -0,0 +1,91 @@ +#include + +#ifdef EE_MEMORY_MANAGER + +void* operator new( std::size_t size ) { + return EE::MemoryManager::allocateGlobal( size, alignof( std::max_align_t ) ); +} + +void* operator new[]( std::size_t size ) { + return EE::MemoryManager::allocateGlobal( size, alignof( std::max_align_t ) ); +} + +void* operator new( std::size_t size, std::align_val_t alignment ) { + return EE::MemoryManager::allocateGlobal( size, static_cast( alignment ) ); +} + +void* operator new[]( std::size_t size, std::align_val_t alignment ) { + return EE::MemoryManager::allocateGlobal( size, static_cast( alignment ) ); +} + +void* operator new( std::size_t size, const std::nothrow_t& ) noexcept { + try { + return EE::MemoryManager::allocateGlobal( size, alignof( std::max_align_t ) ); + } catch ( ... ) { + return nullptr; + } +} + +void* operator new[]( std::size_t size, const std::nothrow_t& ) noexcept { + try { + return EE::MemoryManager::allocateGlobal( size, alignof( std::max_align_t ) ); + } catch ( ... ) { + return nullptr; + } +} + +void* operator new( std::size_t size, std::align_val_t alignment, const std::nothrow_t& ) noexcept { + try { + return EE::MemoryManager::allocateGlobal( size, static_cast( alignment ) ); + } catch ( ... ) { + return nullptr; + } +} + +void* operator new[]( std::size_t size, std::align_val_t alignment, + const std::nothrow_t& ) noexcept { + try { + return EE::MemoryManager::allocateGlobal( size, static_cast( alignment ) ); + } catch ( ... ) { + return nullptr; + } +} + +void operator delete( void* ptr ) noexcept { + EE::MemoryManager::freeGlobal( ptr ); +} +void operator delete[]( void* ptr ) noexcept { + EE::MemoryManager::freeGlobal( ptr ); +} +void operator delete( void* ptr, std::size_t ) noexcept { + EE::MemoryManager::freeGlobal( ptr ); +} +void operator delete[]( void* ptr, std::size_t ) noexcept { + EE::MemoryManager::freeGlobal( ptr ); +} +void operator delete( void* ptr, std::align_val_t ) noexcept { + EE::MemoryManager::freeGlobal( ptr ); +} +void operator delete[]( void* ptr, std::align_val_t ) noexcept { + EE::MemoryManager::freeGlobal( ptr ); +} +void operator delete( void* ptr, std::size_t, std::align_val_t ) noexcept { + EE::MemoryManager::freeGlobal( ptr ); +} +void operator delete[]( void* ptr, std::size_t, std::align_val_t ) noexcept { + EE::MemoryManager::freeGlobal( ptr ); +} +void operator delete( void* ptr, const std::nothrow_t& ) noexcept { + EE::MemoryManager::freeGlobal( ptr ); +} +void operator delete[]( void* ptr, const std::nothrow_t& ) noexcept { + EE::MemoryManager::freeGlobal( ptr ); +} +void operator delete( void* ptr, std::align_val_t, const std::nothrow_t& ) noexcept { + EE::MemoryManager::freeGlobal( ptr ); +} +void operator delete[]( void* ptr, std::align_val_t, const std::nothrow_t& ) noexcept { + EE::MemoryManager::freeGlobal( ptr ); +} + +#endif diff --git a/src/eepp/graphics/text.cpp b/src/eepp/graphics/text.cpp index a50b5c98a..cb244a243 100644 --- a/src/eepp/graphics/text.cpp +++ b/src/eepp/graphics/text.cpp @@ -1856,16 +1856,20 @@ void Text::draw( const Float& X, const Float& Y, const Vector2f& scale, const Fl ensureColorUpdate(); if ( mFontStyleConfig.Style & Shadow ) { - std::vector colors; Color shadowColor( getShadowColor() ); if ( getFillColor().a != 255 ) { shadowColor.a = (Uint8)( (Float)shadowColor.a * ( (Float)getFillColor().a / (Float)255 ) ); } - colors.assign( mColors.size(), shadowColor ); + if ( mShadowColors.size() != mColors.size() || + ( !mShadowColors.empty() && mShadowColors.front() != shadowColor ) ) + mShadowColors.assign( mColors.size(), shadowColor ); + + static const std::vector emptyColors; + const std::vector& shadowOutlineColors = + mFontStyleConfig.OutlineThickness > 0 ? mOutlineColors : emptyColors; draw( X + mFontStyleConfig.ShadowOffset.x, Y + mFontStyleConfig.ShadowOffset.y, scale, - rotation, effect, rotationCenter, scaleCenter, colors, - mFontStyleConfig.OutlineThickness > 0 ? mOutlineColors : std::vector{}, + rotation, effect, rotationCenter, scaleCenter, mShadowColors, shadowOutlineColors, Color::Transparent ); } diff --git a/src/eepp/ui/lineargradientdrawable.cpp b/src/eepp/ui/lineargradientdrawable.cpp index 268ebceb9..c0c10df97 100644 --- a/src/eepp/ui/lineargradientdrawable.cpp +++ b/src/eepp/ui/lineargradientdrawable.cpp @@ -80,11 +80,12 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size ) Float txLen = tMax - tMin; // Normalize all stops to [0,1] using the gradient-line pixel length - std::vector stops; + SmallVector stops; stops.reserve( mColorStops.size() ); - for ( const auto& s : mColorStops ) + for ( const auto& s : mColorStops ) { stops.push_back( ColorStop( s.getNormalized( txLen ), s.color, CSS::StyleSheetLength::Percentage ) ); + } std::sort( stops.begin(), stops.end(), []( const ColorStop& a, const ColorStop& b ) { return a.value < b.value; } ); @@ -196,7 +197,7 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size ) Vector2f a; Vector2f b; }; - std::vector segments; + SmallVector segments; segments.reserve( stops.size() ); for ( size_t i = 0; i < stops.size(); i++ ) { diff --git a/src/eepp/ui/uistyle.cpp b/src/eepp/ui/uistyle.cpp index 63cd2d774..5efe4e935 100644 --- a/src/eepp/ui/uistyle.cpp +++ b/src/eepp/ui/uistyle.cpp @@ -392,7 +392,10 @@ void UIStyle::onStateChange() { mWidget->getUISceneNode()->getStyleSheet().getElementStyles( mWidget, true ); if ( newDefinition != mDefinition || mForceReapplyProperties ) { - PropertyIdSet changedProperties; + if ( !mChangedProperties ) + mChangedProperties.emplace(); + PropertyIdSet& changedProperties = *mChangedProperties; + changedProperties.clear(); if ( mDefinition ) changedProperties = mDefinition->getPropertyIds(); diff --git a/src/tests/unit_tests/main.cpp b/src/tests/unit_tests/main.cpp index 30dad370e..36685ccbe 100644 --- a/src/tests/unit_tests/main.cpp +++ b/src/tests/unit_tests/main.cpp @@ -1,3 +1,11 @@ #include "utest.h" -UTEST_MAIN() +#include +#include + +UTEST_STATE(); + +int main( int argc, const char* const argv[] ) { + EE::System::FileSystem::changeWorkingDirectory( EE::System::Sys::getProcessPath() ); + return utest_main( argc, argv ); +} diff --git a/src/tests/unit_tests/memorymanager_tests.cpp b/src/tests/unit_tests/memorymanager_tests.cpp new file mode 100644 index 000000000..8221a6e03 --- /dev/null +++ b/src/tests/unit_tests/memorymanager_tests.cpp @@ -0,0 +1,37 @@ +#include "utest.h" +#include + +using namespace EE; + +namespace { + +struct AllocationProbe { + AllocationProbe( int first, int second ) : value( first + second ) {} + int value; +}; + +} // namespace + +UTEST( MemoryManager, tracesPlainNew ) { + const size_t before = MemoryManager::getTotalMemoryUsage(); + auto* value = new Uint64( 42 ); + const size_t allocated = MemoryManager::getTotalMemoryUsage(); + delete value; + const size_t after = MemoryManager::getTotalMemoryUsage(); + + EXPECT_GE( allocated, before + sizeof( Uint64 ) ); + EXPECT_EQ( after, before ); +} + +UTEST( MemoryManager, supportsExpressionAndLegacyNewSyntax ) { + const size_t before = MemoryManager::getTotalMemoryUsage(); + auto* expression = eeNew( AllocationProbe( 20, 22 ) ); + auto* legacy = eeNew( AllocationProbe, ( 19, 23 ) ); + const bool valuesAreValid = expression->value == 42 && legacy->value == 42; + eeDelete( expression ); + eeDelete( legacy ); + const size_t after = MemoryManager::getTotalMemoryUsage(); + + EXPECT_TRUE( valuesAreValid ); + EXPECT_EQ( after, before ); +} diff --git a/src/tests/unit_tests/propertyidset_tests.cpp b/src/tests/unit_tests/propertyidset_tests.cpp new file mode 100644 index 000000000..4f94f1ab4 --- /dev/null +++ b/src/tests/unit_tests/propertyidset_tests.cpp @@ -0,0 +1,72 @@ +#include "utest.h" +#include +#include + +using namespace EE; +using namespace EE::UI::CSS; + +UTEST( PropertyIdSet, setOperations ) { + PropertyIdSet first; + first.insert( 30 ); + first.insert( 10 ); + first.insert( 20 ); + first.insert( 20 ); + + PropertyIdSet second; + second.insert( 20 ); + second.insert( 40 ); + + const PropertyIdSet setUnion = first | second; + const PropertyIdSet intersection = first & second; + + EXPECT_EQ( first.size(), 3u ); + EXPECT_TRUE( setUnion.contains( 10 ) ); + EXPECT_TRUE( setUnion.contains( 20 ) ); + EXPECT_TRUE( setUnion.contains( 30 ) ); + EXPECT_TRUE( setUnion.contains( 40 ) ); + EXPECT_EQ( intersection.size(), 1u ); + EXPECT_TRUE( intersection.contains( 20 ) ); + + PropertyIdSet sameAsFirst; + sameAsFirst.insert( 20 ); + sameAsFirst.insert( 30 ); + sameAsFirst.insert( 10 ); + EXPECT_TRUE( first == sameAsFirst ); + + first &= second; + EXPECT_TRUE( first == intersection ); + first.erase( 20 ); + EXPECT_TRUE( first.empty() ); +} + +UTEST( PropertyIdSet, iteratorErase ) { + PropertyIdSet ids; + ids.insert( 10 ); + ids.insert( 20 ); + ids.insert( 30 ); + + for ( auto it = ids.begin(); it != ids.end(); ) { + if ( *it == 20 ) + it = ids.erase( it ); + else + ++it; + } + + EXPECT_EQ( ids.size(), 2u ); + EXPECT_FALSE( ids.contains( 20 ) ); +} + +UTEST( PropertyIdSet, clearedSetReusesStorage ) { + PropertyIdSet ids; + for ( Uint32 id = 0; id < 16; ++id ) + ids.insert( id ); + ids.clear(); + + const size_t memoryBefore = MemoryManager::getTotalMemoryUsage(); + for ( Uint32 id = 0; id < 16; ++id ) + ids.insert( id ); + const size_t memoryAfter = MemoryManager::getTotalMemoryUsage(); + + EXPECT_EQ( ids.size(), 16u ); + EXPECT_EQ( memoryAfter, memoryBefore ); +} diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp index 6804e7d97..abb436e57 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp @@ -827,22 +827,21 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event } const auto command = editor->getKeyBindings().getCommandFromKeyBind( eventShortcut ); const bool choiceNavigationShortcut = - mSnippetChoiceSuggestions && - ( mShortcuts["autocomplete-prev-suggestion"] == eventShortcut || - mShortcuts["autocomplete-next-suggestion"] == eventShortcut || - mShortcuts["autocomplete-first-suggestion"] == eventShortcut || - mShortcuts["autocomplete-last-suggestion"] == eventShortcut || - mShortcuts["autocomplete-prev-suggestion-page"] == eventShortcut || - mShortcuts["autocomplete-next-suggestion-page"] == eventShortcut ); + mSnippetChoiceSuggestions && ( mShortcuts.prevSuggestion == eventShortcut || + mShortcuts.nextSuggestion == eventShortcut || + mShortcuts.firstSuggestion == eventShortcut || + mShortcuts.lastSuggestion == eventShortcut || + mShortcuts.prevSuggestionPage == eventShortcut || + mShortcuts.nextSuggestionPage == eventShortcut ); if ( !choiceNavigationShortcut && isSnippetNavigationCommand( command ) ) cancelSnippetSession( &editor->getDocument(), true ); } if ( mSignatureHelpVisible ) { - if ( mShortcuts["autocomplete-close-signature-help"] == eventShortcut ) { + if ( mShortcuts.closeSignatureHelp == eventShortcut ) { resetSignatureHelp(); editor->invalidateDraw(); return true; - } else if ( mShortcuts["autocomplete-prev-signature-help"] == eventShortcut ) { + } else if ( mShortcuts.prevSignatureHelp == eventShortcut ) { if ( mSignatureHelp.signatures.size() > 1 ) { mSignatureHelpSelected = mSignatureHelpSelected == -1 ? 0 : mSignatureHelpSelected; ++mSignatureHelpSelected; @@ -853,7 +852,7 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event } else if ( mSuggestions.empty() ) { resetSignatureHelp(); } - } else if ( mShortcuts["autocomplete-next-signature-help"] == eventShortcut ) { + } else if ( mShortcuts.nextSignatureHelp == eventShortcut ) { if ( mSignatureHelp.signatures.size() > 1 ) { mSignatureHelpSelected = mSignatureHelpSelected <= 0 ? mSignatureHelp.signatures.size() @@ -881,15 +880,14 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event } } - if ( !mSnippetChoiceSuggestions && - mShortcuts["autocomplete-update-suggestions"] == eventShortcut ) { + if ( !mSnippetChoiceSuggestions && mShortcuts.updateSuggestions == eventShortcut ) { std::string partialSymbol( getPartialSymbol( &editor->getDocument() ) ); updateSuggestions( partialSymbol, editor ); return true; } if ( !mSuggestions.empty() ) { - if ( mShortcuts["autocomplete-next-suggestion"] == eventShortcut ) { + if ( mShortcuts.nextSuggestion == eventShortcut ) { if ( mSuggestionIndex + 1 < (int)mSuggestions.size() ) { mSuggestionIndex++; if ( mSuggestionIndex < mSuggestionsStartIndex ) @@ -904,7 +902,7 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event } editor->invalidateDraw(); return true; - } else if ( mShortcuts["autocomplete-prev-suggestion"] == eventShortcut ) { + } else if ( mShortcuts.prevSuggestion == eventShortcut ) { if ( mSuggestionIndex - 1 < 0 ) { mSuggestionIndex = mSuggestions.size() - 1; mSuggestionsStartIndex = @@ -916,22 +914,22 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event mSuggestionsStartIndex = mSuggestionIndex; editor->invalidateDraw(); return true; - } else if ( mShortcuts["autocomplete-close-suggestion"] == eventShortcut ) { + } else if ( mShortcuts.closeSuggestion == eventShortcut ) { resetSuggestions( editor ); resetSignatureHelp(); editor->invalidateDraw(); return true; - } else if ( mShortcuts["autocomplete-first-suggestion"] == eventShortcut ) { + } else if ( mShortcuts.firstSuggestion == eventShortcut ) { mSuggestionIndex = 0; mSuggestionsStartIndex = 0; editor->invalidateDraw(); return true; - } else if ( mShortcuts["autocomplete-last-suggestion"] == eventShortcut ) { + } else if ( mShortcuts.lastSuggestion == eventShortcut ) { mSuggestionIndex = mSuggestions.size() - 1; mSuggestionsStartIndex = eemax( 0, (int)mSuggestions.size() - mSuggestionsMaxVisible ); editor->invalidateDraw(); return true; - } else if ( mShortcuts["autocomplete-prev-suggestion-page"] == eventShortcut ) { + } else if ( mShortcuts.prevSuggestionPage == eventShortcut ) { if ( mSuggestionIndex - (int)( mSuggestionsMaxVisible - 1 ) >= 0 ) { mSuggestionIndex -= ( mSuggestionsMaxVisible - 1 ); if ( mSuggestionIndex < mSuggestionsStartIndex ) @@ -942,7 +940,7 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event } editor->invalidateDraw(); return true; - } else if ( mShortcuts["autocomplete-next-suggestion-page"] == eventShortcut ) { + } else if ( mShortcuts.nextSuggestionPage == eventShortcut ) { if ( mSuggestionIndex + mSuggestionsMaxVisible < (int)mSuggestions.size() ) { mSuggestionIndex += mSuggestionsMaxVisible - 1; } else { @@ -952,9 +950,9 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event eemax( 0, mSuggestionIndex - ( mSuggestionsMaxVisible - 1 ) ); editor->invalidateDraw(); return true; - } else if ( mShortcuts["autocomplete-pick-suggestion"] == eventShortcut || - mShortcuts["autocomplete-pick-suggestion-alt"] == eventShortcut || - mShortcuts["autocomplete-pick-suggestion-alt-2"] == eventShortcut ) { + } else if ( mShortcuts.pickSuggestion == eventShortcut || + mShortcuts.pickSuggestionAlt == eventShortcut || + mShortcuts.pickSuggestionAlt2 == eventShortcut ) { pickSuggestion( editor ); return true; } @@ -1166,10 +1164,8 @@ SnippetParser::VariableMap AutoCompletePlugin::snippetVariables( TextDocument& d { "CURRENT_SECOND", formatSnippetTime( localTime, "%S" ) }, { "CURRENT_MILLISECOND", String::format( "%03d", static_cast( milliseconds % 1000 ) ) }, - { "CURRENT_SECONDS_UNIX", - String::toString( static_cast( milliseconds / 1000 ) ) }, - { "CURRENT_MILLISECONDS_UNIX", - String::toString( static_cast( milliseconds ) ) }, + { "CURRENT_SECONDS_UNIX", String::toString( static_cast( milliseconds / 1000 ) ) }, + { "CURRENT_MILLISECONDS_UNIX", String::toString( static_cast( milliseconds ) ) }, { "CURRENT_TIMEZONE_OFFSET", timezoneOffset }, { "CURRENT_TIMEZONE_NAME", formatSnippetTime( localTime, "%Z" ) }, { "RANDOM", String::format( "%06d", Math::randi( 0, 999999 ) ) }, @@ -1864,8 +1860,21 @@ void AutoCompletePlugin::updateShortcuts() { getManager()->getUISceneNode()->getEventDispatcher()->getInput(), keys ); }; - for ( const auto& kb : mKeyBindings ) - mShortcuts[kb.first] = toShortcut( kb.second ); + mShortcuts.closeSuggestion = toShortcut( mKeyBindings["autocomplete-close-suggestion"] ); + mShortcuts.prevSuggestion = toShortcut( mKeyBindings["autocomplete-prev-suggestion"] ); + mShortcuts.nextSuggestion = toShortcut( mKeyBindings["autocomplete-next-suggestion"] ); + mShortcuts.firstSuggestion = toShortcut( mKeyBindings["autocomplete-first-suggestion"] ); + mShortcuts.lastSuggestion = toShortcut( mKeyBindings["autocomplete-last-suggestion"] ); + mShortcuts.prevSuggestionPage = toShortcut( mKeyBindings["autocomplete-prev-suggestion-page"] ); + mShortcuts.nextSuggestionPage = toShortcut( mKeyBindings["autocomplete-next-suggestion-page"] ); + mShortcuts.pickSuggestion = toShortcut( mKeyBindings["autocomplete-pick-suggestion"] ); + mShortcuts.pickSuggestionAlt = toShortcut( mKeyBindings["autocomplete-pick-suggestion-alt"] ); + mShortcuts.pickSuggestionAlt2 = + toShortcut( mKeyBindings["autocomplete-pick-suggestion-alt-2"] ); + mShortcuts.updateSuggestions = toShortcut( mKeyBindings["autocomplete-update-suggestions"] ); + mShortcuts.closeSignatureHelp = toShortcut( mKeyBindings["autocomplete-close-signature-help"] ); + mShortcuts.prevSignatureHelp = toShortcut( mKeyBindings["autocomplete-prev-signature-help"] ); + mShortcuts.nextSignatureHelp = toShortcut( mKeyBindings["autocomplete-next-signature-help"] ); } PluginRequestHandle AutoCompletePlugin::processResponse( const PluginMessage& msg ) { diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp index 1913b5673..87c97627c 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp @@ -193,7 +193,22 @@ class AutoCompletePlugin : public Plugin { size_t mMaxLabelCharacters{ 100 }; String::HashType mConfigHash{ 0 }; std::unordered_map mKeyBindings; - UnorderedMap mShortcuts; + struct Shortcuts { + KeyBindings::Shortcut closeSuggestion; + KeyBindings::Shortcut prevSuggestion; + KeyBindings::Shortcut nextSuggestion; + KeyBindings::Shortcut firstSuggestion; + KeyBindings::Shortcut lastSuggestion; + KeyBindings::Shortcut prevSuggestionPage; + KeyBindings::Shortcut nextSuggestionPage; + KeyBindings::Shortcut pickSuggestion; + KeyBindings::Shortcut pickSuggestionAlt; + KeyBindings::Shortcut pickSuggestionAlt2; + KeyBindings::Shortcut updateSuggestions; + KeyBindings::Shortcut closeSignatureHelp; + KeyBindings::Shortcut prevSignatureHelp; + KeyBindings::Shortcut nextSignatureHelp; + } mShortcuts; std::string mMaxSuggestionDocumentationWidth{ "100%" }; std::string mMaxSignatureHelperWidth{ "90%" }; bool mSignatureHelpMultiLine{ true };