Repository navigation
Try to allow memoization of components with state #348
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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!"); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I should use CKAssert or whatever here
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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]; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. might as well |
||
| 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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 <> | ||
|
|
@@ -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)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we just move this check inside
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| component = nil; | ||
| } | ||
| } | ||
| if (!component && block) { | ||
| component = block(); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -34,4 +34,16 @@ struct CKComponentScopeFramePair { | |
|
|
||
| @property (nonatomic, strong, readonly) CKComponentScopeHandle *handle; | ||
|
|
||
| struct _CKChildScopeFrameRet { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's fine.
|
||
| Class cls; | ||
| id identifier; | ||
| CKComponentScopeFrame *frame; | ||
| }; | ||
|
|
||
| - (_CKChildScopeFrameRet)childScopeFrameForOldIdentifier:(CKComponentScopeHandleIdentifier)globalIdentifier; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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 |
||
|
|
||
| - (void)injectScopeHierarchy:(_CKChildScopeFrameRet)oldFrame; | ||
|
|
||
| - (BOOL)anyDescendentHasPendingUpdates:(const CKComponentStateUpdateMap &)stateUpdates; | ||
|
|
||
| @end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 <> | ||
|
|
@@ -88,4 +88,40 @@ - (instancetype)initWithHandle:(CKComponentScopeHandle *)handle | |
| return self; | ||
| } | ||
|
|
||
| - (void)injectScopeHierarchy:(_CKChildScopeFrameRet)oldFrame | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pass const reference:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope, carry on then
|
||
| { | ||
| Class componentClass = oldFrame.cls; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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."); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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}); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. descendant not descendent |
||
| { | ||
| auto it = stateUpdates.find(_handle.globalIdentifier); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not as far as I can see because 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) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (the copy is necessary since |
||
| if ([pr.second anyDescendentHasPendingUpdates:stateUpdates]) { | ||
| return YES; | ||
| } | ||
| } | ||
| return NO; | ||
| } | ||
|
|
||
| @end | ||
There was a problem hiding this comment.
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?There was a problem hiding this comment.
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...