Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Sources/OCCTBridge/include/OCCTBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,13 @@ OCCTShapeRef OCCTImportSTEP(const char* path);
// Cancellation: if shouldCancel returns true, OCCT stops at the next polling
// boundary. The *Progress entry points return NULL and set *outCancelled=true.
// If the import otherwise fails, NULL is returned and *outCancelled stays false.
//
// Both halves of that hold on every exit path, not only at the bridge's own
// checkpoints (#525): a break during a transfer surfaces as zero transferred
// roots, a null shape, a non-Done status or an exception depending on where it
// lands, and each of those is still reported as a cancellation rather than as a
// failure. One true from shouldCancel is also enough -- it is latched, so a
// caller that answers true once and false afterwards still stops the call.

typedef struct OCCTImportProgress {
/// Called as the importer advances. fraction is 0.0...1.0; step is a
Expand Down
114 changes: 84 additions & 30 deletions Sources/OCCTBridge/src/OCCTBridge_IO.mm

Large diffs are not rendered by default.

32 changes: 31 additions & 1 deletion Sources/OCCTSwift/ImportProgress.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,44 @@ import OCCTBridge
/// `shouldCancel()` is polled at OCCT's progress-checkpoint boundaries
/// (typically once per transferred entity in STEP/IGES). Returning `true` aborts
/// the in-flight import; the loader throws `ImportError.cancelled`.
///
/// ## What a cancelled call reports
///
/// One `true` is enough, and it is remembered: the answer is not re-asked into a different
/// outcome by the polls that follow, so a one-shot flag or an already-consumed
/// `Task.isCancelled` works as a canceller.
///
/// A cancelled call always throws `ImportError.cancelled` (`ExportError.cancelled` for the
/// exporters), whichever phase the cancellation lands in — a break during a STEP transfer
/// leaves the reader reporting zero transferred roots, which the bridge used to pass on as
/// `ImportError.importFailed` for an import the caller had explicitly stopped (#525).
///
/// ```swift
/// final class Cancel: ImportProgress, @unchecked Sendable {
/// private let flag = NSLock()
/// private var stop = false
/// func cancel() { flag.lock(); stop = true; flag.unlock() }
/// func progress(fraction: Double, step: String) {}
/// func shouldCancel() -> Bool { flag.lock(); defer { flag.unlock() }; return stop }
/// }
///
/// let canceller = Cancel()
/// do {
/// let shape = try Shape.loadRobust(from: stepURL, progress: canceller)
/// print(shape.faceCount)
/// } catch ImportError.cancelled {
/// print("stopped") // never .importFailed, whichever phase was running
/// }
/// ```
public protocol ImportProgress: AnyObject, Sendable {
/// Called as the importer advances. `fraction` is `0.0...1.0`. `step` is a
/// human-readable name of the current sub-task (may be empty).
func progress(fraction: Double, step: String)

/// Return `true` to cooperatively cancel the in-flight import. Polled at
/// each progress checkpoint. The loader throws `ImportError.cancelled` on
/// the next boundary after this returns `true`.
/// the next boundary after this returns `true`, and later polls returning
/// `false` do not undo that.
func shouldCancel() -> Bool
}

Expand Down
3 changes: 3 additions & 0 deletions Sources/OCCTSwift/Shape.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,9 @@ public final class Shape: @unchecked Sendable {
/// deadline in `shouldCancel()` therefore bounds the whole call, and cancelling mid-repair
/// throws rather than returning the partially-repaired shape.
///
/// Cancelling *anywhere* throws ``ImportError/cancelled``, including during the transfer, which
/// used to report `ImportError.importFailed` instead (#525).
///
/// ```swift
/// final class Deadline: ImportProgress, @unchecked Sendable {
/// private let start = Date()
Expand Down
Loading
Loading