-
Notifications
You must be signed in to change notification settings - Fork 9.4k
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
misc: drive-by code cleanup in BaseNode #7723
Conversation
slightly better diff with ?w=1 |
if (predicate(node)) { | ||
node.traverse( | ||
node => idsToInclude.add(node.id), | ||
node => node._dependencies.filter(parent => !idsToInclude.has(parent)) |
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.
dangers of Set<any>
...we were checking the parent
node object against a set of string IDs, so weren't saving any time here :)
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.
nice fix!!
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.
I guess this one is difficult to test since it's just a marginal time savings huh
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.
I guess this one is difficult to test since it's just a marginal time savings huh
yeah, and even at its worst it's really not that bad compared to other things we do with the graph
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.
nice looks pretty good to me! just the bit about skipping some cloning when there's not a predicate
// Walk back up dependencies, cloning all nodes in the path to this one. | ||
node.traverse( | ||
node => idsToIncludedClones.set(node.id, node.cloneWithoutRelationships()), | ||
node => node._dependencies.filter(parent => !idsToIncludedClones.has(parent.id)) |
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.
can we get a comment on this one that explains the filter while we're at it? :) it even took me a sec to remember why this was happening at all
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.
can we get a comment on this one that explains the filter while we're at it?
good call
if (idsToIncludedClones.has(node.id)) return; | ||
if (predicate && !predicate(node)) return; | ||
|
||
// Walk back up dependencies, cloning all nodes in the path to this one. |
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.
can we only do the reverse traversal when there's a predicate?
that's one nice feature of the previous impl I wouldn't want to lose
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.
can we only do the reverse traversal when there's a predicate?
do you mean something like
// Walk down dependents.
rootNode.traverse(node => {
if (idsToIncludedClones.has(node.id)) return;
if (!predicate) {
// Clone every node.
idsToIncludedClones.set(node.id, node.cloneWithoutRelationships());
} else if (predicate(node)) {
// Walk back up dependencies, cloning all nodes in the path to this one.
node.traverse(
node => idsToIncludedClones.set(node.id, node.cloneWithoutRelationships()),
// Dependencies already cloned have already cloned ancestors, so no need to visit again.
node => node._dependencies.filter(parent => !idsToIncludedClones.has(parent.id))
);
}
});
Or other ideas for formatting to make it less dense looking?
Another option is to make a default always true predicate, so it's a little clearer.
Agreed it is a bit weird to do the reverse traversal in the no predicate case. My thinking before was that we only have a single call of cloneWithRelationships
without a predicate, so it didn't seem to be a super important case to call out (and since the filter line makes there be no next nodes for every node, it doesn't do any significant extra work)
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.
that's exactly what I meant :)
we could do an early return in the !predicate
case to avoid the nesting? but IMO this is fine
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.
done
getNext = node => node.getDependents(); | ||
} | ||
const visited = new Set([this.id]); | ||
const queue = [[/** @type {Node} */(/** @type {BaseNode} */(this))]]; |
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.
do we still need this? I'm surprised we need to cast BaseNode
for this
hopefully this is something that was fixed :)
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.
do we still need this?
Annoyingly, yes. this
extends BaseNode
, but the lie is that it's a Node
. Humans know the only types of BaseNode
are CPUNode | NetworkNode
so the cast to Node
is ok, but the compiler accounts for the fact that we could be calling on a different class derived from BaseNode
, so it's not necessarily compatible with Node
.
So to cast while following the type assertion rules, we have to upcast to BaseNode
and then downcast to Node
. The other options is an assertion and a @ts-ignore
...maybe that's more honest (albeit also ugly).
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.
Humans know the only types of
BaseNode
areCPUNode | NetworkNode
so
I mean, even that's not entirely true. In tests we call traverse()
with a graph of BaseNode
nodes, but in real code it's much more convenient to only get CPUNode | NetworkNode
back, so we should continue to pretend it's true :)
*/ | ||
_traversePaths(iterator, getNext) { | ||
const stack = [[/** @type {Node} */(/** @type {BaseNode} */(this))]]; |
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.
lol I guess this is a queue :)
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.
lol I guess this is a queue :)
your comment in hasCycle
(which does DFS) underlined the humor:
// Get the last node in the stack (DFS uses stack, not queue)
I was thinking the approach changed at some point during the writing of traverse
:)
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.
haha yeah that was my guess as well when I was looking at this, I'll take your benefit of the doubt on this one lol 🤞
return nodesToVisit; | ||
}; | ||
for (const nextNode of getNextNodes(node)) { | ||
if (visited.has(nextNode.id)) continue; |
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.
having trouble finding the substantial changes here, it's just inlining the getNext
rewrite from before and eliminating traverse
right?
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.
it's just inlining the
getNext
rewrite from before and eliminating_traversePaths
right?
yeah, main thing was the getNext
rewriting and call to _traversePaths
was breaking my brain when I was first (re)reading it, and it seemed like traverse
has proven its design, so it seemed ok to inline at this point.
This was probably the bigger one for me saying it's ok to push back on changes if you don't think it reads better :)
technically we save n-1 visited.add(node.id)
calls, but that's not worth much and could have been done without all the other things.
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.
nah, I like this. originally I had used _traversePaths
in another method but that need hasn't materialized in the 1.5 years since so I think we're pretty safe :)
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.
LGTM!
While starting work on robust TTI, there were some things that were a little hard to follow as I was stepping through graphs. Tried to make them a bit clearer in code and comments while keeping everything working the same.
@patrickhulce let me know if this feels like too much change for change's sake and I can scale it back :)