undo stuff + render lifetime fixes
undo / redo
the undo system relied way too much on runtime entity IDs and direct pointers into components.
that works until an entity gets deleted, an ECS slot gets recycled, or a dense component pool moves stuff around…
entity commands now store the entity UUID and resolve the current runtime entity when they execute / undo instead.
this now applies to transforms, renaming, adding / removing components, script changes, component field edits and map painting.
so older commands can still find the right entity even if it was deleted and later restored with a completely different runtime ID.
deleting things is actually undoable now
finally added proper undo for deleting entities. !!!!!!
deleting an entity snapshots the whole subtree first, including components, UUIDs, names, hierarchy + sibling order, scripts, custom data and asset references.
undo rebuilds the tree, reloads required assets, restores the UUIDs and puts everything back where it was.
this also means edits from before the deletion still work afterwards, since those commands just resolve the restored entity by UUID.
paste uses the same snapshot system now too, so undo / redo keeps the same pasted entity identities instead of creating unrelated ones every time.
there are checks for conflicting UUIDs, missing parents, invalid hierarchies, missing assets etc. failed restores shouldn’t leave half-created entities behind.
failed commands also no longer get added to undo history or wipe an existing redo chain.
custom ObSL data
custom script data needed special handling too.
arrays / objects can share references or even be cyclic, so just copying ObSL::Values wasn’t enough.
there’s now a snapshot format that captures the object graph and rebuilds it through the interpreter + GC when an entity is restored.
CustomDataComponent also roots stored ObSL values properly so GC-managed objects stay alive.
runtime functions aren’t snapshotable though, so deletion just fails instead of restoring garbage.
render thread lifetime stuff
also did a pretty big pass over renderer resource lifetime, mostly because scene switching could unload resources while an already submitted frame was still using them.
(this started because someone mightve pointed out a lil crash for me…)
render commands now pin the meshes, materials, shaders and textures they need until that frame is done.
same idea for UI textures and the editor viewport framebuffer.
OpenGL objects also don’t call glDelete* from whichever thread destroys the C++ object anymore. textures, shaders, buffers, VAOs and framebuffers queue deletion for the render thread, while it actually owns the GL context.
less bugs yay
frame state + shutdown
renderer state that can change while the render thread is behind is captured per frame now too, including clear color, post-processing and UI resolution / projection state.
shutdown is also more deliberate now: worker threads stop first, background font loads finish, scripts stop, scenes/resources are cleared, renderer/UI resources shut down, then the remaining GL delete queue is processed while a context still exists.
font loading uses tracked jthreads now too instead of detached threads.
small things
exporting now reports success / failure back to the editor and shows a little result popup instead of silently relying on the console.
also added tests around delete / restore, UUID reuse, hierarchy restoration, script components, map edits, pasting, command failures and repeated undo / redo without leaking entities or asset references.
so. .. . less explosions hopefully
sorry if wording is weird.. character limit..