@@ -239,23 +239,46 @@ abstract class OneShotSkiaObject<T extends Object> extends SkiaObject<T> {
239239 }
240240}
241241
242- /// Manages the lifecycle of a Skia object owned by a wrapper object.
242+ /// Uses reference counting to manage the lifecycle of a Skia object owned by a
243+ /// wrapper object.
243244///
244- /// When the wrapper is garbage collected, deletes the corresponding
245- /// [skObject] (only in browsers that support weak references).
245+ /// When the wrapper is garbage collected, decrements the refcount (only in
246+ /// browsers that support weak references).
246247///
247- /// The [delete] method can be used to eagerly delete the [skObject]
248- /// before the wrapper is garbage collected.
248+ /// The [delete] method can be used to eagerly decrement the refcount before the
249+ /// wrapper is garbage collected.
249250///
250251/// The [delete] method may be called any number of times. The box
251252/// will only delete the object once.
252253class SkiaObjectBox {
253- SkiaObjectBox (Object wrapper, this .skObject) {
254+ SkiaObjectBox (Object wrapper, SkDeletable skObject)
255+ : this ._(wrapper, skObject, < SkiaObjectBox > {});
256+
257+ SkiaObjectBox ._(Object wrapper, this .skObject, this ._refs) {
258+ if (assertionsEnabled) {
259+ _debugStackTrace = StackTrace .current;
260+ }
261+ _refs.add (this );
254262 if (browserSupportsFinalizationRegistry) {
255263 boxRegistry.register (wrapper, this );
256264 }
257265 }
258266
267+ /// Reference handles to the same underlying [skObject] .
268+ final Set <SkiaObjectBox > _refs;
269+
270+ late final StackTrace ? _debugStackTrace;
271+ /// If asserts are enabled, the [StackTrace] s representing when a reference
272+ /// was created.
273+ List <StackTrace >? debugGetStackTraces () {
274+ if (assertionsEnabled) {
275+ return _refs
276+ .map <StackTrace >((SkiaObjectBox box) => box._debugStackTrace! )
277+ .toList ();
278+ }
279+ return null ;
280+ }
281+
259282 /// The Skia object whose lifecycle is being managed.
260283 final SkDeletable skObject;
261284
@@ -269,16 +292,33 @@ class SkiaObjectBox {
269292 box.delete ();
270293 }));
271294
272- /// Deletes the [skObject] .
295+ /// Returns a clone of this object, which increases its reference count.
296+ ///
297+ /// Clones must be [dispose] d when finished.
298+ SkiaObjectBox clone (Object wrapper) {
299+ assert (! _isDeleted, 'Cannot clone from a deleted handle.' );
300+ assert (_refs.isNotEmpty);
301+ return SkiaObjectBox ._(wrapper, skObject, _refs);
302+ }
303+
304+ /// Decrements the reference count for the [skObject] .
273305 ///
274306 /// Does nothing if the object has already been deleted.
307+ ///
308+ /// If this causes the reference count to drop to zero, deletes the
309+ /// [skObject] .
275310 void delete () {
276311 if (_isDeleted) {
312+ assert (! _refs.contains (this ));
277313 return ;
278314 }
315+ final bool removed = _refs.remove (this );
316+ assert (removed);
279317 _isDeleted = true ;
280- _skObjectDeleteQueue.add (skObject);
281- _skObjectCollector ?? = _scheduleSkObjectCollection ();
318+ if (_refs.isEmpty) {
319+ _skObjectDeleteQueue.add (skObject);
320+ _skObjectCollector ?? = _scheduleSkObjectCollection ();
321+ }
282322 }
283323}
284324
0 commit comments