refactor(#2772): enumflags2 for the edge set; drop re-export shims
Review follow-ups on #2785. `DepWhen` wraps `BitFlags<TerminalState>` instead of a hand-rolled `u8`, so the bit manipulation belongs to the library and `TerminalState` gains its flag value from `#[bitflags]` rather than a `bit()` match anyone could get wrong. `of`/`accepts`/`is_empty` become one-liners over it. Serialization is written out by hand rather than derived: clippy's `unsafe_derive_deserialize` fires on deriving over a type with unsafe internals, and the honest fix is to say what the wire form is. It is now the list of accepted outcomes — `["done","failed"]` — which reads better than a bitmask and survives the bits being renumbered. Also drops the `pub use` re-export of `DepWhen` / `TerminalState` from hive-c0re's `model`. It existed so that `use super::model::…` kept compiling, which is a shim for one consumer's convenience; the sites import from `hive_jobq` directly now. And removes comments narrating what the code used to be. Git holds that.
This commit is contained in:
parent
07078b76ef
commit
940c928fee
8 changed files with 74 additions and 50 deletions
|
|
@ -59,6 +59,8 @@ impl NodeId {
|
|||
/// How a node finished. The terminal subset of [`State`], as its own type so an
|
||||
/// edge condition cannot name `Pending` / `Running` / `Finishing` — those are
|
||||
/// meaningless in a dependency and are better unrepresentable than rejected.
|
||||
#[enumflags2::bitflags]
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TerminalState {
|
||||
|
|
@ -80,18 +82,6 @@ pub enum TerminalState {
|
|||
Skipped,
|
||||
}
|
||||
|
||||
impl TerminalState {
|
||||
/// Bit for this outcome in a [`DepWhen`] set.
|
||||
const fn bit(self) -> u8 {
|
||||
match self {
|
||||
TerminalState::Done => 1,
|
||||
TerminalState::Failed => 1 << 1,
|
||||
TerminalState::Cancelled => 1 << 2,
|
||||
TerminalState::Skipped => 1 << 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// This state as a [`TerminalState`], or `None` while the node is still
|
||||
/// in flight.
|
||||
|
|
@ -122,15 +112,31 @@ impl State {
|
|||
///
|
||||
/// [`AFTER_OK`]: DepWhen::AFTER_OK
|
||||
/// [`AFTER_ANY`]: DepWhen::AFTER_ANY
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DepWhen(u8);
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct DepWhen(enumflags2::BitFlags<TerminalState>);
|
||||
|
||||
/// Serialised as the list of outcomes it accepts (`["done","failed"]`) rather
|
||||
/// than the underlying bitmask, so the wire form stays readable and survives the
|
||||
/// bits being renumbered.
|
||||
impl serde::Serialize for DepWhen {
|
||||
fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
|
||||
serde::Serialize::serialize(&self.0.iter().collect::<Vec<_>>(), ser)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for DepWhen {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
|
||||
let outcomes = Vec::<TerminalState>::deserialize(de)?;
|
||||
Ok(Self(outcomes.into_iter().collect()))
|
||||
}
|
||||
}
|
||||
|
||||
impl DepWhen {
|
||||
/// The dependency must reach [`TerminalState::Done`]. The default chain
|
||||
/// edge: if the dependency fails, the dependent must not run and is
|
||||
/// cancelled down the chain — e.g. a failed `Prebuild` must not let
|
||||
/// `StopForUpdate` stop a healthy container.
|
||||
pub const AFTER_OK: Self = Self(TerminalState::Done.bit());
|
||||
pub const AFTER_OK: Self = Self(enumflags2::make_bitflags!(TerminalState::{Done}));
|
||||
/// Anything **except the work being dropped** — `Done`, `Failed` or
|
||||
/// `Skipped`. For steps that must converge regardless of how the run went,
|
||||
/// e.g. `Reconcile` bringing a container back up even when the preceding
|
||||
|
|
@ -140,32 +146,25 @@ impl DepWhen {
|
|||
/// started at all there is nothing to converge, and running the recovery
|
||||
/// step anyway would act on work that provably never happened. A node that
|
||||
/// must report a cancellation names `Cancelled` explicitly.
|
||||
pub const AFTER_ANY: Self = Self(
|
||||
TerminalState::Done.bit() | TerminalState::Failed.bit() | TerminalState::Skipped.bit(),
|
||||
);
|
||||
pub const AFTER_ANY: Self =
|
||||
Self(enumflags2::make_bitflags!(TerminalState::{Done | Failed | Skipped}));
|
||||
|
||||
/// An edge satisfied by exactly the listed outcomes.
|
||||
#[must_use]
|
||||
pub const fn of(outcomes: &[TerminalState]) -> Self {
|
||||
let mut bits = 0u8;
|
||||
let mut i = 0;
|
||||
while i < outcomes.len() {
|
||||
bits |= outcomes[i].bit();
|
||||
i += 1;
|
||||
}
|
||||
Self(bits)
|
||||
pub fn of(outcomes: &[TerminalState]) -> Self {
|
||||
Self(outcomes.iter().copied().collect())
|
||||
}
|
||||
|
||||
/// Whether `outcome` satisfies this edge.
|
||||
#[must_use]
|
||||
pub const fn accepts(self, outcome: TerminalState) -> bool {
|
||||
self.0 & outcome.bit() != 0
|
||||
pub fn accepts(self, outcome: TerminalState) -> bool {
|
||||
self.0.contains(outcome)
|
||||
}
|
||||
|
||||
/// An edge no outcome can satisfy — rejected at [`Graph::validate`].
|
||||
#[must_use]
|
||||
pub const fn is_empty(self) -> bool {
|
||||
self.0 == 0
|
||||
pub fn is_empty(self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Whether a dependency in `dep_state` satisfies this edge. A non-terminal
|
||||
|
|
|
|||
Loading…
Reference in a new issue