Threading and failure handling
Many Reverse operations useCompletionStage or CompletableFuture. Let’s separate two questions: where do I call the operation? and where does its completion callback run?
Start on the right thread
Call recording start/stop and live playback/UI controls on the Bukkit main thread. Let the service move storage work off that thread.stopSession itself into an async task.
Handle both completion and failure
Inside a JavaPlugin callback, we can handle a stop result without touching player/world state:ReplayMetadataUpdatedEvent; a deletion stage failure also emits ReplayDeletedEvent with its cause. A dispatch exception emits TRANSFER_FAILED and may leave the stage exceptional, while some failures are converted to a normal result. Synchronous validation/setup failures can occur before any notification hook is attached. See the event contracts, and retain stage handling wherever your integration must observe every outcome.
Schedule Bukkit work back to the main thread
SupposeviewerId identifies the player to notify after saving. Capture the ID before asynchronous work, then look up the current player on the main thread:
Do not wait for I/O in a normal server tick
Avoid this pattern in a command, match callback, or inventory action:Catch synchronous validation failures where they happen
Not every error arrives through a future. For example, user-supplied metadata is validated synchronously:Respect runtime ownership
Services belong to the Reverse runtime. Your plugin owns the match-to-session IDs, listeners, request guards, and custom registrations it creates.- Stop the recordings your matches own on end/cancellation/disable paths.
- Do not call
stopAlljust to clean up your own match when other integrations may be recording. - Remove your menu actions/items and retire hotbar callbacks as described in UI extensions.
- Do not close borrowed storage/availability or reinitialize its packet adapter.

