Skip to content

Commit

Permalink
[web:canvaskit] move path API to UniqueRef (flutter#41136)
Browse files Browse the repository at this point in the history
[web:canvaskit] move path API to UniqueRef
  • Loading branch information
yjbanov authored Apr 12, 2023
1 parent f242cbc commit 66868fa
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 145 deletions.
2 changes: 1 addition & 1 deletion lib/web_ui/lib/src/engine/canvaskit/native_memory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class UniqueRef<T extends Object> {
/// The returned reference must not be stored. I should only be borrowed
/// temporarily. Storing this reference may result in dangling pointer errors.
T get nativeObject {
assert(!isDisposed, 'Native object was disposed.');
assert(!isDisposed, 'The native object of $_debugOwnerLabel was disposed.');
return _nativeObject!;
}

Expand Down
56 changes: 22 additions & 34 deletions lib/web_ui/lib/src/engine/canvaskit/path.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,38 @@ import 'package:ui/ui.dart' as ui;

import '../vector_math.dart';
import 'canvaskit_api.dart';
import 'native_memory.dart';
import 'path_metrics.dart';
import 'skia_object_cache.dart';

/// An implementation of [ui.Path] which is backed by an `SkPath`.
///
/// The `SkPath` is required for `CkCanvas` methods which take a path.
class CkPath extends ManagedSkiaObject<SkPath> implements ui.Path {
CkPath() : _fillType = ui.PathFillType.nonZero;
class CkPath implements ui.Path {
factory CkPath() {
final SkPath skPath = SkPath();
skPath.setFillType(toSkFillType(ui.PathFillType.nonZero));
return CkPath._(skPath, ui.PathFillType.nonZero);
}

factory CkPath.from(CkPath other) {
final SkPath skPath = other.skiaObject.copy();
skPath.setFillType(toSkFillType(other._fillType));
return CkPath._(skPath, other._fillType);
}

CkPath.from(CkPath other)
: _fillType = other.fillType,
super(other.skiaObject.copy()) {
skiaObject.setFillType(toSkFillType(_fillType));
factory CkPath.fromSkPath(SkPath skPath, ui.PathFillType fillType) {
skPath.setFillType(toSkFillType(fillType));
return CkPath._(skPath, fillType);
}

CkPath.fromSkPath(SkPath super.skPath, this._fillType) {
skiaObject.setFillType(toSkFillType(_fillType));
CkPath._(SkPath nativeObject, this._fillType) {
_ref = UniqueRef<SkPath>(this, nativeObject, 'Path');
}

late final UniqueRef<SkPath> _ref;

SkPath get skiaObject => _ref.nativeObject;

ui.PathFillType _fillType;

@override
Expand Down Expand Up @@ -309,29 +322,4 @@ class CkPath extends ManagedSkiaObject<SkPath> implements ui.Path {
bool get isEmpty {
return skiaObject.isEmpty();
}

@override
bool get isResurrectionExpensive => true;

@override
SkPath createDefault() {
final SkPath path = SkPath();
path.setFillType(toSkFillType(_fillType));
return path;
}

late List<dynamic> _cachedCommands;

@override
void delete() {
_cachedCommands = skiaObject.toCmds();
rawSkiaObject?.delete();
}

@override
SkPath resurrect() {
final SkPath path = canvasKit.Path.MakeFromCmds(_cachedCommands);
path.setFillType(toSkFillType(_fillType));
return path;
}
}
96 changes: 21 additions & 75 deletions lib/web_ui/lib/src/engine/canvaskit/path_metrics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@ import 'dart:typed_data';
import 'package:ui/ui.dart' as ui;

import 'canvaskit_api.dart';
import 'native_memory.dart';
import 'path.dart';
import 'skia_object_cache.dart';

class CkPathMetrics extends IterableBase<ui.PathMetric>
implements ui.PathMetrics {
class CkPathMetrics extends IterableBase<ui.PathMetric> implements ui.PathMetrics {
CkPathMetrics(this._path, this._forceClosed);

final CkPath _path;
Expand All @@ -23,18 +22,21 @@ class CkPathMetrics extends IterableBase<ui.PathMetric>
late final Iterator<ui.PathMetric> iterator = _path.isEmpty
? const CkPathMetricIteratorEmpty._()
: CkContourMeasureIter(this);

/// A fresh [CkContourMeasureIter] which is only used for resurrecting a
/// [CkContourMeasure]. We can't use [iterator] here because [iterator] is
/// memoized.
CkContourMeasureIter _iteratorForResurrection() => CkContourMeasureIter(this);
}

class CkContourMeasureIter extends ManagedSkiaObject<SkContourMeasureIter>
implements Iterator<ui.PathMetric> {
CkContourMeasureIter(this._metrics);
class CkContourMeasureIter implements Iterator<ui.PathMetric> {
CkContourMeasureIter(this._metrics) {
_ref = UniqueRef<SkContourMeasureIter>(this, SkContourMeasureIter(
_metrics._path.skiaObject,
_metrics._forceClosed,
1.0,
), 'Iterator<PathMetric>');
}

final CkPathMetrics _metrics;
late final UniqueRef<SkContourMeasureIter> _ref;

SkContourMeasureIter get skiaObject => _ref.nativeObject;

/// A monotonically increasing counter used to generate [ui.PathMetric.contourIndex].
///
Expand Down Expand Up @@ -68,45 +70,22 @@ class CkContourMeasureIter extends ManagedSkiaObject<SkContourMeasureIter>
_contourIndexCounter += 1;
return true;
}

@override
SkContourMeasureIter createDefault() {
return SkContourMeasureIter(
_metrics._path.skiaObject,
_metrics._forceClosed,
1.0,
);
}

@override
SkContourMeasureIter resurrect() {
final SkContourMeasureIter iterator = createDefault();

// When resurrecting we must advance the iterator to the last known
// position.
for (int i = 0; i < _contourIndexCounter; i++) {
iterator.next();
}

return iterator;
}

@override
void delete() {
rawSkiaObject?.delete();
}
}

class CkContourMeasure extends ManagedSkiaObject<SkContourMeasure>
implements ui.PathMetric {
CkContourMeasure(this._metrics, SkContourMeasure jsObject, this.contourIndex)
: super(jsObject);
class CkContourMeasure implements ui.PathMetric {
CkContourMeasure(this._metrics, SkContourMeasure skiaObject, this.contourIndex) {
_ref = UniqueRef<SkContourMeasure>(this, skiaObject, 'PathMetric');
}

/// The path metrics used to create this measure.
///
/// This is used to resurrect the object if it is deleted prematurely.
final CkPathMetrics _metrics;

late final UniqueRef<SkContourMeasure> _ref;

SkContourMeasure get skiaObject => _ref.nativeObject;

@override
final int contourIndex;

Expand Down Expand Up @@ -134,39 +113,6 @@ class CkContourMeasure extends ManagedSkiaObject<SkContourMeasure>
double get length {
return skiaObject.length();
}

@override
SkContourMeasure createDefault() {
// This method must never be called. The default instance comes from the
// iterator's [SkContourMeasureIter.next] method initialized by the
// constructor.
throw StateError('Unreachable code');
}

@override
SkContourMeasure resurrect() {
final CkContourMeasureIter iterator = _metrics._iteratorForResurrection();
final SkContourMeasureIter skIterator = iterator.skiaObject;

// When resurrecting we must advance the iterator to the last known
// position.
for (int i = 0; i < contourIndex; i++) {
skIterator.next();
}

final SkContourMeasure? result = skIterator.next();

if (result == null) {
throw StateError('Failed to resurrect SkContourMeasure');
}

return result;
}

@override
void delete() {
rawSkiaObject?.delete();
}
}

class CkPathMetricIteratorEmpty implements Iterator<ui.PathMetric> {
Expand Down
37 changes: 2 additions & 35 deletions lib/web_ui/test/canvaskit/path_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,7 @@ void testMain() {
expect(path.getBounds(), testRect);
});

test('CkPath resurrection', () {
const ui.Rect rect = ui.Rect.fromLTRB(0, 0, 10, 10);
final CkPath path = CkPath();
path.addRect(rect);
path.delete();

final SkPath resurrectedCopy = path.resurrect();
expect(fromSkRect(resurrectedCopy.getBounds()), rect);
});

test('Resurrect CkContourMeasure in the middle of iteration', () {
test('CkContourMeasure iteration', () {
browserSupportsFinalizationRegistry = false;
final ui.Path path = ui.Path();
expect(path, isA<CkPath>());
Expand All @@ -119,18 +109,9 @@ void testMain() {
expect(iterator.current.contourIndex, 0);
expect(iterator.moveNext(), isTrue);
expect(iterator.current.contourIndex, 1);

// Delete iterator in the middle of iteration
iterator.delete();
iterator.rawSkiaObject = null;

// Check that the iterator can continue from the last position.
expect(iterator.moveNext(), isTrue);
expect(iterator.current.contourIndex, 2);
expect(iterator.moveNext(), isFalse);
});

test('Resurrect CkContourMeasure', () {
test('CkContourMeasure index', () {
browserSupportsFinalizationRegistry = false;
final ui.Path path = ui.Path();
expect(path, isA<CkPath>());
Expand All @@ -150,20 +131,6 @@ void testMain() {
final CkContourMeasure measure1 = iterator.current as CkContourMeasure;
expect(measure1.contourIndex, 1);
expect(measure1.extractPath(0, 15).getBounds(), const ui.Rect.fromLTRB(20, 20, 30, 25));

// Delete iterator and the measure in the middle of iteration
iterator.delete();
iterator.rawSkiaObject = null;
measure0.delete();
measure0.rawSkiaObject = null;
measure1.delete();
measure1.rawSkiaObject = null;

// Check that the measure is still value after resurrection.
expect(measure0.contourIndex, 0);
expect(measure0.extractPath(0, 15).getBounds(), const ui.Rect.fromLTRB(0, 0, 10, 5));
expect(measure1.contourIndex, 1);
expect(measure1.extractPath(0, 15).getBounds(), const ui.Rect.fromLTRB(20, 20, 30, 25));
});

test('Path.from', () {
Expand Down

0 comments on commit 66868fa

Please sign in to comment.