Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ComponentKit/Core/CKComponent.mm
Original file line number Diff line number Diff line change
Expand Up @@ -300,4 +300,9 @@ - (id)scopeFrameToken
return _scopeHandle ? @(_scopeHandle.globalIdentifier) : nil;
}

- (CKComponentScopeHandleIdentifier)globalIdentifier
{
return _scopeHandle.globalIdentifier;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This already exists as scopeFrameToken, can we just use that?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see why you didn't and exposed this instead. Will consider...

}

@end
4 changes: 4 additions & 0 deletions ComponentKit/Core/CKComponentInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#import <ComponentKit/ComponentMountContext.h>
#import <ComponentKit/CKComponent.h>
#import <ComponentKit/CKComponentLayout.h>
#import <ComponentKit/CKComponentScopeTypes.h>

@interface CKComponent ()

Expand Down Expand Up @@ -67,6 +68,9 @@
/** For internal use only; don't touch this. */
@property (nonatomic, strong, readonly) id scopeFrameToken;

/** For internal use only; don't touch this. */
@property (nonatomic, readonly) CKComponentScopeHandleIdentifier globalIdentifier;

@end

// Internal, for CKComponent
Expand Down
41 changes: 41 additions & 0 deletions ComponentKit/Core/CKComponentMemoizer.mm
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,47 @@
#import "CKComponentSubclass.h"
#import "CKMacros.h"
#import "CKInternalHelpers.h"
#import "CKThreadLocalComponentScope.h"
#import "CKComponentScopeHandle.h"

#include <map>


static NSString *CKComponentMemoizerThreadKey = @"CKComponentMemoizer";

static bool CKCanUseMemoizedComponent(CKComponent *c) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: { on new line

nit: don't need "CK" prefix if it's static

CKThreadLocalComponentScope *s = CKThreadLocalComponentScope::currentScope();

NSCAssert(s != nullptr, @"No thread local scope!");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should use CKAssert or whatever here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup

if (!s) {
return false;
}

auto stackTop = s->stack.top();
CKComponentScopeHandleIdentifier oldIdent = c.globalIdentifier;

// Look for the equivalent child frame
auto oldFrameRet = [stackTop.equivalentPreviousFrame childScopeFrameForOldIdentifier:oldIdent];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might as well const auto

CKComponentScopeFrame *oldScopeFrame = oldFrameRet.frame;

// Make sure none of the descendents of the oldScopeFrame have pending state updates
if (oldScopeFrame && s->stateUpdates.size() > 0) {
if ([oldScopeFrame anyDescendentHasPendingUpdates:s->stateUpdates]) {
//NSLog(@"Cannot reuse component %@ because state has been updated.", c);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clean up these logs of course…

return false;
}
}

// Copy oldScopeFrame into new scope frame
if (oldScopeFrame) {
[stackTop.frame injectScopeHierarchy:oldFrameRet];
}

//NSLog(@"Ok to reuse component %@.", c);

return true;
}

// Define hash as just pulling out the precomputed hash field
namespace std {
template <>
Expand Down Expand Up @@ -149,6 +185,11 @@ id CKMemoize(CKMemoizationKey memoizationKey, id (^block)(void))
{
_CKComponentMemoizerImpl *impl = [_CKComponentMemoizerImpl currentMemoizer];
CKComponent *component = [impl dequeueComponentForKey:memoizationKey];
if (component) {
if (!CKCanUseMemoizedComponent(component)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we just move this check inside dequeue? Or do you prefer to keep it here for cleanliness of the queue? your call

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I wrote some tests, so going to try changing it and re-running them. One thing is that we could probably leave the old component in the queue if it's not usable if I move the call into dequeue.

component = nil;
}
}
if (!component && block) {
component = block();
}
Expand Down
14 changes: 13 additions & 1 deletion ComponentKit/Core/Scope/CKComponentScopeFrame.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ struct CKComponentScopeFramePair {

+ (CKComponentScopeFramePair)childPairForPair:(const CKComponentScopeFramePair &)pair
newRoot:(CKComponentScopeRoot *)newRoot
componentClass:(Class)aClass
componentClass:(Class)componentClass
identifier:(id)identifier
initialStateCreator:(id (^)(void))initialStateCreator
stateUpdates:(const CKComponentStateUpdateMap &)stateUpdates;
Expand All @@ -34,4 +34,16 @@ struct CKComponentScopeFramePair {

@property (nonatomic, strong, readonly) CKComponentScopeHandle *handle;

struct _CKChildScopeFrameRet {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is somewhat inelegant, but I need to return more than just one of these items… thoughts?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine.

  • Move it to the top of the file
  • Rename to CKChildScopeHierarchy (drop underscore, lose abbreviation, and I like your term "scope hierarchy" below better than "child scope frame")

Class cls;
id identifier;
CKComponentScopeFrame *frame;
};

- (_CKChildScopeFrameRet)childScopeFrameForOldIdentifier:(CKComponentScopeHandleIdentifier)globalIdentifier;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

childScopeHierarchyForScopeFrameToken:(id)scopeFrameToken

Inside the implementation just check if the scope frame token is an NSNumber and unwrap it if so. Kinda sucks as it's a hidden coupling with CKComponent's scopeFrameToken, but it is better than exposing another method on CKComponent (even if it's internal-only)


- (void)injectScopeHierarchy:(_CKChildScopeFrameRet)oldFrame;

- (BOOL)anyDescendentHasPendingUpdates:(const CKComponentStateUpdateMap &)stateUpdates;

@end
40 changes: 38 additions & 2 deletions ComponentKit/Core/Scope/CKComponentScopeFrame.mm
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@
#import "CKMacros.h"
#import "CKThreadLocalComponentScope.h"

typedef struct _CKStateScopeKey {
struct _CKStateScopeKey {
Class __unsafe_unretained componentClass;
id identifier;

bool operator==(const _CKStateScopeKey &v) const {
return (CKObjectIsEqual(this->componentClass, v.componentClass) && CKObjectIsEqual(this->identifier, v.identifier));
}
} _CKStateScopeKey;
};

namespace std {
template <>
Expand Down Expand Up @@ -88,4 +88,40 @@ - (instancetype)initWithHandle:(CKComponentScopeHandle *)handle
return self;
}

- (void)injectScopeHierarchy:(_CKChildScopeFrameRet)oldFrame

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pass const reference:

  • (void)injectChildScopeHierarchy:(const CKChildScopeHierarchy &)hierarchy;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing const reference instead of by value is actually less efficient since it can't move in. Do you have a specific reason for this request?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, carry on then

On Aug 24, 2015, at 3:25 PM, Andrew Pouliot notifications@github.com wrote:

In ComponentKit/Core/Scope/CKComponentScopeFrame.mm:

@@ -88,4 +88,40 @@ - (instancetype)initWithHandle:(CKComponentScopeHandle *)handle
return self;
}

+- (void)injectScopeHierarchy:(_CKChildScopeFrameRet)oldFrame
Passing const reference instead of by value is actually less efficient since it can't move in. Do you have a specific reason for this request?

—
Reply to this email directly or view it on GitHub.

{
Class componentClass = oldFrame.cls;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably unnecessary; the compiler will do this for you in C++ if you're doing it for efficiency reasons. if you just prefer it for readability, carry on

id identifier = oldFrame.identifier;
CKAssert([oldFrame.frame isKindOfClass:[CKComponentScopeFrame class]], @"Can't inject with incorrect frame class.");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this here? it's typechecked, right?


auto result = _children.insert({{componentClass, identifier}, oldFrame.frame});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please review this carefully, I'm not familiar enough with the code to know it it's really safe.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup it's safe. You're injecting a scope frame from the old tree, which by now is immutable.


CKAssert(result.second, @"Scope collision: attempting to create duplicate scope %@:%@", componentClass, identifier);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, good.

}

- (_CKChildScopeFrameRet)childScopeFrameForOldIdentifier:(CKComponentScopeHandleIdentifier)globalIdentifier
{
for (auto &pr : _children) {
if (pr.second->_handle.globalIdentifier == globalIdentifier) {
return _CKChildScopeFrameRet{pr.first.componentClass, pr.first.identifier, pr.second};
}
}
return {nil, nil, 0};
}

- (BOOL)anyDescendentHasPendingUpdates:(const CKComponentStateUpdateMap &)stateUpdates

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

descendant not descendent

{
auto it = stateUpdates.find(_handle.globalIdentifier);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way we could use immutable ptrs to avoid this recursion in the no-change case, which should be very common?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not as far as I can see because stateUpdates is just a flat map of ids -> updates.

We could annotate each node in the state tree with a hash-set of all the IDs below it, which would make this an O(1) operation here, but given the typical size of a state tree (10-15 elements) I don't think it's worth avoiding the recursion. For now let's do this, and come back to it if it's a hotspot (doubt it)

if (it != stateUpdates.end()) {
return YES;
}
// Look in children
for (auto &pr : _children) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const auto &pr

will avoid copying, not that it matters much since they're just pairs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(the copy is necessary since stateUpdates is const, so to give you a non-const pr it must copy the pair)

if ([pr.second anyDescendentHasPendingUpdates:stateUpdates]) {
return YES;
}
}
return NO;
}

@end