From cf7edffefbea5322822d73c192e5f372a50fc2a6 Mon Sep 17 00:00:00 2001 From: Eetu Rantanen Date: Tue, 14 Apr 2026 20:27:02 +0300 Subject: [PATCH 1/3] Update copyright holder text - Add Solar2D community credit alongside Corona Labs - Remove spaced-out "C o r o n a L a b s I n c ." formatting --- librtt/Core/Rtt_Version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/librtt/Core/Rtt_Version.h b/librtt/Core/Rtt_Version.h index d82e6f446..0d7df9449 100644 --- a/librtt/Core/Rtt_Version.h +++ b/librtt/Core/Rtt_Version.h @@ -51,7 +51,7 @@ // 2010.9.26 #define Rtt_STRING_BUILD_DATE Rtt_MACRO_TO_STRING( Rtt_BUILD_YEAR ) "." Rtt_MACRO_TO_STRING( Rtt_BUILD_MONTH ) "." Rtt_MACRO_TO_STRING( Rtt_BUILD_DAY ) -#define Rtt_STRING_COPYRIGHT "Copyright (C) 2009-" Rtt_MACRO_TO_STRING( Rtt_BUILD_YEAR ) " C o r o n a L a b s I n c ." +#define Rtt_STRING_COPYRIGHT "Copyright (C) 2009-" Rtt_MACRO_TO_STRING( Rtt_BUILD_YEAR ) " Corona Labs Inc. and The Solar2D Community" #define Rtt_STRING_CREDITS "Walter Luh | Perry Clarke, Alex Frangeti, Sean Head, Ajay McCaleb, Tom Newman, Josh Quick, Vlad Shcherban, Michael Wallar" From 9294862aefea8e643d7758c8ca60c9ba5c87eb2d Mon Sep 17 00:00:00 2001 From: Eetu Rantanen Date: Fri, 17 Apr 2026 00:46:24 +0300 Subject: [PATCH 2/3] Add display.isValidObject - 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 --- librtt/Display/Rtt_LuaLibDisplay.cpp | 20 +++- librtt/Rtt_LuaProxyVTable.cpp | 136 +++++++++++++++++++++++++-- platform/resources/init.lua | 7 ++ 3 files changed, 152 insertions(+), 11 deletions(-) diff --git a/librtt/Display/Rtt_LuaLibDisplay.cpp b/librtt/Display/Rtt_LuaLibDisplay.cpp index e188edac6..b6d1892e9 100644 --- a/librtt/Display/Rtt_LuaLibDisplay.cpp +++ b/librtt/Display/Rtt_LuaLibDisplay.cpp @@ -1143,7 +1143,9 @@ DisplayLibrary::newImage( lua_State *L ) Runtime& runtime = library->GetDisplay().GetRuntime(); BitmapPaint *paint = BitmapPaint::NewBitmap( runtime, imageName, baseDir, flags ); - if ( paint && paint->GetBitmap() && paint->GetBitmap()->NumBytes() == 0 ) + bool isInvalid = paint && paint->GetBitmap() && paint->GetBitmap()->NumBytes() == 0; + + if ( isInvalid ) { CoronaLuaWarning(L, "file '%s' does not contain a valid image", imageName); } @@ -1151,6 +1153,12 @@ DisplayLibrary::newImage( lua_State *L ) if ( paint ) { result = NULL != PushImage( L, p, paint, display, parent, replacement ); + if ( result && isInvalid ) + { + lua_pushstring( L, "_isInvalid" ); + lua_pushboolean( L, 1 ); + lua_rawset( L, -3 ); + } } } else if ( lua_isuserdata( L, nextArg ) ) @@ -1257,13 +1265,21 @@ DisplayLibrary::newImageRect( lua_State *L ) Runtime& runtime = library->GetDisplay().GetRuntime(); BitmapPaint *paint = BitmapPaint::NewBitmap( runtime, imageName, baseDir, flags ); - if ( paint && paint->GetBitmap() && paint->GetBitmap()->NumBytes() == 0 ) + bool isInvalid = paint && paint->GetBitmap() && paint->GetBitmap()->NumBytes() == 0; + + if ( isInvalid ) { CoronaLuaWarning(L, "file '%s' does not contain a valid image", imageName); } if ( Rtt_VERIFY( paint ) ) { result = NULL != PushImage( L, NULL, paint, display, parent, w, h, replacement ); + if ( result && isInvalid ) + { + lua_pushstring( L, "_isInvalid" ); + lua_pushboolean( L, 1 ); + lua_rawset( L, -3 ); + } } } else diff --git a/librtt/Rtt_LuaProxyVTable.cpp b/librtt/Rtt_LuaProxyVTable.cpp index 88aea44dc..01dac93b2 100644 --- a/librtt/Rtt_LuaProxyVTable.cpp +++ b/librtt/Rtt_LuaProxyVTable.cpp @@ -3846,6 +3846,83 @@ LuaGroupObjectProxyVTable::Constant() return kVTable; } +// Marks a single object as removed via its Lua proxy table +static void +MarkObjectAsRemoved( lua_State *L, DisplayObject* object ) +{ + LuaProxy* proxy = object->GetProxy(); + if ( proxy ) + { + proxy->PushTable( L ); + lua_pushstring( L, "_isRemoved" ); + lua_pushboolean( L, 1 ); + lua_rawset( L, -3 ); + lua_pop( L, 1 ); + } +} + +// Forward declaration for MarkSnapshotInternalsAsRemoved <-> MarkDescendantsAsRemoved recursion. +static void MarkDescendantsAsRemoved( lua_State *L, GroupObject* group ); + +// SnapshotObject extends RectObject, not GroupObject, so snapshot.group and +// snapshot.canvas are not walked by the normal child cascade. Mark them and their +// contents explicitly. +static void +MarkSnapshotInternalsAsRemoved( lua_State *L, SnapshotObject* snap ) +{ + GroupObject& snapshotGroup = snap->GetGroup(); + MarkObjectAsRemoved( L, &snapshotGroup ); + MarkDescendantsAsRemoved( L, &snapshotGroup ); + + GroupObject& snapshotCanvas = snap->GetCanvas(); + MarkObjectAsRemoved( L, &snapshotCanvas ); + MarkDescendantsAsRemoved( L, &snapshotCanvas ); +} + +// Recursively marks all descendants of a group as removed +static void +MarkDescendantsAsRemoved( lua_State *L, GroupObject* group ) +{ + for ( S32 i = group->NumChildren(); --i >= 0; ) + { + DisplayObject& child = group->ChildAt( i ); + MarkObjectAsRemoved( L, &child ); + + GroupObject* subGroup = child.AsGroupObject(); + if ( subGroup ) + { + MarkDescendantsAsRemoved( L, subGroup ); + } + else if ( &child.ProxyVTable() == &LuaSnapshotObjectProxyVTable::Constant() ) + { + MarkSnapshotInternalsAsRemoved( L, static_cast< SnapshotObject* >( &child ) ); + } + } +} + +// Recursively clears _isRemoved flag on an object and all descendants +static void +ClearRemovedFlag( lua_State *L, DisplayObject* object ) +{ + LuaProxy* proxy = object->GetProxy(); + if ( proxy ) + { + proxy->PushTable( L ); + lua_pushstring( L, "_isRemoved" ); + lua_pushnil( L ); + lua_rawset( L, -3 ); + lua_pop( L, 1 ); + } + GroupObject* group = object->AsGroupObject(); + if ( group ) + { + for ( S32 i = group->NumChildren(); --i >= 0; ) + { + ClearRemovedFlag( L, &group->ChildAt( i ) ); + } + } +} + int LuaGroupObjectProxyVTable::Insert( lua_State *L, GroupObject *parent ) { @@ -3897,13 +3974,31 @@ LuaGroupObjectProxyVTable::Insert( lua_State *L, GroupObject *parent ) if ( oldParent != parent ) { StageObject* canvas = parent->GetStage(); - if ( canvas && oldParent == canvas->GetDisplay().Orphanage() ) + if ( canvas ) { - lua_pushvalue( L, childIndex ); // push table representing child - child->GetProxy()->AcquireTableRef( L ); // reacquire a ref for table - lua_pop( L, 1 ); + if ( oldParent == canvas->GetDisplay().Orphanage() ) + { + lua_pushvalue( L, childIndex ); // push table representing child + child->GetProxy()->AcquireTableRef( L ); // reacquire a ref for table + lua_pop( L, 1 ); + + child->WillMoveOnscreen(); + } - child->WillMoveOnscreen(); + // Clear _isRemoved on re-insertion; flag may be set directly or via + // MarkDescendantsAsRemoved on an ancestor. + LuaProxy* proxy = child->GetProxy(); + if ( proxy ) + { + proxy->PushTable( L ); + lua_getfield( L, -1, "_isRemoved" ); + bool wasMarkedRemoved = lua_toboolean( L, -1 ); + lua_pop( L, 2 ); + if ( wasMarkedRemoved ) + { + ClearRemovedFlag( L, child ); + } + } } } } @@ -3943,8 +4038,15 @@ LuaDisplayObjectProxyVTable::PushAndRemove( lua_State *L, GroupObject* parent, S StageObject *stage = parent->GetStage(); if ( stage ) { - Rtt_ASSERT( LuaContext::GetRuntime( L )->GetDisplay().HitTestOrphanage() != parent - && LuaContext::GetRuntime( L )->GetDisplay().Orphanage() != parent ); + Display& display = LuaContext::GetRuntime( L )->GetDisplay(); + if ( display.HitTestOrphanage() == parent || display.Orphanage() == parent ) + { + // Parent is already the orphanage: the object is mid-removal. + // Treat as a no-op so double-remove (direct, or via stale + // reference after a parent group was removed) stays safe. + lua_pushnil( L ); + return; + } SUMMED_TIMING( par1, "Object: PushAndRemove (release)" ); @@ -3972,14 +4074,30 @@ LuaDisplayObjectProxyVTable::PushAndRemove( lua_State *L, GroupObject* parent, S LuaProxy* proxy = child->GetProxy(); proxy->PushTable( L ); + // Mark the object as removed for immediate Lua-side detection + lua_pushstring( L, "_isRemoved" ); + lua_pushboolean( L, 1 ); + lua_rawset( L, -3 ); + + // If the object is a group, recursively mark all descendants. + // Snapshots are not GroupObjects but expose internal snapshot.group / + // snapshot.canvas via their proxy, so cascade those separately. + GroupObject* childGroup = child->AsGroupObject(); + if ( childGroup ) + { + MarkDescendantsAsRemoved( L, childGroup ); + } + else if ( &child->ProxyVTable() == &LuaSnapshotObjectProxyVTable::Constant() ) + { + MarkSnapshotInternalsAsRemoved( L, static_cast< SnapshotObject* >( child ) ); + } + // Rtt_TRACE( ( "release table ref(%x)\n", lua_topointer( L, -1 ) ) ); // Anytime we add to the Orphanage, it means the DisplayObject is no // longer on the display. Therefore, we should luaL_unref the // DisplayObject's table. If it's later re-inserted, then we simply // luaL_ref the incoming table. - Display& display = LuaContext::GetRuntime( L )->GetDisplay(); - // NOTE: Snapshot renamed to HitTest orphanage to clarify usage // TODO: Remove snapshot orphanage --- or verify that we still need it? diff --git a/platform/resources/init.lua b/platform/resources/init.lua index 53d606863..695156e95 100755 --- a/platform/resources/init.lua +++ b/platform/resources/init.lua @@ -575,6 +575,13 @@ display.remove = function( object ) end end +-- check if object is a valid, usable display object. +-- removeSelf confirms it's a display object. _isRemoved is set by the engine at the moment of removal, bypassing the one-frame delay where +-- an object's properties are still valid. _isInvalid is set by display.newImage / newImageRect when the file is not an image or is corrupted. +display.isValidObject = function( object ) + return "table" == type( object ) and "function" == type( object.removeSelf ) and not (object._isRemoved or object._isInvalid) +end + -- display function to create retina-compatible text for double-pixel devices function display.newRetinaText( ... ) print( "WARNING: display.newRetinaText() has been deprecated. display.newText() is now retina-aware." ) From efc886017fb1ef258da4d50907ee9ff0bd52d259 Mon Sep 17 00:00:00 2001 From: Eetu Rantanen Date: Fri, 17 Apr 2026 01:14:20 +0300 Subject: [PATCH 3/3] Revert "Add display.isValidObject" - moved branch This reverts commit 9294862aefea8e643d7758c8ca60c9ba5c87eb2d. --- librtt/Display/Rtt_LuaLibDisplay.cpp | 20 +--- librtt/Rtt_LuaProxyVTable.cpp | 136 ++------------------------- platform/resources/init.lua | 7 -- 3 files changed, 11 insertions(+), 152 deletions(-) diff --git a/librtt/Display/Rtt_LuaLibDisplay.cpp b/librtt/Display/Rtt_LuaLibDisplay.cpp index b6d1892e9..e188edac6 100644 --- a/librtt/Display/Rtt_LuaLibDisplay.cpp +++ b/librtt/Display/Rtt_LuaLibDisplay.cpp @@ -1143,9 +1143,7 @@ DisplayLibrary::newImage( lua_State *L ) Runtime& runtime = library->GetDisplay().GetRuntime(); BitmapPaint *paint = BitmapPaint::NewBitmap( runtime, imageName, baseDir, flags ); - bool isInvalid = paint && paint->GetBitmap() && paint->GetBitmap()->NumBytes() == 0; - - if ( isInvalid ) + if ( paint && paint->GetBitmap() && paint->GetBitmap()->NumBytes() == 0 ) { CoronaLuaWarning(L, "file '%s' does not contain a valid image", imageName); } @@ -1153,12 +1151,6 @@ DisplayLibrary::newImage( lua_State *L ) if ( paint ) { result = NULL != PushImage( L, p, paint, display, parent, replacement ); - if ( result && isInvalid ) - { - lua_pushstring( L, "_isInvalid" ); - lua_pushboolean( L, 1 ); - lua_rawset( L, -3 ); - } } } else if ( lua_isuserdata( L, nextArg ) ) @@ -1265,21 +1257,13 @@ DisplayLibrary::newImageRect( lua_State *L ) Runtime& runtime = library->GetDisplay().GetRuntime(); BitmapPaint *paint = BitmapPaint::NewBitmap( runtime, imageName, baseDir, flags ); - bool isInvalid = paint && paint->GetBitmap() && paint->GetBitmap()->NumBytes() == 0; - - if ( isInvalid ) + if ( paint && paint->GetBitmap() && paint->GetBitmap()->NumBytes() == 0 ) { CoronaLuaWarning(L, "file '%s' does not contain a valid image", imageName); } if ( Rtt_VERIFY( paint ) ) { result = NULL != PushImage( L, NULL, paint, display, parent, w, h, replacement ); - if ( result && isInvalid ) - { - lua_pushstring( L, "_isInvalid" ); - lua_pushboolean( L, 1 ); - lua_rawset( L, -3 ); - } } } else diff --git a/librtt/Rtt_LuaProxyVTable.cpp b/librtt/Rtt_LuaProxyVTable.cpp index 01dac93b2..88aea44dc 100644 --- a/librtt/Rtt_LuaProxyVTable.cpp +++ b/librtt/Rtt_LuaProxyVTable.cpp @@ -3846,83 +3846,6 @@ LuaGroupObjectProxyVTable::Constant() return kVTable; } -// Marks a single object as removed via its Lua proxy table -static void -MarkObjectAsRemoved( lua_State *L, DisplayObject* object ) -{ - LuaProxy* proxy = object->GetProxy(); - if ( proxy ) - { - proxy->PushTable( L ); - lua_pushstring( L, "_isRemoved" ); - lua_pushboolean( L, 1 ); - lua_rawset( L, -3 ); - lua_pop( L, 1 ); - } -} - -// Forward declaration for MarkSnapshotInternalsAsRemoved <-> MarkDescendantsAsRemoved recursion. -static void MarkDescendantsAsRemoved( lua_State *L, GroupObject* group ); - -// SnapshotObject extends RectObject, not GroupObject, so snapshot.group and -// snapshot.canvas are not walked by the normal child cascade. Mark them and their -// contents explicitly. -static void -MarkSnapshotInternalsAsRemoved( lua_State *L, SnapshotObject* snap ) -{ - GroupObject& snapshotGroup = snap->GetGroup(); - MarkObjectAsRemoved( L, &snapshotGroup ); - MarkDescendantsAsRemoved( L, &snapshotGroup ); - - GroupObject& snapshotCanvas = snap->GetCanvas(); - MarkObjectAsRemoved( L, &snapshotCanvas ); - MarkDescendantsAsRemoved( L, &snapshotCanvas ); -} - -// Recursively marks all descendants of a group as removed -static void -MarkDescendantsAsRemoved( lua_State *L, GroupObject* group ) -{ - for ( S32 i = group->NumChildren(); --i >= 0; ) - { - DisplayObject& child = group->ChildAt( i ); - MarkObjectAsRemoved( L, &child ); - - GroupObject* subGroup = child.AsGroupObject(); - if ( subGroup ) - { - MarkDescendantsAsRemoved( L, subGroup ); - } - else if ( &child.ProxyVTable() == &LuaSnapshotObjectProxyVTable::Constant() ) - { - MarkSnapshotInternalsAsRemoved( L, static_cast< SnapshotObject* >( &child ) ); - } - } -} - -// Recursively clears _isRemoved flag on an object and all descendants -static void -ClearRemovedFlag( lua_State *L, DisplayObject* object ) -{ - LuaProxy* proxy = object->GetProxy(); - if ( proxy ) - { - proxy->PushTable( L ); - lua_pushstring( L, "_isRemoved" ); - lua_pushnil( L ); - lua_rawset( L, -3 ); - lua_pop( L, 1 ); - } - GroupObject* group = object->AsGroupObject(); - if ( group ) - { - for ( S32 i = group->NumChildren(); --i >= 0; ) - { - ClearRemovedFlag( L, &group->ChildAt( i ) ); - } - } -} - int LuaGroupObjectProxyVTable::Insert( lua_State *L, GroupObject *parent ) { @@ -3974,31 +3897,13 @@ LuaGroupObjectProxyVTable::Insert( lua_State *L, GroupObject *parent ) if ( oldParent != parent ) { StageObject* canvas = parent->GetStage(); - if ( canvas ) + if ( canvas && oldParent == canvas->GetDisplay().Orphanage() ) { - if ( oldParent == canvas->GetDisplay().Orphanage() ) - { - lua_pushvalue( L, childIndex ); // push table representing child - child->GetProxy()->AcquireTableRef( L ); // reacquire a ref for table - lua_pop( L, 1 ); - - child->WillMoveOnscreen(); - } + lua_pushvalue( L, childIndex ); // push table representing child + child->GetProxy()->AcquireTableRef( L ); // reacquire a ref for table + lua_pop( L, 1 ); - // Clear _isRemoved on re-insertion; flag may be set directly or via - // MarkDescendantsAsRemoved on an ancestor. - LuaProxy* proxy = child->GetProxy(); - if ( proxy ) - { - proxy->PushTable( L ); - lua_getfield( L, -1, "_isRemoved" ); - bool wasMarkedRemoved = lua_toboolean( L, -1 ); - lua_pop( L, 2 ); - if ( wasMarkedRemoved ) - { - ClearRemovedFlag( L, child ); - } - } + child->WillMoveOnscreen(); } } } @@ -4038,15 +3943,8 @@ LuaDisplayObjectProxyVTable::PushAndRemove( lua_State *L, GroupObject* parent, S StageObject *stage = parent->GetStage(); if ( stage ) { - Display& display = LuaContext::GetRuntime( L )->GetDisplay(); - if ( display.HitTestOrphanage() == parent || display.Orphanage() == parent ) - { - // Parent is already the orphanage: the object is mid-removal. - // Treat as a no-op so double-remove (direct, or via stale - // reference after a parent group was removed) stays safe. - lua_pushnil( L ); - return; - } + Rtt_ASSERT( LuaContext::GetRuntime( L )->GetDisplay().HitTestOrphanage() != parent + && LuaContext::GetRuntime( L )->GetDisplay().Orphanage() != parent ); SUMMED_TIMING( par1, "Object: PushAndRemove (release)" ); @@ -4074,30 +3972,14 @@ LuaDisplayObjectProxyVTable::PushAndRemove( lua_State *L, GroupObject* parent, S LuaProxy* proxy = child->GetProxy(); proxy->PushTable( L ); - // Mark the object as removed for immediate Lua-side detection - lua_pushstring( L, "_isRemoved" ); - lua_pushboolean( L, 1 ); - lua_rawset( L, -3 ); - - // If the object is a group, recursively mark all descendants. - // Snapshots are not GroupObjects but expose internal snapshot.group / - // snapshot.canvas via their proxy, so cascade those separately. - GroupObject* childGroup = child->AsGroupObject(); - if ( childGroup ) - { - MarkDescendantsAsRemoved( L, childGroup ); - } - else if ( &child->ProxyVTable() == &LuaSnapshotObjectProxyVTable::Constant() ) - { - MarkSnapshotInternalsAsRemoved( L, static_cast< SnapshotObject* >( child ) ); - } - // Rtt_TRACE( ( "release table ref(%x)\n", lua_topointer( L, -1 ) ) ); // Anytime we add to the Orphanage, it means the DisplayObject is no // longer on the display. Therefore, we should luaL_unref the // DisplayObject's table. If it's later re-inserted, then we simply // luaL_ref the incoming table. + Display& display = LuaContext::GetRuntime( L )->GetDisplay(); + // NOTE: Snapshot renamed to HitTest orphanage to clarify usage // TODO: Remove snapshot orphanage --- or verify that we still need it? diff --git a/platform/resources/init.lua b/platform/resources/init.lua index 695156e95..53d606863 100755 --- a/platform/resources/init.lua +++ b/platform/resources/init.lua @@ -575,13 +575,6 @@ display.remove = function( object ) end end --- check if object is a valid, usable display object. --- removeSelf confirms it's a display object. _isRemoved is set by the engine at the moment of removal, bypassing the one-frame delay where --- an object's properties are still valid. _isInvalid is set by display.newImage / newImageRect when the file is not an image or is corrupted. -display.isValidObject = function( object ) - return "table" == type( object ) and "function" == type( object.removeSelf ) and not (object._isRemoved or object._isInvalid) -end - -- display function to create retina-compatible text for double-pixel devices function display.newRetinaText( ... ) print( "WARNING: display.newRetinaText() has been deprecated. display.newText() is now retina-aware." )