-
Notifications
You must be signed in to change notification settings - Fork 28
add helper methods to project MemoRef on some types #684
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
base: main
Are you sure you want to change the base?
Conversation
9840abf
to
18564b2
Compare
18564b2
to
6e230c8
Compare
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.
Will have to re-read and review more deeply. This is awesome!!! Was not expecting this level of shenanigans 🤩
6e230c8
to
d10852f
Compare
d10852f
to
f86b863
Compare
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.
Partial review, overall, LGTM and I just want to reason about the unit tests and other details
|
||
#[inline(always)] | ||
fn step_identity(value: &dyn Any) -> &dyn Any { | ||
value |
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 can panic, right? The len should always stop before we reach a step_identity, 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.
array is always initialised with step_identity
function pointers. But because we use len: 0
it's never called. If somehow there is a weird bug that increases len, then step_identity
is almost noop. The only optimisation I believe we can apply here is using MaybeUninit
, but it is tiny.
crates/pico/src/memo_ref.rs
Outdated
db: self.db, | ||
derived_node_id: self.derived_node_id, | ||
projectors: self.projectors, | ||
projectors_len: self.projectors_len, |
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 would be cleaner, I think, to just say self.projectors_len + 1
. Wdyt?
pub(crate) db: *const dyn DatabaseDyn, | ||
pub(crate) derived_node_id: DerivedNodeId, | ||
projectors: [MemoRefProjectorStep; MAX_PROJECTOR_STEPS], | ||
projectors_len: u8, |
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 add some comments here explaining what's happening?
pub struct MemoRef<T> { | ||
pub(crate) db: *const dyn DatabaseDyn, | ||
pub(crate) derived_node_id: DerivedNodeId, | ||
projectors: [MemoRefProjectorStep; MAX_PROJECTOR_STEPS], |
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 think that the common case is that projectors_len
is 0. So, perhaps it would be more performant to have an projectors: Option<...>
(and no projectors_len
)? (Do you think this is true? I'm not 100% convinced)
If it's indeed true that the common case is 0/None
, then perhaps we don't need a fixed-length array, but can instead have a Vec<...>
.
Oh also, Vec
of length zero doesn't heap allocate anything, so perhaps we just have projectors: Vec<...>
?
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.
we need len
to properly iterate over projectors chain and avoid calling unnecessary identity_step
. I don't think Option
gives us anything here, there is still a check for None/Some and it is the same as check for len
in for loop.
for step in &self.projectors[..len] { | ||
any_ref = (step)(any_ref); | ||
} | ||
any_ref |
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.
hm. Can we optimize the case where len == 0
and avoid the any_ref = any_ref as dyn Any; any_ref.downcast_ref()
?
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 think we can check for len == 0
and avoid additional pointer copy and bound check.
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.
more nits/comments.
} | ||
|
||
#[derive(Debug, Clone, Error, PartialEq, Eq)] | ||
enum ProcessInputError { |
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.
not that I'm opposed to it per se, but does deriving Error get us anything in this test?
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.
Yeah, we can just use a struct here. Because it is a Result
I just without any thought used Error
lol
}); | ||
|
||
let result = process_input(&db, id).unwrap(); | ||
assert_eq!(*consume_result(&db, result), 'a'); |
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.
What are we testing in this test that we aren't testing in try_ok_projects_ok_value
?
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.
we check if memo_ref that were passed as an argument is correctly cloned from the store with proper projector
} | ||
|
||
#[memo] | ||
fn ok_pair_value(_db: &TestDatabase) -> Result<(PanicOnClone, PanicOnClone), ()> { |
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.
QQ: what need do we have for tuples? Is that a specific, concrete thing we need? (Not opposed, just wondering)
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.
we use it after parse_graphql_schema
, because we need to clone one part and pass the second one as memo_ref
crates/pico/tests/try_some.rs
Outdated
let db = TestDatabase::default(); | ||
|
||
let some_ref = some_value(&db); | ||
let Some(inner) = some_ref.try_some() else { |
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.
nit: .expect(...)
?
db: db as *const _ as *const dyn DatabaseDyn, | ||
derived_node_id, | ||
projectors: [step_identity; MAX_PROJECTOR_STEPS], | ||
projectors_len: 0, |
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.
Coming back to this... if the typical thing is len == 0
(is it?) then, perhaps we can take inspiration from how iterators work: https://doc.rust-lang.org/src/core/iter/adapters/map.rs.html#63 they are structs that wrap the parent, and have impl MemoRef<Item = T>
everywhere? Then, we need only Map
and FlatMap
structs that wrap the underlying memo ref.
Is that doable? Worse than this solution? I don't know. I'm just pattern-matching on how stuff is done elsewhere in Rust
f86b863
to
dc7f6c7
Compare
Sometimes we need to use Ok value from
MemoRef<Result<T,E>>
without cloning because it is supposed to be passed as an argument to another memoized function asMemoRef<T>
for performance reasons. This PR allows us to transformMemoRef<Result<T,E>>
toResult<MemoRef<T>,E>
without cloning the wholeResult
struct - onlyError
is cloned which is usually used with?
operator.Same applies to
MemoRef<Option<T>>
andMemoRef<(T1, T2)>
. And we also can chain this calls (up to 4) liketry_ok()?.split()
.I have to remove optimisation when
MemoRef
skips interning as param, but it's not a big deal - it's cheap to hash and copy.NOTE: stacked on #683