Crate topo[−][src]
Expand description
topo provides stable callgraph identifiers for implementing higher
level Incremental Computing abstractions like those in the
moxie crate.
Scoping queries with CallIds
There are several ways this crate’s caches can be indexed, but for incrementally computing repetitive hierarchical structures (like UI trees) it can be very useful to describe cache queries in terms of a function’s abstract “location” within the runtime callgraph.
To extract CallIds from code, the runtime callgraph must be “annotated”
with invocations of call, call_in_slot, and user-defined
nested-annotated functions. Each of these enters the scope of a child
CallId when called, causing CallId::current calls in that inner
scope to return the new CallId.
CallIds are deterministic and unique based on the preceding chain of
parent CallIds and data slots. Every chain has a root, either defined
implicitly by making a call without a parent, or explicitly by calling
root. By controlling the creation of roots, a runtime can ensure that
aside from changes to the executed graph itself, subsequent calls to the
same function will produce the same CallIds.
Example
use topo::{call, root, CallId};
let returns_two_ids = || {
let first = call(|| CallId::current());
let second = call(|| CallId::current());
assert_ne!(first, second, "these are always distinct calls");
(first, second)
};
// running the closure as a nested call(...) gives different siblings
assert_ne!(call(returns_two_ids), call(returns_two_ids));
// a call to root(...) gives each of these closure calls an identical parent CallId
assert_eq!(root(returns_two_ids), root(returns_two_ids));Caching slots
call_in_slot allows one to specify a “slot” – a runtime value that
represents the call’s “location” within its parents scope. This is
particularly useful for creating CallIds for child calls in collections
whose iteration order does not map exactly to the child’s logical scope.
An example might be a list of users where usernames are stable and their associated resources should be cached by username, but the list order and length change in ways unrelated to usernames. The username would be used as the slot to prevent destruction and recreation of username-associated resources when the list order changes.
use topo::{call_in_slot, CallId};
let get_name_id = |name| call_in_slot(name, || CallId::current());
// reusing the same slot will get the same CallId
let bob = get_name_id("bob");
let bob_again = get_name_id("bob");
assert_eq!(bob, bob_again);
// different names produce different slots
let alice_hello = get_name_id("alice");
assert_ne!(bob, alice_hello);Internally, slots are interned in a global [dyn-cache].
Structs
Identifies the scope of a nested function call in a way that can be deterministically reproduced across multiple executions.
Functions
Calls the provided function as a child of CallId::current, using for a
slot the number of times the given source location has been called during
the current parent’s scope.
Calls the provided function as a child of CallId::current, using slot
as an input for the new CallId.
Calls the provided function as the root of a new call tree, ignoring the
current CallId.
Attribute Macros
Gives a function a unique CallId in its caller’s topology by applying
#[track_caller] to the function and wrapping its body in call or
call_in_slot if the slot parameter is given.