mirror of
https://github.com/luanti-org/luanti.git
synced 2025-12-13 10:45:21 +01:00
Profiler and profiler graph improvements
This commit is contained in:
@@ -263,7 +263,6 @@ void ClientEnvironment::step(float dtime)
|
||||
/*
|
||||
Step and handle simple objects
|
||||
*/
|
||||
g_profiler->avg("ClientEnv: CSO count [#]", m_simple_objects.size());
|
||||
for (auto i = m_simple_objects.begin(); i != m_simple_objects.end();) {
|
||||
ClientSimpleObject *simple = *i;
|
||||
|
||||
|
||||
@@ -717,7 +717,6 @@ void ClientMap::updateDrawList()
|
||||
g_profiler->avg("MapBlock sides skipped [#]", sides_skipped);
|
||||
g_profiler->avg("MapBlocks examined [#]", blocks_visited);
|
||||
}
|
||||
g_profiler->avg("MapBlocks shortlist [#]", shortlist.size());
|
||||
|
||||
// must populate either only to avoid duplicates
|
||||
assert(m_drawlist.empty() || shortlist.empty());
|
||||
|
||||
@@ -108,7 +108,6 @@ void Clouds::updateMesh()
|
||||
return;
|
||||
}
|
||||
|
||||
ScopeProfiler sp(g_profiler, "Clouds::updateMesh()", SPT_AVG);
|
||||
m_mesh_origin = m_origin;
|
||||
m_last_noise_center = center_of_drawing_in_noise_i;
|
||||
m_mesh_valid = true;
|
||||
@@ -374,7 +373,7 @@ void Clouds::render()
|
||||
return;
|
||||
|
||||
#if 0
|
||||
{
|
||||
{ // proof that m_box is correct
|
||||
video::SMaterial tmp;
|
||||
tmp.Thickness = 1.f;
|
||||
driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
|
||||
|
||||
@@ -1257,16 +1257,6 @@ void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
|
||||
profiler_print_interval = 3;
|
||||
}
|
||||
|
||||
if (profiler_interval.step(dtime, profiler_print_interval)) {
|
||||
if (print_to_log) {
|
||||
infostream << "Profiler:" << std::endl;
|
||||
g_profiler->print(infostream);
|
||||
}
|
||||
|
||||
m_game_ui->updateProfiler();
|
||||
g_profiler->clear();
|
||||
}
|
||||
|
||||
// Update graphs
|
||||
g_profiler->graphAdd("Time non-rendering [us]",
|
||||
draw_times.busy_time - stats.drawtime);
|
||||
@@ -1281,6 +1271,16 @@ void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
|
||||
stats2.PrimitivesDrawn / float(stats2.Drawcalls));
|
||||
g_profiler->avg("Irr: HW buffers uploaded", stats2.HWBuffersUploaded);
|
||||
g_profiler->avg("Irr: HW buffers active", stats2.HWBuffersActive);
|
||||
|
||||
if (profiler_interval.step(dtime, profiler_print_interval)) {
|
||||
if (print_to_log) {
|
||||
infostream << "Profiler:" << std::endl;
|
||||
g_profiler->print(infostream);
|
||||
}
|
||||
|
||||
m_game_ui->updateProfiler();
|
||||
g_profiler->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
|
||||
@@ -3644,8 +3644,11 @@ void Game::drawScene(ProfilerGraph *graph, RunStats *stats)
|
||||
*/
|
||||
v2u32 screensize = this->driver->getScreenSize();
|
||||
|
||||
if (this->m_game_ui->m_flags.show_profiler_graph)
|
||||
graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
|
||||
if (this->m_game_ui->m_flags.show_profiler_graph) {
|
||||
auto font = g_fontengine->getFont(
|
||||
g_fontengine->getDefaultFontSize() * 0.9f, FM_Mono);
|
||||
graph->draw(10, screensize.Y - 10, driver, font);
|
||||
}
|
||||
|
||||
/*
|
||||
Damage flash
|
||||
|
||||
@@ -44,15 +44,14 @@ void GameUI::init()
|
||||
{
|
||||
// First line of debug text
|
||||
m_guitext = gui::StaticText::add(guienv, utf8_to_wide(PROJECT_NAME_C).c_str(),
|
||||
core::rect<s32>(0, 0, 0, 0), false, true, guiroot);
|
||||
core::recti(), false, true, guiroot);
|
||||
|
||||
// Second line of debug text
|
||||
m_guitext2 = gui::StaticText::add(guienv, L"", core::rect<s32>(0, 0, 0, 0), false,
|
||||
m_guitext2 = gui::StaticText::add(guienv, L"", core::recti(), false,
|
||||
true, guiroot);
|
||||
|
||||
// Chat text
|
||||
m_guitext_chat = gui::StaticText::add(guienv, L"", core::rect<s32>(0, 0, 0, 0),
|
||||
//false, false); // Disable word wrap as of now
|
||||
m_guitext_chat = gui::StaticText::add(guienv, L"", core::recti(),
|
||||
false, true, guiroot);
|
||||
u16 chat_font_size = g_settings->getU16("chat_font_size");
|
||||
if (chat_font_size != 0) {
|
||||
@@ -74,12 +73,12 @@ void GameUI::init()
|
||||
|
||||
// Status text (displays info when showing and hiding GUI stuff, etc.)
|
||||
m_guitext_status = gui::StaticText::add(guienv, L"<Status>",
|
||||
core::rect<s32>(0, 0, 0, 0), false, false, guiroot);
|
||||
core::recti(), false, false, guiroot);
|
||||
m_guitext_status->setVisible(false);
|
||||
|
||||
// Profiler text (size is updated when text is updated)
|
||||
m_guitext_profiler = gui::StaticText::add(guienv, L"<Profiler>",
|
||||
core::rect<s32>(0, 0, 0, 0), false, false, guiroot);
|
||||
core::recti(), false, false, guiroot);
|
||||
m_guitext_profiler->setOverrideFont(g_fontengine->getFont(
|
||||
g_fontengine->getDefaultFontSize() * 0.9f, FM_Mono));
|
||||
m_guitext_profiler->setVisible(false);
|
||||
@@ -97,7 +96,7 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_
|
||||
|
||||
// Minimal debug text must only contain info that can't give a gameplay advantage
|
||||
if (m_flags.show_minimal_debug) {
|
||||
const u16 fps = 1.0 / stats.dtime_jitter.avg;
|
||||
const u16 fps = 1.0f / stats.dtime_jitter.avg;
|
||||
m_drawtime_avg *= 0.95f;
|
||||
m_drawtime_avg += 0.05f * (stats.drawtime / 1000);
|
||||
|
||||
@@ -105,7 +104,7 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_
|
||||
os << std::fixed
|
||||
<< PROJECT_NAME_C " " << g_version_hash
|
||||
<< " | FPS: " << fps
|
||||
<< std::setprecision(fps >= 100 ? 1 : 0)
|
||||
<< std::setprecision(m_drawtime_avg < 10 ? 1 : 0)
|
||||
<< " | drawtime: " << m_drawtime_avg << "ms"
|
||||
<< std::setprecision(1)
|
||||
<< " | dtime jitter: "
|
||||
@@ -118,7 +117,7 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_
|
||||
|
||||
m_guitext->setRelativePosition(core::rect<s32>(5, 5, screensize.X, screensize.Y));
|
||||
|
||||
setStaticText(m_guitext, utf8_to_wide(os.str()).c_str());
|
||||
setStaticText(m_guitext, utf8_to_wide(os.str()));
|
||||
|
||||
minimal_debug_height = m_guitext->getTextHeight();
|
||||
}
|
||||
@@ -240,7 +239,7 @@ void GameUI::updateChatSize()
|
||||
if (m_flags.show_basic_debug)
|
||||
chat_y += m_guitext2->getTextHeight();
|
||||
|
||||
const v2u32 &window_size = RenderingEngine::getWindowSize();
|
||||
const v2u32 window_size = RenderingEngine::getWindowSize();
|
||||
|
||||
core::rect<s32> chat_size(10, chat_y, window_size.X - 20, 0);
|
||||
chat_size.LowerRightCorner.Y = std::min((s32)window_size.Y,
|
||||
@@ -255,28 +254,41 @@ void GameUI::updateChatSize()
|
||||
|
||||
void GameUI::updateProfiler()
|
||||
{
|
||||
if (m_profiler_current_page != 0) {
|
||||
std::ostringstream os(std::ios_base::binary);
|
||||
os << " Profiler page " << (int)m_profiler_current_page <<
|
||||
", elapsed: " << g_profiler->getElapsedMs() << " ms)" << std::endl;
|
||||
|
||||
g_profiler->print(os, m_profiler_current_page, m_profiler_max_page);
|
||||
|
||||
EnrichedString str(utf8_to_wide(os.str()));
|
||||
str.setBackground(video::SColor(120, 0, 0, 0));
|
||||
setStaticText(m_guitext_profiler, str);
|
||||
|
||||
core::dimension2d<u32> size = m_guitext_profiler->getOverrideFont()->
|
||||
getDimension(str.c_str());
|
||||
core::position2di upper_left(6, m_guitext->getTextHeight() * 2.5f);
|
||||
core::position2di lower_right = upper_left;
|
||||
lower_right.X += size.Width + 10;
|
||||
lower_right.Y += size.Height;
|
||||
|
||||
m_guitext_profiler->setRelativePosition(core::rect<s32>(upper_left, lower_right));
|
||||
}
|
||||
|
||||
m_guitext_profiler->setVisible(m_profiler_current_page != 0);
|
||||
if (m_profiler_current_page == 0)
|
||||
return;
|
||||
|
||||
std::ostringstream oss(std::ios_base::binary);
|
||||
oss << "Profiler page " << (int)m_profiler_current_page
|
||||
<< "/" << (int)m_profiler_max_page
|
||||
<< ", elapsed: " << g_profiler->getElapsedMs() << " ms" << std::endl;
|
||||
g_profiler->print(oss, m_profiler_current_page, m_profiler_max_page);
|
||||
|
||||
EnrichedString str(utf8_to_wide(oss.str()));
|
||||
str.setBackground(video::SColor(120, 0, 0, 0));
|
||||
setStaticText(m_guitext_profiler, str);
|
||||
|
||||
v2s32 upper_left(5, 10);
|
||||
if (m_flags.show_minimal_debug)
|
||||
upper_left.Y += m_guitext->getTextHeight();
|
||||
if (m_flags.show_basic_debug)
|
||||
upper_left.Y += m_guitext2->getTextHeight();
|
||||
|
||||
v2s32 lower_right = upper_left;
|
||||
lower_right.X += m_guitext_profiler->getTextWidth() + 5;
|
||||
lower_right.Y += m_guitext_profiler->getTextHeight();
|
||||
|
||||
m_guitext_profiler->setRelativePosition(core::recti(upper_left, lower_right));
|
||||
|
||||
// Really dumb heuristic (we have a fixed number of pages, not a fixed page size)
|
||||
const v2u32 window_size = RenderingEngine::getWindowSize();
|
||||
if (upper_left.Y + m_guitext_profiler->getTextHeight()
|
||||
> window_size.Y * 0.7f) {
|
||||
if (m_profiler_max_page < 5) {
|
||||
m_profiler_max_page++;
|
||||
updateProfiler(); // do it again
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameUI::toggleChat(Client *client)
|
||||
|
||||
@@ -102,5 +102,5 @@ private:
|
||||
|
||||
gui::IGUIStaticText *m_guitext_profiler = nullptr; // Profiler text
|
||||
u8 m_profiler_current_page = 0;
|
||||
const u8 m_profiler_max_page = 3;
|
||||
u8 m_profiler_max_page = 1;
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "profilergraph.h"
|
||||
#include "IVideoDriver.h"
|
||||
#include "util/string.h"
|
||||
#include "util/basic_macros.h"
|
||||
|
||||
void ProfilerGraph::put(const Profiler::GraphValues &values)
|
||||
{
|
||||
@@ -34,35 +35,33 @@ void ProfilerGraph::draw(s32 x_left, s32 y_bottom, video::IVideoDriver *driver,
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value < j->second.min)
|
||||
j->second.min = value;
|
||||
|
||||
if (value > j->second.max)
|
||||
j->second.max = value;
|
||||
j->second.min = std::min(j->second.min, value);
|
||||
j->second.max = std::max(j->second.max, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_meta.empty())
|
||||
return;
|
||||
|
||||
// Assign colors
|
||||
static const video::SColor usable_colors[] = {video::SColor(255, 255, 100, 100),
|
||||
video::SColor(255, 90, 225, 90),
|
||||
video::SColor(255, 100, 100, 255),
|
||||
video::SColor(255, 255, 150, 50),
|
||||
video::SColor(255, 220, 220, 100)};
|
||||
static const u32 usable_colors_count =
|
||||
sizeof(usable_colors) / sizeof(*usable_colors);
|
||||
static const video::SColor usable_colors[] = {
|
||||
0xffc5000b, 0xffff950e, 0xffaecf00, 0xffffd320,
|
||||
0xffff420e, 0xffff8080, 0xff729fcf, 0xffff99cc,
|
||||
};
|
||||
u32 next_color_i = 0;
|
||||
|
||||
for (auto &i : m_meta) {
|
||||
Meta &meta = i.second;
|
||||
video::SColor color(255, 200, 200, 200);
|
||||
|
||||
if (next_color_i < usable_colors_count)
|
||||
if (next_color_i < ARRLEN(usable_colors))
|
||||
color = usable_colors[next_color_i++];
|
||||
|
||||
meta.color = color;
|
||||
}
|
||||
|
||||
s32 graphh = 50;
|
||||
const s32 texth = font->getDimension(L"Ay").Height;
|
||||
const s32 graphh = 52;
|
||||
s32 textx = x_left + m_log_max_size + 15;
|
||||
s32 textx2 = textx + 200 - 15;
|
||||
s32 meta_i = 0;
|
||||
@@ -71,23 +70,22 @@ void ProfilerGraph::draw(s32 x_left, s32 y_bottom, video::IVideoDriver *driver,
|
||||
const std::string &id = p.first;
|
||||
const Meta &meta = p.second;
|
||||
s32 x = x_left;
|
||||
s32 y = y_bottom - meta_i * 50;
|
||||
s32 y = y_bottom - meta_i * graphh;
|
||||
float show_min = meta.min;
|
||||
float show_max = meta.max;
|
||||
|
||||
if (show_min >= -0.0001 && show_max >= -0.0001) {
|
||||
if (show_min <= show_max * 0.5)
|
||||
if (show_min >= -0.0001f && show_max >= -0.0001f) {
|
||||
if (show_min <= show_max * 0.5f || show_max <= graphh)
|
||||
show_min = 0;
|
||||
}
|
||||
|
||||
const s32 texth = 15;
|
||||
char buf[20];
|
||||
if (floorf(show_max) == show_max)
|
||||
porting::mt_snprintf(buf, sizeof(buf), "%.5g", show_max);
|
||||
else
|
||||
porting::mt_snprintf(buf, sizeof(buf), "%.3g", show_max);
|
||||
font->draw(utf8_to_wide(buf).c_str(),
|
||||
core::rect<s32>(textx, y - graphh, textx2,
|
||||
core::recti(textx, y - graphh, textx2,
|
||||
y - graphh + texth),
|
||||
meta.color);
|
||||
|
||||
@@ -96,23 +94,23 @@ void ProfilerGraph::draw(s32 x_left, s32 y_bottom, video::IVideoDriver *driver,
|
||||
else
|
||||
porting::mt_snprintf(buf, sizeof(buf), "%.3g", show_min);
|
||||
font->draw(utf8_to_wide(buf).c_str(),
|
||||
core::rect<s32>(textx, y - texth, textx2, y), meta.color);
|
||||
core::recti(textx, y - texth, textx2, y), meta.color);
|
||||
|
||||
font->draw(utf8_to_wide(id).c_str(),
|
||||
core::rect<s32>(textx, y - graphh / 2 - texth / 2, textx2,
|
||||
core::recti(textx, y - graphh / 2 - texth / 2, textx2,
|
||||
y - graphh / 2 + texth / 2),
|
||||
meta.color);
|
||||
|
||||
s32 graph1y = y;
|
||||
s32 graph1h = graphh;
|
||||
bool relativegraph = (show_min != 0 && show_min != show_max);
|
||||
float lastscaledvalue = 0.0;
|
||||
float lastscaledvalue = 0;
|
||||
bool lastscaledvalue_exists = false;
|
||||
|
||||
for (const Piece &piece : m_log) {
|
||||
float value = 0;
|
||||
bool value_exists = false;
|
||||
Profiler::GraphValues::const_iterator k = piece.values.find(id);
|
||||
auto k = piece.values.find(id);
|
||||
|
||||
if (k != piece.values.end()) {
|
||||
value = k->second;
|
||||
@@ -125,12 +123,12 @@ void ProfilerGraph::draw(s32 x_left, s32 y_bottom, video::IVideoDriver *driver,
|
||||
continue;
|
||||
}
|
||||
|
||||
float scaledvalue = 1.0;
|
||||
float scaledvalue = 1.0f;
|
||||
|
||||
if (show_max != show_min)
|
||||
scaledvalue = (value - show_min) / (show_max - show_min);
|
||||
|
||||
if (scaledvalue == 1.0 && value == 0) {
|
||||
if (scaledvalue == 1.0f && value == 0) {
|
||||
x++;
|
||||
lastscaledvalue_exists = false;
|
||||
continue;
|
||||
|
||||
@@ -18,9 +18,10 @@ namespace video {
|
||||
class ProfilerGraph
|
||||
{
|
||||
private:
|
||||
// FIXME: this data structure is awfully inefficient
|
||||
struct Piece
|
||||
{
|
||||
Piece(Profiler::GraphValues v) : values(std::move(v)) {}
|
||||
Piece(const Profiler::GraphValues &v) : values(v) {}
|
||||
Profiler::GraphValues values;
|
||||
};
|
||||
struct Meta
|
||||
|
||||
@@ -897,7 +897,7 @@ void Peer::RTTStatistics(float rtt, const std::string &profiler_id,
|
||||
m_rtt.max_rtt = rtt;
|
||||
|
||||
/* do average calculation */
|
||||
if (m_rtt.avg_rtt < 0.0)
|
||||
if (m_rtt.avg_rtt < 0)
|
||||
m_rtt.avg_rtt = rtt;
|
||||
else
|
||||
m_rtt.avg_rtt = m_rtt.avg_rtt * (num_samples/(num_samples-1)) +
|
||||
@@ -919,7 +919,7 @@ void Peer::RTTStatistics(float rtt, const std::string &profiler_id,
|
||||
if (jitter >= m_rtt.jitter_max)
|
||||
m_rtt.jitter_max = jitter;
|
||||
|
||||
if (m_rtt.jitter_avg < 0.0)
|
||||
if (m_rtt.jitter_avg < 0)
|
||||
m_rtt.jitter_avg = jitter;
|
||||
else
|
||||
m_rtt.jitter_avg = m_rtt.jitter_avg * (num_samples/(num_samples-1)) +
|
||||
@@ -1002,7 +1002,7 @@ void UDPPeer::reportRTT(float rtt)
|
||||
{
|
||||
if (rtt < 0)
|
||||
return;
|
||||
RTTStatistics(rtt,"rudp",MAX_RELIABLE_WINDOW_SIZE*10);
|
||||
RTTStatistics(rtt, "network", MAX_RELIABLE_WINDOW_SIZE*10);
|
||||
|
||||
// use this value to decide the resend timeout
|
||||
const float rtt_stat = getStat(AVG_RTT);
|
||||
|
||||
@@ -220,7 +220,8 @@ void ConnectionSendThread::runTimeouts(float dtime, u32 peer_packet_quota)
|
||||
resend_timeout, peer_packet_quota);
|
||||
|
||||
channel.UpdatePacketLossCounter(timed_outs.size());
|
||||
g_profiler->graphAdd("packets_lost", timed_outs.size());
|
||||
if (timed_outs.size() > 0)
|
||||
g_profiler->graphAdd("packets_lost", timed_outs.size());
|
||||
|
||||
// Note that this only happens during connection setup, it would
|
||||
// break badly otherwise.
|
||||
|
||||
@@ -11,6 +11,42 @@
|
||||
static Profiler main_profiler;
|
||||
Profiler *g_profiler = &main_profiler;
|
||||
|
||||
/*
|
||||
Splits a list into "pages". For example, the list [1,2,3,4,5] split
|
||||
into two pages would be [1,2,3],[4,5]. This function computes the
|
||||
minimum and maximum indices of a single page.
|
||||
|
||||
length: Length of the list that should be split
|
||||
page: Page number, 1 <= page <= pagecount
|
||||
pagecount: The number of pages, >= 1
|
||||
minindex: Receives the minimum index (inclusive).
|
||||
maxindex: Receives the maximum index (exclusive).
|
||||
|
||||
Ensures 0 <= minindex <= maxindex <= length.
|
||||
*/
|
||||
static void paging(u32 length, u32 page, u32 pagecount, u32 &minindex, u32 &maxindex)
|
||||
{
|
||||
if (length < 1 || pagecount < 1 || page < 1 || page > pagecount) {
|
||||
// Special cases or invalid parameters
|
||||
minindex = maxindex = 0;
|
||||
} else if(pagecount <= length) {
|
||||
// Less pages than entries in the list:
|
||||
// Each page contains at least one entry
|
||||
minindex = (length * (page-1) + (pagecount-1)) / pagecount;
|
||||
maxindex = (length * page + (pagecount-1)) / pagecount;
|
||||
} else {
|
||||
// More pages than entries in the list:
|
||||
// Make sure the empty pages are at the end
|
||||
if (page < length) {
|
||||
minindex = page-1;
|
||||
maxindex = page;
|
||||
} else {
|
||||
minindex = 0;
|
||||
maxindex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScopeProfiler::ScopeProfiler(Profiler *profiler, const std::string &name,
|
||||
ScopeProfilerType type, TimePrecision prec) :
|
||||
m_profiler(profiler),
|
||||
@@ -137,14 +173,15 @@ int Profiler::print(std::ostream &o, u32 page, u32 pagecount)
|
||||
|
||||
{
|
||||
// Padding
|
||||
s32 space = std::max(0, 46 - (s32)i.first.size());
|
||||
s32 space = std::max(0, 48 - (s32)i.first.size());
|
||||
memset(buffer, '_', space);
|
||||
buffer[space] = '\0';
|
||||
o << buffer;
|
||||
}
|
||||
|
||||
porting::mt_snprintf(buffer, sizeof(buffer), "% 5ix % 7g",
|
||||
getAvgCount(i.first), floor(i.second * 1000.0) / 1000.0);
|
||||
float rounded = floorf(i.second * 1000.0f) / 1000.0f;
|
||||
porting::mt_snprintf(buffer, sizeof(buffer), "% 5ix % 8g",
|
||||
getAvgCount(i.first), rounded);
|
||||
o << buffer << std::endl;
|
||||
}
|
||||
return values.size();
|
||||
|
||||
@@ -136,7 +136,6 @@ void *ServerThread::run()
|
||||
|
||||
while (!stopRequested()) {
|
||||
framemarker.start();
|
||||
ScopeProfiler spm(g_profiler, "Server::RunStep() (max)", SPT_MAX);
|
||||
|
||||
u64 t0 = porting::getTimeUs();
|
||||
|
||||
|
||||
@@ -407,42 +407,6 @@ private:
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Splits a list into "pages". For example, the list [1,2,3,4,5] split
|
||||
into two pages would be [1,2,3],[4,5]. This function computes the
|
||||
minimum and maximum indices of a single page.
|
||||
|
||||
length: Length of the list that should be split
|
||||
page: Page number, 1 <= page <= pagecount
|
||||
pagecount: The number of pages, >= 1
|
||||
minindex: Receives the minimum index (inclusive).
|
||||
maxindex: Receives the maximum index (exclusive).
|
||||
|
||||
Ensures 0 <= minindex <= maxindex <= length.
|
||||
*/
|
||||
inline void paging(u32 length, u32 page, u32 pagecount, u32 &minindex, u32 &maxindex)
|
||||
{
|
||||
if (length < 1 || pagecount < 1 || page < 1 || page > pagecount) {
|
||||
// Special cases or invalid parameters
|
||||
minindex = maxindex = 0;
|
||||
} else if(pagecount <= length) {
|
||||
// Less pages than entries in the list:
|
||||
// Each page contains at least one entry
|
||||
minindex = (length * (page-1) + (pagecount-1)) / pagecount;
|
||||
maxindex = (length * page + (pagecount-1)) / pagecount;
|
||||
} else {
|
||||
// More pages than entries in the list:
|
||||
// Make sure the empty pages are at the end
|
||||
if (page < length) {
|
||||
minindex = page-1;
|
||||
maxindex = page;
|
||||
} else {
|
||||
minindex = 0;
|
||||
maxindex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr inline bool is_power_of_two(u32 n)
|
||||
{
|
||||
return n != 0 && (n & (n - 1)) == 0;
|
||||
|
||||
Reference in New Issue
Block a user