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 CallId
s
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 CallId
s 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
.
CallId
s are deterministic and unique based on the preceding chain of
parent CallId
s 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 CallId
s.
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 CallId
s 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.