# Using Runtime Fields

This guide shows how to use runtime fields inside a feature.

Runtime fields are used when a feature works with derived values that should be cached, observed, or updated only when their input changes.

***

### Overview

OMS provides two tools for working with derived runtime state:

* `configField(...)` - observed field, automatically checked every tick
* `cachedField(...)` - cached value, recomputed only when needed

***

### Key Idea

* `configField` is intended for **config-dependent values that must stay up-to-date**
* `cachedField` is intended for **cached derived values that should avoid unnecessary recomputation**

***

### Using `configField`

`configField` is designed for values derived from configuration that should remain synchronized with it.

```kotlin
val upperMessage = configField {
    key = { config.message() }
    value = { config.message().uppercase() }
    onUpdate = { _, _ ->
        markConfigAsDirty()
    }
}
```

#### How it works

* every tick, OMS calls `watchConfig()`
* each registered `configField` is checked
* if its `key` changed, the value is recomputed
* `onUpdate` may call `markConfigAsDirty()`
* on the next tick, `onConfigUpdated(...)` is invoked

This makes `configField` suitable for values that should always reflect current configuration state.

***

### Reacting to changes

```kotlin
override fun onConfigUpdated(event: OMSLifecycle.TickingEvent) {
    super.onConfigUpdated(event)
    event.server.playerList.broadcastSystemMessage(
        Component.literal(
            "HelloWorldFeature's config updated!\n" +
                    "upperMessage: ${upperMessage.get()}"
        ),
        false
    )
}
```

***

### Real example

A feature may derive parsed config values and refresh its runtime state when they change:

```kotlin
val restartTimes = configField {
    key = { config.restartTimes.get() }
    value = {
        config.restartTimes.get()
            .mapNotNull(TimeFormatter::parseToLocalTimeOrNull)
            .sortedBy { it.toSecondOfDay() }
    }
    onUpdate = { _, _ ->
        markConfigAsDirty()
    }
}
```

This pattern is useful when config values must stay parsed and ready for use during runtime.

***

### Using `cachedField`

`cachedField` is useful when a value is expensive to compute and should not be recalculated unless its dependency changes.

#### Pattern 1 - Key-based recomputation

```kotlin
val formattedMessage = cachedField {
    key = { config.message() }
    value = {
        "[OMS] ${config.message().uppercase()}"
    }
}
```

Here the key is explicit.

This means:

* the value is cached
* it is recomputed only when `config.message()` changes
* repeated calls to `get()` do not repeat the computation unnecessarily

This is the most common `cachedField` pattern.

***

#### Pattern 2 - Manual invalidation with a constant key

A constant key can be used when a value should be initialized once and refreshed only when the feature decides to do so.

```kotlin
val restartTimeTarget = cachedField {
    key = { "" }
    value = ::getRestartTime
}
```

Here the key never changes automatically.

This means:

* the value is initialized once
* it will not be recomputed automatically
* recomputation happens only when `invalidate()` is called manually

```kotlin
restartTimeTarget.invalidate()
```

This pattern is useful for controlled runtime state that should remain stable until explicitly refreshed.

***

### Snapshot access

If you need best-effort access for diagnostics or metadata, use:

```kotlin
formattedMessage.getSnapshotSafely()
```

#### Snapshot access example

`getSnapshotSafely()` is useful when you want to expose runtime field values for diagnostics, status output, or FeatureInfo, but you do not want this access to fail if the value has not been initialized yet.

<pre class="language-kotlin"><code class="lang-kotlin">override fun info(): FeatureInfo {
<strong>    return super.info().copy(
</strong>        id = CHelloWorldFeature.NAME,
        priority = Priority.COMMON,
        data = hashMapOf(
            “formattedMessage” to formattedMessage.getSnapshotSafely()
        )
    )
}
</code></pre>

Unlike `get()`, snapshot access should be treated as best-effort access:

* it is safe for informational output
* it should not be used for critical feature logic
* critical logic should use get() when the value is expected to be available

***

### When to use each

#### Use `configField` when:

* the value critically depends on config
* it must stay synchronized automatically
* the feature should react through `onConfigUpdated(...)`

#### Use `cachedField` when:

* the computation is heavy
* the value is requested often
* recomputation should be minimized
* the value should be updated either:
  * when its key changes
  * or only after manual invalidation

***

### Notes

* `configField` is built on top of `CachedField`
* `configField` is automatically observed during feature ticking
* `cachedField` is lazy and does not participate in automatic config watching
* `onConfigUpdated(...)` is triggered only when watched state actually changes


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://conboi.gitbook.io/oms-wiki/developer-guide/implementation-guides/using-runtime-fields.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
