graphics: migrate drawables to shared ownership

- introduce DrawablePtr and per-consumer createInstance() semantics
  - add TextureDrawable wrappers backed by shared TexturePtr handles
  - migrate regions, sprites, nine-patches, state lists, skins, groups, gradients,
    UI widgets, tools, modules, ecode, and eeiv to shared drawable ownership
  - remove ownIt flags, manual drawable deletion, and unload-based lifetime handling
  - replace drawable callback IDs with RAII change connections
  - move Variant drawable storage outside its scalar union
  - split UIIcon source lookup from explicit drawable instance creation
  - retain icon instances used by rendering loops instead of cloning per frame
  - avoid shared source mutation in texture-region consumers
  - use drawable type tags instead of RTTI casts
  - fix nearest-size UIIcon lookup
  - document the drawable source/instance contract and add ownership tests
This commit is contained in:
Martín Lucas Golini
2026-07-20 01:09:36 -03:00
parent 04f600bef5
commit dc377335b9
131 changed files with 1747 additions and 1178 deletions
@@ -1,7 +1,6 @@
# eepp shared-resource ownership architecture
Status: active implementation baseline; Stage 0 through Stage 3 complete; Stage 4 is next,
2026-07-19.
Status: active implementation baseline; Stage 0 through Stage 4 complete, 2026-07-20.
This document freezes the contracts that must be true before the public texture API is changed. The
implementation may refine names and small mechanics, but changing an invariant below requires an
@@ -344,35 +343,73 @@ fixed:
Shared lifetime and shareable instance state are separate concerns. `isStateful()` is not a sharing
contract and will not be used as one.
### 7.1 Frozen source/instance split
### 7.1 Source/instance split
Resource resolution caches immutable source data. UI consumers own per-consumer drawable instances:
```cpp
using DrawableSourcePtr = ResourcePtr<const DrawableSource>;
using DrawablePtr = ResourcePtr<Drawable>;
DrawableSourcePtr DrawableResolver::findSource( const DrawableRequest& request );
DrawablePtr Drawable::createInstance() const;
DrawablePtr DrawableResolver::createDrawable( const DrawableRequest& request );
```
Stage 4 established this contract without introducing a parallel `DrawableSource` class hierarchy.
Existing drawable resource types serve as source prototypes while retained by an atlas, theme,
icon, catalog, or resolver. A prototype is never handed directly to an unrelated consumer:
`createInstance()` returns independently mutable presentation state while sharing underlying
texture/resource handles. This is simpler than duplicating every drawable type into source and
instance classes and remains compatible with introducing immutable source-only types later when a
concrete resource requires one.
eepp continues to use `Drawable::Type` for runtime drawable dispatch. Generic handle conversion
checks that tag and then uses `static_pointer_cast`; cloning code for a statically known concrete
type also uses `static_pointer_cast`. The ownership migration does not introduce RTTI casts.
Representative split:
- `Texture` is shared GPU/resource data, not a globally shared mutable drawable instance.
- `TextureRegionSource` contains a TexturePtr, immutable source rectangle, offset, and intrinsic size.
- `NinePatchSource` contains immutable region and border data.
- `TextureDrawable`/`TextureRegionDrawable` hold per-consumer destination size, tint, alpha, position,
and other presentation state while retaining their source.
- `TextureRegion` prototypes and instances retain a TexturePtr; instances copy rectangle, offset,
intrinsic size, destination size, tint, and position.
- `NinePatch` instances clone their nine mutable region children while sharing the textures.
- `TextureDrawable` holds per-consumer destination size, tint, alpha, and position while retaining
the shared TexturePtr.
- `StateListDrawable`, `DrawableGroup`, and `Sprite` are per-consumer state machines/instances that
refer to source handles or private child instances.
`DrawableImageParser::createDrawable()` always returns a fresh consumer instance for CSS-generated
or resolved content, even when its immutable source came from a cache.
`UIIcon`, `UIGlyphIcon`, and `UISVGIcon` expose the split directly:
```cpp
const DrawablePtr& UIIcon::getSource( int size ) const;
DrawablePtr UIIcon::createDrawable( int size ) const;
```
`getSource()` supports lookup and measurement without cloning an existing prototype. Glyph and SVG
icons may materialize and cache a missing size source once. `createDrawable()` is the explicit
consumer-instance boundary. Callers retain its result for as long as they render that icon.
The migration will remove draw-time mutation of shared child/source objects. Rendering APIs may use
external draw parameters where that simplifies an implementation, but no shared source can be
temporarily recolored, resized, repositioned, or advanced by a consumer.
No rendering callback may call `createInstance()`, `UIIcon::createDrawable()`, or an API that
performs either operation internally. Rendered instances are prepared during assignment,
deserialization, style/resource resolution, scheduled update, or plugin update and retained by the
consumer. The Stage 4 call-site audit classifies all remaining direct `createInstance()` calls as:
- implementations recursively cloning their private child state;
- constructors and setters adopting a private region/sprite/map instance;
- theme, skin, icon, CSS, and name-resolution source-to-instance boundaries;
- widget deserialization and one-time assignment; or
- focused ownership tests.
The code editor lock icon and ecode debugger, linter, LSP breadcrumb, and autocomplete icon paths
retain instances populated before drawing. Draw callbacks only look up and render those retained
instances; a cache miss skips the icon for that frame instead of cloning while rendering.
### 7.2 Consumer API
Consumers store a strong per-consumer instance:
@@ -645,6 +682,25 @@ Exit criteria:
### Stage 4: drawable source/instance conversion
Status: complete, 2026-07-20. Drawable ownership
now uses `DrawablePtr`; textures create private
`TextureDrawable` wrappers; mutable prototypes implement `createInstance()`; sprites, state lists,
skins, groups, nine-patches, regions, glyphs, gradients, and primitive drawables clone their
presentation state. UIImage, UINodeDrawable, menus/icons/themes, parsers, editor/tool consumers,
maps, physics, ecode, and eeiv were migrated in the same API cut.
`DrawableResource::Unload` and callback IDs were replaced by Change-only RAII connections.
`Variant` stores DrawablePtr outside its scalar union. UITextureRegion and ScrollParallax render
with local geometry rather than temporarily resizing shared source regions; region-based map
objects retain private instances. `DrawableSearcher` already returns fresh instances as a safe
bridge, but its replacement by the layered UI resolver remains Stage 5.
`UIIcon::getSource()` now returns a cached source/prototype for lookup and measurement, while
`UIIcon::createDrawable()` explicitly creates one private consumer instance. `UIGlyphIcon` and
`UISVGIcon` cache their lazily materialized sources under the same contract. The complete
`createInstance()` call-site audit found no remaining render-loop cloning; previously hot code
editor, debugger, linter, LSP breadcrumb, and autocomplete paths retain update-time instances.
Introduce source types and per-consumer instances, remove shared draw-state mutation, replace manual
ownership with DrawablePtr, remove Unload lifetime callbacks, add RAII change connections, and
migrate Variant's storage.