Adding asset resolver for loading out of band assets

This commit is contained in:
Luigi Rosso
2021-11-18 16:32:12 -08:00
committed by Luigi Rosso
parent f9f431c90c
commit 22b2d4c8e0
9 changed files with 194 additions and 13 deletions

View File

@ -4,12 +4,32 @@ import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/assets/file_asset.dart'; import 'package:rive/src/rive_core/assets/file_asset.dart';
import 'package:rive/src/rive_core/assets/file_asset_contents.dart'; import 'package:rive/src/rive_core/assets/file_asset_contents.dart';
/// A helper for resolving Rive file assets (like images) that are provided out
/// of band with regards to the .riv file itself.
// ignore: one_member_abstracts
abstract class RiveAssetResolver {
Future<Uint8List> loadContents(FileAsset asset);
}
class FileAssetImporter extends ImportStackObject { class FileAssetImporter extends ImportStackObject {
final RiveAssetResolver? assetResolver;
final FileAsset fileAsset; final FileAsset fileAsset;
FileAssetImporter(this.fileAsset); FileAssetImporter(this.fileAsset, this.assetResolver);
bool _loadedContents = false;
void loadContents(FileAssetContents contents) { void loadContents(FileAssetContents contents) {
_loadedContents = true;
fileAsset.decode(contents.bytes as Uint8List); fileAsset.decode(contents.bytes as Uint8List);
} }
@override
bool resolve() {
if (!_loadedContents) {
// try to get them out of band
assetResolver?.loadContents(fileAsset).then(fileAsset.decode);
}
return super.resolve();
}
} }

View File

@ -9,4 +9,33 @@ abstract class AssetBase<T extends CoreContext> extends Core<T> {
int get coreType => AssetBase.typeKey; int get coreType => AssetBase.typeKey;
@override @override
Set<int> get coreTypes => {AssetBase.typeKey}; Set<int> get coreTypes => {AssetBase.typeKey};
/// --------------------------------------------------------------------------
/// Name field with key 203.
static const String nameInitialValue = '';
String _name = nameInitialValue;
static const int namePropertyKey = 203;
/// Name of the asset
String get name => _name;
/// Change the [_name] field value.
/// [nameChanged] will be invoked only if the field's value has changed.
set name(String value) {
if (_name == value) {
return;
}
String from = _name;
_name = value;
if (hasValidated) {
nameChanged(from, value);
}
}
void nameChanged(String from, String to);
@override
void copy(covariant AssetBase source) {
_name = source._name;
}
} }

View File

@ -10,4 +10,34 @@ abstract class FileAssetBase extends Asset {
int get coreType => FileAssetBase.typeKey; int get coreType => FileAssetBase.typeKey;
@override @override
Set<int> get coreTypes => {FileAssetBase.typeKey, AssetBase.typeKey}; Set<int> get coreTypes => {FileAssetBase.typeKey, AssetBase.typeKey};
/// --------------------------------------------------------------------------
/// AssetId field with key 204.
static const int assetIdInitialValue = 0;
int _assetId = assetIdInitialValue;
static const int assetIdPropertyKey = 204;
/// Id of the asset as stored on the backend
int get assetId => _assetId;
/// Change the [_assetId] field value.
/// [assetIdChanged] will be invoked only if the field's value has changed.
set assetId(int value) {
if (_assetId == value) {
return;
}
int from = _assetId;
_assetId = value;
if (hasValidated) {
assetIdChanged(from, value);
}
}
void assetIdChanged(int from, int to);
@override
void copy(covariant FileAssetBase source) {
super.copy(source);
_assetId = source._assetId;
}
} }

View File

@ -43,7 +43,9 @@ import 'package:rive/src/generated/animation/transition_number_condition_base.da
import 'package:rive/src/generated/animation/transition_trigger_condition_base.dart'; import 'package:rive/src/generated/animation/transition_trigger_condition_base.dart';
import 'package:rive/src/generated/animation/transition_value_condition_base.dart'; import 'package:rive/src/generated/animation/transition_value_condition_base.dart';
import 'package:rive/src/generated/artboard_base.dart'; import 'package:rive/src/generated/artboard_base.dart';
import 'package:rive/src/generated/assets/asset_base.dart';
import 'package:rive/src/generated/assets/drawable_asset_base.dart'; import 'package:rive/src/generated/assets/drawable_asset_base.dart';
import 'package:rive/src/generated/assets/file_asset_base.dart';
import 'package:rive/src/generated/assets/file_asset_contents_base.dart'; import 'package:rive/src/generated/assets/file_asset_contents_base.dart';
import 'package:rive/src/generated/assets/folder_base.dart'; import 'package:rive/src/generated/assets/folder_base.dart';
import 'package:rive/src/generated/assets/image_asset_base.dart'; import 'package:rive/src/generated/assets/image_asset_base.dart';
@ -1098,6 +1100,16 @@ class RiveCoreContext {
object.ty = value; object.ty = value;
} }
break; break;
case AssetBase.namePropertyKey:
if (object is AssetBase && value is String) {
object.name = value;
}
break;
case FileAssetBase.assetIdPropertyKey:
if (object is FileAssetBase && value is int) {
object.assetId = value;
}
break;
case DrawableAssetBase.heightPropertyKey: case DrawableAssetBase.heightPropertyKey:
if (object is DrawableAssetBase && value is double) { if (object is DrawableAssetBase && value is double) {
object.height = value; object.height = value;
@ -1127,6 +1139,7 @@ class RiveCoreContext {
case ComponentBase.namePropertyKey: case ComponentBase.namePropertyKey:
case AnimationBase.namePropertyKey: case AnimationBase.namePropertyKey:
case StateMachineComponentBase.namePropertyKey: case StateMachineComponentBase.namePropertyKey:
case AssetBase.namePropertyKey:
return stringType; return stringType;
case ComponentBase.parentIdPropertyKey: case ComponentBase.parentIdPropertyKey:
case DrawTargetBase.drawableIdPropertyKey: case DrawTargetBase.drawableIdPropertyKey:
@ -1180,6 +1193,7 @@ class RiveCoreContext {
case ImageBase.assetIdPropertyKey: case ImageBase.assetIdPropertyKey:
case DrawRulesBase.drawTargetIdPropertyKey: case DrawRulesBase.drawTargetIdPropertyKey:
case TendonBase.boneIdPropertyKey: case TendonBase.boneIdPropertyKey:
case FileAssetBase.assetIdPropertyKey:
return uintType; return uintType;
case ConstraintBase.strengthPropertyKey: case ConstraintBase.strengthPropertyKey:
case DistanceConstraintBase.distancePropertyKey: case DistanceConstraintBase.distancePropertyKey:
@ -1301,6 +1315,8 @@ class RiveCoreContext {
return (object as AnimationBase).name; return (object as AnimationBase).name;
case StateMachineComponentBase.namePropertyKey: case StateMachineComponentBase.namePropertyKey:
return (object as StateMachineComponentBase).name; return (object as StateMachineComponentBase).name;
case AssetBase.namePropertyKey:
return (object as AssetBase).name;
} }
return ''; return '';
} }
@ -1411,6 +1427,8 @@ class RiveCoreContext {
return (object as DrawRulesBase).drawTargetId; return (object as DrawRulesBase).drawTargetId;
case TendonBase.boneIdPropertyKey: case TendonBase.boneIdPropertyKey:
return (object as TendonBase).boneId; return (object as TendonBase).boneId;
case FileAssetBase.assetIdPropertyKey:
return (object as FileAssetBase).assetId;
} }
return 0; return 0;
} }
@ -1662,6 +1680,11 @@ class RiveCoreContext {
object.name = value; object.name = value;
} }
break; break;
case AssetBase.namePropertyKey:
if (object is AssetBase) {
object.name = value;
}
break;
} }
} }
@ -1927,6 +1950,11 @@ class RiveCoreContext {
object.boneId = value; object.boneId = value;
} }
break; break;
case FileAssetBase.assetIdPropertyKey:
if (object is FileAssetBase) {
object.assetId = value;
}
break;
} }
} }

View File

@ -13,14 +13,14 @@ class Asset extends AssetBase {
_backboard = value; _backboard = value;
} }
@override
void nameChanged(String from, String to) {}
@override @override
void onAdded() {} void onAdded() {}
@override @override
void onAddedDirty() {} void onAddedDirty() {}
@override
void parentIdChanged(int from, int to) {}
bool get isUsable => false; bool get isUsable => false;
} }

View File

@ -10,4 +10,9 @@ abstract class DrawableAsset extends DrawableAssetBase {
@override @override
void widthChanged(double from, double to) {} void widthChanged(double from, double to) {}
@override
void assetIdChanged(int from, int to) {
width = height = 0;
}
} }

View File

@ -7,6 +7,9 @@ import 'package:rive/src/rive_core/backboard.dart';
export 'package:rive/src/generated/assets/file_asset_base.dart'; export 'package:rive/src/generated/assets/file_asset_base.dart';
abstract class FileAsset extends FileAssetBase { abstract class FileAsset extends FileAssetBase {
@override
void assetIdChanged(int from, int to) {}
Future<void> decode(Uint8List bytes); Future<void> decode(Uint8List bytes);
@override @override
@ -20,6 +23,20 @@ abstract class FileAsset extends FileAssetBase {
return super.import(stack); return super.import(stack);
} }
/// Returns the file extension, for example for an image it would be png.
String get fileExtension;
/// Returns a unique filename, useful for resolving files out of band.
String get uniqueFilename {
// remove final extension
var uniqueFilename = name;
var index = uniqueFilename.lastIndexOf('.');
if (index != -1) {
uniqueFilename = uniqueFilename.substring(0, index);
}
return '$uniqueFilename-$assetId.$fileExtension';
}
} }
/// A mixin for any class that references a generic FileAsset. /// A mixin for any class that references a generic FileAsset.

View File

@ -1,7 +1,10 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:rive/src/generated/assets/image_asset_base.dart'; import 'package:rive/src/generated/assets/image_asset_base.dart';
import 'package:rive/src/rive_core/shapes/image.dart';
export 'package:rive/src/generated/assets/image_asset_base.dart'; export 'package:rive/src/generated/assets/image_asset_base.dart';
class ImageAsset extends ImageAssetBase { class ImageAsset extends ImageAssetBase {
@ -17,4 +20,13 @@ class ImageAsset extends ImageAssetBase {
}); });
return completer.future; return completer.future;
} }
Image getDefaultObject() => Image()
..asset = this
..name = name;
/// The editor works with images as PNGs, even if their sources may have come
/// from other formats.
@override
String get fileExtension => 'png';
} }

View File

@ -89,10 +89,12 @@ class RiveFile {
Backboard _backboard = Backboard.unknown; Backboard _backboard = Backboard.unknown;
final _artboards = <Artboard>[]; final _artboards = <Artboard>[];
final RiveAssetResolver? _assetResolver;
RiveFile._( RiveFile._(
BinaryReader reader, BinaryReader reader,
this.header, this.header,
this._assetResolver,
) { ) {
/// Property fields table of contents /// Property fields table of contents
final propertyToField = HashMap<int, CoreFieldType>(); final propertyToField = HashMap<int, CoreFieldType>();
@ -182,7 +184,7 @@ class RiveFile {
break; break;
} }
case ImageAssetBase.typeKey: case ImageAssetBase.typeKey:
stackObject = FileAssetImporter(object as FileAsset); stackObject = FileAssetImporter(object as FileAsset, _assetResolver);
stackType = FileAssetBase.typeKey; stackType = FileAssetBase.typeKey;
break; break;
default: default:
@ -243,22 +245,38 @@ class RiveFile {
/// Imports a Rive file from an array of bytes. Will throw /// Imports a Rive file from an array of bytes. Will throw
/// [RiveFormatErrorException] if data is malformed. Will throw /// [RiveFormatErrorException] if data is malformed. Will throw
/// [RiveUnsupportedVersionException] if the version is not supported. /// [RiveUnsupportedVersionException] if the version is not supported.
factory RiveFile.import(ByteData bytes) { factory RiveFile.import(
ByteData bytes, {
RiveAssetResolver? assetResolver,
}) {
var reader = BinaryReader(bytes); var reader = BinaryReader(bytes);
return RiveFile._(reader, RuntimeHeader.read(reader)); return RiveFile._(reader, RuntimeHeader.read(reader), assetResolver);
} }
/// Imports a Rive file from an asset bundle /// Imports a Rive file from an asset bundle. Provide [basePath] if any nested
static Future<RiveFile> asset(String bundleKey) async { /// Rive asset isn't in the same path as the [bundleKey].
static Future<RiveFile> asset(String bundleKey, {String? basePath}) async {
final bytes = await rootBundle.load(bundleKey); final bytes = await rootBundle.load(bundleKey);
return RiveFile.import(bytes); if (basePath == null) {
int index = bundleKey.lastIndexOf('/');
if (index != -1) {
basePath = bundleKey.substring(0, index + 1);
} else {
// ignore: parameter_assignments
basePath = '';
}
}
return RiveFile.import(bytes, assetResolver: _LocalAssetResolver(basePath));
} }
/// Imports a Rive file from a url over http /// Imports a Rive file from a url over http. Provide an [assetResolver] if
static Future<RiveFile> network(String url) async { /// your file contains images that needed to be loaded with separate network
/// requests.
static Future<RiveFile> network(String url,
{RiveAssetResolver? assetResolver}) async {
final res = await http.get(Uri.parse(url)); final res = await http.get(Uri.parse(url));
final bytes = ByteData.view(res.bodyBytes.buffer); final bytes = ByteData.view(res.bodyBytes.buffer);
return RiveFile.import(bytes); return RiveFile.import(bytes, assetResolver: assetResolver);
} }
/// Imports a Rive file from local folder /// Imports a Rive file from local folder
@ -278,3 +296,25 @@ class RiveFile {
Artboard? artboardByName(String name) => Artboard? artboardByName(String name) =>
_artboards.firstWhereOrNull((a) => a.name == name); _artboards.firstWhereOrNull((a) => a.name == name);
} }
/// Resolves a Rive asset from the network provided a [baseUrl].
class NetworkAssetResolver extends RiveAssetResolver {
final String baseUrl;
NetworkAssetResolver(this.baseUrl);
@override
Future<Uint8List> loadContents(FileAsset asset) async {
final res = await http.get(Uri.parse(baseUrl + asset.uniqueFilename));
return Uint8List.view(res.bodyBytes.buffer);
}
}
class _LocalAssetResolver extends RiveAssetResolver {
String basePath;
_LocalAssetResolver(this.basePath);
@override
Future<Uint8List> loadContents(FileAsset asset) async {
final bytes = await rootBundle.load(basePath + asset.uniqueFilename);
return Uint8List.view(bytes.buffer);
}
}