Skip to main content

vortex_scan/planning/
planner.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! The planner side of the protocol: work scopes, object state, and planner outputs.
5
6use std::fmt;
7use std::ops::Range;
8
9use vortex_error::VortexResult;
10use vortex_io::request::IoBatch;
11use vortex_io::request::IoConsumer;
12
13use crate::planning::morsel::Morsel;
14
15/// Logical position carried with work and output for future driver ordering policies.
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct WorkScope {
18    /// Ordinal of the file the work belongs to.
19    pub file_ordinal: u64,
20    /// File-local row domain covered by the work.
21    pub rows: Range<u64>,
22}
23
24/// What an object needs next. Cheap and side-effect free.
25///
26/// `state()` is called before every `compute()`, and `compute()` only when `state()` is
27/// `NeedsCompute`. It never lists requests: every request is published once, from `compute()`,
28/// and the driver keeps each `Fetch` until it is delivered.
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub enum State {
31    /// The object has finished and will be dropped without further calls.
32    Done,
33    /// The object can make progress on the CPU.
34    NeedsCompute,
35    /// The object waits for a delivery of one of the `Fetch` requests it published; any delivery
36    /// may make it runnable. Waiting with no fetch outstanding is a protocol error.
37    Waiting,
38}
39
40/// What one `compute()` produced.
41///
42/// Returning `NeedsIO` registers the batch immediately, and each request in it exactly once. The
43/// next `state()` determines whether the object waits or has more CPU work. Optional requests
44/// never require a wait or delivery. After `Done` the object is dropped without further calls.
45pub enum PlannerOutput {
46    /// The planner has finished.
47    Done,
48    /// A CPU-only checkpoint; the driver requeues the planner.
49    Continue,
50    /// Requests the planner publishes: fetches it needs, and optional hints.
51    NeedsIO(IoBatch),
52    /// A child planner, with the scope it is responsible for.
53    Planner(WorkScope, Box<dyn Planner>),
54    /// A morsel authorised to produce arrays for the scope.
55    Morsel(WorkScope, Box<dyn Morsel>),
56}
57
58/// A planner decides which work is needed.
59///
60/// It privately owns cursors, selections, and pending inputs. `compute()` advances CPU work
61/// when `state()` reports `NeedsCompute` and may produce one child, one IO batch, a checkpoint,
62/// or finish. A planner moves between threads with the run that owns it.
63pub trait Planner: IoConsumer + Send {
64    /// Reports what the planner needs next without doing work.
65    fn state(&self) -> State;
66
67    /// Advances CPU work. Only called when [`state`](Self::state) is `NeedsCompute`.
68    fn compute(&mut self) -> VortexResult<PlannerOutput>;
69}
70
71impl fmt::Debug for PlannerOutput {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        match self {
74            Self::Done => f.write_str("Done"),
75            Self::Continue => f.write_str("Continue"),
76            Self::NeedsIO(batch) => f.debug_tuple("NeedsIO").field(batch).finish(),
77            Self::Planner(scope, _) => f.debug_tuple("Planner").field(scope).finish(),
78            Self::Morsel(scope, _) => f.debug_tuple("Morsel").field(scope).finish(),
79        }
80    }
81}