use polars_async::primitives::distributor_channel::distributor_channel;
use polars_async::primitives::wait_group::WaitGroup;
use polars_core::prelude::{Column, DataType, FillNullStrategy};
use polars_error::PolarsResult;
use polars_utils::IdxSize;
use polars_utils::pl_str::PlSmallStr;
use super::compute_node_prelude::*;
use crate::DEFAULT_DISTRIBUTOR_BUFFER_SIZE;
use crate::morsel::{MorselSeq, SourceToken, get_ideal_morsel_size};
pub struct BackwardFillNode {
dtype: DataType,
limit: IdxSize,
seq: MorselSeq,
pending_nulls: IdxSize,
col_name: PlSmallStr,
}
impl BackwardFillNode {
pub fn new(limit: Option<IdxSize>, dtype: DataType, col_name: PlSmallStr) -> Self {
Self {
limit: limit.unwrap_or(IdxSize::MAX),
dtype,
seq: MorselSeq::default(),
pending_nulls: 0,
col_name,
}
}
}
impl ComputeNode for BackwardFillNode {
fn name(&self) -> &str {
"backward_fill"
}
fn update_state(
&mut self,
recv: &mut [PortState],
send: &mut [PortState],
_state: &StreamingExecutionState,
) -> PolarsResult<()> {
assert!(recv.len() == 1 && send.len() == 1);
if send[0] == PortState::Done {
recv[0] = PortState::Done;
self.pending_nulls = 0;
} else if recv[0] == PortState::Done {
if self.pending_nulls > 0 {
send[0] = PortState::Ready;
} else {
send[0] = PortState::Done;
}
} else {
recv.swap_with_slice(send);
}
Ok(())
}
fn spawn<'env, 's>(
&'env mut self,
scope: &'s TaskScope<'s, 'env>,
recv_ports: &mut [Option<RecvPort<'_>>],
send_ports: &mut [Option<SendPort<'_>>],
_state: &'s StreamingExecutionState,
join_handles: &mut Vec<JoinHandle<PolarsResult<()>>>,
) {
assert_eq!(recv_ports.len(), 1);
assert_eq!(send_ports.len(), 1);
let recv = recv_ports[0].take();
let send = send_ports[0].take().unwrap();
let limit = self.limit;
let dtype = self.dtype.clone();
let pending_nulls = &mut self.pending_nulls;
let seq = &mut self.seq;
let col_name = self.col_name.clone();
let Some(recv) = recv else {
if *pending_nulls == 0 {
return;
}
let pending = *pending_nulls;
let mut send = send.serial();
join_handles.push(scope.spawn_task(TaskPriority::High, async move {
let source_token = SourceToken::new();
let morsel_size = get_ideal_morsel_size();
let mut remaining = pending as usize;
while remaining > 0 {
let chunk_size = morsel_size.min(remaining);
let df = Column::full_null(col_name.clone(), chunk_size, &dtype).into_frame();
if send
.send(Morsel::new(df, *seq, source_token.clone()))
.await
.is_err()
{
break;
}
*seq = seq.successor();
remaining -= chunk_size;
}
Ok(())
}));
*pending_nulls = 0;
return;
};
let mut receiver = recv.serial();
let senders = send.parallel();
let (mut distributor, distr_receivers) =
distributor_channel(senders.len(), *DEFAULT_DISTRIBUTOR_BUFFER_SIZE);
let serial_dtype = dtype.clone();
join_handles.push(scope.spawn_task(TaskPriority::High, async move {
let dtype = serial_dtype;
let source_token = SourceToken::new();
let ideal_morsel_size = get_ideal_morsel_size() as IdxSize;
while let Ok(morsel) = receiver.recv().await {
let column = &morsel.df()[0];
let height = column.len();
if height == 0 {
continue;
}
let null_count = column.null_count();
if null_count == height {
*pending_nulls += height as IdxSize;
}
while *pending_nulls > limit {
let chunk_size = ideal_morsel_size.min(*pending_nulls - limit);
let col = Column::full_null(col_name.clone(), chunk_size as usize, &dtype);
let null_morsel = Morsel::new(col.into_frame(), *seq, source_token.clone());
*seq = seq.successor();
*pending_nulls -= chunk_size;
if distributor.send(null_morsel).await.is_err() {
return Ok(());
}
}
if null_count == height {
continue;
}
let new_pending_nulls = if null_count == 0 {
0
} else {
let trailing_nulls = height - column.last_non_null().unwrap() - 1;
(trailing_nulls as IdxSize).min(limit)
};
let mut column = if new_pending_nulls > 0 {
column.slice(0, column.len() - new_pending_nulls as usize)
} else {
column.clone()
};
if *pending_nulls > 0 {
let mut c =
Column::full_null(col_name.clone(), *pending_nulls as usize, &dtype);
c.append_owned(column)?;
column = c;
}
let morsel = Morsel::new(column.into_frame(), *seq, source_token.clone());
*seq = seq.successor();
*pending_nulls = new_pending_nulls;
if distributor.send(morsel).await.is_err() {
return Ok(());
}
}
Ok(())
}));
for (mut send, mut recv) in senders.into_iter().zip(distr_receivers) {
join_handles.push(scope.spawn_task(TaskPriority::High, async move {
let wait_group = WaitGroup::default();
while let Ok(mut morsel) = recv.recv().await {
let col = &morsel.df()[0];
if col.has_nulls() {
*morsel.df_mut() = col
.fill_null(FillNullStrategy::Backward(Some(limit)))?
.into_frame();
}
morsel.set_consume_token(wait_group.token());
if send.send(morsel).await.is_err() {
break;
}
wait_group.wait().await;
}
Ok(())
}));
}
}
}