core: extend debug allocation tracing and reduce render-time allocations

- intercept global new/delete operations in debug memory-manager builds
  - distinguish anonymous global allocations from source-tracked leaks
  - modernize eeNew to support constructor-expression syntax
  - improve memory reports with tabulated leaks and separate largest allocations
  - cache autocomplete shortcuts instead of hashing string keys on key events
  - reuse UIStyle property sets across state changes
  - reuse Text shadow colors and avoid temporary outline-color copies
  - use inline storage for temporary linear-gradient rendering data
  - make unit tests resolve assets from the executable directory
  - add memory-manager and PropertyIdSet regression coverage
This commit is contained in:
Martín Lucas Golini
2026-08-05 11:02:35 -03:00
parent 445aac792d
commit afb2d44bda
16 changed files with 507 additions and 130 deletions
+45 -20
View File
@@ -6,20 +6,23 @@
#include <cstring> #include <cstring>
#include <eepp/config.hpp> #include <eepp/config.hpp>
#include <string> #include <string>
#include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <utility>
namespace EE { namespace EE {
class EE_API AllocatedPointer { class EE_API AllocatedPointer {
public: public:
AllocatedPointer( void* data, const std::string& File, int Line, size_t memory, AllocatedPointer( void* data, const std::string& File, int Line, size_t memory,
bool track = false ); bool track = false, bool globalAllocation = false );
std::string mFile; std::string mFile;
int mLine; int mLine;
size_t mMemory; size_t mMemory;
void* mData; void* mData;
bool mTrack; bool mTrack;
bool mGlobalAllocation;
}; };
typedef std::unordered_map<void*, AllocatedPointer> AllocatedPointerMap; typedef std::unordered_map<void*, AllocatedPointer> AllocatedPointerMap;
@@ -39,14 +42,19 @@ class EE_API MemoryManager {
static bool removePointer( void* data, const char* file, const size_t& line ); 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(); static void showResults();
template <class T> static T* deletePtr( T* data ) { template <class T> static T* deletePtr( T* data, const char* file, size_t line ) {
removePointer( data, file, line );
delete data; delete data;
return data; return data;
} }
template <class T> static T* deleteArrayPtr( T* data ) { template <class T> static T* deleteArrayPtr( T* data, const char* file, size_t line ) {
removePointer( data, file, line );
delete[] data; delete[] data;
return data; return data;
} }
@@ -60,25 +68,46 @@ class EE_API MemoryManager {
static void* reallocate( void* ptr, size_t size ); 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 <typename Factory>
static auto create( Factory&& factory, const char* file, int line ) -> decltype( factory() ) {
auto* pointer = std::forward<Factory>( factory )();
using Type = typename std::remove_pointer<decltype( pointer )>::type;
return static_cast<Type*>(
addPointer( AllocatedPointer( pointer, file, line, sizeof( Type ) ) ) );
}
static size_t getPeakMemoryUsage(); static size_t getPeakMemoryUsage();
static size_t getTotalMemoryUsage(); static size_t getTotalMemoryUsage();
static AllocatedPointer getBiggestAllocation(); static AllocatedPointer getBiggestAllocation();
static AllocatedPointer getBiggestNonAnonymousAllocation();
}; };
#if defined( __GNUC__ ) && __GNUC__ >= 12 #if defined( __GNUC__ ) && __GNUC__ >= 12
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
#endif #endif
#ifdef EE_MEMORY_MANAGER #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 ) \ #define eeNewTracked( classType, constructor ) \
(classType*)EE::MemoryManager::addPointer( EE::AllocatedPointer( \ (classType*)EE::MemoryManager::addPointer( EE::AllocatedPointer( \
new classType constructor, __FILE__, __LINE__, sizeof( classType ), true ) ) 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 ) \ #define eeNewInPlace( place, classType, constructor ) \
(classType*)EE::MemoryManager::addPointerInPlace( \ (classType*)EE::MemoryManager::addPointerInPlace( \
place, EE::AllocatedPointer( new place classType constructor, __FILE__, __LINE__, \ place, EE::AllocatedPointer( new place classType constructor, __FILE__, __LINE__, \
@@ -104,12 +133,7 @@ class EE_API MemoryManager {
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wuse-after-free" #pragma GCC diagnostic ignored "-Wuse-after-free"
#endif #endif
#define eeDelete( data ) \ #define eeDelete( data ) EE::MemoryManager::deletePtr( data, __FILE__, __LINE__ )
{ \
if ( EE::MemoryManager::removePointer( EE::MemoryManager::deletePtr( data ), __FILE__, \
__LINE__ ) == false ) \
printf( "Deleting at '%s' %d\n", __FILE__, __LINE__ ); \
}
#if defined( __GNUC__ ) && __GNUC__ >= 12 #if defined( __GNUC__ ) && __GNUC__ >= 12
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
@@ -117,12 +141,7 @@ class EE_API MemoryManager {
#pragma GCC diagnostic ignored "-Wuse-after-free" #pragma GCC diagnostic ignored "-Wuse-after-free"
#endif #endif
#define eeDeleteArray( data ) \ #define eeDeleteArray( data ) EE::MemoryManager::deleteArrayPtr( data, __FILE__, __LINE__ )
{ \
if ( EE::MemoryManager::removePointer( EE::MemoryManager::deleteArrayPtr( data ), \
__FILE__, __LINE__ ) == false ) \
printf( "Deleting at '%s' %d\n", __FILE__, __LINE__ ); \
}
#if defined( __GNUC__ ) && __GNUC__ >= 12 #if defined( __GNUC__ ) && __GNUC__ >= 12
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
@@ -143,7 +162,13 @@ class EE_API MemoryManager {
#define eeNewTracked( classType, constructor ) new classType constructor #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 #define eeNewInPlace( place, classType, constructor ) new place classType constructor
+1
View File
@@ -422,6 +422,7 @@ class EE_API Text {
std::vector<VertexCoords> mVertices; std::vector<VertexCoords> mVertices;
std::vector<Color> mColors; std::vector<Color> mColors;
std::vector<Color> mShadowColors;
std::vector<GlyphRenderMode> mRenderModes; std::vector<GlyphRenderMode> mRenderModes;
std::vector<VertexCoords> mOutlineVertices; std::vector<VertexCoords> mOutlineVertices;
std::vector<Color> mOutlineColors; std::vector<Color> mOutlineColors;
+4 -4
View File
@@ -25,9 +25,9 @@ class EE_API PropertyIdSet {
size_t size() const { return mIds.size(); } 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 // Union with another set
PropertyIdSet& operator|=( const PropertyIdSet& other ) { PropertyIdSet& operator|=( const PropertyIdSet& other ) {
@@ -43,7 +43,7 @@ class EE_API PropertyIdSet {
// Intersection with another set // Intersection with another set
PropertyIdSet& operator&=( const PropertyIdSet& other ) { 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(); ) for ( auto it = mIds.begin(); it != mIds.end(); )
if ( other.mIds.count( *it ) == 0 ) if ( other.mIds.count( *it ) == 0 )
it = mIds.erase( it ); it = mIds.erase( it );
@@ -57,7 +57,7 @@ class EE_API PropertyIdSet {
PropertyIdSet operator&( const PropertyIdSet& other ) const { PropertyIdSet operator&( const PropertyIdSet& other ) const {
PropertyIdSet result; PropertyIdSet result;
if ( mIds.size() > 0 && other.mIds.size() > 0 ) { if ( !mIds.empty() && !other.mIds.empty() ) {
for ( Uint32 id : mIds ) for ( Uint32 id : mIds )
if ( other.mIds.count( id ) == 1 ) if ( other.mIds.count( id ) == 1 )
result.mIds.insert( id ); result.mIds.insert( id );
+2
View File
@@ -5,6 +5,7 @@
#include <eepp/math/ease.hpp> #include <eepp/math/ease.hpp>
#include <eepp/ui/css/animationdefinition.hpp> #include <eepp/ui/css/animationdefinition.hpp>
#include <eepp/ui/css/elementdefinition.hpp> #include <eepp/ui/css/elementdefinition.hpp>
#include <eepp/ui/css/propertyidset.hpp>
#include <eepp/ui/css/stylesheetproperty.hpp> #include <eepp/ui/css/stylesheetproperty.hpp>
#include <eepp/ui/css/stylesheetstyle.hpp> #include <eepp/ui/css/stylesheetstyle.hpp>
#include <eepp/ui/css/transitiondefinition.hpp> #include <eepp/ui/css/transitiondefinition.hpp>
@@ -103,6 +104,7 @@ class EE_API UIStyle : public UIState {
UnorderedSet<UIWidget*> mRelatedWidgets; UnorderedSet<UIWidget*> mRelatedWidgets;
UnorderedSet<UIWidget*> mSubscribedWidgets; UnorderedSet<UIWidget*> mSubscribedWidgets;
UnorderedSet<UIWidget*> mStructurallyVolatileChildren; UnorderedSet<UIWidget*> mStructurallyVolatileChildren;
std::optional<CSS::PropertyIdSet> mChangedProperties;
Uint32 mStateDepthCounter{ 0 }; Uint32 mStateDepthCounter{ 0 };
Uint64 mLoadedVersion{ 0 }; Uint64 mLoadedVersion{ 0 };
const CSS::StyleSheet* mLoadedStyleSheet{ nullptr }; const CSS::StyleSheet* mLoadedStyleSheet{ nullptr };
+5
View File
@@ -509,10 +509,15 @@ function popen( executable_path )
end end
function build_link_configuration( package_name, use_ee_icon ) function build_link_configuration( package_name, use_ee_icon )
configuration {}
includedirs { "include" } includedirs { "include" }
local extension = ""; local extension = "";
if package_name ~= "eepp" and package_name ~= "eepp-static" then
files { "src/eepp/core/memorymanagerglobal.cpp" }
end
if package_name == "eepp" then if package_name == "eepp" then
defines { "EE_EXPORTS" } defines { "EE_EXPORTS" }
elseif package_name == "eepp-static" then elseif package_name == "eepp-static" then
+5
View File
@@ -459,9 +459,14 @@ function get_architecture()
end end
function build_link_configuration( package_name, use_ee_icon ) function build_link_configuration( package_name, use_ee_icon )
filter {}
incdirs { "include" } incdirs { "include" }
local extension = ""; 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 if os.istarget("emscripten") and package_name ~= "eepp" and package_name ~= "eepp-static" then
local without_assets = { local without_assets = {
["eepp-empty-window"] = true, ["eepp-empty-window"] = true,
+167 -68
View File
@@ -1,3 +1,4 @@
#include <cstddef>
#include <eepp/core/debug.hpp> #include <eepp/core/debug.hpp>
#include <eepp/core/memorymanager.hpp> #include <eepp/core/memorymanager.hpp>
#include <eepp/system/filesystem.hpp> #include <eepp/system/filesystem.hpp>
@@ -5,42 +6,47 @@
#include <eepp/system/log.hpp> #include <eepp/system/log.hpp>
#include <eepp/system/mutex.hpp> #include <eepp/system/mutex.hpp>
#include <eepp/window/engine.hpp> #include <eepp/window/engine.hpp>
#include <new>
#include <tabulate/tabulate.hpp>
#if EE_PLATFORM == EE_PLATFORM_WIN
#include <malloc.h>
#endif
using namespace EE::System; using namespace EE::System;
using namespace EE::Window; 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 { namespace EE {
AllocatedPointer::AllocatedPointer( void* data, const std::string& file, int line, size_t memory, AllocatedPointer::AllocatedPointer( void* data, const std::string& file, int line, size_t memory,
bool track ) { bool track, bool globalAllocation ) {
mData = data; mData = data;
mFile = file; mFile = file;
mLine = line; mLine = line;
mMemory = memory; mMemory = memory;
mTrack = track; mTrack = track;
mGlobalAllocation = globalAllocation;
} }
namespace { namespace {
thread_local bool insideMemoryManager = false;
class MemoryManagerScope {
public:
MemoryManagerScope() : mWasInside( insideMemoryManager ) { insideMemoryManager = true; }
~MemoryManagerScope() { insideMemoryManager = mWasInside; }
private:
bool mWasInside;
};
struct MemoryManagerState { struct MemoryManagerState {
AllocatedPointerMap pointers; AllocatedPointerMap pointers;
size_t totalMemoryUsage{ 0 }; size_t totalMemoryUsage{ 0 };
size_t peakMemoryUsage{ 0 }; size_t peakMemoryUsage{ 0 };
AllocatedPointer biggestAllocation{ NULL, "", 0, 0 }; AllocatedPointer biggestAllocation{ NULL, "", 0, 0 };
AllocatedPointer biggestNonAnonymousAllocation{ NULL, "", 0, 0 };
Mutex allocationMutex; Mutex allocationMutex;
}; };
@@ -63,6 +69,7 @@ void* MemoryManager::reallocate( void* ptr, size_t size ) {
} }
void* MemoryManager::addPointerInPlace( void* place, const AllocatedPointer& aAllocatedPointer ) { void* MemoryManager::addPointerInPlace( void* place, const AllocatedPointer& aAllocatedPointer ) {
MemoryManagerScope scope;
auto& state = getMemoryManagerState(); auto& state = getMemoryManagerState();
Lock lock( state.allocationMutex ); Lock lock( state.allocationMutex );
AllocatedPointerMapIt it = state.pointers.find( place ); AllocatedPointerMapIt it = state.pointers.find( place );
@@ -75,11 +82,16 @@ void* MemoryManager::addPointerInPlace( void* place, const AllocatedPointer& aAl
} }
void* MemoryManager::addPointer( const AllocatedPointer& aAllocatedPointer ) { void* MemoryManager::addPointer( const AllocatedPointer& aAllocatedPointer ) {
MemoryManagerScope scope;
auto& state = getMemoryManagerState(); auto& state = getMemoryManagerState();
Lock lock( state.allocationMutex ); Lock lock( state.allocationMutex );
state.pointers.insert( auto result = state.pointers.insert(
AllocatedPointerMap::value_type( aAllocatedPointer.mData, aAllocatedPointer ) ); AllocatedPointerMap::value_type( aAllocatedPointer.mData, aAllocatedPointer ) );
if ( !result.second ) {
state.totalMemoryUsage -= result.first->second.mMemory;
result.first->second = aAllocatedPointer;
}
state.totalMemoryUsage += aAllocatedPointer.mMemory; state.totalMemoryUsage += aAllocatedPointer.mMemory;
@@ -91,6 +103,11 @@ void* MemoryManager::addPointer( const AllocatedPointer& aAllocatedPointer ) {
state.biggestAllocation = aAllocatedPointer; state.biggestAllocation = aAllocatedPointer;
} }
if ( !aAllocatedPointer.mGlobalAllocation &&
aAllocatedPointer.mMemory > state.biggestNonAnonymousAllocation.mMemory ) {
state.biggestNonAnonymousAllocation = aAllocatedPointer;
}
if ( aAllocatedPointer.mTrack ) if ( aAllocatedPointer.mTrack )
eePRINTL( "Allocating pointer %p at '%s' %d", aAllocatedPointer.mData, eePRINTL( "Allocating pointer %p at '%s' %d", aAllocatedPointer.mData,
aAllocatedPointer.mFile.c_str(), aAllocatedPointer.mLine ); aAllocatedPointer.mFile.c_str(), aAllocatedPointer.mLine );
@@ -99,6 +116,7 @@ void* MemoryManager::addPointer( const AllocatedPointer& aAllocatedPointer ) {
} }
void* MemoryManager::reallocPointer( void* data, const AllocatedPointer& aAllocatedPointer ) { void* MemoryManager::reallocPointer( void* data, const AllocatedPointer& aAllocatedPointer ) {
MemoryManagerScope scope;
auto& state = getMemoryManagerState(); auto& state = getMemoryManagerState();
Lock lock( state.allocationMutex ); Lock lock( state.allocationMutex );
@@ -134,12 +152,18 @@ void* MemoryManager::reallocPointer( void* data, const AllocatedPointer& aAlloca
if ( aAllocatedPointer.mMemory > state.biggestAllocation.mMemory ) { if ( aAllocatedPointer.mMemory > state.biggestAllocation.mMemory ) {
state.biggestAllocation = aAllocatedPointer; state.biggestAllocation = aAllocatedPointer;
} }
if ( !aAllocatedPointer.mGlobalAllocation &&
aAllocatedPointer.mMemory > state.biggestNonAnonymousAllocation.mMemory ) {
state.biggestNonAnonymousAllocation = aAllocatedPointer;
}
} }
return aAllocatedPointer.mData; return aAllocatedPointer.mData;
} }
bool MemoryManager::removePointer( void* data, const char* file, const size_t& line ) { bool MemoryManager::removePointer( void* data, const char* file, const size_t& line ) {
MemoryManagerScope scope;
auto& state = getMemoryManagerState(); auto& state = getMemoryManagerState();
Lock lock( state.allocationMutex ); Lock lock( state.allocationMutex );
@@ -161,24 +185,88 @@ bool MemoryManager::removePointer( void* data, const char* file, const size_t& l
return true; 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, "<global new>", 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() { size_t MemoryManager::getPeakMemoryUsage() {
MemoryManagerScope scope;
auto& state = getMemoryManagerState(); auto& state = getMemoryManagerState();
Lock lock( state.allocationMutex ); Lock lock( state.allocationMutex );
return state.peakMemoryUsage; return state.peakMemoryUsage;
} }
size_t MemoryManager::getTotalMemoryUsage() { size_t MemoryManager::getTotalMemoryUsage() {
MemoryManagerScope scope;
auto& state = getMemoryManagerState(); auto& state = getMemoryManagerState();
Lock lock( state.allocationMutex ); Lock lock( state.allocationMutex );
return state.totalMemoryUsage; return state.totalMemoryUsage;
} }
AllocatedPointer MemoryManager::getBiggestAllocation() { AllocatedPointer MemoryManager::getBiggestAllocation() {
MemoryManagerScope scope;
auto& state = getMemoryManagerState(); auto& state = getMemoryManagerState();
Lock lock( state.allocationMutex ); Lock lock( state.allocationMutex );
return state.biggestAllocation; return state.biggestAllocation;
} }
AllocatedPointer MemoryManager::getBiggestNonAnonymousAllocation() {
MemoryManagerScope scope;
auto& state = getMemoryManagerState();
Lock lock( state.allocationMutex );
return state.biggestNonAnonymousAllocation;
}
void MemoryManager::showResults() { void MemoryManager::showResults() {
#ifdef EE_MEMORY_MANAGER #ifdef EE_MEMORY_MANAGER
@@ -188,63 +276,74 @@ void MemoryManager::showResults() {
} }
Engine::destroySingleton(); Engine::destroySingleton();
MemoryManagerScope scope;
auto& state = getMemoryManagerState(); auto& state = getMemoryManagerState();
Lock lock( state.allocationMutex ); Lock lock( state.allocationMutex );
size_t globalAllocationCount = 0;
eePRINTL( "\n|--Memory Manager Report-------------------------------------|" ); size_t globalAllocationMemory = 0;
eePRINTL( "|" ); size_t leakCount = 0;
for ( const auto& pointer : state.pointers ) {
if ( state.pointers.empty() ) { if ( pointer.second.mGlobalAllocation ) {
eePRINTL( "| No memory leaks detected." ); ++globalAllocationCount;
} else { globalAllocationMemory += pointer.second.mMemory;
eePRINTL( "| Memory leaks detected: " ); } else {
eePRINTL( "|" ); ++leakCount;
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 );
} }
} }
eePRINTL( "|" ); eePRINTL( "\nMemory Manager Report" );
eePRINTL( "| Memory left: %s", eePRINTL( "=====================" );
FileSystem::sizeToString( static_cast<Int64>( state.totalMemoryUsage ) ).c_str() );
eePRINTL( "| Biggest allocation:" ); if ( leakCount == 0 ) {
eePRINTL( "| %s in file: %s at line: %d", eePRINTL( "\nNo actionable memory leaks detected." );
FileSystem::sizeToString( state.biggestAllocation.mMemory ).c_str(), } else {
state.biggestAllocation.mFile.c_str(), state.biggestAllocation.mLine ); eePRINTL( "\nActionable memory leaks: %zu\n", leakCount );
eePRINTL( "| Peak Memory Usage: %s", tabulate::Table leaks;
FileSystem::sizeToString( state.peakMemoryUsage ).c_str() ); leaks.add_row( { "Address", "File", "Line", "Memory usage" } );
eePRINTL( "|------------------------------------------------------------|\n" ); 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<Int64>( 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<Int64>( globalAllocationMemory ) ),
"Snapshot only; includes process-static and shared-library allocations" } );
summary.add_row( { "Total memory still live",
FileSystem::sizeToString( static_cast<Int64>( 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 #endif
} }
+91
View File
@@ -0,0 +1,91 @@
#include <eepp/core/memorymanager.hpp>
#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<std::size_t>( alignment ) );
}
void* operator new[]( std::size_t size, std::align_val_t alignment ) {
return EE::MemoryManager::allocateGlobal( size, static_cast<std::size_t>( 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<std::size_t>( 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<std::size_t>( 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
+8 -4
View File
@@ -1856,16 +1856,20 @@ void Text::draw( const Float& X, const Float& Y, const Vector2f& scale, const Fl
ensureColorUpdate(); ensureColorUpdate();
if ( mFontStyleConfig.Style & Shadow ) { if ( mFontStyleConfig.Style & Shadow ) {
std::vector<Color> colors;
Color shadowColor( getShadowColor() ); Color shadowColor( getShadowColor() );
if ( getFillColor().a != 255 ) { if ( getFillColor().a != 255 ) {
shadowColor.a = shadowColor.a =
(Uint8)( (Float)shadowColor.a * ( (Float)getFillColor().a / (Float)255 ) ); (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<Color> emptyColors;
const std::vector<Color>& shadowOutlineColors =
mFontStyleConfig.OutlineThickness > 0 ? mOutlineColors : emptyColors;
draw( X + mFontStyleConfig.ShadowOffset.x, Y + mFontStyleConfig.ShadowOffset.y, scale, draw( X + mFontStyleConfig.ShadowOffset.x, Y + mFontStyleConfig.ShadowOffset.y, scale,
rotation, effect, rotationCenter, scaleCenter, colors, rotation, effect, rotationCenter, scaleCenter, mShadowColors, shadowOutlineColors,
mFontStyleConfig.OutlineThickness > 0 ? mOutlineColors : std::vector<Color>{},
Color::Transparent ); Color::Transparent );
} }
+4 -3
View File
@@ -80,11 +80,12 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size )
Float txLen = tMax - tMin; Float txLen = tMax - tMin;
// Normalize all stops to [0,1] using the gradient-line pixel length // Normalize all stops to [0,1] using the gradient-line pixel length
std::vector<ColorStop> stops; SmallVector<ColorStop, 4> stops;
stops.reserve( mColorStops.size() ); stops.reserve( mColorStops.size() );
for ( const auto& s : mColorStops ) for ( const auto& s : mColorStops ) {
stops.push_back( stops.push_back(
ColorStop( s.getNormalized( txLen ), s.color, CSS::StyleSheetLength::Percentage ) ); ColorStop( s.getNormalized( txLen ), s.color, CSS::StyleSheetLength::Percentage ) );
}
std::sort( stops.begin(), stops.end(), std::sort( stops.begin(), stops.end(),
[]( const ColorStop& a, const ColorStop& b ) { return a.value < b.value; } ); []( 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 a;
Vector2f b; Vector2f b;
}; };
std::vector<PerpSeg> segments; SmallVector<PerpSeg, 4> segments;
segments.reserve( stops.size() ); segments.reserve( stops.size() );
for ( size_t i = 0; i < stops.size(); i++ ) { for ( size_t i = 0; i < stops.size(); i++ ) {
+4 -1
View File
@@ -392,7 +392,10 @@ void UIStyle::onStateChange() {
mWidget->getUISceneNode()->getStyleSheet().getElementStyles( mWidget, true ); mWidget->getUISceneNode()->getStyleSheet().getElementStyles( mWidget, true );
if ( newDefinition != mDefinition || mForceReapplyProperties ) { if ( newDefinition != mDefinition || mForceReapplyProperties ) {
PropertyIdSet changedProperties; if ( !mChangedProperties )
mChangedProperties.emplace();
PropertyIdSet& changedProperties = *mChangedProperties;
changedProperties.clear();
if ( mDefinition ) if ( mDefinition )
changedProperties = mDefinition->getPropertyIds(); changedProperties = mDefinition->getPropertyIds();
+9 -1
View File
@@ -1,3 +1,11 @@
#include "utest.h" #include "utest.h"
UTEST_MAIN() #include <eepp/system/filesystem.hpp>
#include <eepp/system/sys.hpp>
UTEST_STATE();
int main( int argc, const char* const argv[] ) {
EE::System::FileSystem::changeWorkingDirectory( EE::System::Sys::getProcessPath() );
return utest_main( argc, argv );
}
@@ -0,0 +1,37 @@
#include "utest.h"
#include <eepp/core/memorymanager.hpp>
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 );
}
@@ -0,0 +1,72 @@
#include "utest.h"
#include <eepp/core/memorymanager.hpp>
#include <eepp/ui/css/propertyidset.hpp>
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 );
}
@@ -827,22 +827,21 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event
} }
const auto command = editor->getKeyBindings().getCommandFromKeyBind( eventShortcut ); const auto command = editor->getKeyBindings().getCommandFromKeyBind( eventShortcut );
const bool choiceNavigationShortcut = const bool choiceNavigationShortcut =
mSnippetChoiceSuggestions && mSnippetChoiceSuggestions && ( mShortcuts.prevSuggestion == eventShortcut ||
( mShortcuts["autocomplete-prev-suggestion"] == eventShortcut || mShortcuts.nextSuggestion == eventShortcut ||
mShortcuts["autocomplete-next-suggestion"] == eventShortcut || mShortcuts.firstSuggestion == eventShortcut ||
mShortcuts["autocomplete-first-suggestion"] == eventShortcut || mShortcuts.lastSuggestion == eventShortcut ||
mShortcuts["autocomplete-last-suggestion"] == eventShortcut || mShortcuts.prevSuggestionPage == eventShortcut ||
mShortcuts["autocomplete-prev-suggestion-page"] == eventShortcut || mShortcuts.nextSuggestionPage == eventShortcut );
mShortcuts["autocomplete-next-suggestion-page"] == eventShortcut );
if ( !choiceNavigationShortcut && isSnippetNavigationCommand( command ) ) if ( !choiceNavigationShortcut && isSnippetNavigationCommand( command ) )
cancelSnippetSession( &editor->getDocument(), true ); cancelSnippetSession( &editor->getDocument(), true );
} }
if ( mSignatureHelpVisible ) { if ( mSignatureHelpVisible ) {
if ( mShortcuts["autocomplete-close-signature-help"] == eventShortcut ) { if ( mShortcuts.closeSignatureHelp == eventShortcut ) {
resetSignatureHelp(); resetSignatureHelp();
editor->invalidateDraw(); editor->invalidateDraw();
return true; return true;
} else if ( mShortcuts["autocomplete-prev-signature-help"] == eventShortcut ) { } else if ( mShortcuts.prevSignatureHelp == eventShortcut ) {
if ( mSignatureHelp.signatures.size() > 1 ) { if ( mSignatureHelp.signatures.size() > 1 ) {
mSignatureHelpSelected = mSignatureHelpSelected == -1 ? 0 : mSignatureHelpSelected; mSignatureHelpSelected = mSignatureHelpSelected == -1 ? 0 : mSignatureHelpSelected;
++mSignatureHelpSelected; ++mSignatureHelpSelected;
@@ -853,7 +852,7 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event
} else if ( mSuggestions.empty() ) { } else if ( mSuggestions.empty() ) {
resetSignatureHelp(); resetSignatureHelp();
} }
} else if ( mShortcuts["autocomplete-next-signature-help"] == eventShortcut ) { } else if ( mShortcuts.nextSignatureHelp == eventShortcut ) {
if ( mSignatureHelp.signatures.size() > 1 ) { if ( mSignatureHelp.signatures.size() > 1 ) {
mSignatureHelpSelected = mSignatureHelpSelected <= 0 mSignatureHelpSelected = mSignatureHelpSelected <= 0
? mSignatureHelp.signatures.size() ? mSignatureHelp.signatures.size()
@@ -881,15 +880,14 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event
} }
} }
if ( !mSnippetChoiceSuggestions && if ( !mSnippetChoiceSuggestions && mShortcuts.updateSuggestions == eventShortcut ) {
mShortcuts["autocomplete-update-suggestions"] == eventShortcut ) {
std::string partialSymbol( getPartialSymbol( &editor->getDocument() ) ); std::string partialSymbol( getPartialSymbol( &editor->getDocument() ) );
updateSuggestions( partialSymbol, editor ); updateSuggestions( partialSymbol, editor );
return true; return true;
} }
if ( !mSuggestions.empty() ) { if ( !mSuggestions.empty() ) {
if ( mShortcuts["autocomplete-next-suggestion"] == eventShortcut ) { if ( mShortcuts.nextSuggestion == eventShortcut ) {
if ( mSuggestionIndex + 1 < (int)mSuggestions.size() ) { if ( mSuggestionIndex + 1 < (int)mSuggestions.size() ) {
mSuggestionIndex++; mSuggestionIndex++;
if ( mSuggestionIndex < mSuggestionsStartIndex ) if ( mSuggestionIndex < mSuggestionsStartIndex )
@@ -904,7 +902,7 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event
} }
editor->invalidateDraw(); editor->invalidateDraw();
return true; return true;
} else if ( mShortcuts["autocomplete-prev-suggestion"] == eventShortcut ) { } else if ( mShortcuts.prevSuggestion == eventShortcut ) {
if ( mSuggestionIndex - 1 < 0 ) { if ( mSuggestionIndex - 1 < 0 ) {
mSuggestionIndex = mSuggestions.size() - 1; mSuggestionIndex = mSuggestions.size() - 1;
mSuggestionsStartIndex = mSuggestionsStartIndex =
@@ -916,22 +914,22 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event
mSuggestionsStartIndex = mSuggestionIndex; mSuggestionsStartIndex = mSuggestionIndex;
editor->invalidateDraw(); editor->invalidateDraw();
return true; return true;
} else if ( mShortcuts["autocomplete-close-suggestion"] == eventShortcut ) { } else if ( mShortcuts.closeSuggestion == eventShortcut ) {
resetSuggestions( editor ); resetSuggestions( editor );
resetSignatureHelp(); resetSignatureHelp();
editor->invalidateDraw(); editor->invalidateDraw();
return true; return true;
} else if ( mShortcuts["autocomplete-first-suggestion"] == eventShortcut ) { } else if ( mShortcuts.firstSuggestion == eventShortcut ) {
mSuggestionIndex = 0; mSuggestionIndex = 0;
mSuggestionsStartIndex = 0; mSuggestionsStartIndex = 0;
editor->invalidateDraw(); editor->invalidateDraw();
return true; return true;
} else if ( mShortcuts["autocomplete-last-suggestion"] == eventShortcut ) { } else if ( mShortcuts.lastSuggestion == eventShortcut ) {
mSuggestionIndex = mSuggestions.size() - 1; mSuggestionIndex = mSuggestions.size() - 1;
mSuggestionsStartIndex = eemax( 0, (int)mSuggestions.size() - mSuggestionsMaxVisible ); mSuggestionsStartIndex = eemax( 0, (int)mSuggestions.size() - mSuggestionsMaxVisible );
editor->invalidateDraw(); editor->invalidateDraw();
return true; return true;
} else if ( mShortcuts["autocomplete-prev-suggestion-page"] == eventShortcut ) { } else if ( mShortcuts.prevSuggestionPage == eventShortcut ) {
if ( mSuggestionIndex - (int)( mSuggestionsMaxVisible - 1 ) >= 0 ) { if ( mSuggestionIndex - (int)( mSuggestionsMaxVisible - 1 ) >= 0 ) {
mSuggestionIndex -= ( mSuggestionsMaxVisible - 1 ); mSuggestionIndex -= ( mSuggestionsMaxVisible - 1 );
if ( mSuggestionIndex < mSuggestionsStartIndex ) if ( mSuggestionIndex < mSuggestionsStartIndex )
@@ -942,7 +940,7 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event
} }
editor->invalidateDraw(); editor->invalidateDraw();
return true; return true;
} else if ( mShortcuts["autocomplete-next-suggestion-page"] == eventShortcut ) { } else if ( mShortcuts.nextSuggestionPage == eventShortcut ) {
if ( mSuggestionIndex + mSuggestionsMaxVisible < (int)mSuggestions.size() ) { if ( mSuggestionIndex + mSuggestionsMaxVisible < (int)mSuggestions.size() ) {
mSuggestionIndex += mSuggestionsMaxVisible - 1; mSuggestionIndex += mSuggestionsMaxVisible - 1;
} else { } else {
@@ -952,9 +950,9 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event
eemax<int>( 0, mSuggestionIndex - ( mSuggestionsMaxVisible - 1 ) ); eemax<int>( 0, mSuggestionIndex - ( mSuggestionsMaxVisible - 1 ) );
editor->invalidateDraw(); editor->invalidateDraw();
return true; return true;
} else if ( mShortcuts["autocomplete-pick-suggestion"] == eventShortcut || } else if ( mShortcuts.pickSuggestion == eventShortcut ||
mShortcuts["autocomplete-pick-suggestion-alt"] == eventShortcut || mShortcuts.pickSuggestionAlt == eventShortcut ||
mShortcuts["autocomplete-pick-suggestion-alt-2"] == eventShortcut ) { mShortcuts.pickSuggestionAlt2 == eventShortcut ) {
pickSuggestion( editor ); pickSuggestion( editor );
return true; return true;
} }
@@ -1166,10 +1164,8 @@ SnippetParser::VariableMap AutoCompletePlugin::snippetVariables( TextDocument& d
{ "CURRENT_SECOND", formatSnippetTime( localTime, "%S" ) }, { "CURRENT_SECOND", formatSnippetTime( localTime, "%S" ) },
{ "CURRENT_MILLISECOND", { "CURRENT_MILLISECOND",
String::format( "%03d", static_cast<int>( milliseconds % 1000 ) ) }, String::format( "%03d", static_cast<int>( milliseconds % 1000 ) ) },
{ "CURRENT_SECONDS_UNIX", { "CURRENT_SECONDS_UNIX", String::toString( static_cast<Int64>( milliseconds / 1000 ) ) },
String::toString( static_cast<Int64>( milliseconds / 1000 ) ) }, { "CURRENT_MILLISECONDS_UNIX", String::toString( static_cast<Int64>( milliseconds ) ) },
{ "CURRENT_MILLISECONDS_UNIX",
String::toString( static_cast<Int64>( milliseconds ) ) },
{ "CURRENT_TIMEZONE_OFFSET", timezoneOffset }, { "CURRENT_TIMEZONE_OFFSET", timezoneOffset },
{ "CURRENT_TIMEZONE_NAME", formatSnippetTime( localTime, "%Z" ) }, { "CURRENT_TIMEZONE_NAME", formatSnippetTime( localTime, "%Z" ) },
{ "RANDOM", String::format( "%06d", Math::randi( 0, 999999 ) ) }, { "RANDOM", String::format( "%06d", Math::randi( 0, 999999 ) ) },
@@ -1864,8 +1860,21 @@ void AutoCompletePlugin::updateShortcuts() {
getManager()->getUISceneNode()->getEventDispatcher()->getInput(), keys ); getManager()->getUISceneNode()->getEventDispatcher()->getInput(), keys );
}; };
for ( const auto& kb : mKeyBindings ) mShortcuts.closeSuggestion = toShortcut( mKeyBindings["autocomplete-close-suggestion"] );
mShortcuts[kb.first] = toShortcut( kb.second ); 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 ) { PluginRequestHandle AutoCompletePlugin::processResponse( const PluginMessage& msg ) {
@@ -193,7 +193,22 @@ class AutoCompletePlugin : public Plugin {
size_t mMaxLabelCharacters{ 100 }; size_t mMaxLabelCharacters{ 100 };
String::HashType mConfigHash{ 0 }; String::HashType mConfigHash{ 0 };
std::unordered_map<std::string, std::string> mKeyBindings; std::unordered_map<std::string, std::string> mKeyBindings;
UnorderedMap<std::string, KeyBindings::Shortcut> 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 mMaxSuggestionDocumentationWidth{ "100%" };
std::string mMaxSignatureHelperWidth{ "90%" }; std::string mMaxSignatureHelperWidth{ "90%" };
bool mSignatureHelpMultiLine{ true }; bool mSignatureHelpMultiLine{ true };