Files
rive-ios/Source/Renderer/RiveFile.mm
luigi-rosso 27de5abf4b Ios out of band
few bits to sort out
- [x] make our mix of simulator/emulator consistent, settling on emulator
- [x] passing the factory in works great for just in time asset decoding, but its not amazing when you want to decode ahead of time.
- [x] couple of places left to pass this function signature through. (Question) is there a neater way to get this done, feels a bit like we are going back to parameter explosion a bit?
- [x] should do a few examples, i think the complexity grows quite a bit in this one as you add caching, or callbacks
- [x] should get the cached images/fonts to draw on init as well, either warming up cache, or jitting
- [x] examples loading assets from the bundle (also there seem to be actual asset things too? should we use those?!)
- [x] add test
- [x] re-add "preview" project & rev the preview project once this has been deployed. (do this after new ios deploy)
- [x] fix up race condition (see comment)

https://github.com/rive-app/rive/assets/1216025/2c14330f-e8a4-481b-bc27-4807cabe3b82

(simple example, both swift ui and standard)

![CleanShot 2023-11-20 at 16 54 59](https://github.com/rive-app/rive/assets/1216025/a71e207c-30ad-44dd-9e4b-ad7431b22186)

Diffs=
fabb7f97f Ios out of band (#6232)

Co-authored-by: Gordon Hayes <pggordonhayes@gmail.com>
Co-authored-by: Maxwell Talbot <talbot.maxwell@gmail.com>
2023-12-05 21:23:08 +00:00

432 lines
14 KiB
Plaintext

//
// RiveFile.m
// RiveRuntime
//
// Created by Maxwell Talbot on 5/14/21.
// Copyright © 2021 Rive. All rights reserved.
//
#import <Rive.h>
#import <RivePrivateHeaders.h>
#import <RenderContext.h>
#import <RenderContextManager.h>
#import <RiveFileAssetLoader.h>
#import <CDNFileAssetLoader.h>
#import <FileAssetLoaderAdapter.hpp>
/*
* RiveFile
*/
@implementation RiveFile
{
std::unique_ptr<rive::File> riveFile;
rive::FileAssetLoader* fileAssetLoader;
}
+ (uint)majorVersion
{
return UInt8(rive::File::majorVersion);
}
+ (uint)minorVersion
{
return UInt8(rive::File::minorVersion);
}
- (nullable instancetype)initWithByteArray:(NSArray*)array loadCdn:(bool)cdn error:(NSError**)error
{
if (self = [super init])
{
UInt8* bytes;
@try
{
bytes = (UInt8*)calloc(array.count, sizeof(UInt64));
[array enumerateObjectsUsingBlock:^(NSNumber* number, NSUInteger index, BOOL* stop) {
bytes[index] = number.unsignedIntValue;
}];
BOOL ok = [self import:bytes byteLength:array.count loadCdn:cdn error:error];
if (!ok)
{
return nil;
}
self.isLoaded = true;
}
@finally
{
free(bytes);
}
return self;
}
return nil;
}
- (nullable instancetype)initWithByteArray:(NSArray*)array
loadCdn:(bool)cdn
customAssetLoader:(LoadAsset)customAssetLoader
error:(NSError**)error
{
if (self = [super init])
{
UInt8* bytes;
@try
{
bytes = (UInt8*)calloc(array.count, sizeof(UInt64));
[array enumerateObjectsUsingBlock:^(NSNumber* number, NSUInteger index, BOOL* stop) {
bytes[index] = number.unsignedIntValue;
}];
BOOL ok = [self import:bytes
byteLength:array.count
loadCdn:cdn
customAssetLoader:customAssetLoader
error:error];
if (!ok)
{
return nil;
}
self.isLoaded = true;
}
@finally
{
free(bytes);
}
return self;
}
return nil;
}
// QUESTION: deprecate? init with NSData feels like its all we need?
- (nullable instancetype)initWithBytes:(UInt8*)bytes
byteLength:(UInt64)length
loadCdn:(bool)cdn
error:(NSError**)error
{
if (self = [super init])
{
BOOL ok = [self import:bytes byteLength:length loadCdn:cdn error:error];
if (!ok)
{
return nil;
}
self.isLoaded = true;
return self;
}
return nil;
}
- (nullable instancetype)initWithBytes:(UInt8*)bytes
byteLength:(UInt64)length
loadCdn:(bool)cdn
customAssetLoader:(LoadAsset)customAssetLoader
error:(NSError**)error
{
if (self = [super init])
{
BOOL ok = [self import:bytes
byteLength:length
loadCdn:cdn
customAssetLoader:customAssetLoader
error:error];
if (!ok)
{
return nil;
}
self.isLoaded = true;
return self;
}
return nil;
}
- (nullable instancetype)initWithData:(NSData*)data loadCdn:(bool)cdn error:(NSError**)error
{
UInt8* bytes = (UInt8*)[data bytes];
return [self initWithBytes:bytes byteLength:data.length loadCdn:cdn error:error];
}
- (nullable instancetype)initWithData:(NSData*)data
loadCdn:(bool)cdn
customAssetLoader:(LoadAsset)customAssetLoader
error:(NSError**)error
{
UInt8* bytes = (UInt8*)[data bytes];
return [self initWithBytes:bytes
byteLength:data.length
loadCdn:cdn
customAssetLoader:customAssetLoader
error:error];
}
/*
* Creates a RiveFile from a binary resource
*/
- (nullable instancetype)initWithResource:(NSString*)resourceName
withExtension:(NSString*)extension
loadCdn:(bool)cdn
error:(NSError**)error
{
// QUESTION: good ideas on how we can combine a few of these into following the same path
// better?
// there's a lot of copy pasta here.
NSString* filepath = [[NSBundle mainBundle] pathForResource:resourceName ofType:extension];
NSURL* fileUrl = [NSURL fileURLWithPath:filepath];
NSData* fileData = [NSData dataWithContentsOfURL:fileUrl];
return [self initWithData:fileData loadCdn:cdn error:error];
}
/*
* Creates a RiveFile from a binary resource, and assumes the resource extension is '.riv'
*/
- (nullable instancetype)initWithResource:(NSString*)resourceName
loadCdn:(bool)cdn
error:(NSError**)error
{
return [self initWithResource:resourceName withExtension:@"riv" loadCdn:cdn error:error];
}
- (nullable instancetype)initWithResource:(nonnull NSString*)resourceName
loadCdn:(bool)cdn
customAssetLoader:(nonnull LoadAsset)customAssetLoader
error:(NSError* __autoreleasing _Nullable* _Nullable)error
{
return [self initWithResource:resourceName
withExtension:@"riv"
loadCdn:cdn
customAssetLoader:customAssetLoader
error:error];
}
- (nullable instancetype)initWithResource:(nonnull NSString*)resourceName
withExtension:(nonnull NSString*)extension
loadCdn:(bool)cdn
customAssetLoader:(nonnull LoadAsset)customAssetLoader
error:(NSError* __autoreleasing _Nullable* _Nullable)error
{
NSString* filepath = [[NSBundle mainBundle] pathForResource:resourceName ofType:extension];
NSURL* fileUrl = [NSURL fileURLWithPath:filepath];
NSData* fileData = [NSData dataWithContentsOfURL:fileUrl];
return [self initWithData:fileData loadCdn:cdn customAssetLoader:customAssetLoader error:error];
}
/*
* Creates a RiveFile from an HTTP url
*/
- (nullable instancetype)initWithHttpUrl:(NSString*)url
loadCdn:(bool)loadCdn
withDelegate:(id<RiveFileDelegate>)delegate
{
return [self initWithHttpUrl:url
loadCdn:loadCdn
customAssetLoader:^bool(RiveFileAsset* asset, NSData* data, RiveFactory* factory) {
return false;
}
withDelegate:delegate];
}
- (nullable instancetype)initWithHttpUrl:(nonnull NSString*)url
loadCdn:(bool)cdn
customAssetLoader:(nonnull LoadAsset)customAssetLoader
withDelegate:(nonnull id<RiveFileDelegate>)delegate
{
self.isLoaded = false;
if (self = [super init])
{
self.delegate = delegate;
// Set up the http download task
NSURL* URL = [NSURL URLWithString:url];
// TODO: we are still adding 8MB of memory when we load our first http url.
NSURLSessionTask* task = [[NSURLSession sharedSession]
downloadTaskWithURL:URL
completionHandler:^(NSURL* location, NSURLResponse* response, NSError* error) {
if (!error)
{
// Load the data into the reader
NSData* data = [NSData dataWithContentsOfURL:location];
UInt8* bytes = (UInt8*)[data bytes];
// TODO: Do something with this error the proper way with delegates.
NSError* error = nil;
[self import:bytes
byteLength:[data length]
loadCdn:true
customAssetLoader:customAssetLoader
error:&error];
self.isLoaded = true;
dispatch_async(dispatch_get_main_queue(), ^{
if ([[NSThread currentThread] isMainThread])
{
if ([self.delegate respondsToSelector:@selector(riveFileDidLoad:error:)])
{
NSError* error = nil;
[self.delegate riveFileDidLoad:self error:&error];
}
}
});
}
}];
// Kick off the http download
[task resume];
return self;
}
return nil;
}
- (BOOL)import:(UInt8*)bytes byteLength:(UInt64)length loadCdn:(bool)loadCdn error:(NSError**)error
{
return [self import:bytes
byteLength:length
loadCdn:loadCdn
customAssetLoader:^bool(RiveFileAsset* asset, NSData* data, RiveFactory* factory) {
return false;
}
error:error];
}
- (BOOL)import:(UInt8*)bytes
byteLength:(UInt64)length
loadCdn:(bool)loadCdn
customAssetLoader:(LoadAsset)custom
error:(NSError**)error
{
rive::ImportResult result;
RenderContext* renderContext = [[RenderContextManager shared] getDefaultContext];
assert(renderContext);
rive::Factory* factory = [renderContext factory];
FallbackFileAssetLoader* fallbackLoader = [[FallbackFileAssetLoader alloc] init];
CustomFileAssetLoader* customAssetLoader =
[[CustomFileAssetLoader alloc] initWithLoader:custom];
[fallbackLoader addLoader:customAssetLoader];
if (loadCdn)
{
CDNFileAssetLoader* cdnLoader = [[CDNFileAssetLoader alloc] init];
[fallbackLoader addLoader:cdnLoader];
}
fileAssetLoader = new rive::FileAssetLoaderAdapter(fallbackLoader);
auto file = rive::File::import(rive::Span(bytes, length), factory, &result, fileAssetLoader);
if (result == rive::ImportResult::success)
{
riveFile = std::move(file);
return true;
}
switch (result)
{
case rive::ImportResult::unsupportedVersion:
*error =
[NSError errorWithDomain:RiveErrorDomain
code:RiveUnsupportedVersion
userInfo:@{
NSLocalizedDescriptionKey : @"Unsupported Rive File Version",
@"name" : @"UnsupportedVersion"
}];
break;
case rive::ImportResult::malformed:
*error = [NSError errorWithDomain:RiveErrorDomain
code:RiveMalformedFile
userInfo:@{
NSLocalizedDescriptionKey : @"Malformed Rive File.",
@"name" : @"Malformed"
}];
break;
default:
*error = [NSError errorWithDomain:RiveErrorDomain
code:RiveUnknownError
userInfo:@{
NSLocalizedDescriptionKey : @"Unknown error loading file.",
@"name" : @"Unknown"
}];
break;
}
return false;
}
- (RiveArtboard*)artboard:(NSError**)error
{
auto artboard = riveFile->artboardDefault();
if (artboard == nullptr)
{
*error = [NSError errorWithDomain:RiveErrorDomain
code:RiveNoArtboardsFound
userInfo:@{
NSLocalizedDescriptionKey : @"No Artboards Found.",
@"name" : @"NoArtboardsFound"
}];
return nil;
}
else
{
return [[RiveArtboard alloc] initWithArtboard:std::move(artboard)];
}
}
- (NSInteger)artboardCount
{
return riveFile->artboardCount();
}
- (RiveArtboard*)artboardFromIndex:(NSInteger)index error:(NSError**)error
{
auto artboard = riveFile->artboardAt(index);
if (artboard == nullptr)
{
*error = [NSError
errorWithDomain:RiveErrorDomain
code:RiveNoArtboardFound
userInfo:@{
NSLocalizedDescriptionKey : [NSString
stringWithFormat:@"No Artboard Found at index %ld.", (long)index],
@"name" : @"NoArtboardFound"
}];
return nil;
}
return [[RiveArtboard alloc] initWithArtboard:std::move(artboard)];
}
- (RiveArtboard*)artboardFromName:(NSString*)name error:(NSError**)error
{
std::string stdName = std::string([name UTF8String]);
auto artboard = riveFile->artboardNamed(stdName);
if (artboard == nullptr)
{
*error = [NSError errorWithDomain:RiveErrorDomain
code:RiveNoArtboardFound
userInfo:@{
NSLocalizedDescriptionKey : [NSString
stringWithFormat:@"No Artboard Found with name %@.", name],
@"name" : @"NoArtboardFound"
}];
return nil;
}
else
{
return [[RiveArtboard alloc] initWithArtboard:std::move(artboard)];
}
}
- (NSArray*)artboardNames
{
NSMutableArray* artboardNames = [NSMutableArray array];
for (NSUInteger i = 0; i < [self artboardCount]; i++)
{
[artboardNames addObject:[[self artboardFromIndex:i error:nil] name]];
}
return artboardNames;
}
/// Clean up rive file
- (void)dealloc
{
riveFile.reset(nullptr);
delete fileAssetLoader;
}
@end