Particle cleanup (#13394)

This commit is contained in:
DS 2023-10-11 17:07:30 +02:00 committed by GitHub
parent 352a403bd0
commit 12e98678f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 262 additions and 261 deletions

@ -42,19 +42,38 @@ Particle::Particle(
LocalPlayer *player,
ClientEnvironment *env,
const ParticleParameters &p,
const ClientTexRef& texture,
const ClientParticleTexRef &texture,
v2f texpos,
v2f texsize,
video::SColor color
video::SColor color,
ParticleSpawner *parent,
std::unique_ptr<ClientParticleTexture> owned_texture
) :
scene::ISceneNode(((Client *)gamedef)->getSceneManager()->getRootSceneNode(),
((Client *)gamedef)->getSceneManager()),
m_texture(texture)
{
// Misc
m_gamedef = gamedef;
m_env = env;
m_expiration(p.expirationtime),
m_env(env),
m_gamedef(gamedef),
m_collisionbox(aabb3f(v3f(-p.size / 2.0f), v3f(p.size / 2.0f))),
m_texture(texture),
m_texpos(texpos),
m_texsize(texsize),
m_pos(p.pos),
m_velocity(p.vel),
m_acceleration(p.acc),
m_p(p),
m_player(player),
m_base_color(color),
m_color(color),
m_parent(parent),
m_owned_texture(std::move(owned_texture))
{
// Set material
{
// translate blend modes to GL blend functions
video::E_BLEND_FACTOR bfsrc, bfdst;
video::E_BLEND_OPERATION blendop;
@ -108,35 +127,9 @@ Particle::Particle(
video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
m_material.BlendOperation = blendop;
m_material.setTexture(0, m_texture.ref);
m_texpos = texpos;
m_texsize = texsize;
m_animation = p.animation;
// Color
m_base_color = color;
m_color = color;
// Particle related
m_pos = p.pos;
m_velocity = p.vel;
m_acceleration = p.acc;
m_drag = p.drag;
m_jitter = p.jitter;
m_bounce = p.bounce;
m_expiration = p.expirationtime;
m_player = player;
m_size = p.size;
m_collisiondetection = p.collisiondetection;
m_collision_removal = p.collision_removal;
m_object_collision = p.object_collision;
m_vertical = p.vertical;
m_glow = p.glow;
m_alpha = 0;
m_parent = nullptr;
}
// Irrlicht stuff
const float c = p.size / 2;
m_collisionbox = aabb3f(-c, -c, -c, c, c, c);
this->setAutomaticCulling(scene::EAC_OFF);
// Init lighting
@ -146,14 +139,6 @@ Particle::Particle(
updateVertices();
}
Particle::~Particle()
{
/* if our textures aren't owned by a particlespawner, we need to clean
* them up ourselves when the particle dies */
if (m_parent == nullptr)
delete m_texture.tex;
}
void Particle::OnRegisterSceneNode()
{
if (IsVisible)
@ -180,20 +165,20 @@ void Particle::step(float dtime)
// apply drag (not handled by collisionMoveSimple) and brownian motion
v3f av = vecAbsolute(m_velocity);
av -= av * (m_drag * dtime);
m_velocity = av*vecSign(m_velocity) + v3f(m_jitter.pickWithin())*dtime;
av -= av * (m_p.drag * dtime);
m_velocity = av*vecSign(m_velocity) + v3f(m_p.jitter.pickWithin())*dtime;
if (m_collisiondetection) {
if (m_p.collisiondetection) {
aabb3f box = m_collisionbox;
v3f p_pos = m_pos * BS;
v3f p_velocity = m_velocity * BS;
collisionMoveResult r = collisionMoveSimple(m_env, m_gamedef, BS * 0.5f,
box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr,
m_object_collision);
m_p.object_collision);
f32 bounciness = m_bounce.pickWithin();
if (r.collides && (m_collision_removal || bounciness > 0)) {
if (m_collision_removal) {
f32 bounciness = m_p.bounce.pickWithin();
if (r.collides && (m_p.collision_removal || bounciness > 0)) {
if (m_p.collision_removal) {
// force expiration of the particle
m_expiration = -1.0f;
} else if (bounciness > 0) {
@ -226,10 +211,10 @@ void Particle::step(float dtime)
m_velocity += m_acceleration * dtime;
}
if (m_animation.type != TAT_NONE) {
if (m_p.animation.type != TAT_NONE) {
m_animation_time += dtime;
int frame_length_i, frame_count;
m_animation.determineParams(
m_p.animation.determineParams(
m_material.getTexture(0)->getSize(),
&frame_count, &frame_length_i, NULL);
float frame_length = frame_length_i / 1000.0;
@ -273,7 +258,7 @@ void Particle::updateLight()
else
light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
u8 m_light = decode_light(light + m_glow);
u8 m_light = decode_light(light + m_p.glow);
m_color.set(m_alpha*255,
m_light * m_base_color.getRed() / 255,
m_light * m_base_color.getGreen() / 255,
@ -290,12 +275,12 @@ void Particle::updateVertices()
else
scale = v2f(1.f, 1.f);
if (m_animation.type != TAT_NONE) {
if (m_p.animation.type != TAT_NONE) {
const v2u32 texsize = m_material.getTexture(0)->getSize();
v2f texcoord, framesize_f;
v2u32 framesize;
texcoord = m_animation.getTextureCoords(texsize, m_animation_frame);
m_animation.determineParams(texsize, NULL, NULL, &framesize);
texcoord = m_p.animation.getTextureCoords(texsize, m_animation_frame);
m_p.animation.determineParams(texsize, NULL, NULL, &framesize);
framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y);
tx0 = m_texpos.X + texcoord.X;
@ -309,7 +294,7 @@ void Particle::updateVertices()
ty1 = m_texpos.Y + m_texsize.Y;
}
auto half = m_size * .5f,
auto half = m_p.size * .5f,
hx = half * scale.X,
hy = half * scale.Y;
m_vertices[0] = video::S3DVertex(-hx, -hy,
@ -328,7 +313,7 @@ void Particle::updateVertices()
m_box.reset(v3f());
for (video::S3DVertex &vertex : m_vertices) {
if (m_vertical) {
if (m_p.vertical) {
v3f ppos = m_player->getPosition()/BS;
vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) /
core::DEGTORAD + 90);
@ -347,23 +332,20 @@ void Particle::updateVertices()
ParticleSpawner::ParticleSpawner(
IGameDef *gamedef,
LocalPlayer *player,
const ParticleSpawnerParameters &p,
const ParticleSpawnerParameters &params,
u16 attached_id,
std::unique_ptr<ClientTexture[]>& texpool,
size_t texcount,
std::vector<ClientParticleTexture> &&texpool,
ParticleManager *p_manager
) :
m_particlemanager(p_manager), p(p)
m_active(0),
m_particlemanager(p_manager),
m_time(0.0f),
m_gamedef(gamedef),
m_player(player),
p(params),
m_texpool(std::move(texpool)),
m_attached_id(attached_id)
{
m_gamedef = gamedef;
m_player = player;
m_attached_id = attached_id;
m_texpool = std::move(texpool);
m_texcount = texcount;
m_time = 0;
m_active = 0;
m_dying = false;
m_spawntimes.reserve(p.amount + 1);
for (u16 i = 0; i <= p.amount; i++) {
float spawntime = myrand_float() * p.time;
@ -538,7 +520,7 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
p.copyCommon(pp);
ClientTexRef texture;
ClientParticleTexRef texture;
v2f texpos, texsize;
video::SColor color(0xFFFFFFFF);
@ -549,9 +531,10 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
texpos, texsize, &color, p.node_tile))
return;
} else {
if (m_texcount == 0)
if (m_texpool.size() == 0)
return;
texture = decltype(texture)(m_texpool[m_texcount == 1 ? 0 : myrand_range(0,m_texcount-1)]);
texture = ClientParticleTexRef(m_texpool[m_texpool.size() == 1 ? 0
: myrand_range(0, m_texpool.size()-1)]);
texpos = v2f(0.0f, 0.0f);
texsize = v2f(1.0f, 1.0f);
if (texture.tex->animated)
@ -581,7 +564,7 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
pp.size = r_size.pickWithin();
++m_active;
auto pa = new Particle(
m_particlemanager->addParticle(std::make_unique<Particle>(
m_gamedef,
m_player,
env,
@ -589,10 +572,9 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
texture,
texpos,
texsize,
color
);
pa->m_parent = this;
m_particlemanager->addParticle(pa);
color,
this
));
}
void ParticleSpawner::step(float dtime, ClientEnvironment *env)
@ -664,19 +646,28 @@ void ParticleManager::step(float dtime)
void ParticleManager::stepSpawners(float dtime)
{
MutexAutoLock lock(m_spawner_list_lock);
for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) {
if (i->second->getExpired()) {
for (size_t i = 0; i < m_dying_particle_spawners.size();) {
// the particlespawner owns the textures, so we need to make
// sure there are no active particles before we free it
if (i->second->m_active == 0) {
delete i->second;
m_particle_spawners.erase(i++);
if (!m_dying_particle_spawners[i]->hasActive()) {
m_dying_particle_spawners[i] = std::move(m_dying_particle_spawners.back());
m_dying_particle_spawners.pop_back();
} else {
++i;
}
}
for (auto it = m_particle_spawners.begin(); it != m_particle_spawners.end();) {
auto &ps = it->second;
if (ps->getExpired()) {
// same as above
if (ps->hasActive())
m_dying_particle_spawners.push_back(std::move(ps));
it = m_particle_spawners.erase(it);
} else {
i->second->step(dtime, m_env);
++i;
ps->step(dtime, m_env);
++it;
}
}
}
@ -684,17 +675,22 @@ void ParticleManager::stepSpawners(float dtime)
void ParticleManager::stepParticles(float dtime)
{
MutexAutoLock lock(m_particle_list_lock);
for (auto i = m_particles.begin(); i != m_particles.end();) {
if ((*i)->get_expired()) {
if ((*i)->m_parent) {
assert((*i)->m_parent->m_active != 0);
--(*i)->m_parent->m_active;
for (size_t i = 0; i < m_particles.size();) {
Particle &p = *m_particles[i];
if (p.isExpired()) {
ParticleSpawner *parent = p.getParent();
if (parent) {
assert(parent->hasActive());
parent->decrActive();
}
(*i)->remove();
delete *i;
i = m_particles.erase(i);
// remove scene node
p.remove();
// delete
m_particles[i] = std::move(m_particles.back());
m_particles.pop_back();
} else {
(*i)->step(dtime);
p.step(dtime);
++i;
}
}
@ -704,17 +700,19 @@ void ParticleManager::clearAll()
{
MutexAutoLock lock(m_spawner_list_lock);
MutexAutoLock lock2(m_particle_list_lock);
for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) {
delete i->second;
m_particle_spawners.erase(i++);
}
for(auto i = m_particles.begin(); i != m_particles.end();)
{
(*i)->remove();
delete *i;
i = m_particles.erase(i);
// clear particle spawners
m_particle_spawners.clear();
m_dying_particle_spawners.clear();
// clear particles
for (std::unique_ptr<Particle> &p : m_particles) {
// remove scene node
p->remove();
// delete
p.reset();
}
m_particles.clear();
}
void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
@ -732,31 +730,27 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
const ParticleSpawnerParameters &p = *event->add_particlespawner.p;
// texture pool
std::unique_ptr<ClientTexture[]> texpool = nullptr;
size_t txpsz = 0;
std::vector<ClientParticleTexture> texpool;
if (!p.texpool.empty()) {
txpsz = p.texpool.size();
texpool = decltype(texpool)(new ClientTexture [txpsz]);
size_t txpsz = p.texpool.size();
texpool.reserve(txpsz);
for (size_t i = 0; i < txpsz; ++i) {
texpool[i] = ClientTexture(p.texpool[i], client->tsrc());
texpool.emplace_back(p.texpool[i], client->tsrc());
}
} else {
// no texpool in use, use fallback texture
txpsz = 1;
texpool = decltype(texpool)(new ClientTexture[1] {
ClientTexture(p.texture, client->tsrc())
});
texpool.emplace_back(p.texture, client->tsrc());
}
auto toadd = new ParticleSpawner(client, player,
addParticleSpawner(event->add_particlespawner.id,
std::make_unique<ParticleSpawner>(
client,
player,
p,
event->add_particlespawner.attached_id,
texpool,
txpsz,
this);
addParticleSpawner(event->add_particlespawner.id, toadd);
std::move(texpool),
this)
);
delete event->add_particlespawner.p;
break;
@ -764,7 +758,8 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
case CE_SPAWN_PARTICLE: {
ParticleParameters &p = *event->spawn_particle;
ClientTexRef texture;
ClientParticleTexRef texture;
std::unique_ptr<ClientParticleTexture> texstore;
v2f texpos, texsize;
video::SColor color(0xFFFFFFFF);
@ -778,9 +773,9 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
/* with no particlespawner to own the texture, we need
* to save it on the heap. it will be freed when the
* particle is destroyed */
auto texstore = new ClientTexture(p.texture, client->tsrc());
texstore = std::make_unique<ClientParticleTexture>(p.texture, client->tsrc());
texture = ClientTexRef(*texstore);
texture = ClientParticleTexRef(*texstore);
texpos = v2f(0.0f, 0.0f);
texsize = v2f(1.0f, 1.0f);
}
@ -790,10 +785,9 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
p.size = oldsize;
if (texture.ref) {
Particle *toadd = new Particle(client, player, m_env,
p, texture, texpos, texsize, color);
addParticle(toadd);
addParticle(std::make_unique<Particle>(client, player, m_env,
p, texture, texpos, texsize, color, nullptr,
std::move(texstore)));
}
delete event->spawn_particle;
@ -890,43 +884,53 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
(f32)pos.Z + myrand_range(0.f, .5f) - .25f
);
Particle *toadd = new Particle(
addParticle(std::make_unique<Particle>(
gamedef,
player,
m_env,
p,
ClientTexRef(ref),
ClientParticleTexRef(ref),
texpos,
texsize,
color);
addParticle(toadd);
color));
}
void ParticleManager::reserveParticleSpace(size_t max_estimate)
{
MutexAutoLock lock(m_particle_list_lock);
m_particles.reserve(m_particles.size() + max_estimate);
}
void ParticleManager::addParticle(Particle *toadd)
void ParticleManager::addParticle(std::unique_ptr<Particle> toadd)
{
MutexAutoLock lock(m_particle_list_lock);
m_particles.push_back(toadd);
m_particles.push_back(std::move(toadd));
}
void ParticleManager::addParticleSpawner(u64 id, ParticleSpawner *toadd)
void ParticleManager::addParticleSpawner(u64 id, std::unique_ptr<ParticleSpawner> toadd)
{
MutexAutoLock lock(m_spawner_list_lock);
m_particle_spawners[id] = toadd;
auto &slot = m_particle_spawners[id];
if (slot) {
// do not kill spawners here. children are still alive
errorstream << "ParticleManager: Failed to add spawner with id " << id
<< ". Id already in use." << std::endl;
return;
}
slot = std::move(toadd);
}
void ParticleManager::deleteParticleSpawner(u64 id)
{
MutexAutoLock lock(m_spawner_list_lock);
auto it = m_particle_spawners.find(id);
if (it != m_particle_spawners.end()) {
it->second->setDying();
m_dying_particle_spawners.push_back(std::move(it->second));
m_particle_spawners.erase(it);
}
}

@ -31,33 +31,34 @@ class ClientEnvironment;
struct MapNode;
struct ContentFeatures;
struct ClientTexture
struct ClientParticleTexture
{
/* per-spawner structure used to store the ParticleTexture structs
* that spawned particles will refer to through ClientTexRef */
* that spawned particles will refer to through ClientParticleTexRef */
ParticleTexture tex;
video::ITexture *ref = nullptr;
ClientTexture() = default;
ClientTexture(const ServerParticleTexture& p, ITextureSource *t):
ClientParticleTexture() = default;
ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *t):
tex(p),
ref(t->getTextureForMesh(p.string)) {};
};
struct ClientTexRef
struct ClientParticleTexRef
{
/* per-particle structure used to avoid massively duplicating the
* fairly large ParticleTexture struct */
ParticleTexture *tex = nullptr;
video::ITexture *ref = nullptr;
ClientTexRef() = default;
ClientParticleTexRef() = default;
/* constructor used by particles spawned from a spawner */
ClientTexRef(ClientTexture& t):
explicit ClientParticleTexRef(ClientParticleTexture &t):
tex(&t.tex), ref(t.ref) {};
/* constructor used for node particles */
ClientTexRef(decltype(ref) tp): ref(tp) {};
explicit ClientParticleTexRef(video::ITexture *tp): ref(tp) {};
};
class ParticleSpawner;
@ -70,12 +71,13 @@ public:
LocalPlayer *player,
ClientEnvironment *env,
const ParticleParameters &p,
const ClientTexRef &texture,
const ClientParticleTexRef &texture,
v2f texpos,
v2f texsize,
video::SColor color
video::SColor color,
ParticleSpawner *parent = nullptr,
std::unique_ptr<ClientParticleTexture> owned_texture = nullptr
);
~Particle();
virtual const aabb3f &getBoundingBox() const
{
@ -97,10 +99,10 @@ public:
void step(float dtime);
bool get_expired ()
bool isExpired ()
{ return m_expiration < m_time; }
ParticleSpawner *m_parent;
ParticleSpawner *getParent() { return m_parent; }
private:
void updateLight();
@ -115,33 +117,27 @@ private:
IGameDef *m_gamedef;
aabb3f m_box;
aabb3f m_collisionbox;
ClientTexRef m_texture;
ClientParticleTexRef m_texture;
video::SMaterial m_material;
v2f m_texpos;
v2f m_texsize;
v3f m_pos;
v3f m_velocity;
v3f m_acceleration;
v3f m_drag;
ParticleParamTypes::v3fRange m_jitter;
ParticleParamTypes::f32Range m_bounce;
const ParticleParameters m_p;
LocalPlayer *m_player;
float m_size;
//! Color without lighting
video::SColor m_base_color;
//! Final rendered color
video::SColor m_color;
bool m_collisiondetection;
bool m_collision_removal;
bool m_object_collision;
bool m_vertical;
v3s16 m_camera_offset;
struct TileAnimationParams m_animation;
float m_animation_time = 0.0f;
int m_animation_frame = 0;
u8 m_glow;
float m_alpha = 0.0f;
ParticleSpawner *m_parent = nullptr;
// Used if not spawned from a particlespawner
std::unique_ptr<ClientParticleTexture> m_owned_texture;
};
class ParticleSpawner
@ -149,31 +145,30 @@ class ParticleSpawner
public:
ParticleSpawner(IGameDef *gamedef,
LocalPlayer *player,
const ParticleSpawnerParameters &p,
const ParticleSpawnerParameters &params,
u16 attached_id,
std::unique_ptr<ClientTexture[]> &texpool,
size_t texcount,
std::vector<ClientParticleTexture> &&texpool,
ParticleManager *p_manager);
void step(float dtime, ClientEnvironment *env);
size_t m_active;
bool getExpired() const
{ return m_dying || (p.amount <= 0 && p.time != 0); }
void setDying() { m_dying = true; }
{ return p.amount <= 0 && p.time != 0; }
bool hasActive() const { return m_active != 0; }
void decrActive() { m_active -= 1; }
private:
void spawnParticle(ClientEnvironment *env, float radius,
const core::matrix4 *attached_absolute_pos_rot_matrix);
size_t m_active;
ParticleManager *m_particlemanager;
float m_time;
bool m_dying;
IGameDef *m_gamedef;
LocalPlayer *m_player;
ParticleSpawnerParameters p;
std::unique_ptr<ClientTexture[]> m_texpool;
std::vector<ClientParticleTexture> m_texpool;
size_t m_texcount;
std::vector<float> m_spawntimes;
u16 m_attached_id;
@ -187,6 +182,7 @@ class ParticleManager
friend class ParticleSpawner;
public:
ParticleManager(ClientEnvironment* env);
DISABLE_CLASS_COPY(ParticleManager)
~ParticleManager();
void step (float dtime);
@ -219,10 +215,10 @@ protected:
ParticleParameters &p, video::ITexture **texture, v2f &texpos,
v2f &texsize, video::SColor *color, u8 tilenum = 0);
void addParticle(Particle* toadd);
void addParticle(std::unique_ptr<Particle> toadd);
private:
void addParticleSpawner(u64 id, ParticleSpawner *toadd);
void addParticleSpawner(u64 id, std::unique_ptr<ParticleSpawner> toadd);
void deleteParticleSpawner(u64 id);
void stepParticles(float dtime);
@ -230,8 +226,9 @@ private:
void clearAll();
std::vector<Particle*> m_particles;
std::unordered_map<u64, ParticleSpawner*> m_particle_spawners;
std::vector<std::unique_ptr<Particle>> m_particles;
std::unordered_map<u64, std::unique_ptr<ParticleSpawner>> m_particle_spawners;
std::vector<std::unique_ptr<ParticleSpawner>> m_dying_particle_spawners;
// Start the particle spawner ids generated from here after u32_max. lower values are
// for server sent spawners.
u64 m_next_particle_spawner_id = U32_MAX + 1;