Would it be correct to say: - `remember` is used ...
# compose
c
Would it be correct to say: •
remember
is used to keep values across recomposition (with a normal variable it would be re-computed everytime) •
State<T>
is used to represent something that can change overtime (or not) The thing I don't understand is how Compose knows which state force a recompose, and which don't. I thought
remember
was used to tell Compose that the current
Composable
needed to ‘subscribe/observe' that state; however after reading other comments in this channel I'm confused whether I completely misunderstood something.
d
There's a b log post by @Zach Klippenstein (he/him) [MOD] that explains this well
m
Compose learns that a certain
@Composable
needs to "subscribe" to a state by creating a snapshot scope and listening for reads of states inside that scope. It keeps track of what states are read, and if they change it recomposes things (still listing for more reads of states). This handles any possible use of state variables, even if the use of a state is controlled by a condition based on another state.
c
Oh, that's very interesting. How does the snapshot system catch these, though?
m
All states should internally notify the current snapshot that a value was read (or written, you can listen to those too). The current snapshot is a thread-local global variable sort of like a stack, meaning you can nest snapshots within one another.
c
Oh, so State.getValue/State.setValue (or whatever they're called) tell the current snapshot, and compose asks the current snapshot?
I thought State was a standalone thing, I didn't realize it was tied to the snapshots
d
(Now wondering how this will interact with multi-threaded composition)
m
Yes, that's generally how it works internally. You can learn more about the internals by viewing the source code in your IDE. As far as I know, composition is multi-threaded but not concurrent: the current coroutine thread can move around to different threads as the workload shifts, but multiple things can't be composing at the same time.
c
From what I read the current version is able to safely have threads interacting, and recomposition can only happen in a single thread, but it is an objective for the future to have different parts of the UI recompose on different threads
z
Snapshot state is an implementation of multi version concurrency control. I think this is roughly how it works: “the current snapshot” just stores an ID number and the previous snapshot, it doesn’t store any actual data itself, it doesn’t actually store any state values itself. Each mutable state object is actually a linked list of “record” values where each record is associated with the ID of the snapshot that was used to write that value. When you read a state, the state looks at the current snapshot and finds the latest record for that state that was created by or before that snapshot, ignoring values that were written by “later” snapshots. This means that even if other threads write to that state object, they will do so in later snapshots and will just append new values to the record list that will be ignored by the current snapshot.
😮 2
🆒 1
👍 1
The literature on MVCC explains how that’s thread safe and everything.
The last time I look at the source for concurrent composition, it made composition both concurrent and parallel. IIRC when the runtime was ready to prepare the next frame, it would take the list of all recompose scopes that had been invalidated and just basically fan them out to a thread pool. I haven’t checked recently if that logic has changed though, and I don’t remember exactly how it handles stuff like where one recomposition invalidates another that then also needs to be recomposed for the current frame.
But the source is all very readable. You’re just as well off going to read it (either in Android Studio or on cs.android.com) if you’re trying to answer these questions about how things really work
g
@CLOVIS I highly recommend you watch the following presentation.

https://www.youtube.com/watch?v=6BRlI5zfCCk&amp;t=3s

Tip: Don’t underestimate the video length. It’s 40 min “academic time” - it packs a lot (e.g principles / scenarios) and your concern is addressed in more detail towards the end so make time
c
Thanks for the conv, I'll definitely watch that ^^ Am I right to think that compose would become the first UI framework that can use multiple threads to display stuff? All other frameworks I've played with always force the UI to be its own thread
I'll trust the team for the performance aspects, I'm amazed by how it works
g
@CLOVIS "Am I right to think that compose would become the first UI framework that can use multiple threads to display stuff?" - I'm not entirely clear on how multi threading is utilized or to what degree things get parallelized, perhaps a topic for part #2 🙂
c
After watching that conference, I am now convinced it's dark magic More seriously though, Compose is amazing
z
It’s really not. The code that does this is literally all in one function I think.
Look at
runRecomposeConcurrentlyAndApplyChanges
, introduced here
g
@Zach Klippenstein (he/him) [MOD] I’m definitely not familiar enough with the code base to concur, but I don’t see how you can parallelize composable writes to the slot table while still maintaining a DFS representation of the call graph … like even if you could statically allocate the positions for the initial composition you would still need a way to make adjustments for those composable that shift when the gap buffer shifts which would presumably require another level of indirection (map) which - all in all - doesn’t seem to me like something that would result in a clear performance boost as opposed to a single threaded logical DFS traversal that skips the gap, so yes I guess composable are not parallelized as far as their interaction with the Slot Table. However, I can imagine parallelism kicking in at the end of the call graph when the slot table reflects the composition and rendering begins. So perhaps “parallel execution of composable” means parallel rendering of composables … which is yet another reason why I’m seeking to understating how rendering works :)
z
I know effects still happen on the main thread, I’m pretty sure Appliers are still executed on the main thread, and I think drawing has to be done on the main thread because of Android limitations. I’m not sure how access to the slot table is synchronized either though, that’s a good point.