Dies soll einfach eine Sammlung von kleineren Code-Schnipseln sein, die aber manchmal ganz nützlich sein könnten...

EEPSetElem/EEPGetElem

Da EEP für Weichen und Signale den gleichen ID-Bereich verwendet (es also keine Signale und Weichen mit gleicher ID gibt), will man sich nicht immer um die Unterscheidung kümmern müssen.

Sobald der folgende Code-Schnipsel irgendwo ins Anlagenskript kopiert wurde, funktionieren die beiden Funktionen EEPSetElem und EEPGetElem genauso wie EEPSetSignal bzw. EEPSetSwitch und EEPGetSignal bzw. EEPGetSwitch.

function EEPGetElem(id)
  local signalRes=EEPGetSignal(id)
  if signalRes>0 then return signalRes
  else return EEPGetSwitch(id) end
end

function EEPSetElem(id,pos,useCallback)
  if useCallback then
    local signalRes=EEPSetSignal(id,pos,useCallback)
    if signalRes>0 then return signalRes
    else return EEPSetSwitch(id,pos,useCallback) end
  else
    local signalRes=EEPSetSignal(id,pos)
    if signalRes>0 then return signalRes
    else return EEPSetSwitch(id,pos) end
  end
end

ResBase

Der unten stehende Teil ist veraltet. Ich lasse ihn der Vollständigkeit halber trotzdem stehen. Darunter gibt es eine bessere Lösung.

Wenn man irgendwo einen Pfad zu einer Datei innerhalb des EEP-Ordner angeben muss, dann muss man diesen Pfad in Lua komplett ausschreiben, also mit Laufwerksbuchstabe und allem drum und dran. Da aber nicht bei jedem EEP auf Laufwerk C: installiert ist, gibt es spätestens bei der Weitergabe der Anlage Problem.

ResBase=io.popen("echo %cd%"):read("*line")

Wenn man diese Zeile irgendwo an den Anfang des Anlagen-Skripts kopiert, kann man danach die Variable ResBase überall verwenden, wo man irgendwas relativ zum Resourcen-Ordner aufrufen will. Zum Beispiel so:

dofile(ResBase .. "\\..\\LUA\\Skriptname.lua")

Diese Zeile bindet ein weiteres Skriptfile ein, das im EEP-Hauptverzeichnis im Unterordner LUA abgelegt ist.

Wie genau funktioniert die Code-Zeile oben? Es wird das Kommandozeilenargument "echo %cd%" ausgeführt. %cd% ist hier eine Art Umgebungsvariable für das aktuelle Verzeichnis, und zeigt innerhalb von EEP auf den Resourcen-Ordner. Beim Ausführen des Kommandozeilenarguments kann es sein, dass für den Bruchteil einer Sekunde ein schwarzes Konsolenfenster aufflackert.

So geht es besser: Lua unterstützt auch relative Pfade, man muss nur wissen, auf was sich dieses „relativ“ bezieht. Bei Lua innerhalb von EEP scheint das immer der Resourcenordner zu sein. Relative Pfade werden durch einen Punkt am Anfang gekennzeichnet (der Punkt steht für den aktuellen Ordner, zwei Punkte stehen für den darüberliegenden Ordner eine Ebene höher).

Für das Beispiel nehmen wir mal an, EEP ist im Verzeichnis C:\Programme\Trend\EEP10 installiert.

Dann lässt sich die Datei C:\Programme\Trend\EEP10\Resourcen\Anlagen\MeineAnlage.txt (wo z.B. weitere Parameter für die Anlage drinstehen könnten), durch folgenden Befehl laden:

file = io.open(".\\Anlagen\\MeineAnlage.txt")

Dass hier die Backslashes \ plötzlich paarweise auftreten, hat einen einfachen Grund: In Strings (Zeichenketten) bedeutet ein Backslash, dass jetzt ein Steuerzeichen kommt. Zum Beispiel bedeutet \n, dass an dieser Stelle ein Zeilenumbruch stehen soll. Will man aber einfach einen ganz normalen Backslash ohne Sonderbedeutung da stehen haben (unter Windows ist das das Trennzeichen zwischen zwei Verzeichnisssen), schreibt man eben zwei Backslashes: \\.

Oder, das Beispiel von oben, dass das Skript unter C:\Programme\Trend\EEP10\LUA\Skriptname.lua eingebunden werden soll. Ob man noch einen Punkt für den aktuellen Ordner an den Anfang schreibt, oder gleich mit „gehe einen Ordner höher“ anfängt, ist egal. Die beiden folgenden Zeilen machen also genau das gleiche:

dofile(".\\..\\LUA\\Skriptname.lua")
dofile("..\\LUA\\Skriptname.lua")

Funktionsparameter (und noch mehr) in Kontaktpunkten

Seit der Einführung von Lua in EEP lassen sich über Kontaktpunkte Lua-Funktionen aufrufen. Dabei kann man aber nur einen Funktionsnamen angeben, und keine zusätzlichen Parameter. Mit folgendem Trick, man könnte es fast schon „Hack“ nennen, lässt sich diese Einschränkung umgehen. Es lassen sich dann nicht nur Funktionsaufrufe mit Parametern tätigen, man kann sogar jede gültige Lua-Anweisung direkt in einen Kontaktpunkt schreiben.

Für ganz eilige: Einfach die passende der folgenden Code-Zeilen irgendwo an den Anfang eures Anlagenskripts kopieren, das Skript neu laden, einmal die 3D-Ansicht aufrufen (damit das Skript einmal ausgeführt wird), und ihr könnt Funktionsaufrufe mit Parametern in Kontaktpunkten verwenden.

Bis EEP11, Plugin 1 (ohne Zug-Parameter):

setmetatable(_ENV,{__index=function(s,k) local n=k:gsub("%.",","); local p=load(n);if p then _ENV[k]=p;_ENV[n]=p;return p end;return nil end})

Für alle Versionen (mit Zug-Parameter):

setmetatable(_ENV,{__index=function(s,k) local n=k:gsub("%.",","); local p=load(n);if p then local f=function(z) local s=Zugname;Zugname=z;p();Zugname=s end;_ENV[k]=f;_ENV[n]=f;return f end;return nil end})

Ab EEP11 (mit Zugparameter, ohne Punkt-Komma-Ersetzung):

setmetatable(_ENV,{__index=function(s,k) local p=load(k);if p then local f=function(z) local s=Zugname;Zugname=z;p();Zugname=s end;_ENV[k]=f;return f end;return nil end})

Ab EEP11, Plugin 2 wird von EEP bei jedem Kontaktpunktaufruf der Zugname mitgeteilt, von dem dieser Kontaktpunkt überfahren wurde. Wenn eine letzten beiden Codezeilen oben verwendet wird, steht dieser Zugname innerhalb des KP-Eintrags in der Variable Zugname bereit und kann verwendet werden. Ein Eintrag von print("Der Zug namens ",Zugname," hat den KP überfahren!") in der Lua-Zeile im KP bewirkt bei jeder Überfahrt dieses KPs eine Ausgabe im Ereignisfenster.

Einzige Einschränkung bei den ersten beiden Varianten: Da EEP leider automatisch alle Kommas in Punkte umwandelt, kann zwischen Punkt und Komma nicht mehr unterschieden werden. Mit den oberen beiden Zeile werden zwar alle Punkte wieder in Kommas zurückverwandelt (um Funktionsaufrufe mit mehreren Parametern zu ermöglichen), allerdings sind dann auch gewollte Punkte verschwunden.

Ab EEP11(?) werden die Kommas von EEP nicht mehr in Punkte automatisch verwandelt - die letzte der drei Codezeilen verzichtet daher auch auf die Zurückverwandlung und erlaubt so auch (gewollte) Punkte in der Lua-Anweisung im KP. Das funktioniert aber nur, wenn auf der Anlage keine „alten“ Kontakte (mehr) liegen, wo EEP schon die Kommas durch Punkte ersetzt hat.

Und noch eine Warnung: Wurde die EEPMain mittels return 0 beendet (dann wird sie nicht mehr aufgerufen), hängt sich EEP auf, sobald man in einem KP einen „komplexeren“ Lua-Eintrag einfügt (der durch meine Code-Zeile verarbeitet wird), und diesen Eintrag mit OK bestätigt. Also einfach am besten den Rückgabewert der EEPMain auf 1 lassen, und alles ist gut ;-).

Für die, die es auch verstehen wollen: Die obigen Einzeiler lassen sich natürlich auch deutlich übersichtlicher darstellen. Das sieht dann (am Beispiel der zweiten Variante) so aus:

mt = {}
setmetatable(_ENV,mt)
function mt:__index(key)
  local newkey=key:gsub("%.",",")
  local parsed=load(newkey)
  if parsed then
    local myFunction=function(myZugname)
      local save=Zugname
      Zugname=myZugname
      parsed()
      Zugname=save
    end
    _ENV[key]=myFunction
    _ENV[newkey]=myFunction
    return myFunction
  end
  return nil
end

Um den Code zu verstehen, sollte man zuerstmal folgendes wissen: Alle in Lua definierten Variablen und Funktionen sind „in Wirklichkeit“ nichts anderes als Einträge in der speziellen Tabelle _ENV. Statt myVar=1 kann man also auch genausogut _ENV["myVar"]=1 schreiben. Oder der Funktionsaufruf myFunc() kann auch als _ENV["myFunc"]() geschrieben werden. Wenn man in EEP nun irgendetwas in das Lua-Feld eines Kontaktpunkts einträgt, schaut EEP nach, ob unter diesem Eintrag in der Tabelle _ENV eine Funktion gespeichert ist. Wenn ja, ist alles gut. Wenn nicht, dann meckert EEP, dass die Funktion nicht gefunden wurde.

Gibt man in die Lua-Eingabezeile im Kontaktpunktdialog beispielsweise „myFunc(1,2)“ ein (weil man die Funktion myFunc mit den Parametern 1 und 2 aufrufen will), sucht EEP nach dem Eintrag _ENV["myFunc(1,2)"]. Wohlgemerkt, die Klammern und Parameter gehören hier zum Funktionsnamen, den EEP sucht! (Um genau zu sein, ersetzt EEP (zumindest in EEP10) nach dem Klick auf OK das Komma durch einen Punkt, und sucht demzufolge nach dem Eintrag _ENV["myFunc(1.2)"])

Und genau in dieser Stelle setzt der Code an. Lua erlaubt es nämlich, eine spezielle Funktion aufzurufen, wenn in einer Tabelle auf ein Element zugegriffen werden soll, dass gar nicht existiert.

In den ersten beiden Zeilen wird eine leere Tabelle unter dem Namen mt (das soll hier für „metatable“ stehen) angelegt und als Metatabelle der _ENV-Tabelle zugewiesen.

mt = {}
setmetatable(_ENV,mt)

Das Konzept der „Metatabellen“ ist recht komplex, hier zum Verständnis aber nicht zwingend nötig. Wichtig ist hier nur, dass die Funktion, die im Schlüssel __index der Metatabelle definiert ist, immer dann aufgerufen wird, wenn in der eigentlichen Tabelle ein Eintrag nicht gefunden wurde. Und genau diese Funktion wird in der nächsten Zeile definiert:

function mt:__index(key)

Die Funktion hat nur einen Parameter, nämlich key. In diesem Parameter wird übergeben, welcher Schlüssel eigentlich gesucht, aber nicht gefunden wurde. Wenn die Variable gibtEsNicht nicht existiert (es also den Eintrag _ENV["gibtEsNicht"] nicht gibt), wird die Funktion mt:__index("gibtEsNicht") aufgerufen. In der Funktion sollte dann natürlich irgendwas stehen, das diesen nicht gefundenen Schlüssel in irgendeiner Form verarbeitet und evtl. ein Ergebnis zurückgibt. Und diese Verarbeitung fängt erstmal damit an, dass alle Punkte durch Kommas ersetzt werden:

  local newkey=key:gsub("%.",",")

gsub steht für „global substitution“, also „globale Ersetzung“. Das Ergebnis der Ersetzung, also der Schlüssel mit Kommas statt Punkten, wird in einer neuen lokalen Variable namens newkey gespeichert.

Die eigentliche „Magie“ steckt in der nächsten Zeile:

  local parsed=load(newkey)

Die Lua-Funktion load versucht, aus dem übergebenen String einen ausführbaren Code zu erzeugen. Das Ergebnis wird in der lokalen Variable parsed gespeichert.

Hat das Parsen, also die Umwandlung vom Text zu ausführbarem Code geklappt, kommt ab EEP11.2 der neue Teil, der den übergebenen Zugnamen verarbeitet. Dazu wird eine neue Funktion erstellt, die einen Parameter erwartet (nämlich den Zugnamen, der von EEP übergeben wird).

  if parsed then
    local myFunction=function(myZugname)

Innerhalb der Funktion wird der übergebene Zugname so verarbeitet, dass er innerhalb der Kontaktpunkt-Zeile unter dem Namen Zugname zur Verfügung steht. Die einzige Möglichkeit, die ich dazu gefunden habe, war der Weg über die globale Variable Zugname. Weil die aber vielleicht schon irgendwo anders im Skript definiert wurde, wird sie erstmal „gesichert“ (in der Variable save), bevor sie mit dem übergebenem Zugnamen überschrieben wird.

      local save=Zugname
      Zugname=myZugname

Anschließend kann die Funktion aufgerufen werden, die zuvor aus dem Code aus der Kontaktpunkt erzeugt wurde. Hier wird also die gewünschte Funktion wirklich ausgeführt (aber erst, wenn die Funktion myFunction später aufgerufen wird. Hier wird sie erstmal nur definiert).

      parsed()

Anschließend wird die zwischengespeicherte Variable Zugname wieder zurückgeschrieben, sodass keine unerwünschten (und schwer zu findenden) Nebeneffekte auftreten.

      Zugname=save

Die gerade eben definierte Funktion myFunction wird nun in der Tabelle _ENV unter dem ursprünglichen und dem neuen Schlüssel abgespeichert, denn jetzt ist ja schon bekannt, welche Funktion hinter dem KP-Eintrag steckt. Auf diese Weise wird auch überflüssiger Aufwand vermieden, dass der String bei jedem Überfahren des Kontaktpunkts neu ausgewertet werden muss. Anschließend wird das Ergebnis der Auswertung zurückgegeben, EEP bekommt seine Funktion und ist glücklich.

    _ENV[key]=myFunction
    _ENV[newkey]=myFunction
    return myFunction

War die Auswertung nicht erfolgreich (und wurde die Funktion deshalb nicht schon mittels return myFunction beendet, wird stattdessen nil zurückgegeben, sprich: „Mit dem Schlüssel kann ich auch nichts anfangen, das ist kein gültiger Funktionsaufruf oder sonstwas; es gibt kein Ergebnis“

  return nil

Die oben genannte Einschränkung, dass in den KP-Aufrufen keine Punkte verwendet werden können, gilt natürlich leider auch für die ausführlich erklärte Version...

Abschließend noch ein Tipp: Es lassen sich mit dieser Methode nicht nur Funktionen mit Parametern aufrufen, sondern auch globale Variablen ändern. Zum Beispiel bewirkt der KP-Eintrag I=I+100 bei jedem Überfahren, dass der Programmzähler (wenn man das Standardskript beibehalten hat) einen Sprung um 100 macht.