Sterowanie runtime

Dokumentacja wersji v0.1.0-beta.3Zobacz źródło na GitHubie

Tłumaczenie oryginalnej strony w języku angielskim dla OmsiLaunch 0.1.0-beta3. Wiążąca jest strona angielska: w razie rozbieżności obowiązują strona angielska i kod.

Sterowanie runtime to zestaw operacji odczytu, zapisu i akcji, które OmsiLaunch wykonuje wewnątrz działającej sesji OMSI. Ta strona wyjaśnia trzy sposoby dostępu do niego (API właściciela, klient CLI, lokalna płaszczyzna sterowania), drogę żądań od wywołującego do wtyczki i z powrotem, działanie uchwytów i identyfikatorów żądań, obowiązujące limity czasu, to, co celowo nie jest zapisywane w dzienniku, oraz konwencje argumentów i wyników, z przykładem dla każdej rodziny operacji. Sam spis operacji, z argumentami, kluczami wyniku i błędami, znajduje się w opisie możliwości. Źródła: OmsiLaunchService.ExecuteRuntimeAsync, CurrentRuntimeCommandStore (src/OmsiLaunch.Process/RuntimeDeployment.cs), CurrentRuntimeCommandMailbox i CurrentRuntimeControl (src/OmsiLaunch.Plugin/CurrentRuntimeControl.cs), LocalControlPlane i CliInput (tools/OmsiLaunch.Cli/).

Trzy punkty wejścia#

Punkt wejściaKtoŚcieżkaLimit czasu
API właścicielaIntegrator, który uruchomił sesję w swoim procesie za pomocą IOmsiLaunch.StartSessionAsyncExecuteRuntimeAsync(session, RuntimeCommand, timeout) -> weryfikacja w rejestrze -> wyszukanie sesji -> skrzynka runtimeTimeSpan podany przez wywołującego
CLI właściciela (/runtime:<op>)Proces OmsiLaunch.exe, który jest właścicielem sesjijedna operacja wykonywana tuż po osiągnięciu Running, wynik zapisywany do .omsilaunch\diagnostics\<session>-runtime-operation.json i na konsolę; sesja trwa dalej5 s (15 s dla road-vehicles.spawn)
Klient CLIDowolne wywołanie OmsiLaunch.exe bez argumentu instalacji, na przykład OmsiLaunch.exe time getpotok nazwany runtime.execute do właściciela instalacji, w której znajduje się plik wykonywalny -> właściciel wywołuje ExecuteRuntimeAsync8 s (30 s dla road-vehicles.spawn) zarówno po stronie klienta, jak i właściciela
Lokalna płaszczyzna sterowaniaDowolny proces tego samego użytkownika Windowsten sam protokół potoku co klient CLI; patrz lokalne sterowaniejak wyżej

Każda ścieżka kończy się w ExecuteRuntimeAsync, które egzekwuje granicę publiczną w następującej kolejności:

  1. PublicCapabilityRegistry.ValidateRuntimeArguments: operacja spoza PublicRuntimeOperationIds (w tym każda operacja internal.*) zwraca Succeeded=false, ErrorCode=OL_E_RUNTIME_OPERATION_UNKNOWN; brakujący lub pusty wymagany argument zwraca OL_E_RUNTIME_ARGUMENT_REQUIRED. Żaden z tych przypadków nie dotyka sesji.
  2. Wyszukanie sesji (KeyNotFoundException dla nieznanego uchwytu), OL_E_RUNTIME_SESSION_MISMATCH, gdy RuntimeCommand.SessionId różni się od uchwytu, OL_E_SESSION_NOT_RUNNING, jeśli stan nie jest Running.
  3. Żądanie do skrzynki runtime (poniżej), a następnie ScrubInternalValues usuwa każdy klucz wyniku zaczynający się od internal_ lub kończący się na _address, _pointer, _vmt.

Klient CLI i płaszczyzna sterowania same wykonują krok 1 przed skontaktowaniem się z właścicielem, dlatego nieprawidłowa nazwa polecenia jest zgłaszana jako kod wyjścia 2 nawet wtedy, gdy żadna sesja nie jest aktywna (OL_E_RUNTIME_OPERATION_UNKNOWN i OL_E_RUNTIME_ARGUMENT_REQUIRED są mapowane na InvalidArguments; inne odrzucenia na 7; brak właściciela na 4).

Punkt końcowy płaszczyzny sterowania istnieje tylko wtedy, gdy właściciel jest w stanie Running, po uruchomieniu i po zakończeniu każdej uprzęży testowej /runtime-batch, /runtime-write-batch lub /d3d-batch. Polecenia modyfikujące (runtime.execute, session.stop) muszą zawierać aktywny session_id; CLI pobiera go automatycznie z session.status (w przeciwnym razie OL_E_CONTROL_SESSION_MISMATCH).

Tożsamość żądania i skrzynka runtime#

RuntimeCommand(SessionId, RequestId, Operation, Arguments) jest serializowany do koperty RuntimeCommandWire (sygnatura OLRC, wersja 1, 72-bajtowy nagłówek, SHA-256 ładunku JSON) i umieszczany w 64 KiB skrzynce runtime sesji obsługującej jedno żądanie naraz (OmsiLaunch.Runtime.<sessionId>). Wtyczka odpytuje skrzynkę co 50 ms w wątku interfejsu użytkownika OMSI, tam wykonuje operację i zapisuje odpowiedź.

Reguły zapewniające odporność kanału:

RegułaSkutek
Jedno żądanie narazJedno żądanie jednocześnie na sesję; host szereguje wywołujących za pomocą semafora. Slot, który nadal jest w stanie requested, gdy nadchodzi nowe żądanie, daje OL_E_RUNTIME_CHANNEL_BUSY.
Powiązanie z sesjąWtyczka ignoruje (i czyści) żądanie, którego GUID sesji nie jest jej własnym; host odrzuca odpowiedź, której identyfikator sesji lub żądania nie pasuje (OL_E_RUNTIME_RESPONSE_INVALID).
Limit czasuPo upływie terminu host przywraca slot do stanu bezczynności i zgłasza TimeoutException("OL_E_RUNTIME_REQUEST_TIMEOUT").
Spóźniona odpowiedźWtyczka publikuje odpowiedź tylko wtedy, gdy slot nadal przechowuje identyfikator jej żądania; odpowiedź na porzucone żądanie jest odrzucana. Jeśli mimo to taka odpowiedź trafi do slotu, następne żądanie hosta znajduje nieaktualny slot w stanie responded, odrzuca go i kontynuuje; jeśli ta nieaktualna odpowiedź ma ten sam identyfikator żądania co nowe żądanie, host zgłasza OL_E_RUNTIME_REQUEST_ID_REUSED. Wywołujący nie mogą więc nigdy ponownie używać identyfikatora żądania w obrębie sesji.
RozmiarŻądanie większe niż slot jest odrzucane przed umieszczeniem w skrzynce (ArgumentOutOfRangeException). Wynik listy ograniczonej, który się nie mieści, jest skracany przez wtyczkę (ostatnie wiersze są odrzucane, truncated=true, mniejsza wartość returned_count); każda inna zbyt duża odpowiedź jest zastępowana typowanym błędem OL_E_RUNTIME_RESPONSE_TOO_LARGE.
Kanał zamkniętyPo zakończeniu sesji: OL_E_RUNTIME_CHANNEL_CLOSED.

Identyfikatory żądań są wartościami ulong wybieranymi przez wywołującego. CLI używa stałych zakresów: 10_001 dla /runtime:, 50_001+ dla żądań przekazywanych przez płaszczyznę sterowania, 1+ dla wsadu odczytów, 20_000+ dla wsadu D3D, 30_000+ dla D3DRuntimeApi. Integratorzy powinni używać monotonicznie rosnącego licznika dla każdej sesji.

Uchwyty i wykrywanie nieaktualności#

PrefiksTypWydawany przezFormat
rv-RoadVehicleroad-vehicles.list, road-vehicles.spawn (created_handle), player-vehicle.readrv- + sześć cyfr dziesiętnych (rv-000003)
hb-Humanhumans.listhb- + sześć cyfr dziesiętnych
d3dtex-Tekstura D3Dd3d.texture.created3dtex-<sessionId N>-<16 hex digits>

Uchwyty są nieprzezroczyste i mają zakres sesji: wydaje je wtyczka, nigdy nie kodują adresu i nie mają znaczenia w innej sesji. Przy rozwiązywaniu uchwytu wtyczka sprawdza, czy adres, na który on wskazuje, nadal znajduje się w aktywnej kolekcji OMSI (według ostatniego odczytu listy; road-vehicles.list i humans.list odświeżają ten widok), i ponownie odczytuje odcisk obiektu (VMT Delphi plus wskaźnik definicji pojazdu albo indeks modelu postaci). Niezgodność oznacza, że obiekt natywny został zniszczony, a jego adres ponownie użyty: OL_E_RUNTIME_OBJECT_HANDLE_STALE. Dla tego samego aktywnego obiektu przy kolejnych odczytach listy zwracany jest ten sam token. Pozostały martwy punkt: obiektu tej samej klasy i definicji, ponownie utworzonego pod tym samym adresem między dwoma odczytami listy, nie da się odróżnić. Uchwyty D3D weryfikuje mostek natywny: nieznany uchwyt lub uchwyt z innej sesji daje OL_E_D3D_STALE_RESOURCE_HANDLE, zwolniony – OL_E_D3D_RESOURCE_RELEASED, a zmiana generacji urządzenia oznacza tekstury jako STALE.

Uchwyty nigdy nie są adresami natywnymi i nigdy nie należy ich parsować, porównywać liczbowo, utrwalać między sesjami ani przekazywać do innej sesji: należy je traktować jako nieprzezroczyste ciągi znaków ważne w sesji, która je wydała.

Dowody z runtime: zwolniony uchwyt D3D pozostaje odrzucany po utworzeniu nowych tekstur, a uchwyt z poprzedniej sesji jest odrzucany przez następną (domknięcie runtime H02); reset urządzenia D3D unieważnia każdą aktywną teksturę (OL_E_D3D_STALE_RESOURCE_HANDLE, D01). Wykrywanie nieaktualności uchwytów RoadVehicle i Human po naturalnym usunięciu (RV-002) nie ma bezpiecznego źródła w runtime (OMSI nie usuwał obiektów w oknach obserwacji, a żadna publiczna operacja nie usuwa obiektu); jest pokryte testami offline.

Czego nie zapisuje się w dzienniku#

Mutacje runtime zmieniają wyłącznie pamięć OMSI. Nie są rejestrowane w dzienniku transakcji i nie są przywracane po zakończeniu sesji: time.set, camera.set, camera.lock (polityka przestaje działać, gdy wtyczka się zamyka), vehicle.variable.set, road-vehicles.spawn, road-vehicles.place-random oraz każdy zasób d3d.texture.*. Znikają razem z procesem OMSI, który OmsiLaunch kończy na koniec sesji, nie pozwalając OMSI niczego utrwalić (patrz transakcje i odzyskiwanie). Nic w rodzinie runtime nie dotyka systemu plików.

Konwencje argumentów i wyników#

  • Argumenty są parami klucz/wartość w postaci ciągów znaków. W CLI --key=value po słowach polecenia staje się argumentem runtime (OmsiLaunch.exe vehicles get --handle=rv-000001); /runtime-arg:key=value jest odpowiednikiem dla /runtime:<op>. Liczby używają kultury niezmiennej (separator dziesiętny .); wartości logiczne to true/false.
  • Słowa polecenia są mapowane na identyfikatory operacji za pomocą CliInput.HierarchicalRoutes (na przykład time get -> time.read, vehicles summary -> road-vehicles.read, scripts variable set -> vehicle.variable.set). Pełna tabela ścieżek poleceń znajduje się w dokumentacji CLI. Operacje D3D nie mają ścieżki opartej na słowach polecenia; należy użyć /runtime:d3d.status lub /runtime:d3d.texture.create --width=8 --height=8 --format=A8R8G8B8.
  • Wyniki są płaskimi słownikami ciągów znaków. Listy używają kluczy <row>.<n>.<field> (vehicle.0.handle, track.3.filename, name.12) z count, a w przypadku list ograniczonych także z returned_count i truncated.
  • Niepowodzenia zawierają Succeeded=false, kod ErrorCode z prefiksem OL_E_ oraz, w przypadku niepowodzeń po stronie wtyczki, detail (a także native_status dla D3D i exception dla nieoczekiwanych błędów).

Koperta CLI (--json)#

{
  "ok": true,
  "command": "time.read",
  "protocol_version": "0.1",
  "result": {
    "SessionId": "5f641c8d-5828-42a9-b811-5e45b9d05533",
    "RequestId": 50001,
    "Succeeded": true,
    "ErrorCode": null,
    "Values": { "hour": "6", "minute": "31", "second": "12", "day": "20", "month": "9", "year": "2026" }
  }
}

Koperta błędu ma postać { "ok": false, "command": ..., "protocol_version": "0.1", "error": { "code": "OL_E_...", "category": "...", "message": "..." } }. Bez --json CLI wypisuje obiekt wyniku jako JSON z wcięciami lub jako CODE: message.

Koperta API#

var result = await launch.ExecuteRuntimeAsync(session,
    new RuntimeCommand(session.SessionId, requestId++, "vehicle.variable.set",
        new Dictionary<string, string> { ["handle"] = "rv-000001", ["name"] = "Refresh_Strings", ["value"] = "1" }),
    TimeSpan.FromSeconds(5));
if (!result.Succeeded) Console.WriteLine(result.ErrorCode);
else Console.WriteLine(result.Values!["value"]);

ExecuteRuntimeAsync zgłasza wyjątek przy naruszeniach granicy wykrytych po weryfikacji w rejestrze (OL_E_RUNTIME_SESSION_MISMATCH, OL_E_SESSION_NOT_RUNNING, OL_E_RUNTIME_CHANNEL_*, OL_E_RUNTIME_REQUEST_TIMEOUT, OL_E_RUNTIME_RESPONSE_INVALID) i zwraca Succeeded=false dla odrzuceń przez rejestr oraz błędów po stronie wtyczki.

Przykłady#

Wszystkie przykłady CLI zakładają, że dla instalacji zawierającej OmsiLaunch.exe działa właściciel (uruchomiony na przykład poleceniem OmsiLaunch.exe /new /map:maps\Grundorf\global.cfg /entrypoint-index:1 albo OmsiLaunch.exe "/saved:situations\Linie 5.osn", gdy przykład wymaga pojazdu gracza), i są uruchamiane z drugiej konsoli w tej samej instalacji.

RodzinaPolecenieDziałanie
SesjaOmsiLaunch.exe session status --jsonOdczytuje SessionId, State, komunikaty diagnostyczne i ograniczoną listę zdarzeń runtime.
CzasOmsiLaunch.exe time get, a następnie OmsiLaunch.exe time set --hour=7 --minute=30Odczytuje zegar; ustawia go przez profilowane SetTime i zwraca odczyt kontrolny.
PogodaOmsiLaunch.exe weather get i OmsiLaunch.exe weather actual getOdczytuje bieżący stan pogody oraz stan pogody rzeczywistej/ICAO. OmsiLaunch.exe weather set --wind_speed=1 zwraca OL_E_RUNTIME_SETTING_NOT_PERSISTENT.
MapaOmsiLaunch.exe map getTożsamość mapy, nazwa, opis, liczba kafelków, zakres lat i strona ruchu.
KameraOmsiLaunch.exe camera set --field_of_view=50, a następnie OmsiLaunch.exe camera lock --family=0 --preset=1 i OmsiLaunch.exe camera unlockZapisuje FOV; przypina rodzinę kierowcy z ustawieniem wstępnym 1 (wymaga pojazdu gracza, na przykład z zapisanej sytuacji); zwalnia politykę.
PojazdyOmsiLaunch.exe vehicles summary, OmsiLaunch.exe vehicles list, OmsiLaunch.exe vehicles get --handle=rv-000001Liczby; uchwyty; jedna migawka.
Utworzenie pojazduOmsiLaunch.exe vehicles spawn --model=Vehicles\MAN_SD200\MAN_SD77.busTworzy jeden pojazd RoadVehicle sterowany przez AI (limit czasu 30 s); zwraca created_handle. OmsiLaunch.exe vehicles place-random --group=1 wywołuje PlaceRandomBus.
GraczOmsiLaunch.exe player getpresent=false przy uruchomieniu bez pojazdu gracza (headless), w przeciwnym razie migawka gracza.
PostacieOmsiLaunch.exe humans summary, OmsiLaunch.exe humans list, OmsiLaunch.exe humans get --handle=hb-000001Liczby; uchwyty; jedna migawka.
Rozkład jazdyOmsiLaunch.exe timetable get, OmsiLaunch.exe timetable tracks list, OmsiLaunch.exe timetable logs listLiczby menedżera; ograniczone wiersze tras; dzienniki rozkładu jazdy.
SkryptyOmsiLaunch.exe scripts variable list --handle=rv-000001, OmsiLaunch.exe scripts variable get --handle=rv-000001 --name=Refresh_Strings, OmsiLaunch.exe scripts variable set --handle=rv-000001 --name=Refresh_Strings --value=1, OmsiLaunch.exe scripts string get --handle=rv-000001 --name=act_routeLista/odczyt/zapis zmiennych liczbowych; odczyt zmiennej tekstowej.
Stałe i krzyweOmsiLaunch.exe constants list --handle=rv-000001, OmsiLaunch.exe constants get --handle=rv-000001 --name=antrieb_getr_version, OmsiLaunch.exe curves list --handle=rv-000001, OmsiLaunch.exe curves evaluate --handle=rv-000001 --name=retarder_stufe1 --x=0.5Stałe pojazdu i obliczanie wartości krzywej. Nazwy zależą od modelu: należy używać nazw zwróconych przez polecenie list (te zostały wypisane dla autobusu gracza z situations\Linie 5.osn).
HOFOmsiLaunch.exe hof get --handle=rv-000001Metadane HOF definicji pojazdu.
Kierowcy i biletyOmsiLaunch.exe drivers list, OmsiLaunch.exe tickets getRekordy kierowców; pakiet biletów.
D3DOmsiLaunch.exe /runtime:d3d.texture.create --width=8 --height=8 --format=A8R8G8B8, a następnie OmsiLaunch.exe /runtime:d3d.texture.update --handle=<HANDLE> --width=8 --height=8 --pixels_base64=<BASE64> i OmsiLaunch.exe /runtime:d3d.texture.release --handle=<HANDLE>Cykl życia tekstury w wątku renderowania; <HANDLE> to wartość handle wypisana przez create; --pixels_base64 po zdekodowaniu musi mieć width * height * 4 bajtów (formaty 32-bitowe) i najwyżej 48 KiB.
ZdarzeniaOmsiLaunch.exe events read, OmsiLaunch.exe events watchOgraniczona lista zdarzeń; obserwacja przez odpytywanie (250 ms) aż do Ctrl+C.
ZatrzymanieOmsiLaunch.exe session stopŻąda kanonicznego zatrzymania: właściciel kończy OMSI i przywraca transakcję.

Stabilność#

Sam kanał (runtime.command-channel) ma status STABLE_BETA: integralność formatu przesyłu, powiązanie z sesją, odrzucanie spóźnionych odpowiedzi i typowane odrzucanie zbyt dużych danych są pokryte testami offline runtime-command.wire-guard, runtime-command.session-binding i runtime-command.late-response-ignored, a w runtime przez RV-003 (sesja 5f641c8d), rundę A RA-019 (klient porzucił road-vehicles.spawn po 250 ms; następne żądanie zakończyło się powodzeniem) oraz zestaw testów kanału z domknięcia runtime R03 (limit czasu, anulowanie, ponownie użyty identyfikator żądania, odrzucenie przez wtyczkę, operacja wewnętrzna, niewłaściwa sesja i brakujący argument, a po każdym z nich udane żądanie). Stabilność poszczególnych operacji opisano w możliwościach.

Ponowne użycie kanału po niepowodzeniach#

Każda ścieżka zakończenia żądania runtime pozostawia skrzynkę gotową do ponownego użycia: powodzenie, typowany błąd, zniekształcona, zbyt duża, pochodząca z innej sesji lub mająca niewłaściwy identyfikator żądania odpowiedź, przekroczenie limitu czasu, anulowanie przez wywołującego i błędy dekodowania kończą się jednym sprzątaniem, które przywraca slot do stanu bezczynności oraz czyści długość i nagłówek koperty, dzięki czemu następne żądanie nie może odczytać niczego z wcześniejszego. Pozostałe żądanie lub odpowiedź, znalezione przy rozpoczęciu nowego żądania, są najpierw czyszczone (OL_E_RUNTIME_REQUEST_ID_REUSED, jeśli mają identyfikator nowego żądania). Po stronie wtyczki wyjątek wewnątrz operacji skutkuje odpowiedzią OL_E_RUNTIME_OPERATION_FAILED, wynik większy niż slot – odpowiedzią OL_E_RUNTIME_RESPONSE_TOO_LARGE (stała, mała koperta), żądanie dotyczące innej sesji – odpowiedzią OL_E_RUNTIME_SESSION_MISMATCH, a na żądanie porzucone przez hosta odpowiedź nigdy nie jest udzielana. Te reguły są pokryte testami offline (runtime-command.terminal-paths-leave-channel-usable, plugin-runtime.oversized-and-abandoned-responses, plugin-runtime.bounded-list-fits-slot); ścieżki, które może wywołać wywołujący (limit czasu, anulowanie, ponownie użyty identyfikator, typowane odrzucenia, spóźniona odpowiedź), mają także dowody z runtime (RA-019, R03). Uszkodzonych, obcych lub zbyt dużych odpowiedzi nie da się wywołać spoza produktu i pozostają one pokryte tylko offline.