Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions libs/@local/graph/atlas/src/serve/membership/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//! Type membership over the rows a request delivers.
//!
//! A request names the entity types it wants marked, and the response reports, for each delivered
//! row, which of those types it belongs to. Answering that needs one membership set per requested
//! type, held for the length of the response assembly. [`OntologySelection`] is the request's list
//! and [`SelectionSlot`] is a position within it.

mod ontology;

pub(crate) use self::ontology::{OntologySelection, SelectionSlot};
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
131 changes: 131 additions & 0 deletions libs/@local/graph/atlas/src/serve/membership/ontology.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//! Resolution of requested ontology types into per-row membership tests.
//!
//! A generation records type membership in one of two forms. The closure form is a dense bit set
//! over base positions, materialized for the types worth precomputing. The postings form is the
//! sparse list every other type keeps. [`OntologyMembership`] holds whichever form a type has. The
//! per-row test is therefore one call whatever the generation recorded.
#![expect(clippy::empty_enums, reason = "zerocopy uses them in the derive")]

use hashql_core::id::IdVec;

use crate::{
bitset::DenseBitSlice,
identity::BasePosition,
postgres::id::ArchivedOntologyTypeUuid,
salt::{fit::prepare::IdentityProvider as _, postings::artifact::Membership},
serve::world::Ontology,
};

/// One requested type's membership set, in whichever form the generation recorded.
pub(crate) enum OntologyMembership<'ontology> {
/// The sparse postings list of the positions belonging to the type.
Direct(Membership<'ontology>),
/// The materialized closure, a dense bit set over every base position.
Closure(&'ontology DenseBitSlice<BasePosition>),
/// The generation records nothing for the requested type. No row belongs to it.
Unresolved,
}

impl<'ontology> OntologyMembership<'ontology> {
/// Resolves the type `id` against the generation's recorded membership.
///
/// A closure is preferred where one exists, because the dense form answers a test without a
/// search. An identifier the generation never recorded resolves to
/// [`Unresolved`](Self::Unresolved) rather than refusing: a request may name a type this
/// generation has no rows for, and the answer is an empty membership.
pub(crate) fn new(ontology: &'ontology Ontology, id: ArchivedOntologyTypeUuid) -> Self {
let Some(id) = ontology.identity().row_of(id) else {
return Self::Unresolved;
};

ontology.closure().membership(id).map_or_else(
|| {
ontology
.postings()
.membership(id)
.map_or(Self::Unresolved, Self::Direct)
},
Self::Closure,
)
}

/// Returns whether the base row at `position` belongs to this type.
pub(crate) fn contains(&self, position: BasePosition) -> bool {
match self {
Self::Direct(membership) => membership.contains(position),
Self::Closure(membership) => membership.contains(position),
Self::Unresolved => false,
}
}
}

hashql_core::id::newtype! {
/// A requested type's position, with duplicate requests occupying distinct slots.
pub(crate) struct SelectionSlot(u32)
}

/// The ontology types a request asks to have marked, in request order.
///
/// An unsized view over the decoded request's type list, keeping the request's own order and its
/// duplicates. A [`SelectionSlot`] indexes into it, and the response's membership bits are
/// reported against those slots.
#[derive(
Debug, zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable,
)]
#[repr(C)]
pub(crate) struct OntologySelection([ArchivedOntologyTypeUuid]);

/// A resolved membership set per requested slot, borrowed from one generation's ontology.
pub(crate) struct OntologyMemberships<'context> {
/// The membership set of every requested slot, in request order.
memberships: IdVec<SelectionSlot, OntologyMembership<'context>>,
}

impl<'context> OntologyMemberships<'context> {
/// Iterates the resolved memberships beside the slot each answers for.
pub(crate) fn iter_enumerated(
&self,
) -> impl ExactSizeIterator<Item = (SelectionSlot, &OntologyMembership<'context>)> {
self.memberships.iter_enumerated()
}
}

impl OntologySelection {
/// Returns the number of requested slots, counting a repeated type once per request.
pub(crate) const fn len(&self) -> usize {
self.0.len()
}

/// Returns whether the request named no types at all.
pub(crate) const fn is_empty(&self) -> bool {
self.0.is_empty()
}

/// Returns whether `id` occupies at least one requested slot.
pub(crate) fn contains(&self, id: ArchivedOntologyTypeUuid) -> bool {
self.0.contains(&id)
}

/// Views a decoded request's type list as a selection, without copying it.
pub(crate) fn new(ontology: &[ArchivedOntologyTypeUuid]) -> &Self {
zerocopy::transmute_ref!(ontology)
}

/// Resolves every requested slot against `ontology`.
///
/// Resolution happens once per response, and the result borrows the generation's recorded
/// membership rather than copying it. A duplicate request resolves once per slot, which keeps
/// slot indices aligned with the request the client sent.
pub(crate) fn resolve<'context>(
&'context self,
ontology: &'context Ontology,
) -> OntologyMemberships<'context> {
OntologyMemberships {
memberships: self
.0
.iter()
.map(|&id| OntologyMembership::new(ontology, id))
.collect(),
}
}
}
5 changes: 4 additions & 1 deletion libs/@local/graph/atlas/src/serve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! and removal. Without feed options or temporal axes, a generation instead exposes a static
//! publication. An active feed can lag or fail. Each scene-backed delivery request
//! captures a coherent world-and-delta epoch and obtains a cached or newly resolved visibility
//! scope before constructing a `scene::Scene`. A cached mask and schedule may predate the
//! scope before constructing a [`scene::Scene`]. A cached mask and schedule may predate the
//! request's epoch within the same delta lifetime. Scene geometry, identity and topology lookups
//! still use only the request's captured publication.
//!
Expand All @@ -21,7 +21,10 @@ pub(crate) mod delta;
pub(crate) mod density;
pub(crate) mod hydrate;
mod intern;
pub(crate) mod membership;
mod neighbourhood;
pub(crate) mod runtime;
pub(crate) mod scene;
mod schedule;
pub(crate) mod secret;
#[cfg(test)]
Expand Down
Loading
Loading