Créer un mod
Réglages et valeurs effectives
Déclarez cases et entiers, lisez leur disponibilité et préparez des règles de zone sans écrire les profils.
Une déclaration réutilisée par la règle
Un réglage appartient à un modèle. Donnez-lui une clé stable pour les parties sauvegardées, un libellé lisible et un défaut explicite. Le libellé et l’aide peuvent utiliser tr ; la clé technique ne se traduit pas. Déclarer une case ne modifie aucune règle : lisez enabled dans rules.
val active = checkbox(
name = "active", label = "Enabled",
description = "Use this model’s rules.", defaultValue = true
)
rules {
when {
!enabled(active) -> Indication(Aspect.Closed, Reason.Disabled)
!fresh || !routeKnown || block != Occupancy.Clear ||
observation.forcedStop || observation.lampFailed ->
Indication(Aspect.Closed, Reason.Unknown)
else -> Indication(Aspect.Open, Reason.Clear)
}
}Conservez la Checkbox retournée et passez cette même déclaration à enabled. Une case issue d’un autre modèle est refusée, même si elle porte le même nom. Deux modèles peuvent chacun déclarer active avec des défauts différents.
| Statut | Lecture dans SignalRuleContext |
|---|---|
| Present | Profil effectif fourni. Le SDK peut compléter les défauts d’un signal connu sans valeurs sauvegardées. |
| Absent | Le contexte utilise les défauts déclarés et conserve ce statut. Cela ne prouve pas qu’un profil a été lu dans la partie. |
| Unavailable | Profil indisponible : l’observation devient non fraîche. Une valeur par défaut ne constitue pas une lecture réussie. |
enabled utilise le défaut lorsqu’une clé manque. Vérifiez aussi fresh et les observations nécessaires à votre règle ; ne traitez jamais un profil indisponible comme toutes les cases décochées. Le DSL à enums communes conserve lui aussi settingsStatus, mais n’applique pas toute la normalisation de SignalRuleContext.
Placer un entier sous sa case de contrôle
val work = checkbox("work", "Work zone", defaultValue = false)
val workBlocks = NumberSetting(
"workBlocks", "Following blocks", maximum = 64,
defaultValue = 0, visibleWhen = work.name
)
number(workBlocks)
// Inside rules:
// val following = workBlocks.read(settings)visibleWhen désigne la case du même modèle qui affiche le champ. Le panneau place ce champ sous sa case de contrôle. Masquer le champ conserve sa valeur : votre règle doit décider si cette valeur a un effet lorsque la case est désactivée. Une chaîne visibleWhen vide laisse le champ sans condition de visibilité.
| Paramètre ou fonction | Contrat |
|---|---|
| maximum | De 1 à 65535 ; la plage du champ est 0..maximum. |
| defaultValue | Dans 0..maximum. Le sens de zéro est choisi par votre règle. |
| read(settings) | Lit une valeur bornée dans la copie des réglages, sans accès au jeu. |
| withValue(settings, value) | Retourne une nouvelle carte de réglages pour le calcul ou les tests ; ne sauvegarde rien. |
Un modèle accepte jusqu’à quatre NumberSetting distincts. Utilisez leur API, sans construire leurs champs de stockage. Les champs entiers ToolNumberInput d’un formulaire outil constituent une autre API : ils transmettent une saisie à votre service, pas un réglage persistant de signal.
Préparer les réglages d’une zone
prepareNetwork reçoit les signaux observés du mod avant leur résolution. Il peut dériver les réglages utilisés par ce calcul, par exemple propager une option sur un nombre de signaux suivants. Il ne sauvegarde pas ces valeurs. Conservez nombre, ordre, identités, liens, types et observations des signaux ; le SDK vérifie cette conservation.
package wiki.prepared
import nimby.*
val work = Checkbox("work", "Zone de travaux", "Appliquer la règle de travaux de ce modèle.")
val workBlocks = NumberSetting("workBlocks", "Cantons suivants", maximum = 64,
defaultValue = 0, visibleWhen = work.name)
enum class Aspect { Closed, Open }
enum class Reason { Unknown, Clear, Work }
val model = signalModel(
SignalType("example.work", "Signal de travaux", "example_work", checkboxes = listOf(work)),
fallback = Indication(Aspect.Closed, Reason.Unknown)) {
number(workBlocks)
construction(listOf("closed.svg", "open.svg"))
rules {
if (!fresh || !routeKnown || block != Occupancy.Clear || observation.forcedStop || observation.lampFailed)
Indication(Aspect.Closed, Reason.Unknown)
else if (enabled(work)) Indication(Aspect.Closed, Reason.Work)
else Indication(Aspect.Open, Reason.Clear)
}
images { if (it.aspect == Aspect.Open) "open.svg" else "closed.svg" }
driving { if (it.aspect == Aspect.Open) AutomaticDriving.clear() else AutomaticDriving.stop() }
}
// Règle d’exemple : propagation par nextSignal, pas par distance physique.
// Zéro signifie la source seule. Aucun réglage dérivé n’est enregistré.
fun effectiveWorkSettings(signals: List<Signal>): List<Signal> {
val byId = signals.associateBy { it.id }
val affected = HashSet<Long>()
for (source in signals) {
if (source.type != model.type.id || source.settingsStatus != SettingsStatus.Present ||
!source.observation.fresh || source.settings[work.name] != true) continue
var current: Signal? = source
val seen = HashSet<Long>()
repeat(workBlocks.read(source.settings) + 1) {
val signal = current ?: return@repeat
if (!seen.add(signal.id) || signal.type != model.type.id ||
signal.settingsStatus != SettingsStatus.Present || !signal.observation.fresh) {
current = null
return@repeat
}
affected.add(signal.id)
current = byId[signal.nextSignal]
}
}
return signals.map { signal ->
if (signal.id in affected && signal.settings[work.name] != true)
signal.copy(settings = signal.settings + (work.name to true))
else signal
}
}
fun createPreparedMod() = signalMod("prepared-example", "Réglages préparés") {
signal(model)
prepareNetwork(::effectiveWorkSettings)
}
Dans cet exemple, zéro vise seulement la source ; deux vise la source et deux signaux suivants du même modèle. Le parcours s’arrête sur un lien absent, un cycle, un autre modèle ou des données non exploitables. La zone impose ici une fermeture : c’est une politique fictive pour démontrer la préparation, pas une convention de signalisation des travaux.
Testez cette fonction avec des valeurs Kotlin et vérifiez que les entrées restent inchangées. prepareObservedNetwork contrôle les identités et la conservation du réseau ; evaluateNetwork résout ensuite les dépendances. Évitez de refaire la même recherche globale dans chaque règle de signal.
Conserver des identités de réglages stables
Changer un libellé ne nécessite pas de changer la clé. Conservez les identifiants du modèle, du catalogue et des réglages pour retrouver les valeurs d’une partie. onlyWhenEnabled peut servir à un avertissement visible tant qu’il reste à acquitter ; il ne change pas le sens métier de la case.
Si vous renommez volontairement une clé déjà distribuée, migrateSettings reçoit uniquement les valeurs réellement sauvegardées. Convertissez celles qui existent, laissez les autres absentes pour que leurs défauts soient complétés et rendez la transformation idempotente. Les nouveaux projets n’ont pas besoin de cette étape.