Skip to content

Commit

Permalink
Create a centralized configuration API (#747)
Browse files Browse the repository at this point in the history
* Update the dangerfile

* Make a trivial change to test new dangerfile

* Try out the new value with another trivial change

* Add a configuration API to make a unified place for pulling config from clients safely

* Specify properties for delegate

* Finish removing text experiment global enable

* Generate the config file

* Clean up configuration to fix tests

* Work on making it serializable

* Finish it up

* Fix example code

* Update sample project

* Clean up a few things

* Align with new project order

* Make it faster and update license header

* Add an option to specify your config at compile time

* Update another license header

* Add a version field, and bring interface state coalescing into configuration

* Update CA queue code

* Update CATransactionQueue tests

* Turn transaction queue on by default (for now, see comment)

* Update the tests

* Update the tests AGAIN

* Remove unused ordered set
  • Loading branch information
Adlai-Holler committed Mar 25, 2018
1 parent 3d9fe8c commit 27fac9f
Show file tree
Hide file tree
Showing 46 changed files with 838 additions and 320 deletions.
48 changes: 48 additions & 0 deletions AsyncDisplayKit.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler)
- Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler)
- Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler)
- Added a configuration API – a unified place to turn on/off experimental Texture features. See `ASConfiguration.h` for info. [Adlai Holler](https://github.com/Adlai-Holler)
- **Breaking** Changes to ASNetworkImageNode: [Adlai Holler](https://github.com/Adlai-Holler)
- Modified `ASImageDownloaderCompletion` to add an optional `id userInfo` field. Your custom downloader can pass `nil`.
- Modified the last argument to `-[ASNetworkImageNodeDelegate imageNode:didLoadImage:info:]` method from a struct to an object of new class `ASNetworkImageLoadInfo` which includes other metadata about the load operation.
Expand Down
23 changes: 23 additions & 0 deletions Schemas/configuration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"id": "configuration.json",
"title": "configuration",
"description" : "Schema definition of a Texture Configuration",
"$schema": "http://json-schema.org/schema#",
"type": "object",
"properties": {
"version" : {
"type" : "number"
},
"experimental_features": {
"type": "array",
"items": {
"type": "string",
"enum": [
"exp_graphics_contexts",
"exp_text_node",
"exp_interface_state_coalesce"
]
}
}
}
}
62 changes: 62 additions & 0 deletions Source/ASConfiguration.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// ASConfiguration.h
// Texture
//
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//

#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASExperimentalFeatures.h>

@protocol ASConfigurationDelegate;

NS_ASSUME_NONNULL_BEGIN

static NSInteger const ASConfigurationSchemaCurrentVersion = 1;

AS_SUBCLASSING_RESTRICTED
@interface ASConfiguration : NSObject <NSCopying>

/**
* Initialize this configuration with the provided dictionary,
* or nil to create an empty configuration.
*
* The schema is located in `schemas/configuration.json`.
*/
- (instancetype)initWithDictionary:(nullable NSDictionary *)dictionary;

/**
* The delegate for configuration-related events.
* Delegate methods are called from a serial queue.
*/
@property (nonatomic, strong, nullable) id<ASConfigurationDelegate> delegate;

/**
* The experimental features to enable in Texture.
* See ASExperimentalFeatures for functions to convert to/from a string array.
*/
@property (nonatomic) ASExperimentalFeatures experimentalFeatures;

@end

/**
* Implement this method in a category to make your
* configuration available to Texture. It will be read
* only once and copied.
*
* NOTE: To specify your configuration at compile-time, you can
* define AS_FIXED_CONFIG_JSON as a C-string of JSON. This method
* will then be implemented to parse that string and generate
* a configuration.
*/
@interface ASConfiguration (UserProvided)
+ (ASConfiguration *)textureConfiguration NS_RETURNS_RETAINED;
@end

NS_ASSUME_NONNULL_END
67 changes: 67 additions & 0 deletions Source/ASConfiguration.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// ASConfiguration.m
// Texture
//
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//

#import <AsyncDisplayKit/ASConfiguration.h>
#import <AsyncDisplayKit/ASConfigurationInternal.h>

/// Not too performance-sensitive here.

/// Get this from C++, without the extra exception handling.
#define autotype __auto_type

@implementation ASConfiguration

- (instancetype)initWithDictionary:(NSDictionary *)dictionary
{
if (self = [super init]) {
autotype featureStrings = ASDynamicCast(dictionary[@"experimental_features"], NSArray);
autotype version = ASDynamicCast(dictionary[@"version"], NSNumber).integerValue;
if (version != ASConfigurationSchemaCurrentVersion) {
NSLog(@"Texture warning: configuration schema is old version (%zd vs %zd)", version, ASConfigurationSchemaCurrentVersion);
}
self.experimentalFeatures = ASExperimentalFeaturesFromArray(featureStrings);
}
return self;
}

- (id)copyWithZone:(NSZone *)zone
{
ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil];
config.experimentalFeatures = self.experimentalFeatures;
config.delegate = self.delegate;
return config;
}

@end

//#define AS_FIXED_CONFIG_JSON "{ \"version\" : 1, \"experimental_features\": [ \"exp_text_node\" ] }"

#ifdef AS_FIXED_CONFIG_JSON

@implementation ASConfiguration (UserProvided)

+ (ASConfiguration *)textureConfiguration NS_RETURNS_RETAINED
{
NSData *data = [@AS_FIXED_CONFIG_JSON dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *d = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
if (!d) {
NSAssert(NO, @"Error parsing fixed config string '%s': %@", AS_FIXED_CONFIG_JSON, error);
return nil;
} else {
return [[ASConfiguration alloc] initWithDictionary:d];
}
}

@end

#endif // AS_FIXED_CONFIG_JSON
31 changes: 31 additions & 0 deletions Source/ASConfigurationDelegate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// ASConfigurationDelegate.h
// Texture
//
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//

#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASConfiguration.h>

NS_ASSUME_NONNULL_BEGIN

/**
* Used to communicate configuration-related events to the client.
*/
@protocol ASConfigurationDelegate <NSObject>

/**
* Texture performed its first behavior related to the feature(s).
* This can be useful for tracking the impact of the behavior (A/B testing).
*/
- (void)textureDidActivateExperimentalFeatures:(ASExperimentalFeatures)features;

@end

NS_ASSUME_NONNULL_END
6 changes: 3 additions & 3 deletions Source/ASDisplayNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2915,7 +2915,7 @@ - (void)didExitHierarchy
}
};

if ([[ASCATransactionQueue sharedQueue] disabled]) {
if (!ASCATransactionQueue.sharedQueue.enabled) {
dispatch_async(dispatch_get_main_queue(), exitVisibleInterfaceState);
} else {
exitVisibleInterfaceState();
Expand Down Expand Up @@ -2980,7 +2980,7 @@ - (ASInterfaceState)interfaceState

- (void)setInterfaceState:(ASInterfaceState)newState
{
if ([[ASCATransactionQueue sharedQueue] disabled]) {
if (!ASCATransactionQueue.sharedQueue.enabled) {
[self applyPendingInterfaceState:newState];
} else {
ASDN::MutexLocker l(__instanceLock__);
Expand Down Expand Up @@ -3012,7 +3012,7 @@ - (void)applyPendingInterfaceState:(ASInterfaceState)newPendingState
ASDN::MutexLocker l(__instanceLock__);
// newPendingState will not be used when ASCATransactionQueue is enabled
// and use _pendingInterfaceState instead for interfaceState update.
if ([[ASCATransactionQueue sharedQueue] disabled]) {
if (!ASCATransactionQueue.sharedQueue.enabled) {
_pendingInterfaceState = newPendingState;
}
oldState = _interfaceState;
Expand Down
36 changes: 36 additions & 0 deletions Source/ASExperimentalFeatures.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// ASExperimentalFeatures.h
// Texture
//
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//

#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASBaseDefines.h>

NS_ASSUME_NONNULL_BEGIN
ASDISPLAYNODE_EXTERN_C_BEGIN

/**
* A bit mask of features.
*/
typedef NS_OPTIONS(NSUInteger, ASExperimentalFeatures) {
ASExperimentalGraphicsContexts = 1 << 0, // exp_graphics_contexts
ASExperimentalTextNode = 1 << 1, // exp_text_node
ASExperimentalInterfaceStateCoalescing = 1 << 2, // exp_interface_state_coalesce
ASExperimentalFeatureAll = 0xFFFFFFFF
};

/// Convert flags -> name array.
NSArray<NSString *> *ASExperimentalFeaturesGetNames(ASExperimentalFeatures flags);

/// Convert name array -> flags.
ASExperimentalFeatures ASExperimentalFeaturesFromArray(NSArray<NSString *> *array);

ASDISPLAYNODE_EXTERN_C_END
NS_ASSUME_NONNULL_END
45 changes: 45 additions & 0 deletions Source/ASExperimentalFeatures.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// ASExperimentalFeatures.m
// Texture
//
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//

#import <AsyncDisplayKit/ASExperimentalFeatures.h>

NSArray<NSString *> *ASExperimentalFeaturesGetNames(ASExperimentalFeatures flags)
{
NSArray *allNames = ASCreateOnce((@[@"exp_graphics_contexts",
@"exp_text_node",
@"exp_interface_state_coalesce"]));

if (flags == ASExperimentalFeatureAll) {
return allNames;
}

// Go through all names, testing each bit.
NSUInteger i = 0;
return ASArrayByFlatMapping(allNames, NSString *name, ({
(flags & (1 << i++)) ? name : nil;
}));
}

// O(N^2) but with counts this small, it's probably faster
// than hashing the strings.
ASExperimentalFeatures ASExperimentalFeaturesFromArray(NSArray<NSString *> *array)
{
NSArray *allNames = ASExperimentalFeaturesGetNames(ASExperimentalFeatureAll);
ASExperimentalFeatures result = 0;
for (NSString *str in array) {
NSUInteger i = [allNames indexOfObject:str];
if (i != NSNotFound) {
result |= (1 << i);
}
}
return result;
}
10 changes: 7 additions & 3 deletions Source/ASNetworkImageLoadInfo.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
//
// ASNetworkImageLoadInfo.h
// AsyncDisplayKit
// Texture
//
// Created by Adlai on 1/30/18.
// Copyright © 2018 Facebook. All rights reserved.
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//

#import <Foundation/Foundation.h>
Expand Down
10 changes: 7 additions & 3 deletions Source/ASNetworkImageLoadInfo.m
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
//
// ASNetworkImageLoadInfo.m
// AsyncDisplayKit
// Texture
//
// Created by Adlai on 1/30/18.
// Copyright © 2018 Facebook. All rights reserved.
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//

#import <AsyncDisplayKit/ASNetworkImageLoadInfo.h>
Expand Down
9 changes: 3 additions & 6 deletions Source/ASRunLoopQueue.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ AS_SUBCLASSING_RESTRICTED
@interface ASCATransactionQueue : ASAbstractRunLoopQueue

@property (atomic, readonly) BOOL isEmpty;
@property (atomic, readonly) BOOL disabled;

@property (atomic, readonly, getter=isEnabled) BOOL enabled;

/**
* The queue to run on main run loop before CATransaction commit.
*
Expand All @@ -72,11 +74,6 @@ AS_SUBCLASSING_RESTRICTED

- (void)enqueue:(id<ASCATransactionQueueObserving>)object;

/**
* @abstract Apply a node's interfaceState immediately rather than adding to the queue.
*/
- (void)disable;

@end


Expand Down
Loading

0 comments on commit 27fac9f

Please sign in to comment.