Skip to main content

ApplyObserver

Trait ApplyObserver 

Source
pub trait ApplyObserver: Send + Sync {
    // Provided methods
    fn on_chunk_applied(&mut self, ev: ChunkEvent) -> ControlFlow<(), ()> { ... }
    fn should_cancel(&mut self) -> bool { ... }
}
Expand description

Hook trait for observing apply-time progress and signalling cancellation.

All methods have no-op defaults so implementors override only what they need.

§Threading

An observer is borrowed mutably by the apply driver for the lifetime of the apply_patch call. There is no internal synchronisation: implementors that need to forward events to another thread should do so via channels they own.

The trait has Send + Sync supertrait bounds so a boxed observer can be constructed on one thread and driven on another — the typical UI pattern is to construct the observer (often around an mpsc::Sender or an AtomicBool cancellation flag) on the UI thread, hand it to an ApplyConfig, and ship the context to an apply worker. Sync costs nothing for the realistic implementations (channel senders, atomics, Arc<Mutex<_>>) and lets the observer be shared by reference if a downstream consumer ever needs to.

§Async usage

Both Self::on_chunk_applied and Self::should_cancel run inline with the apply loop and are intentionally synchronous — see the crate-level “Async usage” section for the rationale. The cancellation poll in particular is called once per SqpkFile AddFile block and must be cheap (an atomic-bool load is the canonical implementation), which makes it a poor fit for async even hypothetically.

Async consumers wrap the whole apply call in tokio::task::spawn_blocking and use an AtomicBool cancellation flag the async side can flip from a tokio::select! arm or a cancellation token. Per-chunk events that need to reach an async UI go through a channel whose Sender lives inside Self::on_chunk_applied and whose Receiver is polled from the async task.

§Example

use std::ops::ControlFlow;
use zipatch_rs::{ApplyConfig, ApplyObserver, ChunkEvent, open_patch};

struct Progress {
    total: u64,
    applied: u64,
}

impl ApplyObserver for Progress {
    fn on_chunk_applied(&mut self, ev: ChunkEvent) -> ControlFlow<(), ()> {
        self.applied = ev.bytes_read;
        println!("progress: {}/{}", self.applied, self.total);
        ControlFlow::Continue(())
    }
}

let mut ctx = ApplyConfig::new("/opt/ffxiv/game")
    .with_observer(Progress { total: 12_345_678, applied: 0 });
let reader = open_patch("patch.patch").unwrap();
ctx.apply_patch(reader).unwrap();

Provided Methods§

Source

fn on_chunk_applied(&mut self, ev: ChunkEvent) -> ControlFlow<(), ()>

Called after each top-level chunk has been applied successfully.

Returning ControlFlow::Break aborts the apply loop immediately; the apply call returns ApplyError::Cancelled.

Not invoked when the chunk’s apply itself fails — the error propagates from apply_patch without firing this method. The event is therefore a “chunk succeeded” signal, not a “chunk attempted” one.

The default implementation does nothing and continues.

Source

fn should_cancel(&mut self) -> bool

Polled inside long-running chunks to check for user cancellation.

Implementors should make this method cheap — it is called once per block inside the SqpkFile AddFile loop and on every iteration of any future fine-grained loop the apply layer adds. A simple atomic-bool load is the recommended implementation.

Polled before each block within long-running chunks (currently only SQPK F AddFile). Once a block’s I/O has started, it completes — cancellation takes effect at the next block boundary, not mid-write. This means the last block of any chunk always finishes once started.

Returning true causes the current apply operation to abort at the next checkpoint with ApplyError::Cancelled.

The default implementation always returns false.

Implementors§