Guard against deleted bitmaps

Ruby is not guaranteed to delete bitmaps after any windows, sprites, etc that they are attached to. In the event that the attached object is not deleted before the next call to Graphics.update, this will most likely result in a segfault, as isDisposed() is not guaranteed to return true for a deleted bitmap.

Bitmap::invalid was added in an attempt to guard against this for sprites, but since the bitmap in question is deleted it's not guaranteed that the pointer actually points to null, which made the fix unreliable.
This commit is contained in:
Wayward Heart 2023-12-29 09:09:26 -06:00
parent edb3f83777
commit 35d46e627d
8 changed files with 160 additions and 32 deletions

View file

@ -2523,24 +2523,6 @@ int Bitmap::maxSize(){
return glState.caps.maxTexSize; return glState.caps.maxTexSize;
} }
// This might look ridiculous, but apparently, it is possible
// to encounter seemingly empty bitmaps during Graphics::update,
// or specifically, during a Sprite's prepare function.
// I have no idea why it happens, but it seems like just skipping
// them makes it okay, so... that's what this function is for, at
// least unless the actual source of the problem gets found, at
// which point I'd get rid of it.
// I get it to happen by trying to beat the first rival fight in
// Pokemon Flux, on macOS. I don't think I've seen anyone bring up
// something like this happening anywhere else, so... I dunno.
// If a game suddenly explodes during Graphics.update, maybe try
// breakpointing this?
bool Bitmap::invalid() const {
return p == 0;
}
void Bitmap::assumeRubyGC() void Bitmap::assumeRubyGC()
{ {
p->assumingRubyGC = true; p->assumingRubyGC = true;

View file

@ -166,8 +166,6 @@ public:
sigslot::signal<> modified; sigslot::signal<> modified;
static int maxSize(); static int maxSize();
bool invalid() const;
void assumeRubyGC(); void assumeRubyGC();

View file

@ -46,6 +46,8 @@ struct PlanePrivate
{ {
Bitmap *bitmap; Bitmap *bitmap;
sigslot::connection bitmapDispCon;
NormValue opacity; NormValue opacity;
BlendType blendType; BlendType blendType;
Color *color; Color *color;
@ -83,6 +85,14 @@ struct PlanePrivate
~PlanePrivate() ~PlanePrivate()
{ {
prepareCon.disconnect(); prepareCon.disconnect();
bitmapDisposal();
}
void bitmapDisposal()
{
bitmap = 0;
bitmapDispCon.disconnect();
} }
void updateQuadSource() void updateQuadSource()
@ -138,6 +148,9 @@ struct PlanePrivate
void prepare() void prepare()
{ {
if (nullOrDisposed(bitmap))
return;
if (quadSourceDirty) if (quadSourceDirty)
{ {
updateQuadSource(); updateQuadSource();
@ -176,8 +189,15 @@ void Plane::setBitmap(Bitmap *value)
p->bitmap = value; p->bitmap = value;
if (!value) p->bitmapDispCon.disconnect();
if (nullOrDisposed(value))
{
p->bitmap = 0;
return; return;
}
p->bitmapDispCon = value->wasDisposed.connect(&PlanePrivate::bitmapDisposal, p);
value->ensureNonMega(); value->ensureNonMega();
} }

View file

@ -48,6 +48,8 @@ struct SpritePrivate
{ {
Bitmap *bitmap; Bitmap *bitmap;
sigslot::connection bitmapDispCon;
Quad quad; Quad quad;
Transform trans; Transform trans;
@ -137,8 +139,16 @@ struct SpritePrivate
{ {
srcRectCon.disconnect(); srcRectCon.disconnect();
prepareCon.disconnect(); prepareCon.disconnect();
bitmapDisposal();
} }
void bitmapDisposal()
{
bitmap = 0;
bitmapDispCon.disconnect();
}
void recomputeBushDepth() void recomputeBushDepth()
{ {
if (nullOrDisposed(bitmap)) if (nullOrDisposed(bitmap))
@ -189,9 +199,6 @@ struct SpritePrivate
if (nullOrDisposed(bitmap)) if (nullOrDisposed(bitmap))
return; return;
if (bitmap->invalid())
return;
if (!opacity) if (!opacity)
return; return;
@ -372,8 +379,15 @@ void Sprite::setBitmap(Bitmap *bitmap)
p->bitmap = bitmap; p->bitmap = bitmap;
p->bitmapDispCon.disconnect();
if (nullOrDisposed(bitmap)) if (nullOrDisposed(bitmap))
{
p->bitmap = 0;
return; return;
}
p->bitmapDispCon = bitmap->wasDisposed.connect(&SpritePrivate::bitmapDisposal, p);
bitmap->ensureNonMega(); bitmap->ensureNonMega();

View file

@ -331,6 +331,8 @@ struct TilemapPrivate
/* Dispose watches */ /* Dispose watches */
sigslot::connection autotilesDispCon[autotileCount]; sigslot::connection autotilesDispCon[autotileCount];
sigslot::connection tilesetDispCon;
/* Draw prepare call */ /* Draw prepare call */
sigslot::connection prepareCon; sigslot::connection prepareCon;
@ -403,6 +405,7 @@ struct TilemapPrivate
/* Disconnect signal handlers */ /* Disconnect signal handlers */
tilesetCon.disconnect(); tilesetCon.disconnect();
tilesetDispCon.disconnect();
for (int i = 0; i < autotileCount; ++i) for (int i = 0; i < autotileCount; ++i)
{ {
autotilesCon[i].disconnect(); autotilesCon[i].disconnect();
@ -490,6 +493,20 @@ struct TilemapPrivate
atlasDirty = true; atlasDirty = true;
} }
void atlasContentsDisposal(int i)
{
// Guard against deleted bitmaps
autotiles[i] = 0;
invalidateAtlasContents();
}
void tilesetDisposal()
{
tileset = 0;
tilesetDispCon.disconnect();
}
void invalidateBuffers() void invalidateBuffers()
{ {
buffersDirty = true; buffersDirty = true;
@ -1189,12 +1206,18 @@ void Tilemap::Autotiles::set(int i, Bitmap *bitmap)
p->invalidateAtlasContents(); p->invalidateAtlasContents();
p->autotilesCon[i].disconnect(); p->autotilesCon[i].disconnect();
p->autotilesDispCon[i].disconnect();
if (nullOrDisposed(bitmap))
{
p->autotiles[i] = 0;
return;
}
p->autotilesCon[i] = bitmap->modified.connect p->autotilesCon[i] = bitmap->modified.connect
(&TilemapPrivate::invalidateAtlasContents, p); (&TilemapPrivate::invalidateAtlasContents, p);
p->autotilesDispCon[i].disconnect(); p->autotilesDispCon[i] = bitmap->wasDisposed.connect( [i, this] { p->atlasContentsDisposal(i); } );
p->autotilesDispCon[i] = bitmap->wasDisposed.connect
(&TilemapPrivate::invalidateAtlasContents, p);
p->updateAutotileInfo(); p->updateAutotileInfo();
} }
@ -1269,14 +1292,23 @@ void Tilemap::setTileset(Bitmap *value)
p->tileset = value; p->tileset = value;
if (!value) p->tilesetDispCon.disconnect();
p->tilesetCon.disconnect();
if (nullOrDisposed(value))
{
p->tileset = 0;
return; return;
}
p->invalidateAtlasSize(); p->invalidateAtlasSize();
p->tilesetCon.disconnect();
p->tilesetCon = value->modified.connect p->tilesetCon = value->modified.connect
(&TilemapPrivate::invalidateAtlasSize, p); (&TilemapPrivate::invalidateAtlasSize, p);
p->tilesetDispCon = value->wasDisposed.connect
(&TilemapPrivate::tilesetDisposal, p);
p->updateAtlasInfo(); p->updateAtlasInfo();
} }

View file

@ -182,6 +182,14 @@ struct TilemapVXPrivate : public ViewportElement, TileAtlasVX::Reader
atlasDirty = true; atlasDirty = true;
} }
void atlasDisposal(int i)
{
// Guard against deleted bitmaps
bitmaps[i] = 0;
invalidateAtlas();
}
void invalidateBuffers() void invalidateBuffers()
{ {
buffersDirty = true; buffersDirty = true;
@ -415,12 +423,18 @@ void TilemapVX::BitmapArray::set(int i, Bitmap *bitmap)
p->atlasDirty = true; p->atlasDirty = true;
p->bmChangedCons[i].disconnect(); p->bmChangedCons[i].disconnect();
p->bmDisposedCons[i].disconnect();
if (nullOrDisposed(bitmap))
{
p->bitmaps[i] = 0;
return;
}
p->bmChangedCons[i] = bitmap->modified.connect p->bmChangedCons[i] = bitmap->modified.connect
(&TilemapVXPrivate::invalidateAtlas, p); (&TilemapVXPrivate::invalidateAtlas, p);
p->bmDisposedCons[i].disconnect(); p->bmDisposedCons[i] = bitmap->wasDisposed.connect( [i, this] { p->atlasDisposal(i); } );
p->bmDisposedCons[i] = bitmap->wasDisposed.connect
(&TilemapVXPrivate::invalidateAtlas, p);
} }
Bitmap *TilemapVX::BitmapArray::get(int i) const Bitmap *TilemapVX::BitmapArray::get(int i) const

View file

@ -172,6 +172,9 @@ struct WindowPrivate
Bitmap *contents; Bitmap *contents;
sigslot::connection windowskinDispCon;
sigslot::connection contentsDispCon;
bool bgStretch; bool bgStretch;
Rect *cursorRect; Rect *cursorRect;
bool active; bool active;
@ -282,6 +285,21 @@ struct WindowPrivate
shState->texPool().release(baseTex); shState->texPool().release(baseTex);
cursorRectCon.disconnect(); cursorRectCon.disconnect();
prepareCon.disconnect(); prepareCon.disconnect();
windowskinDisposal();
contentsDisposal();
}
void windowskinDisposal()
{
windowskin = 0;
windowskinDispCon.disconnect();
}
void contentsDisposal()
{
contents = 0;
contentsDispCon.disconnect();
} }
void markControlVertDirty() void markControlVertDirty()
@ -720,10 +738,17 @@ void Window::setWindowskin(Bitmap *value)
p->windowskin = value; p->windowskin = value;
p->windowskinDispCon.disconnect();
if (nullOrDisposed(value)) if (nullOrDisposed(value))
{
p->windowskin = 0;
return; return;
}
value->ensureNonMega(); value->ensureNonMega();
p->windowskinDispCon = value->wasDisposed.connect(&WindowPrivate::windowskinDisposal, p);
} }
void Window::setContents(Bitmap *value) void Window::setContents(Bitmap *value)
@ -736,10 +761,18 @@ void Window::setContents(Bitmap *value)
p->contents = value; p->contents = value;
p->controlsVertDirty = true; p->controlsVertDirty = true;
p->contentsDispCon.disconnect();
if (nullOrDisposed(value)) if (nullOrDisposed(value))
{
p->contents = 0;
return; return;
}
p->contentsDispCon = value->wasDisposed.connect(&WindowPrivate::contentsDisposal, p);
value->ensureNonMega(); value->ensureNonMega();
p->contentsQuad.setTexPosRect(value->rect(), value->rect()); p->contentsQuad.setTexPosRect(value->rect(), value->rect());
} }

View file

@ -153,6 +153,9 @@ struct WindowVXPrivate
Bitmap *windowskin; Bitmap *windowskin;
Bitmap *contents; Bitmap *contents;
sigslot::connection windowskinDispCon;
sigslot::connection contentsDispCon;
Rect *cursorRect; Rect *cursorRect;
bool active; bool active;
@ -278,6 +281,21 @@ struct WindowVXPrivate
cursorRectCon.disconnect(); cursorRectCon.disconnect();
toneCon.disconnect(); toneCon.disconnect();
prepareCon.disconnect(); prepareCon.disconnect();
windowskinDisposal();
contentsDisposal();
}
void windowskinDisposal()
{
windowskin = 0;
windowskinDispCon.disconnect();
}
void contentsDisposal()
{
contents = 0;
contentsDispCon.disconnect();
} }
void invalidateCursorVert() void invalidateCursorVert()
@ -908,6 +926,16 @@ void WindowVX::setWindowskin(Bitmap *value)
p->windowskin = value; p->windowskin = value;
p->base.texDirty = true; p->base.texDirty = true;
p->windowskinDispCon.disconnect();
if (nullOrDisposed(value))
{
p->windowskin = 0;
return;
}
p->windowskinDispCon = value->wasDisposed.connect(&WindowVXPrivate::windowskinDisposal, p);
} }
void WindowVX::setContents(Bitmap *value) void WindowVX::setContents(Bitmap *value)
@ -919,8 +947,15 @@ void WindowVX::setContents(Bitmap *value)
p->contents = value; p->contents = value;
p->contentsDispCon.disconnect();
if (nullOrDisposed(value)) if (nullOrDisposed(value))
{
p->contents = 0;
return; return;
}
p->contentsDispCon = value->wasDisposed.connect(&WindowVXPrivate::contentsDisposal, p);
FloatRect rect = p->contents->rect(); FloatRect rect = p->contents->rect();
p->contentsQuad.setTexPosRect(rect, rect); p->contentsQuad.setTexPosRect(rect, rect);