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}