Merge remote-tracking branch 'origin/main' into worktree-library-clients
This commit is contained in:
@@ -87,6 +87,18 @@ object NativeBridge {
|
||||
*/
|
||||
external fun nativeSessionEnded(handle: Long): Boolean
|
||||
|
||||
/**
|
||||
* WHY the session ended, as a [SessionEndReason] ordinal — decode with
|
||||
* [SessionEndReason.fromNative]. `0` (NONE) before it ends, or on a `0` handle.
|
||||
*
|
||||
* The companion to [nativeSessionEnded], which only says THAT it ended. Both are needed: the
|
||||
* flag to leave a dead stream, this to decide what to tell the user. A player quitting their
|
||||
* game and a host falling off the network both end the session, and with no way to separate
|
||||
* them the watchdog said "the host may be asleep" for all of them — wrong for every deliberate
|
||||
* ending. Cheap (one atomic load); UI-safe.
|
||||
*/
|
||||
external fun nativeEndReason(handle: Long): Int
|
||||
|
||||
/**
|
||||
* Run the SPAKE2 PIN ceremony, presenting [certPem]/[keyPem]. Returns the host's verified
|
||||
* fingerprint (64-hex) to persist + pin, or `""` on failure (wrong PIN / MITM / unreachable).
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package io.unom.punktfunk.kit
|
||||
|
||||
/**
|
||||
* Why a stream session ended — the Kotlin mirror of `punktfunk_core::client::PunktfunkEndReason`,
|
||||
* read via [NativeBridge.nativeEndReason].
|
||||
*
|
||||
* The distinction that matters to a UI is **normal vs alarming**, and it is not a spectrum: a
|
||||
* player quitting their game and a host falling off the network both arrive as "the session
|
||||
* ended". With no way to tell them apart this client showed one message for all of them — and it
|
||||
* was the alarming one ("Connection lost — the host may be asleep"), in front of players who had
|
||||
* just quit their own game.
|
||||
*
|
||||
* Ordinals are an ABI contract with the Rust side: append only, never renumber.
|
||||
*/
|
||||
enum class SessionEndReason {
|
||||
/** Not ended, or ended before a reason could be observed. Also the fallback for an unknown value. */
|
||||
NONE,
|
||||
|
||||
/** This client closed the session — the user pressed back or stop. Nothing to report. */
|
||||
LOCAL,
|
||||
|
||||
/**
|
||||
* The host's launched game exited. A normal finish, and the one reason worth acting on: go back
|
||||
* to the library the title was launched from, so the next one is a tap away.
|
||||
*/
|
||||
GAME_EXITED,
|
||||
|
||||
/** The host ended the session deliberately (an operator "End", or it simply finished). Normal. */
|
||||
HOST_ENDED,
|
||||
|
||||
/** The host closed reporting a failure of its own. Worth showing; the host's log has the detail. */
|
||||
HOST_ERROR,
|
||||
|
||||
/**
|
||||
* The connection died rather than being closed: idle timeout, reset, the network going away.
|
||||
* This — and only this — is the "the host may be asleep, wake it" case.
|
||||
*/
|
||||
LOST;
|
||||
|
||||
/**
|
||||
* Is this an ordinary outcome rather than something to alarm the user about?
|
||||
*
|
||||
* The question nearly every caller actually asks. [LOCAL], [GAME_EXITED] and [HOST_ENDED] were
|
||||
* all meant to happen. [NONE] counts as normal — no evidence of trouble is not evidence of it.
|
||||
*/
|
||||
val isNormal: Boolean
|
||||
get() = this != HOST_ERROR && this != LOST
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Decode the JNI byte. An unrecognized value becomes [NONE] rather than throwing: this
|
||||
* crosses an ABI where the native side may be newer than this code.
|
||||
*/
|
||||
fun fromNative(v: Int): SessionEndReason = entries.getOrNull(v) ?: NONE
|
||||
}
|
||||
}
|
||||
@@ -132,6 +132,27 @@ class HostDiscovery(context: Context) {
|
||||
handler.post(poll)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear the browse down and start a fresh one. This is the manual rescan, and the recovery path
|
||||
* for a browse that started while blocked (permission not yet granted, multicast filtered) or
|
||||
* that never started at all ([start] gives up when `nativeDiscoveryStart` returns 0, and
|
||||
* nothing else would ever retry it).
|
||||
*
|
||||
* It also puts a query back on the wire: `mdns-sd` re-queries on a doubling backoff that caps
|
||||
* at an hour, so a long-lived browse is effectively passive — a host that appeared since, or
|
||||
* whose announcement was lost to multicast, may never be asked for again.
|
||||
*
|
||||
* The currently-shown host set is left alone across the swap (rather than blinking empty via
|
||||
* [stop]'s notification); the first poll of the new browse publishes the fresh set.
|
||||
*/
|
||||
fun restart() {
|
||||
val keep = onChange
|
||||
onChange = null
|
||||
stop()
|
||||
onChange = keep
|
||||
start()
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
if (!running && nativeHandle == 0L) return
|
||||
running = false
|
||||
|
||||
Reference in New Issue
Block a user