Please review C++ class BubbleList
design with performance in mind.
Named BubbleList
as pooled (unused) objects bubble to the high end of the vector, providing a continuouse set of active objects, delimited by BubbleList.count
It is designed to reduce memory allocation / free overheads of many short lived objects in game like environments (eg bullets, smoke FX, etc). Objects are pooled rather than deleted.
BubbleList maintains ownership of pooled objects, though this is not enforced.
Note there is no guard for the specialization calls bool BubbleList.Update()
and bool BubbleList.Update(const bool updateFrame, Box2 *bounds) {
/**
* MSVS: Microsoft Visual Studio V17.6.5
* Relevant compile / link flags:
* /std:c17 /std:c++20 /WX /W4 /sdl- /Ob2 /Oi /Ot /GS-
* /permissive- /Zc:rvalueCast- /OPT:REF /OPT:ICF
**/
/**
* Use C style casts to reduce code noise.
* Compiler converts C Style `(target)expression` to C++ style `static_cast<target>(expression)`
* See https://en.cppreference.com/w/cpp/language/explicit_cast#Explanation for detailed conversion rules
**/
/*
From global.h
#define BM67__FORCE_32_BIT_SIZE
From shorthandTypes.h
#ifdef BM67__FORCE_32_BIT_SIZE
typedef uint32_t sZ;
#else
typedef size_t sZ;
#endif
typedef uint32_t u32;
typedef int32_t i32;
typedef const uint32_t cu32;
*/
#pragma once
#include "global.h"
#include "shorthandTypes.h"
#include <vector>
#include "Box2.h"
template <typename Item>
class BubbleList {
public:
BubbleList() = default;
~BubbleList() noexcept { Clear(); }
void Clear() noexcept {
for (Item* item : items) { delete item; }
items.clear();
items.shrink_to_fit();
dirty = true;
count = 0;
idx = 0;
}
void Trim() noexcept {
sZ size = (sZ)items.size();
while (size-- > count) {
delete items[size];
items.pop_back();
}
}
Item* Spawn() noexcept {
Item* item = nullptr;
if ((sZ)items.size() > count) { item = items[count]; }
else { items.push_back(item = new Item); }
dirty = true;
count++;
return item;
}
void Start() noexcept { idx = 0; }
Item* Next() noexcept {
if (idx < count) { return items[idx++]; }
idx = 0;
return nullptr;
}
Item* Next(cu32 type) noexcept {
while (idx < count) {
Item* item = items[idx++];
if (type == (u32)it->type) { return item; }
}
idx = 0;
return nP;
}
void Update(const bool updateFrame, Box2* bounds) noexcept {
sZ head = 0, tail = 0;
while (head < count) {
Item* item = items[head];
if (item->Update(updateFrame, bounds)) {
if (tail != head) {
items[head] = items[tail];
items[tail] = item;
}
tail++;
}
head++;
}
idx = 0;
count = tail;
}
void Update() noexcept {
sZ head = 0, tail = 0;
while (head < count) {
Item* item = items[head];
if (item->Update()) {
if (tail != head) {
items[head] = items[tail];
items[tail] = item;
}
tail++;
}
head++;
}
idx = 0;
count = tail;
}
private:
sZ idx{0};
public:
std::vector<Item *>items;
sZ count{0};
bool dirty{false};
};
Basic usage example.
/*
FXParticle lives for 10 - 100 frames
With odds 1 in 10 of creating a new particle each frame.
UpdateAndRender creates, updates, and draws particles.
*/
SpriteBuffer<spriteRenderer, spriteSheet> spriteBuf;
struct FXParticle {
Vec2 position{0, 0};
Vec2 delta{0, 0};
u32 idx{0};
i32 life{100};
FXParticle* Init() noexcept {
position.Set(Vec2::Rnd(200.0f, 200.0f)).Sub(100.0f, 100.0f);
delta.Polar(Rnd(TAU), Rnd(1.0f, 2.0f));
life = RndU(10, 100);
return this;
}
bool Update() noexcept {
position += delta;
return life-- > 0;
}
void Render(SpriteBuffer<spriteRenderer, spriteSheet>* sB) noexcept {
sB->DrawSprite(position, idx);
}
};
BubbleList<FXParticle> fx;
void AddParticle(cu32 idx) noexcept {
FXParticle* p = fx.Spawn().Init();
p->idx = idx % 10;
}
void UpdateAndRender() { /* Would normally be two functions to handle many types of objects */
if (ROdds(10)) { AddParticle(RndU(10)); }
fx.Update();
spriteBuf.Use();
FXParticle* p = fx.Next();
while (p) {
p->Render(&spriteBuf);
p = fx.Next();
}
// Or as most often used
// sZ i = 0;
// while (i < fx.count) {
// fx.items[i++]->Render(&spriteBuf);
// }
spriteBuf.Flush();
spriteBuf.Close();
}
As would be used to update and render particle FX via a main loop.
item->Update()
goes undocumented as well as unexplained; seems to be keep item. \$\endgroup\$Update()
to not comparehead
andtail
. GCC 13.2 compiles both to the exact same code. I can seecount
doesn't change unlessUpdate()
returns false at least once in one variant more easily than in the other.) \$\endgroup\$