Skip to content

Add display.isValidObject function - #908

Open
XeduR wants to merge 2 commits into
coronalabs:masterfrom
XeduR:display.isValidObject-PR#1
Open

Add display.isValidObject function#908
XeduR wants to merge 2 commits into
coronalabs:masterfrom
XeduR:display.isValidObject-PR#1

Conversation

@XeduR

@XeduR XeduR commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

display.isValidObject

Adds a Lua-level API for checking whether a given object is a valid display object that has not been removed.

Current behaviour

Object removal

Calling display.remove( object ) or object:removeSelf() will take until the next frame to finish removing the object. In certain situations, like with collision events, callbacks, etc. it's possible that an object has been removed, but the removal isn't yet finished, which results in a crash if the app tries to access the object.

Example

local rect = display.newRect( 100, 100, 50, 50 )
display.remove( rect )

print( rect.x ) -- output: 100

timer.performWithDelay( 1, function()
    print( rect.x )    -- output: nil
end )

There is currently no reliable way to programmatically checking if a display object has already been removed and developers need to implement their own ad-hoc approaches to test for it.

Image object validity

Solar2D detects when display.newImage or display.newImageRect is given a file that is not an image, or if the image is corrupted or otherwise invalid. Solar2D sends a warning about it to console via CoronaLuaWarning(L, "file '%s' does not contain a valid image", imageName);. Funnily enough, Solar2D does not currently provide a reliable way to check this programmatically.

What this PR does

This PR adds two internal proxy flags to objects, _isRemoved and _isInvalid, that the engine sets at the moment an object is removed or when Solar2D detects an image is invalid upon trying to create it.

This PR also adds a new display.isValidObject( object ) that verifies if a given object is 1) a display object, 2) it has not been removed, and 3) it is not invalid. The function always return a Boolean value, giving developers a reliable way of checking if their display objects are indeed valid or not.

Example

Example

local rect = display.newRect( 100, 100, 50, 50 )
print( display.isValidObject( rect ) ) -- output: true
display.remove( rect )

print( display.isValidObject( rect ) ) -- output: false

timer.performWithDelay( 1, function()
    print( display.isValidObject( rect ) ) -- output: false
end )

Unit tests

The test project runs 18 unit tests that exercise display.isValidObject across all the edge cases: type safety with non-display-object inputs (nil, numbers, strings, tables, functions), removal via display.remove and removeSelf, idempotent double-removal, stage validity, group cascade at various nesting depths, finalize listener timing, same-frame re-parent rescue for both groups and snapshots, every display primitive type (rect, circle, line, polygon, text, container, snapshot, group, capture), widgets, sprites, emitters, snapshot internals (snapshot.group and snapshot.canvas with children), and corrupt/missing images via newImage and newImageRect to verify the _isInvalid flag. Each test checks isValidObject before removal, same-frame after removal, and next-frame after removal, reporting pass/fail.

isValidObject test.zip

- Add display.isValidObject( object ) to init.lua
- Set _isRemoved on removed proxies; cascade to group and snapshot descendants
- Clear _isRemoved on re-insert so same-frame re-parent rescues work
- Make double-remove a no-op instead of an error
- Set _isInvalid on newImage / newImageRect proxies when the bitmap loads as zero bytes
@XeduR

XeduR commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Here's an updated test suite with 3 added stress tests. The unit tests are commented out by default.

There's a clear performance cost with the recursive marking of objects, but in absolute terms the performance seems to still hold very well.

isValidObject (unit & stress tests).zip

- Moved display.isValidObject function from Lua to C++
  - Now also avoids false positives in case some developer has brilliantly added "removeSelf" function to a regular Lua table
- Rewrote the recursive _isRemoved marking to work lazily by walking the object's parent chain to its root when the function itself is called
- Objects rooted at an orphanage, deleted objects, and broken images all fail
- Reparenting out of a removed subtree restores validity automatically
@XeduR

XeduR commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

I was talking to StarCrunch yesterday and I had a off the cuff idea that this could be written lazily, so I did. 😄

I moved the display.isValidObject function over to C++ and I rewrote the approach so that objects are no longer recursively marked upon removal, but now whether or not they've been removed is determined on demand when the function is called. The check walks the object's parent chain: removed objects (and children of removed groups) are rooted in the orphanage, and already-deleted objects no longer resolve through their proxy. snapshot.group and snapshot.canvas are outside the parent chain, so groups carry an internal owner pointer that lets the check continue from the owning snapshot.

This new approach means that there's no performance impact on removing objects. The same isValidObject (unit & stress tests).zip works, if you want to run the stress tests.

Now that the function is on the C++ side, it also correctly identifies if a target object is actually a display object instead of just naively checking if it has a removeSelf function attached to it.

I also made it so that removing an already-removed object is now an explicit safe no-op; previously it asserted in debug builds and re-ran the removal path in release builds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant