]> git.lizzy.rs Git - irrlicht.git/blob - include/IProfiler.h
de-deprecate clearZBuffer
[irrlicht.git] / include / IProfiler.h
1 // This file is part of the "Irrlicht Engine".\r
2 // For conditions of distribution and use, see copyright notice in irrlicht.h\r
3 // Written by Michael Zeilfelder\r
4 \r
5 #ifndef __I_PROFILER_H_INCLUDED__\r
6 #define __I_PROFILER_H_INCLUDED__\r
7 \r
8 #include "IrrCompileConfig.h"\r
9 #include "irrString.h"\r
10 #include "irrArray.h"\r
11 #include "ITimer.h"\r
12 #include <limits.h>     // for INT_MAX (we should have a S32_MAX...)\r
13 \r
14 namespace irr\r
15 {\r
16 \r
17 class ITimer;\r
18 \r
19 //! Used to store the profile data (and also used for profile group data).\r
20 struct SProfileData\r
21 {\r
22         friend class IProfiler;\r
23 \r
24     SProfileData()\r
25         {\r
26                 GroupIndex = 0;\r
27                 reset();\r
28         }\r
29 \r
30         bool operator<(const SProfileData& pd) const\r
31         {\r
32                 return Id < pd.Id;\r
33         }\r
34 \r
35         bool operator==(const SProfileData& pd) const\r
36         {\r
37                 return Id == pd.Id;\r
38         }\r
39 \r
40         u32 getGroupIndex() const\r
41         {\r
42                 return GroupIndex;\r
43         }\r
44 \r
45         const core::stringw& getName() const\r
46         {\r
47                 return Name;\r
48         }\r
49 \r
50         //! Each time profiling for this data is stopped it increases the counter by 1.\r
51         u32 getCallsCounter() const\r
52         {\r
53                 return CountCalls;\r
54         }\r
55 \r
56         //! Longest time a profile call for this id took from start until it was stopped again.\r
57         u32 getLongestTime() const\r
58         {\r
59                 return LongestTime;\r
60         }\r
61 \r
62         //! Time spend between start/stop\r
63         u32 getTimeSum() const\r
64         {\r
65                 return TimeSum;\r
66         }\r
67 \r
68 private:\r
69 \r
70         // just to be used for searching as it does no initialization besides id\r
71         SProfileData(u32 id) : Id(id) {}\r
72 \r
73         void reset()\r
74         {\r
75                 CountCalls = 0;\r
76                 LongestTime = 0;\r
77                 TimeSum = 0;\r
78                 LastTimeStarted = 0;\r
79                 StartStopCounter = 0;\r
80         }\r
81 \r
82         s32 Id;\r
83     u32 GroupIndex;\r
84         core::stringw Name;\r
85 \r
86         s32 StartStopCounter; // 0 means stopped > 0 means it runs.\r
87     u32 CountCalls;\r
88     u32 LongestTime;\r
89     u32 TimeSum;\r
90 \r
91     u32 LastTimeStarted;\r
92 };\r
93 \r
94 //! Code-profiler. Please check the example in the Irrlicht examples folder about how to use it.\r
95 // Implementer notes:\r
96 // The design is all about allowing to use the central start/stop mechanism with minimal time overhead.\r
97 // This is why the class works without a virtual functions interface contrary to the usual Irrlicht design.\r
98 // And also why it works with id's instead of strings in the start/stop functions even if it makes using\r
99 // the class slightly harder.\r
100 // The class comes without reference-counting because the profiler instance is never released (TBD).\r
101 class IProfiler\r
102 {\r
103 public:\r
104         //! Constructor. You could use this to create a new profiler, but usually getProfiler() is used to access the global instance.\r
105     IProfiler() : Timer(0), NextAutoId(INT_MAX)\r
106         {}\r
107 \r
108         virtual ~IProfiler()\r
109         {}\r
110 \r
111         //! Add an id with given name and group which can be used for profiling with start/stop\r
112         /** After calling this once you can start/stop profiling for the given id.\r
113         \param id: Should be >= 0 as negative id's are reserved for Irrlicht. Also very large numbers (near INT_MAX) might\r
114         have been added automatically by the other add function.\r
115         \param name: Name for displaying profile data.\r
116         \param groupName: Each id belongs into a group - this helps on displaying profile data. */\r
117     inline void add(s32 id, const core::stringw &name, const core::stringw &groupName);\r
118 \r
119         //! Add an automatically generated for the given name and group which can be used for profiling with start/stop.\r
120         /** After calling this once you can start/stop profiling with the returned id.\r
121         \param name: Name for displaying profile data.\r
122         \param groupName: Each id belongs into a group - this helps on displaying profile data.\r
123         \return Automatic id's start at INT_MAX and count down for each new id. If the name already has an id then that id will be returned. */\r
124     inline s32 add(const core::stringw &name, const core::stringw &groupName);\r
125 \r
126         //! Return the number of profile data blocks. There is one for each id.\r
127     u32 getProfileDataCount() const\r
128     {\r
129                 return ProfileDatas.size();\r
130     }\r
131 \r
132         //! Search for the index of the profile data by name\r
133         /** \param result Receives the resulting data index when one was found.\r
134         \param name String with name to search for\r
135         \return true when found, false when not found */\r
136         inline bool findDataIndex(u32 & result, const core::stringw &name) const;\r
137 \r
138         //! Get the profile data\r
139         /** \param index A value between 0 and getProfileDataCount()-1. Indices can change when new id's are added.*/\r
140     const SProfileData& getProfileDataByIndex(u32 index) const\r
141     {\r
142                 return ProfileDatas[index];\r
143     }\r
144 \r
145         //! Get the profile data\r
146         /** \param id Same value as used in ::add\r
147         \return Profile data for the given id or 0 when it does not exist.      */\r
148     inline const SProfileData* getProfileDataById(u32 id);\r
149 \r
150         //! Get the number of profile groups. Will be at least 1.\r
151         /** NOTE: The first groups is always L"overview" which is an overview for all existing groups */\r
152     inline u32 getGroupCount() const\r
153     {\r
154                 return ProfileGroups.size();\r
155     }\r
156 \r
157     //! Get profile data for a group.\r
158     /** NOTE: The first groups is always L"overview" which is an overview for all existing groups */\r
159     inline const SProfileData& getGroupData(u32 index) const\r
160     {\r
161                 return ProfileGroups[index];\r
162     }\r
163 \r
164     //! Find the group index by the group-name\r
165     /** \param result Receives the resulting group index when one was found.\r
166         \param name String with name to search for\r
167         \return true when found, false when not found */\r
168         inline bool findGroupIndex(u32 & result, const core::stringw &name) const;\r
169 \r
170 \r
171         //! Start profile-timing for the given id\r
172         /** This increases an internal run-counter for the given id. It will profile as long as that counter is > 0.\r
173         NOTE: you have to add the id first with one of the ::add functions\r
174         */\r
175         inline void start(s32 id);\r
176 \r
177         //! Stop profile-timing for the given id\r
178         /** This increases an internal run-counter for the given id. If it reaches 0 the time since start is recorded.\r
179                 You should have the same amount of start and stop calls. If stop is called more often than start\r
180                 then the additional stop calls will be ignored (counter never goes below 0)\r
181         */\r
182     inline void stop(s32 id);\r
183 \r
184         //! Reset profile data for the given id\r
185     inline void resetDataById(s32 id);\r
186 \r
187         //! Reset profile data for the given index\r
188     inline void resetDataByIndex(u32 index);\r
189 \r
190     //! Reset profile data for a whole group\r
191     inline void resetGroup(u32 index);\r
192 \r
193     //! Reset all profile data\r
194     /** NOTE: This is not deleting id's or groups, just resetting all timers to 0. */\r
195     inline void resetAll();\r
196 \r
197         //! Write all profile-data into a string\r
198         /** \param result Receives the result string.\r
199         \param includeOverview When true a group-overview is attached first\r
200         \param suppressUncalled When true elements which got never called are not printed */\r
201     virtual void printAll(core::stringw &result, bool includeOverview=false,bool suppressUncalled=true) const = 0;\r
202 \r
203         //! Write the profile data of one group into a string\r
204         /** \param result Receives the result string.\r
205         \param groupIndex_      */\r
206     virtual void printGroup(core::stringw &result, u32 groupIndex, bool suppressUncalled) const = 0;\r
207 \r
208 protected:\r
209 \r
210     inline u32 addGroup(const core::stringw &name);\r
211 \r
212         // I would prefer using os::Timer, but os.h is not in the public interface so far.\r
213         // Timer must be initialized by the implementation.\r
214     ITimer * Timer;\r
215         core::array<SProfileData> ProfileDatas;\r
216     core::array<SProfileData> ProfileGroups;\r
217 \r
218 private:\r
219     s32 NextAutoId;     // for giving out id's automatically\r
220 };\r
221 \r
222 //! Access the Irrlicht profiler object.\r
223 /** Profiler is always accessible, except in destruction of global objects.\r
224 If you want to get internal profiling information about the engine itself\r
225 you will have to re-compile the engine with _IRR_COMPILE_WITH_PROFILING_ enabled.\r
226 But you can use the profiler for profiling your own projects without that. */\r
227 IRRLICHT_API IProfiler& IRRCALLCONV getProfiler();\r
228 \r
229 //! Class where the objects profile their own life-time.\r
230 /** This is a comfort wrapper around the IProfiler start/stop mechanism which is easier to use\r
231 when you want to profile a scope. You only have to create an object and it will profile it's own lifetime\r
232 for the given id. */\r
233 class CProfileScope\r
234 {\r
235 public:\r
236         //! Construct with an known id.\r
237         /** This is the fastest scope constructor, but the id must have been added before.\r
238         \param id Any id which you did add to the profiler before. */\r
239         CProfileScope(s32 id)\r
240         : Id(id), Profiler(getProfiler())\r
241         {\r
242                 Profiler.start(Id);\r
243         }\r
244 \r
245         //! Object will create the given name, groupName combination for the id if it doesn't exist already\r
246         /** \param id: Should be >= 0 as negative id's are reserved for Irrlicht. Also very large numbers (near INT_MAX) might\r
247         have been created already by the automatic add function of ::IProfiler.\r
248         \param name: Name for displaying profile data.\r
249         \param groupName: Each id belongs into a group - this helps on displaying profile data. */\r
250         CProfileScope(s32 id, const core::stringw &name, const core::stringw &groupName)\r
251         : Id(id), Profiler(getProfiler())\r
252         {\r
253                 Profiler.add(Id, name, groupName);\r
254                 Profiler.start(Id);\r
255         }\r
256 \r
257         //! Object will create an id for the given name, groupName combination if they don't exist already\r
258         /** Slowest scope constructor, but usually still fine unless speed is very critical.\r
259         \param name: Name for displaying profile data.\r
260         \param groupName: Each id belongs into a group - this helps on displaying profile data. */\r
261         CProfileScope(const core::stringw &name, const core::stringw &groupName)\r
262         : Profiler(getProfiler())\r
263         {\r
264                 Id = Profiler.add(name, groupName);\r
265                 Profiler.start(Id);\r
266         }\r
267 \r
268         ~CProfileScope()\r
269         {\r
270                 Profiler.stop(Id);\r
271         }\r
272 \r
273 protected:\r
274         s32 Id;\r
275         IProfiler& Profiler;\r
276 };\r
277 \r
278 \r
279 // IMPLEMENTATION for in-line stuff\r
280 \r
281 void IProfiler::start(s32 id)\r
282 {\r
283         s32 idx = ProfileDatas.binary_search(SProfileData(id));\r
284         if ( idx >= 0 && Timer )\r
285         {\r
286                 ++ProfileDatas[idx].StartStopCounter;\r
287                 if (ProfileDatas[idx].StartStopCounter == 1 )\r
288                         ProfileDatas[idx].LastTimeStarted = Timer->getRealTime();\r
289         }\r
290 }\r
291 \r
292 void IProfiler::stop(s32 id)\r
293 {\r
294         if ( Timer )\r
295         {\r
296                 u32 timeNow = Timer->getRealTime();\r
297                 s32 idx = ProfileDatas.binary_search(SProfileData(id));\r
298                 if ( idx >= 0 )\r
299                 {\r
300                         SProfileData &data = ProfileDatas[idx];\r
301                         --ProfileDatas[idx].StartStopCounter;\r
302                         if ( data.LastTimeStarted != 0 && ProfileDatas[idx].StartStopCounter == 0)\r
303                         {\r
304                                 // update data for this id\r
305                                 ++data.CountCalls;\r
306                                 u32 diffTime = timeNow - data.LastTimeStarted;\r
307                                 data.TimeSum += diffTime;\r
308                                 if ( diffTime > data.LongestTime )\r
309                                         data.LongestTime = diffTime;\r
310                                 data.LastTimeStarted = 0;\r
311 \r
312                                 // update data of it's group\r
313                                 SProfileData & group = ProfileGroups[data.GroupIndex];\r
314                                 ++group.CountCalls;\r
315                                 group.TimeSum += diffTime;\r
316                                 if ( diffTime > group.LongestTime )\r
317                                         group.LongestTime = diffTime;\r
318                                 group.LastTimeStarted = 0;\r
319                         }\r
320                         else if ( ProfileDatas[idx].StartStopCounter < 0 )\r
321                         {\r
322                                 // ignore additional stop calls\r
323                                 ProfileDatas[idx].StartStopCounter = 0;\r
324                         }\r
325                 }\r
326         }\r
327 }\r
328 \r
329 s32 IProfiler::add(const core::stringw &name, const core::stringw &groupName)\r
330 {\r
331         u32 index;\r
332         if ( findDataIndex(index, name) )\r
333         {\r
334                 add( ProfileDatas[index].Id, name, groupName );\r
335                 return ProfileDatas[index].Id;\r
336         }\r
337         else\r
338         {\r
339                 s32 id = NextAutoId;\r
340                 --NextAutoId;\r
341                 add( id, name, groupName );\r
342                 return id;\r
343         }\r
344 }\r
345 \r
346 void IProfiler::add(s32 id, const core::stringw &name, const core::stringw &groupName)\r
347 {\r
348         u32 groupIdx;\r
349         if ( !findGroupIndex(groupIdx, groupName) )\r
350         {\r
351                 groupIdx = addGroup(groupName);\r
352         }\r
353 \r
354         SProfileData data(id);\r
355         s32 idx = ProfileDatas.binary_search(data);\r
356         if ( idx < 0 )\r
357         {\r
358                 data.reset();\r
359                 data.GroupIndex = groupIdx;\r
360                 data.Name = name;\r
361 \r
362                 ProfileDatas.push_back(data);\r
363                 ProfileDatas.sort();\r
364         }\r
365         else\r
366         {\r
367                 // only reset on group changes, otherwise we want to keep the data or coding CProfileScope would become tricky.\r
368                 if ( groupIdx != ProfileDatas[idx].GroupIndex )\r
369                 {\r
370                         resetDataByIndex((u32)idx);\r
371                         ProfileDatas[idx].GroupIndex = groupIdx;\r
372                 }\r
373                 ProfileDatas[idx].Name = name;\r
374         }\r
375 }\r
376 \r
377 u32 IProfiler::addGroup(const core::stringw &name)\r
378 {\r
379     SProfileData group;\r
380         group.Id = -1;  // Id for groups doesn't matter so far\r
381         group.Name = name;\r
382     ProfileGroups.push_back(group);\r
383     return ProfileGroups.size()-1;\r
384 }\r
385 \r
386 bool IProfiler::findDataIndex(u32 & result, const core::stringw &name) const\r
387 {\r
388         for ( u32 i=0; i < ProfileDatas.size(); ++i )\r
389         {\r
390                 if ( ProfileDatas[i].Name == name )\r
391                 {\r
392                         result = i;\r
393                         return true;\r
394                 }\r
395         }\r
396 \r
397         return false;\r
398 }\r
399 \r
400 const SProfileData* IProfiler::getProfileDataById(u32 id)\r
401 {\r
402         SProfileData data(id);\r
403     s32 idx = ProfileDatas.binary_search(data);\r
404         if ( idx >= 0 )\r
405                 return &ProfileDatas[idx];\r
406         return NULL;\r
407 }\r
408 \r
409 bool IProfiler::findGroupIndex(u32 & result, const core::stringw &name) const\r
410 {\r
411         for ( u32 i=0; i < ProfileGroups.size(); ++i )\r
412         {\r
413                 if ( ProfileGroups[i].Name == name )\r
414                 {\r
415                         result = i;\r
416                         return true;\r
417                 }\r
418         }\r
419 \r
420         return false;\r
421 }\r
422 \r
423 void IProfiler::resetDataById(s32 id)\r
424 {\r
425         s32 idx = ProfileDatas.binary_search(SProfileData(id));\r
426     if ( idx >= 0 )\r
427     {\r
428                 resetDataByIndex((u32)idx);\r
429     }\r
430 }\r
431 \r
432 void IProfiler::resetDataByIndex(u32 index)\r
433 {\r
434         SProfileData &data = ProfileDatas[index];\r
435 \r
436         SProfileData & group = ProfileGroups[data.GroupIndex];\r
437         group.CountCalls -= data.CountCalls;\r
438         group.TimeSum -= data.TimeSum;\r
439 \r
440         data.reset();\r
441 }\r
442 \r
443 //! Reset profile data for a whole group\r
444 void IProfiler::resetGroup(u32 index)\r
445 {\r
446         for ( u32 i=0; i<ProfileDatas.size(); ++i )\r
447     {\r
448                 if ( ProfileDatas[i].GroupIndex == index )\r
449                         ProfileDatas[i].reset();\r
450     }\r
451     if ( index < ProfileGroups.size() )\r
452                 ProfileGroups[index].reset();\r
453 }\r
454 \r
455 void IProfiler::resetAll()\r
456 {\r
457         for ( u32 i=0; i<ProfileDatas.size(); ++i )\r
458     {\r
459                 ProfileDatas[i].reset();\r
460     }\r
461 \r
462         for ( u32 i=0; i<ProfileGroups.size(); ++i )\r
463     {\r
464                 ProfileGroups[i].reset();\r
465     }\r
466 }\r
467 \r
468 //! For internal engine use:\r
469 //! Code inside IRR_PROFILE is only executed when _IRR_COMPILE_WITH_PROFILING_ is set\r
470 //! This allows disabling all profiler code completely by changing that define.\r
471 //! It's generally useful to wrap profiler-calls in application code with a similar macro.\r
472 #ifdef _IRR_COMPILE_WITH_PROFILING_\r
473         #define IRR_PROFILE(X) X\r
474 #else\r
475         #define IRR_PROFILE(X)\r
476 #endif // IRR_PROFILE\r
477 \r
478 } // namespace irr\r
479 \r
480 #endif // __I_PROFILER_H_INCLUDED__\r