pub struct Unlocks<I, K> { /* private fields */ }Expand description
A graph of things an entity can unlock.
The graph edges are never authored. One node grants a capability, another requires it, and that is the arrow. Change a requirement and the graph changes with it.
use std::collections::BTreeSet;
use plotline::{Requirement, Unlock, Unlocks};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
enum Cap {
Fusion,
Warp,
}
let mut tree = Unlocks::new();
tree.insert("fusion-power", Unlock::free().granting([Cap::Fusion]));
tree.insert(
"warp-drive",
Unlock::new(Requirement::has(Cap::Fusion)).granting([Cap::Warp]),
);
// The arrow from fusion-power to warp-drive is derived, not declared.
assert_eq!(tree.dependencies(&"warp-drive"), vec![&"fusion-power"]);
let mut held = BTreeSet::new();
assert_eq!(tree.available(&held).collect::<Vec<_>>(), vec![&"fusion-power"]);
tree.take(&"fusion-power", &mut held);
// fusion-power stays available, because its requirement still holds. The
// host tracks which nodes it already took.
assert_eq!(
tree.available(&held).collect::<Vec<_>>(),
vec![&"fusion-power", &"warp-drive"],
);Implementations§
Source§impl<I: Ord, K: Ord> Unlocks<I, K>
impl<I: Ord, K: Ord> Unlocks<I, K>
Sourcepub fn is_available(&self, id: &I, holder: &(impl Has<K> + ?Sized)) -> bool
pub fn is_available(&self, id: &I, holder: &(impl Has<K> + ?Sized)) -> bool
Returns whether a node can be taken. A missing node never can.
The registry does not track which nodes a player already took, because two nodes may grant the same capability. Keep that record in the host and overlay it on this answer.
Sourcepub fn status(
&self,
id: &I,
holder: &(impl Has<K> + ?Sized),
taken: &BTreeSet<I>,
) -> Option<NodeStatus>
pub fn status( &self, id: &I, holder: &(impl Has<K> + ?Sized), taken: &BTreeSet<I>, ) -> Option<NodeStatus>
Returns where a node stands, or None when it is missing.
taken is the host’s record of the nodes the entity took. A taken node
reads NodeStatus::Unlocked even when its requirement no longer
holds, because the record, not the rule, says what the entity took.
Sourcepub fn available<'a, H: Has<K> + ?Sized>(
&'a self,
holder: &'a H,
) -> impl Iterator<Item = &'a I> + 'a
pub fn available<'a, H: Has<K> + ?Sized>( &'a self, holder: &'a H, ) -> impl Iterator<Item = &'a I> + 'a
Iterates over the nodes that can be taken now.
Sourcepub fn providers(&self, capability: &K) -> Vec<&I>
pub fn providers(&self, capability: &K) -> Vec<&I>
Returns every node that grants this capability.
Sourcepub fn take<H>(&self, id: &I, holder: &mut H) -> bool
pub fn take<H>(&self, id: &I, holder: &mut H) -> bool
Adds a node’s grants to the holder when the node is available.
Returns whether it applied. Taking a node twice is harmless.
The holder both answers the requirement and receives the grants. A
set does both. A host type that computes some keys implements
Extend to store the grants where its Has reads them.
Source§impl<I: Ord, K: Ord + Clone> Unlocks<I, K>
impl<I: Ord, K: Ord + Clone> Unlocks<I, K>
Sourcepub fn evaluate(
&self,
id: &I,
holder: &(impl Has<K> + ?Sized),
) -> Option<Evaluation<K>>
pub fn evaluate( &self, id: &I, holder: &(impl Has<K> + ?Sized), ) -> Option<Evaluation<K>>
Explains why a node is available or locked, or None when it is missing.
Sourcepub fn dependencies(&self, id: &I) -> Vec<&I>
pub fn dependencies(&self, id: &I) -> Vec<&I>
Returns the nodes that must be taken before this one.
A node is a dependency when it is the only other source of a key that the requirement demands outright. When several nodes grant that key, any one of them unlocks it, so they are optional dependencies instead. A node never depends on itself.
Sourcepub fn optional_dependencies(&self, id: &I) -> Vec<&I>
pub fn optional_dependencies(&self, id: &I) -> Vec<&I>
Returns the nodes that could help unlock this one without being required.
These are the sources of the keys inside a choice, and the sources of
a required key that more than one node grants. A node that
Unlocks::dependencies returns is left out. Draw them differently.
Sourcepub fn ranks(&self) -> BTreeMap<I, usize>where
I: Clone,
pub fn ranks(&self) -> BTreeMap<I, usize>where
I: Clone,
Returns the rank of every node that has one.
A rank is the earliest round in which the entity can take a node, if it takes every node it can in each round. Only required keys count, and a key that no other node grants counts as held from the start. So a node ranks 0 when no other node grants a key it requires. Otherwise, for each required key, take the lowest rank among the nodes that grant it. The node ranks one more than the highest of those.
Use it as the column index in a tree layout. Every edge that
Unlocks::dependencies returns points to a higher rank. A node has
no rank when one of its required keys has other sources and none of
them has a rank, as on a loop. It is left out, and
Unlocks::validate reports it.
Sourcepub fn view(
&self,
holder: &(impl Has<K> + ?Sized),
taken: &BTreeSet<I>,
) -> Vec<NodeView<'_, I>>where
I: Clone,
pub fn view(
&self,
holder: &(impl Has<K> + ?Sized),
taken: &BTreeSet<I>,
) -> Vec<NodeView<'_, I>>where
I: Clone,
Returns everything a tree view draws, one entry per node, in id order.
Each entry holds the node’s status, its rank, and both kinds of edge.
taken is the host’s record of the nodes the entity took. Sort the
entries by rank to lay them out in columns.
use std::collections::BTreeSet;
use plotline::{NodeStatus, Requirement, Unlock, Unlocks};
let mut tree = Unlocks::new();
tree.insert("fusion-power", Unlock::free().granting(["fusion"]));
tree.insert(
"warp-drive",
Unlock::new(Requirement::has("fusion")).granting(["warp"]),
);
let mut held = BTreeSet::new();
let mut taken = BTreeSet::new();
tree.take(&"fusion-power", &mut held);
taken.insert("fusion-power");
let view = tree.view(&held, &taken);
assert_eq!(view[0].status, NodeStatus::Unlocked);
assert_eq!(view[1].id, &"warp-drive");
assert_eq!(view[1].status, NodeStatus::Available);
assert_eq!(view[1].rank, Some(1));
assert_eq!(view[1].dependencies, vec![&"fusion-power"]);Source§impl<I: Ord + Clone, K: Ord + Clone> Unlocks<I, K>
impl<I: Ord + Clone, K: Ord + Clone> Unlocks<I, K>
Sourcepub fn validate(
&self,
external: &(impl Has<K> + ?Sized),
) -> Vec<UnlockWarning<I, K>>
pub fn validate( &self, external: &(impl Has<K> + ?Sized), ) -> Vec<UnlockWarning<I, K>>
Reports authoring problems.
external holds the capabilities that other systems grant, such as a
species trait or a starting bonus. Without it, every requirement that
no node satisfies looks like dead content.
The checks read required keys only, as Unlocks::ranks does. They
do not look inside Any or AtLeast, so a node that only a choice
gates is not checked.