Hierarchical logging of computeds #3467
Unanswered
IanBellomy
asked this question in
Q&A
Replies: 2 comments 9 replies
-
Did you check the `spy` API?
…On Fri, Jul 15, 2022 at 11:35 PM Ian Bellomy ***@***.***> wrote:
I have a complex network of derivations that runs great (thanks MobX!) but
I'd like to log value changes with respect to the upstream values that
caused the change, i.e. hierarchically / as a tree.
Example:
[image: CcZwT]
<https://user-images.githubusercontent.com/17239616/179318805-1d99cd30-3844-4f9d-8402-d43591242e03.png>
Are there any features in mobx that'd make this problem just go away or
otherwise simplify it?
(Note: In the diagram the change to A could cause X to change but did not.)
(In the event that a computed would have been triggered by multiple
changes, it'd be ideal to treat it has having been caused by the first item
touched in the graph.)
I can create reactions for each computed but they will of course run at
the end and without a guaranteed order and sans any kind of info on the
chain-of-causation so to say.
It smells like I might be able to *assemble* the log tree after the fact
if I kept track of the computed's debug names and then do some kind of
dependency lookup in a reaction before appending the log entry — see if a
previous entry in the log is a dependency, and if so, append the new log
entry to that previous entry. But that sounds brittle...
—
Reply to this email directly, view it on GitHub
<#3467>, or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBEHAPM6JE4JDUBX7F3VUHRTFANCNFSM53XAIDBA>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
9 replies
-
Question clarification and working (albeit sloppy) solution Desired use case: // Within some action...
// set a property on an observable
observable.prop = 1
// get a tree/graph where 'prop' is the root and the specified computedProps, if changed, are the deepest nodes.
const results = causalTreeFor(
[observable, 'computedProp'],
[observable, 'computedProp2'],
[observable, 'computedProp3']
)
// place the results where needed I have a working solution but it's kinda of sloppy and involves looking at parts of computed Atoms that aren't public. /**
* Taking for granted that only one observable property changed, we should be able to trace a path back to that single prop.
* If there are alternate routes, it's ok we end up with one.
* I'm almost certain that the observing_ array order corresponds to the order of use in the computed, which is a nice bonus — it should mean the priority for items that dirty the computed (if multiple) ends up being their use in the computed.
*/
function causalChain(atom:ComputedValue<any>):ComputedValue<any>[]{
if(!atom || !atom.dependenciesState_) return [];
const chain = [atom]
let proximateCause = atom.observing_.find((a:ComputedValue<any>)=> !!a.dependenciesState_) as ComputedValue<any>
while(proximateCause){
chain.push(proximateCause)
proximateCause = proximateCause.observing_.find((a:ComputedValue<any>)=> !!a.dependenciesState_) as ComputedValue<any>
}
return chain.reverse();
}
function causalNodeFromAtom(
atom:ComputedValue<any>,
listener:CausalNodeListener = (a,b)=>{return undefined}, // this can be used later to populate the node's description.
salient?:boolean
){
const prop = atom.name_.split(".")[1]
return {
id:atom.name_,
atom,
listener,
prop,
//@ts-ignore
oldValue : atom.value_,
newValue : undefined,
//@ts-ignore
update : ()=> atom.scope_?.[prop], // :|
//@ts-ignore
children : new Set(),
salient:salient ?? false,
description: "" as undefined|string
}
}
function causalTreeFor(...derivedValues:(ReturnType<typeof propListener>|false|undefined)[]){
const causalNodes = derivedValues.filter(clean).map(o=> {
return causalNodeFromAtom(
getAtom(o[0],o[1]) as ComputedValue<any>, // This isn't necessarily true, but non-computed will ultimate be ignored/filtered out later.
o[2],
true
)
});
// filter out nodes that we know for sure didn't change
const dirtyNodes = causalNodes.filter((node)=> !!node.atom.dependenciesState_ );
const nodes = new Map<string,CausalNode>(
dirtyNodes.map(node=>[node.id,node])
)
const causalChains = dirtyNodes.map(node=>node.atom).map(causalChain)
const causalChainNodes = causalChains.map(chain=>{
return chain.map((a:ComputedValue<any>)=>{
const node = nodes.get(a.name_) ?? causalNodeFromAtom(a)
nodes.set(a.name_,node);
return node;
})
})
// update new values
causalChainNodes.forEach(chain=>{
chain.forEach(node=>{
node.newValue = node.update();
node.description = node.listener(node.oldValue,node.newValue)
})
})
causalChainNodes.forEach(chain=>{
chain.reduce(
(previous:CausalNode|null,current)=>{
previous?.children.add(current)
return current
},null)
})
const roots = Array.from(new Set(causalChainNodes.map(chain=>chain[0])))
return roots
} 🤢🙃 |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
I have a complex network of derivations that runs great (thanks MobX!) but I'd like to log value changes with respect to the upstream values that caused the change, i.e. hierarchically / as a tree.
Example:

Before using Mobx, the computed functions were 'update' functions. I could construct an isomorphic log by relying on the call stack and pushing and popping contexts. (In most cases it was safe to assume that if an update was running there was a difference to log but in many cases a previous value was cached to compare against.)
e.g.
With computeds and reactions though there is no clean call stack, so I'm not sure how to properly construct an isomorphic log tree.
I can create reactions for each computed but they will of course run at the end and without a guaranteed order and sans any kind of info on the chain-of-causation so to say.
It smells like I might be able to assemble the log tree after the fact if I kept track of the computed's debug names and then do some kind of dependency lookup in a reaction before appending the log entry — see if a previous entry in the log is a dependency, and if so, append the new log entry to that previous entry. But the log itself is an observable and updating it after the transaction will create other complications — ideally the transaction that triggers the computed would contain the changes to the log.
Are there any features in mobx that'd make this problem just go away or otherwise simplify it?
Beta Was this translation helpful? Give feedback.
All reactions