Fix pasting sometimes getting interrupted by the particle limit

More precisely, fix reaching the particle limit while pasting onto a sim with no stacking, which shouldn't be possible at all, because pasting removes stacking at any position that has particles, and if the sim doesn't already have stacking, in the worst case we should end up filling the screen, which would use exactly as many particles as the limit allows.

The problem was that the removal of stacking happened after all particles were done being pasted, which meant that the particle limit could be reached halfway into the process. The solution is to remove on demand the particles wherever we're pasting one.

This works because assuming there is no stacking in the sim (this is the only case we care about) and that pmap is up to date (it is, we call RecalcFreeParticles early), then a spot that is being pasted over is either free (and therefore there are also slots free in Simulation::parts) or has exactly one particle, which we remove before creating the one we're pasting.
This commit is contained in:
Tamás Bálint Misius 2023-12-15 21:51:01 +01:00
parent c6c4b1de76
commit c75451b34c
No known key found for this signature in database
GPG Key ID: 5B472A12F6ECA9F2

View File

@ -31,6 +31,52 @@ void Simulation::Load(const GameSave *save, bool includePressure, Vec2<int> bloc
RecalcFreeParticles(false);
struct ExistingParticle
{
int id;
Vec2<int> pos;
};
std::vector<ExistingParticle> existingParticles;
auto pasteArea = RES.OriginRect() & RectSized(partP, save->blockSize * CELL);
for (int i = 0; i <= parts_lastActiveIndex; i++)
{
if (parts[i].type)
{
auto p = Vec2<int>{ int(parts[i].x + 0.5f), int(parts[i].y + 0.5f) };
if (pasteArea.Contains(p))
{
existingParticles.push_back({ i, p });
}
}
}
std::sort(existingParticles.begin(), existingParticles.end(), [](const auto &lhs, const auto &rhs) {
return std::tie(lhs.pos.Y, lhs.pos.X) < std::tie(rhs.pos.Y, rhs.pos.X);
});
PlaneAdapter<std::vector<size_t>> existingParticleIndices(pasteArea.Size(), existingParticles.size());
{
auto lastPos = Vec2<int>{ -1, -1 }; // not a valid pos in existingParticles
for (auto it = existingParticles.begin(); it != existingParticles.end(); ++it)
{
if (lastPos != it->pos)
{
existingParticleIndices[it->pos - pasteArea.TopLeft] = it - existingParticles.begin();
lastPos = it->pos;
}
}
}
auto removeExistingParticles = [this, pasteArea, &existingParticles, &existingParticleIndices](Vec2<int> p) {
auto rp = p - pasteArea.TopLeft;
if (existingParticleIndices.Size().OriginRect().Contains(rp))
{
auto index = existingParticleIndices[rp];
for (auto it = existingParticles.begin() + index; it != existingParticles.end() && it->pos == p; ++it)
{
kill_part(it->id);
}
existingParticleIndices[rp] = existingParticles.size();
}
};
std::map<unsigned int, unsigned int> soapList;
for (int n = 0; n < NPART && n < save->particlesCount; n++)
{
@ -69,9 +115,6 @@ void Simulation::Load(const GameSave *save, bool includePressure, Vec2<int> bloc
continue;
}
// Mark location to be cleaned of existing particles.
pmap[y][x] = -1;
if (elements[tempPart.type].CreateAllowed)
{
if (!(*(elements[tempPart.type].CreateAllowed))(this, -3, int(tempPart.x + 0.5f), int(tempPart.y + 0.5f), tempPart.type))
@ -80,6 +123,8 @@ void Simulation::Load(const GameSave *save, bool includePressure, Vec2<int> bloc
}
}
removeExistingParticles({ x, y });
// Allocate particle (this location is guaranteed to be empty due to "full scan" logic above)
if (pfree == -1)
break;
@ -171,35 +216,12 @@ void Simulation::Load(const GameSave *save, bool includePressure, Vec2<int> bloc
{
parts[i].tmp3 = 0;
}
if (!parts[i].type)
{
continue;
}
// Mark to be preserved in the loop below.
parts[i].type |= 1 << PMAPBITS;
}
parts_lastActiveIndex = NPART-1;
force_stacking_check = true;
Element_PPIP_ppip_changed = 1;
// Loop through particles to find particles in need of being killed
for (int i = 0; i <= parts_lastActiveIndex; i++)
{
if (parts[i].type)
{
int x = int(parts[i].x + 0.5f);
int y = int(parts[i].y + 0.5f);
bool preserve = parts[i].type & (1 << PMAPBITS);
parts[i].type &= ~(1 << PMAPBITS);
if (pmap[y][x] == -1 && !preserve)
{
kill_part(i);
}
}
}
// Sort out pmap, just to be on the safe side.
RecalcFreeParticles(false);
// fix SOAP links using soapList, a map of old particle ID -> new particle ID