QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskObjectCounter.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskObjectCounter.h"
7
8#include <qdebug.h>
9#include <qset.h>
10#include <qobject.h>
11#include <qcoreapplication.h>
12
13QSK_QT_PRIVATE_BEGIN
14#include <private/qhooks_p.h>
15#include <private/qquickitem_p.h>
16QSK_QT_PRIVATE_END
17
18#define QSK_OBJECT_INFO 0
19
20#if QSK_OBJECT_INFO
21#include <qset.h>
22#endif
23
24static inline bool qskIsItem( const QObject* object )
25{
26 /*
27 The addObject hook is called from the constructor of QObject,
28 where we don't have the derived class constructed yet.
29 So we can't cast the object itself and also have to rely on
30 RTTI being enabled. TODO ...
31 */
32
33 auto o_p = QObjectPrivate::get( const_cast< QObject* >( object ) );
34 return dynamic_cast< QQuickItemPrivate* >( o_p ) != nullptr;
35}
36
37namespace
38{
39 class Counter
40 {
41 public:
42 Counter()
43 {
44 reset();
45 }
46
47 void reset()
48 {
49 created = destroyed = current = maximum = 0;
50 }
51
52 void increment()
53 {
54 created++;
55 current++;
56
57 if ( current > maximum )
58 maximum = current;
59 }
60
61 void decrement()
62 {
63 destroyed++;
64 current--;
65 }
66
67 int created;
68 int destroyed;
69 int current;
70 int maximum;
71 };
72
73 class CounterData
74 {
75 public:
76 Counter counter[ 2 ];
77
78#if QSK_OBJECT_INFO
79 QSet< const QObject* > objectTable;
80#endif
81 };
82
83 class CounterHook
84 {
85 public:
86 CounterHook();
87 ~CounterHook();
88
89 void registerCounters( CounterData*, bool on );
90 bool isCountersRegistered( const CounterData* ) const;
91
92 bool isActive() const;
93
94 void startup();
95 void addObject( QObject* );
96 void removeObject( QObject* );
97
98 static void cleanupHook();
99
100 private:
101 static void startupHook();
102 static void addObjectHook( QObject* );
103 static void removeObjectHook( QObject* );
104
105 QSet< CounterData* > m_counterDataSet;
106
107 quintptr m_otherStartup;
108 quintptr m_otherAddObject;
109 quintptr m_otherRemoveObject;
110 };
111}
112
113static bool qskAutoDeleteHook = false;
114static CounterHook* qskCounterHook = nullptr;
115
116CounterHook::CounterHook()
117{
118 m_otherStartup = qtHookData[ QHooks::Startup ];
119 m_otherAddObject = qtHookData[ QHooks::AddQObject ];
120 m_otherRemoveObject = qtHookData[ QHooks::RemoveQObject ];
121
122 qtHookData[ QHooks::Startup ] = reinterpret_cast< quintptr >( &startupHook );
123 qtHookData[ QHooks::AddQObject ] = reinterpret_cast< quintptr >( &addObjectHook );
124 qtHookData[ QHooks::RemoveQObject ] = reinterpret_cast< quintptr >( &removeObjectHook );
125}
126
127CounterHook::~CounterHook()
128{
129 qtHookData[ QHooks::Startup ] = m_otherStartup;
130 qtHookData[ QHooks::AddQObject ] = m_otherAddObject;
131 qtHookData[ QHooks::RemoveQObject ] = m_otherRemoveObject;
132}
133
134void CounterHook::registerCounters( CounterData* data, bool on )
135{
136 if ( on )
137 m_counterDataSet.insert( data );
138 else
139 m_counterDataSet.remove( data );
140}
141
142bool CounterHook::isCountersRegistered( const CounterData* data ) const
143{
144 return m_counterDataSet.contains( const_cast< CounterData* >( data ) );
145}
146
147bool CounterHook::isActive() const
148{
149 return !m_counterDataSet.isEmpty();
150}
151
152void CounterHook::startup()
153{
154#if 0
155 qDebug() << "** QskObjectCounterHook enabled";
156#endif
157 if ( m_otherStartup )
158 reinterpret_cast< QHooks::StartupCallback >( m_otherStartup )();
159}
160
161void CounterHook::addObject( QObject* object )
162{
163 const bool isItem = qskIsItem( object );
164
165 for ( auto counterData : std::as_const( m_counterDataSet ) )
166 {
167 counterData->counter[ QskObjectCounter::Objects ].increment();
168
169 if ( isItem )
170 counterData->counter[ QskObjectCounter::Items ].increment();
171
172#if QSK_OBJECT_INFO
173 counterData->objectTable.insert( object );
174#endif
175 }
176
177 if ( m_otherAddObject )
178 reinterpret_cast< QHooks::AddQObjectCallback >( m_otherAddObject )( object );
179}
180
181void CounterHook::removeObject( QObject* object )
182{
183 const bool isItem = qskIsItem( object );
184
185 for ( auto counterData : std::as_const( m_counterDataSet ) )
186 {
187 counterData->counter[ QskObjectCounter::Objects ].decrement();
188
189 if ( isItem )
190 counterData->counter[ QskObjectCounter::Items ].decrement();
191
192#if QSK_OBJECT_INFO
193 counterData->objectTable.remove( object );
194#endif
195 }
196
197 if ( m_otherRemoveObject )
198 reinterpret_cast< QHooks::RemoveQObjectCallback >( m_otherRemoveObject )( object );
199}
200
201void CounterHook::startupHook()
202{
203 if ( qskCounterHook )
204 qskCounterHook->startup();
205}
206
207void CounterHook::addObjectHook( QObject* object )
208{
209 if ( qskCounterHook )
210 qskCounterHook->addObject( object );
211}
212
213void CounterHook::removeObjectHook( QObject* object )
214{
215 if ( qskCounterHook )
216 qskCounterHook->removeObject( object );
217}
218
219void CounterHook::cleanupHook()
220{
221 if ( qskCounterHook && !qskCounterHook->isActive() )
222 {
223 delete qskCounterHook;
224 qskCounterHook = nullptr;
225 }
226
227 // From now on we remove the hooks as soon as there are no counters
228 qskAutoDeleteHook = true;
229}
230
231static void qskInstallCleanupHookHandler()
232{
233 qAddPostRoutine( CounterHook::cleanupHook );
234}
235
236Q_COREAPP_STARTUP_FUNCTION( qskInstallCleanupHookHandler )
237
238class QskObjectCounter::PrivateData
239{
240 public:
241 PrivateData( bool debugAtDestruction )
242 : debugAtDestruction( debugAtDestruction )
243 {
244 }
245
246 CounterData counterData;
247 const bool debugAtDestruction;
248};
249
250QskObjectCounter::QskObjectCounter( bool debugAtDestruction )
251 : m_data( new PrivateData( debugAtDestruction ) )
252{
253 setActive( true );
254}
255
256QskObjectCounter::~QskObjectCounter()
257{
258 setActive( false );
259
260 if ( m_data->debugAtDestruction )
261 dump();
262}
263
264void QskObjectCounter::setActive( bool on )
265{
266 if ( on )
267 {
268 if ( qskCounterHook == nullptr )
269 qskCounterHook = new CounterHook();
270
271 qskCounterHook->registerCounters( &m_data->counterData, on );
272 }
273 else
274 {
275 qskCounterHook->registerCounters( &m_data->counterData, on );
276 if ( !qskCounterHook->isActive() )
277 {
278 if ( qskAutoDeleteHook )
279 {
280 delete qskCounterHook;
281 qskCounterHook = nullptr;
282 }
283 }
284 }
285}
286
287bool QskObjectCounter::isActive() const
288{
289 return qskCounterHook && qskCounterHook->isCountersRegistered( &m_data->counterData );
290}
291
292void QskObjectCounter::reset()
293{
294 auto& counters = m_data->counterData.counter;
295
296 counters[ Objects ].reset();
297 counters[ Items ].reset();
298}
299
300int QskObjectCounter::created( ObjectType objectType ) const
301{
302 return m_data->counterData.counter[ objectType ].created;
303}
304
305int QskObjectCounter::destroyed( ObjectType objectType ) const
306{
307 return m_data->counterData.counter[ objectType ].destroyed;
308}
309
310int QskObjectCounter::current( ObjectType objectType ) const
311{
312 return m_data->counterData.counter[ objectType ].current;
313}
314
315int QskObjectCounter::maximum( ObjectType objectType ) const
316{
317 return m_data->counterData.counter[ objectType ].maximum;
318}
319
320void QskObjectCounter::debugStatistics( QDebug debug, ObjectType objectType ) const
321{
322 const auto& c = m_data->counterData.counter[ objectType ];
323
324 QDebugStateSaver saver( debug );
325 debug.nospace();
326 debug << '(';
327 debug << "created: " << c.created
328 << ", destroyed: " << c.destroyed
329 << ", current: " << c.current
330 << ", maximum: " << c.maximum;
331 debug << ')';
332
333#if QSK_OBJECT_INFO
334 if ( objectType == Objects )
335 {
336 const auto& objectTable = m_data->counterData.objectTable;
337
338 if ( !objectTable.isEmpty() )
339 {
340 debug << "\n\t=== Leaks ===\n";
341 for ( const auto object : objectTable )
342 {
343 debug << "\tClass: " << object->metaObject()->className();
344 if ( !object->objectName().isEmpty() )
345 debug << " Name: " << object->objectName();
346 debug << '\n';
347 }
348 }
349 }
350#endif
351}
352
353void QskObjectCounter::dump() const
354{
355 QDebug debug = qDebug();
356
357 QDebugStateSaver saver( debug );
358
359 debug.nospace();
360
361 debug << "* Statistics\n";
362 debug << " Objects: ";
363 debugStatistics( debug, Objects );
364
365 debug << "\n Items: ";
366 debugStatistics( debug, Items );
367}
368
369#ifndef QT_NO_DEBUG_STREAM
370
371QDebug operator<<( QDebug debug, const QskObjectCounter& counter )
372{
373 counter.debugStatistics( debug, QskObjectCounter::Objects );
374 return debug;
375}
376
377#endif