NRF SDK 0.9

Reading and acting

Observe trains: queries, identities and states

Build train cards from only the required groups and preserve the meaning of unknown data.

Choose the entry point

You will obtain a batch of trains, join their information and prepare display values. A Native tool must already receive an active ToolContext; a JVM application must already own an open Game. Results are retainable copies, but a ToolContext belongs only to the callback that supplied it.

NeedNativeJVM
Batch of train datacontext.trains(query)game.trains.snapshot(query = query)
Join an identified trainsnapshot[trainId]snapshot.train(trainId)
A train’s line plancontext.linePlan(trainId)game.trains.snapshot(selectedTrain = trainId, query = query)
Targeted driving measurementDriving callback observationsgame.trains.read(trainId)

Compose a query for the screen

TrainQuery selects additional data. Declare all the calculation’s needs together, then reuse the batch. Defaults include service and locations; disable them for a screen that does not use them. In Native, a query already covered within the callback reuses its copy; a richer request may trigger a new read.

OptionDefaultWhat it requests
includeServicetrueService and operational state associated with the train.
includeLocationstrueTracks and stations for location joins; does not request the platform table.
includeTimetablesfalseAssignment and deadlines; implies service and tracks/stations even when includeLocations is false.
includeLinesfalseLine catalogue, identities and available parent relationships.
includeTagsfalseTags and line declarations; implies the line catalogue.
includeCharacteristicsfalseCharacteristics of configured and current profiles.
includeCompositionfalseOrdered composition and vehicle-model catalogue.
includePassengersfalseObserved occupant count; independent of capacity.

The specialised capture does not request the whole map, signals, platforms, occupation, reservations or reserved path. Empty network tables in this profile therefore do not prove that the world is empty. For JVM platform data, request game.snapshot() and use Observation.platform. Request the network only when your feature needs it.

Join data without losing unknowns

TrainId, LineId, StationId, TrackId, TimetableId, TagId and VehicleModelId express the kind of identity. Their value is opaque: do not decode it or replace it with a name. Use indexed batch lookups: get and line in Native, train, line and station in JVM. Vehicles directly carry their model; there is no need to look it up for each card.

SituationExpected handling
Group not requestedDo not display “none”. Request that group if the screen needs it.
Missing nullable object or propertyKeep “unknown” or “unavailable”; requesting data does not guarantee it exists.
Supplied nullable list is emptyNo items in that observed list; this does not extend to unrequested tables.
Identity exists, join is missingKeep the identity and display a fallback label without fabricating the joined object.

In Native, Train groups available parts: a location request can supply a partial TrainService with presence and places, without service state. Callback requests accumulate; a false option does not remove a group already read. In JVM, Observation.train(id) returns a TrainRecord grouping train, service, details and metadata; service remains absent when its group was not requested. A train being present therefore does not guarantee all its enriched parts.

Distinguish movement, state and alert

Measured speed describes movement. TrainState describes an operational state and TrainAlert a reported condition; none of those fields grants permission to pass a signal. Interpret each enum within its domain, without assigning severity from its position in entries.

ValueMeaning
nullData unavailable or not requested at this location.
UnknownThe game explicitly reports its unknown state.
OtherAn observed value does not match the SDK’s named categories.
TrainAlert.NoneNo alert reported; this is not movement permission.

Build cards ready for display

Native: call readTrainCards in the tool callback
package wiki.trainqueries

import nimby.*

data class TrainCard(
    val id: TrainId,
    val name: String,
    val speedKmh: Double?,
    val state: TrainState?,
    val alert: TrainAlert?,
    val positionStation: Station?,
    val serviceStation: Station?,
)

// Call from a tool callback; do not retain its ToolContext.
fun readTrainCards(context: ToolContext): List<TrainCard> {
    val snapshot = context.trains(TrainQuery())
    return snapshot.trains.map { train ->
        TrainCard(
            train.trainId, train.name, train.speedKmh,
            train.service?.state, train.service?.alert,
            train.position?.station, train.service?.locationStation,
        )
    }
}

// null stays unknown; a known zero speed really is 0.0.
fun measuredSpeedKmh(card: TrainCard): Double? = card.speedKmh
JVM: reuse the application’s Game
package wiki.jvmtrainqueries

import fr.nimby.sdk.*
import java.nio.file.Path

data class TrainCard(
    val id: TrainId,
    val name: String,
    val speedKmh: Double?,
    val state: TrainState?,
    val alert: TrainAlert?,
    val positionStation: Station?,
    val serviceStation: Station?,
)

fun readTrainCards(game: Game): List<TrainCard> {
    val snapshot = game.trains.snapshot(query = TrainQuery())
    return snapshot.trains.mapNotNull { train ->
        val record = snapshot.train(train.trainId) ?: return@mapNotNull null
        TrainCard(
            train.trainId, train.name, train.speedKmh,
            record.service?.state, record.service?.alertState,
            record.positionStation, record.locationStation,
        )
    }
}

// One-off example; an application keeps its connection between reads.
fun readOnce(sdkLibrary: Path, gamePid: Int): List<TrainCard> =
    Nimby.connect(sdk = sdkLibrary, processId = gamePid).use(::readTrainCards)

The functions return TrainCard values made only from copied data. Position station and service station are separate facts: a train may run between stations while having a service destination. The interface may display a dash for an unknown without modifying the SDK result.

Retain a copy without treating it as current

A copy remains readable after the callback or read, but does not update itself. In Native, worldId and generation describe its context; capturedAtMillis dates the capture and ageMillis is its age when copied, not a ticking counter. Clear associated calculations on a world or generation change.

In JVM, keep joins within a single Observation and invalidate your cache when changing games or connections. processId and gameHash provide process and version context; they are not a durable save identity. A later operation must check its own current preconditions.