Hallo, Besucher der Thread wurde 469 aufgerufen und enthält 4 Antworten

letzter Beitrag von Waeshoe am

Der Train Simulator und sein LUA-Framework

  • Wir wissen alle, dass der Train Simulator schon einige Jahre auf dem Buckel hat. In diesen Jahren ist die Software durch mehrere Hände gegangen.


    Wer ernsthaft Inhalte erstellen möchte, freut sich einerseits über eine Menge Möglichkeiten, auf der anderen Seite wiederum kommt man mit seinen Ideen hin und wieder doch sehr schnell an die Grenzen des TS. Gerade was die mitgelieferte LUA-Umgebung angeht sind die Möglichkeiten außerhalb des Führerstands für Gameplay-Technische Aspekte ziemlich begrenzt.

    Was vor allen Dingen fehlt, ist eine Mechanik, die eine Kommunikation unter Entities ermöglicht.


    Auch wenn man durch geschickte Platzierung gewisser SysCall's zwischen Named-Entities kommunizieren kann, fehlt trotzdem eine Mechanik, die dies auch für Owned-Entities ermöglicht.


    Darum habe ich mir einmal ein paar Gedanken gemacht und die Weise, auf die Lua arbeitet komplett nachvollzogen. Gott sei Dank gibt es für die Version 5.0.2 noch Quellcode und Dokumentationen.

    Lua 5.0.2 wurde in Release 2 am 17. März 2004 veröffentlicht.


    Mit dem Wissen habe ich mich hingesetzt und versucht, die im TS verbaute LUA Bibliothek zu erweitern. Dabei gab es eine ganze Menge Probleme.


    1. Die Lua Bibliothek ist statisch in den TS gelinkt. Das bedeutet, dass der Quellcode nicht in eine Dll gepackt wurde und diese dynamisch mit dem TS gelinkt wurde, sondern dass der Quellcode direkt in den TS statisch hineingelinkt worden ist. Der Unterschied ist klar: Ich komme an Lua im TS nicht über legale Wege heran, da ich mich nicht auch auf eine vorhandene Dll beziehen kann. Eine Dll stellt Funktionen quasi durch einen Export zur Verfügung, während ein fest eingebauter Quellcode dies nicht macht. Das bedeutet, ich muss mir die Lua-Bibliothek komplett mit den gleichen Ausgangsbedingungen (Visual Studio Compiler + Linker - Version, Plattformtoolset-Version, WinSDK-Version, etc.) nachbauen.


    2. Nun habe ich die Bedigungen ausloten können (das war eine Arbeit, da war es nicht mit nem dumpbin-Befehl in Visual Studio getan) und kann in Lua im TS mittels "loadlib" eine DLL laden. Da meine Bibliothek nur nachgebaut ist, kann ich keine eigene Bibliothek in ein Script hinregistrieren. Ich musste jede Funktion einzeln mittels "loadlib" in den TS hineinladen.


    3. Als ich die Funktionen dann im TS habe aufrufen können, probierte ich die lua_State-Structs, die an jede Funktion übergeben wird, zu speichern, um Callbacks in meiner DLL registrieren zu können. Sprich, ich wollte in einem Lua-Script im TS einen EventHandler registrieren, der im Script ausgeführt wird, wenn eine bestimme Sache geschieht. Da ich doch etwas Ahnung von meinem Job habe, wusste ich zwar, dass das nicht einfach wird, habe es aber trotzdem versucht und musste wie erwartet feststellen: Wenn ich mit "loadlib" in Lua eine Dll lade (In Windows liegt dazu die WinAPI Funktion "LoadLibrary" zu Grunde) bekommt meine Dll einen eigenen Heap (Speicherbereich) anstatt sich den mit dem TS Prozess zu teilen. Wäre diese direkt im TS Prozess drin, könnte ich mich einfacher in Lua einklinken, so musste ich diesen Ansatz mit dem Callback leider verwerfen, da der TS crashed, wenn ich auf einen Zeiger in seinem Speicherbereich schreiben möchte, wenn Lua mich nicht darum bittet. Kurz gesagt: Ich darf den lua_State-Zeiger nur dann verwenden, wenn er mir aktiv in die Dll geschickt wird.


    Also ist hier dann Schluss? - Nein, denn es gab noch eine Möglichkeit, die zwar nicht so bequem, wie die erste gedachte ist, aber immerhin eine Möglichkeit.


    Wenn ich also nur dann in den lua-Stack schreiben darf, wenn man mich darum bittet, dann müssen die entsprechenden Scripts, die Nachrichten erwarten eben eine Message-Loop besitzen. Damit fällt es schonmal aus, dass man Signalen im TS generell eine Message-Loop verpasst (Diese muss ja sequentiell abgerufen werden: Performance?). Mittels Helfer-Objekten, die in einem Szenario spezifisch auf Nachrichten warten und diese dann an ein Signal oder einen Zug weiterleiten, ist dennoch einiges möglich. Und siehe da, das ganze funktioniert.


    Neben dem "Router"-Teil der Bibliothek gibt es noch andere, unfertige Teile. Diese werde ich jetzt einmal außer Acht lassen und sie ggf. später einmal vorstellen.


    Alle Funktionen sind in dem Modul "JoinTogether" hinterlegt, welches neben der Dll im "plugins"-Verzeichnis des TS liegt. In ihr befinden sich bis jetzt vier Module: Router, Core, Graphics, Network.


    Als Beispiel einmal ein ScenarioScript und ein Signal-Helfer-Script, jene die Bibliothek nutzen:



    Das Modul "Router" enthält also drei öffentliche Funktionen:


    - Result (0 / 1) = PostMessage(Sender, Target, Major, Minor, Data)

    - Result (0 / 1) = HasMessage(Target)

    - Result (0 / 1), Sender, Major, Minor, Data = GetMessage(Target)


    Im ScenarioScript ist der Router eher fehl am Platz, denn jedes Script kann in der Spielwelt ein TS-Event auslösen und den eingebauten Weg nutzen. Das würde ich sowieso, wenn immer möglich, auch empfehlen.

    Die Call's an die "SendSignalMessage"-Funktion sind nicht ausgeschrieben, da sich diese von Signal zu Signal etwas unterscheiden können. Es gibt zwar Standard-Messages durch den TS, die aber nicht zwingend überall gleich interpretiert werden müssen, jdeoch sollten.


    Schafft es der Kram zum Release?


    Das weiß ich nicht. Ich habe hier etwas aus dem Nähkästchen geplaudert und das mit den Kollegen bei der JTG noch nicht besprochen.

    Zum anderen funktioniert das ganze bisher nur in der 64-Bit Version des TS. Nicht, weil ich die Bibliothek nicht auch für 32 Bit programmiert habe, sondern weil ich keinen ansprechenden Weg gefunden habe, aus Lua heraus die richtige zu laden. Da "loadlib" von Lua eigentlich nur auf "LoadLibrary" in der WinAPI verweist, weiß ich, dass LoadLibrary fehl schlägt, wenn eine nicht zur Prozessarchitektur passende Dll geladen werden soll. So könnte ich zwar erst die x86-Dll laden und auf das Resultat warten und danach ggf. die x64-Dll ranziehen, aber ich finde den Weg nicht ganz so glücklich, eine Notlösung wäre das am Ende aber sicherlich, da kann nichts passieren. Ist nur ne optische Sache.

  • PascalW


    eine Hammer Sache, da gibt es noch Leute mit Pioniergeist und Forscherdrang - dann scheint unser Land nicht ganz verloren zusein. :-)


    ich hoffe Du berichtest weiter hier und bin auf deine weiteren Erkenntnisse gespannt.


    Vielleicht magst Du Dir ja mal in diesen Rahmen auch den Dispatcher anschauen (wenn möglich), der hat ja auch so seine Kinderkrankheiten und auch eine Kommunikation zwischen verschiedenen Objekten ( Signal und Bü z.B.) wäre interessant ob es da Möglichkeiten geben könnte. Auch wenn man KI dazu bewegen könnte auf die Signale zu reagieren und nicht auf den reinen Fahrweg vom Dispatcher, da könnte man den TS noch reichlich Potenial rauskitzeln. :-) Das mal als Anregung, was deine mühsame Arbeit vielleicht bringen könnte.


    Ich habe nur ganz rudimentäre Programmierkenntnisse und weiß nicht wie tief Du in die TS Struktur eindringen kannst.


    Ich wünsche Dir weiterhin viel Erfolg bei deiner Forschung. ;-)

  • Das ist einer der wesentlichsten Punkte, dass die KI endlich mal die Signalbilder richtig interpretiert. Der MSTS konnte das. Dies ist auch wieder ein Beispiel dafür, dass ein Publisher mit permanenter Drängelei, schneller fertig zu werden, es geht ja um das heilige Geld, ein sehr gutes Programm/Spiel vor die Wand fahren kann.


    Im Laufe der Zeit hat sich im TS so viel verändert und weiterentwickelt, dass bei vielen älteren Aufgaben der Dispatcher nicht mehr richtig funktioniert und die KI ständig "über Rot" fährt, was dann zu den bekannten Kollisionsmeldungen führt. Manchmal habe ich das durch veränderte Fahrzeiten beheben können, manchmal musste ich den KI-Zug löschen. Das kann nicht im Sinne des Erfinders sein.

  • Hallo zusammen,


    es sei gedankt für euer Interesse an diesem Projekt!

    Momentan ist mein Ziel, den TS in seinem Lua-Framework etwas aufzuwerten. Mir ist es natürlich durch technische "Analyseverfahren" möglich, den Programmablauf des TS zu erkennen und an bestimmten Stellen Code zu ersetzen. Jedoch ist dieser Weg eine rechtliche Grauzone.


    Zusätzlich kommen noch ein Haufen technischer Probleme des TS auf mich zu, die mir bei meinen jetzigen Versuchen schon ziemlich in den Karren gefahren haben:


    - Die 64-Bit Version des TS ist keine saubere 64 Bit Version. Einfach erklärt: Unter 32 Bit hat ein Integer eine Größe von 4 Bytes, unter 64 Bit eine Größe von 8 Bytes. Man kann auch unter 64 Bit zwar weiterhin mit 32 Bit Datentypen arbeiten (das unterscheidet sich nur im Fassungsvermögen der Daten). Im TS wurden diese aber wild gemischt. Das kann jeder einmal ausprobieren: "string.dump" in Lua nimmt einen String und macht daraus einen Bytecode. Jetzt kann man mittels "loadstring" diesen String normalerweise wieder zurückholen. Dies passt aber nicht, weil: string.dump den String in 64 Bit Datentypen packt, "loadstring" aber 32 Bit Datentypen erwartet. Lua meldet sich mit dem Error: "Die Größte eines Integers hat 4 Bytes, es wurden aber 8 erkannt".


    - Das gesamte Programm läuft in nur einem Thread. Wenn man eine Framerate von mind. 30 Bildern pro Sekunde erreichen will, bleiben für die Berechnung eines Frame 33ms Zeit. Die Framerate unter Windows hängt maßgeblich vom Hauptthread ab (der Thread, der das Fenster enthält). Im TS hat man Rendering, Bewegung, Fensternachrichtenschleife und Lua VMs in diesen Hauptthread gepackt. Das ist sehr ungünstig. In den meisten Fällen würde man zu mind. die Script VM in einen eigenen Thread packen. Damit würden Lua Scripts nicht mehr die Möglichkeit haben, den TS zum einfrieren zu bringen. Wenn der Hauptthread weiterläuft, aber der Script-Thread nicht, kann man vom Hauptthread aus den Script-Thread kontrollieren und entsprechende Maßnahmen ergreifen. Wenn das gesamte Programm lahm liegt, dann nicht mehr.


    - Jedes Lua-Script hat ein eigenes VM. Die Entwickler haben damals die Funktionsweise von Lua nicht verstanden. Lua-Scripts sollten alle in einem einzigen lua_State liegen. Man hatte sicher gemerkt, dass mehrere Call's auf "dofile" mit Dateien, die die gleichen Globals defininieren, nicht funktioniert. Da hat man dann schlicht die dümmste Lösung genommen und für jedes Script ein eigenes VM definiert. Der Grund, wieso Lua scripts im TS nicht miteinander anständig kommunizieren können, weil jede Möglichkeit für das Verschicken von Callbacks unter Scripts nicht mehr möglich ist.


    - Bekommt der TS ein Update, wird meistens alles, was ich im TS verändert habe, wieder nichtig. Heißt, wenn ich also über das Lua-Plugin hinausginge und mich eben beispielsweise in den Dispatcher einklinte. Der Grund dafür ist, dass ich den Speicher des TS analysiere und mich anhand eines Offests (Startpunkt TS + xxx) an einer bestimmten Stelle einklinke. Wenn sich jetzt die GameManager(64).dll des TS ändert, stimmen diese Offsets nicht mehr.


    - Eine Code-Injection ist beim TS nicht so einfach, wie man denkt. Allgemein ist eine Code-Injection problematisch, denn in den meisten Fällen möchte ich den Code direkt von Anfang an mit im Prozess haben. Dafür bietet Windows einige Hilfsmittel an, wie zB den Windows-Hook, bei dem ich jedem Prozess eine eigene DLL aufzwingen kann. Das ist eine Lösung, aber die letzte, die man anstreben sollte. So wäre die fremde DLL zB auch in einem Mail-Clienten, Browser, etc vertreten. Soetwas würde ich als Nutzer nie vertrauen. Ich kann einen Prozess aber auch explizit starten, um ihm eine DLL aufzuzwingen. Funktioniert meistens, aber im TS haben wir noch Steam mit drin. Hier müsste ich auch einiges beachten, denn: Der TS lässt sich nicht selbst starten (zu mind. bei mir startet er beim Öffnen der exe nicht, nur über Steam). Die Code-Injection in das Lua-Plugin einzubauen, ist viel zu spät im Programmverlauf. Da können schon einige Dinge schon nicht so geladen worden sein, wie ich das eigentlich wollte.


    Aus diesen Gründen würde ich mich jetzt lieber auf die LUA-Plugin-DLL ansich beschränken. Da werden schon schicke Möglichkeiten geschaffen. Außerhalb des Router-Moduls gibt es ja noch Core, Graphics und Network. Diese stellen u.a. Funktionen für HTTP-Requests zur Verfügung oder das zeichnen von eigenen UI-Elementen, die bedienbar sind, etc.


    eine Kommunikation zwischen verschiedenen Objekten ( Signal und Bü z.B.)

    Das ist mit dem jetzigen Stand bereits absolut möglich.



    Im Laufe der Zeit hat sich im TS so viel verändert und weiterentwickelt, dass bei vielen älteren Aufgaben der Dispatcher nicht mehr richtig funktioniert und die KI ständig "über Rot" fährt, was dann zu den bekannten Kollisionsmeldungen führt. Manchmal habe ich das durch veränderte Fahrzeiten beheben können, manchmal musste ich den KI-Zug löschen. Das kann nicht im Sinne des Erfinders sein.

    wenn man KI dazu bewegen könnte auf die Signale zu reagieren und nicht auf den reinen Fahrweg vom Dispatcher

    Das ist eine sehr schwierige Aufgabe. Das ist denke ich das Stück, was den TS an sich ausmacht. Der Dispatcher ist meiner Ansicht nach alles, was der TS hat um ein Szenario laufen zu lassen. Da wäre die Neuentwicklung eines Simulators sicher schneller, als sich in fremde Software einzuarbeiten und abzuändern. Aber ich gebe euch Recht, das ist das Hauptproblem im TS.

  • Vor denselben Problemen steht ja auch DTG, die den TS ja nicht selbst entwickelt haben. Sie haben ihn bloß übernommen, weil durch die von mir beschriebenen Umstände der Simulator "z-gestellt" wurde. Der eigentliche Entwickler ist Kuju, die den TS über Electronic Arts angeboten haben. Ob es eine Auftragsarbeit war, weiß ich nicht, aber EA ist durch eine ausgeprägte Geldgier schon aufgefallen.


    Daher ist es auch kein Wunder, dass man sich bei DTG zur Entwicklung und "Bau" des TSW entschlossen hat. Der hat ein gigantisches Potential.