Make graph views attributes live-update - #325
Conversation
A GraphView was a one-time snapshot: writes to the root were not propagated, so a view could return stale attribute values and its listeners were never told anything changed. SQL-rooted views diverged silently; rustworkx-rooted ones only looked correct because they share the root's attribute dicts. Roots now track their views and apply changes to them directly rather than through the signal system. A view is queryable in its own right, so keeping it current is an invariant — it cannot depend on whether an observer happens to be subscribed. Views absorb the change first and emit second, so a listener never sees the root and view disagree. Also fixes SQLGraph not calling super().__init__(), and pickling, which the view registry broke for every graph.
Resolves royerlab#324. A GraphView used to block its root's signal, mutate both graphs, then replay the root's signal itself, so that a listener on either graph would see both graphs updated. That coupled two unrelated things: a listener subscribes to one graph and only needs that graph to be current. Each graph now emits for itself, once, as soon as it is up to date. Writes through a view delegate to the root and let the normal view-maintenance path update the view, so there is a single implementation of "absorb a change" instead of one per direction. The temporary detach that kept those two implementations from both running is gone with them. This also removes a real inconsistency: the replayed payload was rebuilt from a fixed key list, so a root listener received a different set of attribute keys depending on whether the write went through a view.
|
@cmalinmayor, I pushed a fix to a bug that was introduced in this branch, namely: A view does not necessarily contain all node|edge attributes that the root contains. When the attributes in the view are updated because they come from the root, we should check which attributes are actually present on the view, as implemented in my last commit. Details:
Intersect the changed keys with the view's own key list before writing through. Only affects views over a non-rustworkx root; a rustworkx-rooted view shares the root's attribute dicts and never takes this path. |
|
@cmalinmayor, I made this PR more "symmetric" by also propagating the removal of node|edge attributes (remove_node|edge_attr_key) from the root to all its views |
…need copying + sql view only when listeners
|
Point of concern is the regression in the benchmarks (across all backends), because keeping views up to date is just expensive and will always be slower compared to not doing it... bit deeper: |
|
Hi @JoOkuma, from a motile_tracker perspective, this PR does what we want it to do. Can you have a look and let me know what we need to change to have it ready to merge? 🙏 |
JoOkuma
left a comment
There was a problem hiding this comment.
Hi @cmalinmayor, thanks for the major cleanup and organization of the codebase you have done with this PR.
I opened cmalinmayor#2 with some changes. Can you review it?
I'm worried about the runtime with listeners, as the benchmark shows.
What do you think of having the communication from root to views off by default?
If we go for that, I think I can also refactor what sync means in GraphView and its behavior.
Co-authored-by: Jordão Bragantini <jordao.bragantini@gmail.com>
tracksdata royerlab#325 initial review
Started to workshop this and hit enough edge cases that it was worth getting feedback before I went further. Is this like what you were thinking @JoOkuma ? |
|
(Noticed that we didn't wire up any actual changes to graph structure yet, just attributes, but we will need that for multi-user edits, and I don't think it's a huge change once we decide on the framework) |
|
@cmalinmayor, yes, that's exactly it. There are a few edge cases with graph structure that are ambiguous. For example, when you delete an element of the subgraph, are they deleted from the root graph or not (if we assume it's a candidate graph, "not" is the right answer)? |
Use shared helpers for updating the local rustworkx view graph, just sometimes called from root, and sometimes from view.
|
@JoOkuma I think I added back in the old behavior without any performance regressions and with the same functionality 🤞 |
|
Hi @cmalinmayor, I'm back from a few days off. I gotta catch up with work, and I'll review this in a few days. |
|
LGTM, @cmalinmayor. Thanks for your patience. |
|
Thanks @JoOkuma!! |
Add an option for what mode the view is in:
For a totally detached view, just call subgraph and detach.
Every Live view adds work for the root update, regardless of listeners on the view. Write-through views do not, and are fully updated in the case where there is a single view, so they are preferred for any use case other than multi-view synchronization.
Next steps: