// ============================================================
// Incremental batch execution
//
// BatchRunner is for applications that need to execute a list of independent
// tasks under one frame budget. It deliberately executes at most one child per
// tick, making it suitable for server jobs, scripted agents, and UI workflows.
// ============================================================
///|
/// Failure behavior for a batch.
pub(all) enum BatchMode {
ContinueOnFailure
StopOnFailure
} derive(Eq, Debug)
///|
/// Continue after a failed task.
pub fn BatchMode::continue_on_failure() -> BatchMode {
BatchMode::ContinueOnFailure
}
///|
/// Stop immediately after a failed task.
pub fn BatchMode::stop_on_failure() -> BatchMode {
BatchMode::StopOnFailure
}
///|
/// A stable progress snapshot.
pub struct BatchReport {
status : Status
cursor : Int
total : Int
failures : Int
}
///|
/// Status observed for this report.
pub fn BatchReport::status(self : BatchReport) -> Status {
self.status
}
///|
/// Index of the next task.
pub fn BatchReport::cursor(self : BatchReport) -> Int {
self.cursor
}
///|
/// Number of tasks in the batch.
pub fn BatchReport::total(self : BatchReport) -> Int {
self.total
}
///|
/// Number of failed tasks observed so far.
pub fn BatchReport::failures(self : BatchReport) -> Int {
self.failures
}
///|
/// A sequential, incremental task batch.
pub struct BatchRunner {
tasks : Array[Node]
bb : Blackboard
mode : BatchMode
cursor : Ref[Int]
failures : Ref[Int]
stopped : Ref[Bool]
}
///|
/// Create an empty batch.
pub fn BatchRunner::new(bb : Blackboard, mode : BatchMode) -> BatchRunner {
{
tasks: [],
bb,
mode,
cursor: Ref::new(0),
failures: Ref::new(0),
stopped: Ref::new(false),
}
}
///|
/// Append a task before execution begins.
pub fn BatchRunner::add(self : BatchRunner, task : Node) -> Unit {
self.tasks.push(task)
}
///|
/// Number of tasks in the batch.
pub fn BatchRunner::size(self : BatchRunner) -> Int {
self.tasks.length()
}
///|
/// Whether all tasks have been processed or the batch has stopped.
pub fn BatchRunner::is_done(self : BatchRunner) -> Bool {
self.stopped.get() || self.cursor.get() >= self.tasks.length()
}
///|
/// Execute at most one task and return a progress report.
pub fn BatchRunner::tick(self : BatchRunner) -> BatchReport {
if self.is_done() {
return {
status: if self.stopped.get() {
Status::BTFailure
} else {
Status::BTSuccess
},
cursor: self.cursor.get(),
total: self.tasks.length(),
failures: self.failures.get(),
}
}
let index = self.cursor.get()
let status = self.tasks[index].tick(self.bb)
match status {
Status::BTRunning => ()
Status::BTSuccess => {
self.tasks[index].reset()
self.cursor.set(index + 1)
}
Status::BTFailure => {
self.failures.set(self.failures.get() + 1)
self.tasks[index].reset()
self.cursor.set(index + 1)
match self.mode {
BatchMode::ContinueOnFailure => ()
BatchMode::StopOnFailure => self.stopped.set(true)
}
}
}
{
status: if self.is_done() {
if self.stopped.get() {
Status::BTFailure
} else {
Status::BTSuccess
}
} else {
Status::BTRunning
},
cursor: self.cursor.get(),
total: self.tasks.length(),
failures: self.failures.get(),
}
}
///|
/// Reset all tasks and start the batch again.
pub fn BatchRunner::reset(self : BatchRunner) -> Unit {
for task in self.tasks {
task.reset()
}
self.cursor.set(0)
self.failures.set(0)
self.stopped.set(false)
}
///|
/// The blackboard shared by all tasks.
pub fn BatchRunner::blackboard(self : BatchRunner) -> Blackboard {
self.bb
}