Sped up reading with FlutterStandardCodec. (flutter/engine#38327)

* Sped up reading with FlutterStandardCodec.

* added missing license diff

* put the IsStandardType in the header so it's closer to where it should change

* added unittest for subclassing codecs

* fixed lints
This commit is contained in:
gaaclarke 2022-12-15 17:40:56 -08:00 committed by GitHub
parent 7f689ccb23
commit cf3c286780
8 changed files with 381 additions and 109 deletions

View File

@ -2417,6 +2417,8 @@ ORIGIN: ../../../flutter/shell/platform/darwin/common/framework/Source/FlutterCh
ORIGIN: ../../../flutter/shell/platform/darwin/common/framework/Source/FlutterChannelsTest.m + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/common/framework/Source/FlutterCodecs.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/common/framework/Source/FlutterStandardCodec.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/common/framework/Source/FlutterStandardCodecHelper.c + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/common/framework/Source/FlutterStandardCodecHelper.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/common/framework/Source/FlutterStandardCodec_Internal.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/graphics/FlutterDarwinContextMetalImpeller.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/graphics/FlutterDarwinContextMetalImpeller.mm + ../../../flutter/LICENSE
@ -4872,6 +4874,8 @@ FILE: ../../../flutter/shell/platform/darwin/common/framework/Source/FlutterChan
FILE: ../../../flutter/shell/platform/darwin/common/framework/Source/FlutterChannelsTest.m
FILE: ../../../flutter/shell/platform/darwin/common/framework/Source/FlutterCodecs.mm
FILE: ../../../flutter/shell/platform/darwin/common/framework/Source/FlutterStandardCodec.mm
FILE: ../../../flutter/shell/platform/darwin/common/framework/Source/FlutterStandardCodecHelper.c
FILE: ../../../flutter/shell/platform/darwin/common/framework/Source/FlutterStandardCodecHelper.h
FILE: ../../../flutter/shell/platform/darwin/common/framework/Source/FlutterStandardCodec_Internal.h
FILE: ../../../flutter/shell/platform/darwin/graphics/FlutterDarwinContextMetalImpeller.h
FILE: ../../../flutter/shell/platform/darwin/graphics/FlutterDarwinContextMetalImpeller.mm

View File

@ -47,6 +47,7 @@ source_set("flutter_channels_arc") {
"common/framework/Source/FlutterChannels.mm",
"common/framework/Source/FlutterCodecs.mm",
"common/framework/Source/FlutterStandardCodec.mm",
"common/framework/Source/FlutterStandardCodecHelper.c",
"common/framework/Source/FlutterStandardCodec_Internal.h",
]

View File

@ -37,6 +37,7 @@ source_set("framework_shared") {
"framework/Source/FlutterChannels.mm",
"framework/Source/FlutterCodecs.mm",
"framework/Source/FlutterStandardCodec.mm",
"framework/Source/FlutterStandardCodecHelper.c",
"framework/Source/FlutterStandardCodec_Internal.h",
]

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "flutter/shell/platform/darwin/common/framework/Source/FlutterStandardCodecHelper.h"
#import "flutter/shell/platform/darwin/common/framework/Source/FlutterStandardCodec_Internal.h"
FLUTTER_ASSERT_ARC
@ -338,30 +339,16 @@ using namespace flutter;
}
- (void)readBytes:(void*)destination length:(NSUInteger)length {
_range.length = length;
[_data getBytes:destination range:_range];
_range.location += _range.length;
FlutterStandardCodecHelperReadBytes(&_range.location, length, destination,
(__bridge CFDataRef)_data);
}
- (UInt8)readByte {
UInt8 value;
[self readBytes:&value length:1];
return value;
return FlutterStandardCodecHelperReadByte(&_range.location, (__bridge CFDataRef)_data);
}
- (UInt32)readSize {
UInt8 byte = [self readByte];
if (byte < 254) {
return (UInt32)byte;
} else if (byte == 254) {
UInt16 value;
[self readBytes:&value length:2];
return value;
} else {
UInt32 value;
[self readBytes:&value length:4];
return value;
}
return FlutterStandardCodecHelperReadSize(&_range.location, (__bridge CFDataRef)_data);
}
- (NSData*)readData:(NSUInteger)length {
@ -372,86 +359,47 @@ using namespace flutter;
}
- (NSString*)readUTF8 {
NSData* bytes = [self readData:[self readSize]];
return [[NSString alloc] initWithData:bytes encoding:NSUTF8StringEncoding];
return (__bridge NSString*)FlutterStandardCodecHelperReadUTF8(&_range.location,
(__bridge CFDataRef)_data);
}
- (void)readAlignment:(UInt8)alignment {
UInt8 mod = _range.location % alignment;
if (mod) {
_range.location += (alignment - mod);
}
}
- (FlutterStandardTypedData*)readTypedDataOfType:(FlutterStandardDataType)type {
UInt32 elementCount = [self readSize];
UInt8 elementSize = elementSizeForFlutterStandardDataType(type);
[self readAlignment:elementSize];
NSData* data = [self readData:elementCount * elementSize];
return [FlutterStandardTypedData typedDataWithData:data type:type];
FlutterStandardCodecHelperReadAlignment(&_range.location, alignment);
}
- (nullable id)readValue {
return [self readValueOfType:[self readByte]];
return (__bridge id)ReadValue((__bridge CFTypeRef)self);
}
static CFTypeRef ReadValue(CFTypeRef user_data) {
FlutterStandardReader* reader = (__bridge FlutterStandardReader*)user_data;
uint8_t type = FlutterStandardCodecHelperReadByte(&reader->_range.location,
(__bridge CFDataRef)reader->_data);
return (__bridge CFTypeRef)[reader readValueOfType:type];
}
static CFTypeRef ReadTypedDataOfType(FlutterStandardField field, CFTypeRef user_data) {
FlutterStandardReader* reader = (__bridge FlutterStandardReader*)user_data;
unsigned long* location = &reader->_range.location;
CFDataRef data = (__bridge CFDataRef)reader->_data;
FlutterStandardDataType type = FlutterStandardDataTypeForField(field);
UInt64 elementCount = FlutterStandardCodecHelperReadSize(location, data);
UInt64 elementSize = elementSizeForFlutterStandardDataType(type);
FlutterStandardCodecHelperReadAlignment(location, elementSize);
UInt64 length = elementCount * elementSize;
NSRange range = NSMakeRange(*location, length);
// Note: subdataWithRange performs better than CFDataCreate and
// CFDataCreateBytesNoCopy crashes.
NSData* bytes = [(__bridge NSData*)data subdataWithRange:range];
*location += length;
return (__bridge CFTypeRef)[FlutterStandardTypedData typedDataWithData:bytes type:type];
}
- (nullable id)readValueOfType:(UInt8)type {
FlutterStandardField field = (FlutterStandardField)type;
switch (field) {
case FlutterStandardFieldNil:
return nil;
case FlutterStandardFieldTrue:
return @YES;
case FlutterStandardFieldFalse:
return @NO;
case FlutterStandardFieldInt32: {
SInt32 value;
[self readBytes:&value length:4];
return @(value);
}
case FlutterStandardFieldInt64: {
SInt64 value;
[self readBytes:&value length:8];
return @(value);
}
case FlutterStandardFieldFloat64: {
Float64 value;
[self readAlignment:8];
[self readBytes:&value length:8];
return [NSNumber numberWithDouble:value];
}
case FlutterStandardFieldIntHex:
case FlutterStandardFieldString:
return [self readUTF8];
case FlutterStandardFieldUInt8Data:
case FlutterStandardFieldInt32Data:
case FlutterStandardFieldInt64Data:
case FlutterStandardFieldFloat32Data:
case FlutterStandardFieldFloat64Data:
return [self readTypedDataOfType:FlutterStandardDataTypeForField(field)];
case FlutterStandardFieldList: {
UInt32 length = [self readSize];
NSMutableArray* array = [NSMutableArray arrayWithCapacity:length];
for (UInt32 i = 0; i < length; i++) {
id value = [self readValue];
[array addObject:(value == nil ? [NSNull null] : value)];
}
return array;
}
case FlutterStandardFieldMap: {
UInt32 size = [self readSize];
NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:size];
for (UInt32 i = 0; i < size; i++) {
id key = [self readValue];
id val = [self readValue];
[dict setObject:(val == nil ? [NSNull null] : val)
forKey:(key == nil ? [NSNull null] : key)];
}
return dict;
}
default:
NSAssert(NO, @"Corrupted standard message");
}
return (__bridge id)FlutterStandardCodecHelperReadValueOfType(
&_range.location, (__bridge CFDataRef)_data, type, ReadValue, ReadTypedDataOfType,
(__bridge CFTypeRef)self);
}
@end

View File

@ -0,0 +1,166 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/darwin/common/framework/Source/FlutterStandardCodecHelper.h"
#include <stdint.h>
void FlutterStandardCodecHelperReadAlignment(unsigned long* location,
uint8_t alignment) {
uint8_t mod = *location % alignment;
if (mod) {
*location += (alignment - mod);
}
}
static uint8_t PeekByte(unsigned long location, CFDataRef data) {
uint8_t result;
CFRange range = CFRangeMake(location, 1);
CFDataGetBytes(data, range, &result);
return result;
}
void FlutterStandardCodecHelperReadBytes(unsigned long* location,
unsigned long length,
void* destination,
CFDataRef data) {
CFRange range = CFRangeMake(*location, length);
CFDataGetBytes(data, range, destination);
*location += length;
}
uint8_t FlutterStandardCodecHelperReadByte(unsigned long* location,
CFDataRef data) {
uint8_t value;
FlutterStandardCodecHelperReadBytes(location, 1, &value, data);
return value;
}
uint32_t FlutterStandardCodecHelperReadSize(unsigned long* location,
CFDataRef data) {
uint8_t byte = FlutterStandardCodecHelperReadByte(location, data);
if (byte < 254) {
return (uint32_t)byte;
} else if (byte == 254) {
UInt16 value;
FlutterStandardCodecHelperReadBytes(location, 2, &value, data);
return value;
} else {
UInt32 value;
FlutterStandardCodecHelperReadBytes(location, 4, &value, data);
return value;
}
}
static CFDataRef ReadDataNoCopy(unsigned long* location,
unsigned long length,
CFDataRef data) {
CFDataRef result = CFDataCreateWithBytesNoCopy(
kCFAllocatorDefault, CFDataGetBytePtr(data) + *location, length,
kCFAllocatorNull);
*location += length;
return CFAutorelease(result);
}
CFStringRef FlutterStandardCodecHelperReadUTF8(unsigned long* location,
CFDataRef data) {
uint32_t size = FlutterStandardCodecHelperReadSize(location, data);
CFDataRef bytes = ReadDataNoCopy(location, size, data);
CFStringRef result = CFStringCreateFromExternalRepresentation(
kCFAllocatorDefault, bytes, kCFStringEncodingUTF8);
return CFAutorelease(result);
}
// Peeks ahead to see if we are reading a standard type. If so, recurse
// directly to FlutterStandardCodecHelperReadValueOfType, otherwise recurse to
// objc.
static inline CFTypeRef FastReadValue(
unsigned long* location,
CFDataRef data,
CFTypeRef (*ReadValue)(CFTypeRef),
CFTypeRef (*ReadTypedDataOfType)(FlutterStandardField, CFTypeRef),
CFTypeRef user_data) {
uint8_t type = PeekByte(*location, data);
if (FlutterStandardFieldIsStandardType(type)) {
*location += 1;
return FlutterStandardCodecHelperReadValueOfType(
location, data, type, ReadValue, ReadTypedDataOfType, user_data);
} else {
return ReadValue(user_data);
}
}
CFTypeRef FlutterStandardCodecHelperReadValueOfType(
unsigned long* location,
CFDataRef data,
uint8_t type,
CFTypeRef (*ReadValue)(CFTypeRef),
CFTypeRef (*ReadTypedDataOfType)(FlutterStandardField, CFTypeRef),
CFTypeRef user_data) {
FlutterStandardField field = (FlutterStandardField)type;
switch (field) {
case FlutterStandardFieldNil:
return nil;
case FlutterStandardFieldTrue:
return kCFBooleanTrue;
case FlutterStandardFieldFalse:
return kCFBooleanFalse;
case FlutterStandardFieldInt32: {
int32_t value;
FlutterStandardCodecHelperReadBytes(location, 4, &value, data);
return CFAutorelease(
CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value));
}
case FlutterStandardFieldInt64: {
int64_t value;
FlutterStandardCodecHelperReadBytes(location, 8, &value, data);
return CFAutorelease(
CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &value));
}
case FlutterStandardFieldFloat64: {
Float64 value;
FlutterStandardCodecHelperReadAlignment(location, 8);
FlutterStandardCodecHelperReadBytes(location, 8, &value, data);
return CFAutorelease(
CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value));
}
case FlutterStandardFieldIntHex:
case FlutterStandardFieldString:
return FlutterStandardCodecHelperReadUTF8(location, data);
case FlutterStandardFieldUInt8Data:
case FlutterStandardFieldInt32Data:
case FlutterStandardFieldInt64Data:
case FlutterStandardFieldFloat32Data:
case FlutterStandardFieldFloat64Data:
return ReadTypedDataOfType(field, user_data);
case FlutterStandardFieldList: {
UInt32 length = FlutterStandardCodecHelperReadSize(location, data);
CFMutableArrayRef array = CFArrayCreateMutable(
kCFAllocatorDefault, length, &kCFTypeArrayCallBacks);
for (UInt32 i = 0; i < length; i++) {
CFTypeRef value = FastReadValue(location, data, ReadValue,
ReadTypedDataOfType, user_data);
CFArrayAppendValue(array, (value == nil ? kCFNull : value));
}
return CFAutorelease(array);
}
case FlutterStandardFieldMap: {
UInt32 size = FlutterStandardCodecHelperReadSize(location, data);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
kCFAllocatorDefault, size, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
for (UInt32 i = 0; i < size; i++) {
CFTypeRef key = FastReadValue(location, data, ReadValue,
ReadTypedDataOfType, user_data);
CFTypeRef val = FastReadValue(location, data, ReadValue,
ReadTypedDataOfType, user_data);
CFDictionaryAddValue(dict, (key == nil ? kCFNull : key),
(val == nil ? kCFNull : val));
}
return CFAutorelease(dict);
}
default:
// Malformed message.
assert(false);
}
}

View File

@ -0,0 +1,69 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SHELL_PLATFORM_DARWIN_COMMON_FRAMEWORK_SOURCE_FLUTTERSTANDARDCODECHELPER_H_
#define SHELL_PLATFORM_DARWIN_COMMON_FRAMEWORK_SOURCE_FLUTTERSTANDARDCODECHELPER_H_
#include <CoreFoundation/CoreFoundation.h>
#include <stdbool.h>
#include <stdint.h>
#if defined(__cplusplus)
extern "C" {
#endif
// Note: Update FlutterStandardFieldIsStandardType if this changes.
typedef enum {
FlutterStandardFieldNil,
FlutterStandardFieldTrue,
FlutterStandardFieldFalse,
FlutterStandardFieldInt32,
FlutterStandardFieldInt64,
FlutterStandardFieldIntHex,
FlutterStandardFieldFloat64,
FlutterStandardFieldString,
FlutterStandardFieldUInt8Data,
FlutterStandardFieldInt32Data,
FlutterStandardFieldInt64Data,
FlutterStandardFieldFloat64Data,
FlutterStandardFieldList,
FlutterStandardFieldMap,
FlutterStandardFieldFloat32Data,
} FlutterStandardField;
static inline bool FlutterStandardFieldIsStandardType(uint8_t field) {
return field <= FlutterStandardFieldFloat32Data &&
field >= FlutterStandardFieldNil;
}
void FlutterStandardCodecHelperReadAlignment(unsigned long* location,
uint8_t alignment);
void FlutterStandardCodecHelperReadBytes(unsigned long* location,
unsigned long length,
void* destination,
CFDataRef data);
uint8_t FlutterStandardCodecHelperReadByte(unsigned long* location,
CFDataRef data);
uint32_t FlutterStandardCodecHelperReadSize(unsigned long* location,
CFDataRef data);
CFStringRef FlutterStandardCodecHelperReadUTF8(unsigned long* location,
CFDataRef data);
CFTypeRef FlutterStandardCodecHelperReadValueOfType(
unsigned long* location,
CFDataRef data,
uint8_t type,
CFTypeRef (*ReadValue)(CFTypeRef),
CFTypeRef (*ReadTypedDataOfType)(FlutterStandardField, CFTypeRef),
CFTypeRef user_data);
#if defined(__cplusplus)
}
#endif
#endif // SHELL_PLATFORM_DARWIN_COMMON_FRAMEWORK_SOURCE_FLUTTERSTANDARDCODECHELPER_H_

View File

@ -6,27 +6,11 @@
#define SHELL_PLATFORM_DARWIN_COMMON_FRAMEWORK_SOURCE_FLUTTERSTANDARDCODECINTERNAL_H_
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
typedef NS_ENUM(NSInteger, FlutterStandardField) {
FlutterStandardFieldNil,
FlutterStandardFieldTrue,
FlutterStandardFieldFalse,
FlutterStandardFieldInt32,
FlutterStandardFieldInt64,
FlutterStandardFieldIntHex,
FlutterStandardFieldFloat64,
FlutterStandardFieldString,
FlutterStandardFieldUInt8Data,
FlutterStandardFieldInt32Data,
FlutterStandardFieldInt64Data,
FlutterStandardFieldFloat64Data,
FlutterStandardFieldList,
FlutterStandardFieldMap,
FlutterStandardFieldFloat32Data,
};
#import "flutter/shell/platform/darwin/common/framework/Source/FlutterStandardCodecHelper.h"
namespace flutter {
FlutterStandardField FlutterStandardFieldForDataType(FlutterStandardDataType type) {
FlutterStandardField FlutterStandardFieldForDataType(
FlutterStandardDataType type) {
switch (type) {
case FlutterStandardDataTypeUInt8:
return FlutterStandardFieldUInt8Data;
@ -40,7 +24,8 @@ FlutterStandardField FlutterStandardFieldForDataType(FlutterStandardDataType typ
return FlutterStandardFieldFloat64Data;
}
}
FlutterStandardDataType FlutterStandardDataTypeForField(FlutterStandardField field) {
FlutterStandardDataType FlutterStandardDataTypeForField(
FlutterStandardField field) {
switch (field) {
case FlutterStandardFieldUInt8Data:
return FlutterStandardDataTypeUInt8;

View File

@ -6,6 +6,93 @@
#include "gtest/gtest.h"
FLUTTER_ASSERT_NOT_ARC
@interface Pair : NSObject
@property(atomic, readonly, strong, nullable) NSObject* left;
@property(atomic, readonly, strong, nullable) NSObject* right;
- (instancetype)initWithLeft:(NSObject*)first right:(NSObject*)right;
@end
@implementation Pair
- (instancetype)initWithLeft:(NSObject*)left right:(NSObject*)right {
self = [super init];
if (self) {
_left = [left retain];
_right = [right retain];
}
return self;
}
- (void)dealloc {
[_left release];
[_right release];
[super dealloc];
}
@end
static const UInt8 kDATE = 128;
static const UInt8 kPAIR = 129;
@interface ExtendedWriter : FlutterStandardWriter
- (void)writeValue:(id)value;
@end
@implementation ExtendedWriter
- (void)writeValue:(id)value {
if ([value isKindOfClass:[NSDate class]]) {
[self writeByte:kDATE];
NSDate* date = value;
NSTimeInterval time = date.timeIntervalSince1970;
SInt64 ms = (SInt64)(time * 1000.0);
[self writeBytes:&ms length:8];
} else if ([value isKindOfClass:[Pair class]]) {
Pair* pair = value;
[self writeByte:kPAIR];
[self writeValue:pair.left];
[self writeValue:pair.right];
} else {
[super writeValue:value];
}
}
@end
@interface ExtendedReader : FlutterStandardReader
- (id)readValueOfType:(UInt8)type;
@end
@implementation ExtendedReader
- (id)readValueOfType:(UInt8)type {
switch (type) {
case kDATE: {
SInt64 value;
[self readBytes:&value length:8];
NSTimeInterval time = [NSNumber numberWithLong:value].doubleValue / 1000.0;
return [NSDate dateWithTimeIntervalSince1970:time];
}
case kPAIR: {
return [[[Pair alloc] initWithLeft:[self readValue] right:[self readValue]] autorelease];
}
default:
return [super readValueOfType:type];
}
}
@end
@interface ExtendedReaderWriter : FlutterStandardReaderWriter
- (FlutterStandardWriter*)writerWithData:(NSMutableData*)data;
- (FlutterStandardReader*)readerWithData:(NSData*)data;
@end
@implementation ExtendedReaderWriter
- (FlutterStandardWriter*)writerWithData:(NSMutableData*)data {
return [[[ExtendedWriter alloc] initWithData:data] autorelease];
}
- (FlutterStandardReader*)readerWithData:(NSData*)data {
return [[[ExtendedReader alloc] initWithData:data] autorelease];
}
@end
static void CheckEncodeDecode(id value, NSData* expectedEncoding) {
FlutterStandardMessageCodec* codec = [FlutterStandardMessageCodec sharedInstance];
NSData* encoded = [codec encode:value];
@ -252,3 +339,14 @@ TEST(FlutterStandardCodec, HandlesErrorEnvelopes) {
id decoded = [codec decodeEnvelope:encoded];
ASSERT_TRUE([decoded isEqual:error]);
}
TEST(FlutterStandardCodec, HandlesSubclasses) {
ExtendedReaderWriter* extendedReaderWriter = [[[ExtendedReaderWriter alloc] init] autorelease];
FlutterStandardMessageCodec* codec =
[FlutterStandardMessageCodec codecWithReaderWriter:extendedReaderWriter];
Pair* pair = [[[Pair alloc] initWithLeft:@1 right:@2] autorelease];
NSData* encoded = [codec encode:pair];
Pair* decoded = [codec decode:encoded];
ASSERT_TRUE([pair.left isEqual:decoded.left]);
ASSERT_TRUE([pair.right isEqual:decoded.right]);
}