Lua Version

» Siedler Map Source Forum » Siedler DEdK Script Forum » Lua Version

Seiten: 1

Termor
#1
03.04.2016 21:36
Beiträge: 9

Lua Version

Guten Tag alle zusammen!
die trivalste Fragen aller Zeiten: Welche Lua Version verwendet Siedler 5 denn? Da Funktionen wie string.gmatch undefiniert sind vermute ich dass sie >5.1 ist.

Oder ist es einen ganze eigene Lua Version da BB scheinbar auch den IO/OS table (aus gutm Grund!) beschnitten hat.

Wie steht es mit Savegame Sicherheit? Durch etwas rumsuchen habe ich gefunden dass Werte im lokalem Stack nicht gespeichert werden, funktionen und tables als table Keys nicht funktionieren und metatables nicht gespeichert werden(Gibt es bereits einen Workaround zu letzterem? Was eigenes zu schreiben wäre möglich, jedoch sind Weak-References in Lua so ... unspaßig).

Sehr vielen Dank

//Edit, scheint alt zu sein, dump des string tables:
[spoiler]

Log: {
Log:     [1] = "sub",
Log:     [2] = <Function, defined in Game Engine at 0x18813EC0>,
Log:     n = 2,
Log: }
Log: {
Log:     [1] = "gfind",
Log:     [2] = <Function, defined in Game Engine at 0x18814F10>,
Log:     n = 2,
Log: }
Log: {
Log:     [1] = "rep",
Log:     [2] = <Function, defined in Game Engine at 0x188140B0>,
Log:     n = 2,
Log: }
Log: {
Log:     [1] = "gsub",
Log:     [2] = <Function, defined in Game Engine at 0x18815090>,
Log:     n = 2,
Log: }
Log: {
Log:     [1] = "char",
Log:     [2] = <Function, defined in Game Engine at 0x188141B0>,
Log:     n = 2,
Log: }
Log: {
Log:     [1] = "dump",
Log:     [2] = <Function, defined in Game Engine at 0x18814280>,
Log:     n = 2,
Log: }
Log: {
Log:     [1] = "find",
Log:     [2] = <Function, defined in Game Engine at 0x18814C40>,
Log:     n = 2,
Log: }
Log: {
Log:     [1] = "upper",
Log:     [2] = <Function, defined in Game Engine at 0x18814010>,
Log:     n = 2,
Log: }
Log: {
Log:     [1] = "len",
Log:     [2] = <Function, defined in Game Engine at 0x18813E80>,
Log:     n = 2,
Log: }
Log: {
Log:     [1] = "format",
Log:     [2] = <Function, defined in Game Engine at 0x188154A0>,
Log:     n = 2,
Log: }
Log: {
Log:     [1] = "byte",
Log:     [2] = <Function, defined in Game Engine at 0x18814130>,
Log:     n = 2,
Log: }
Log: {
Log:     [1] = "lower",
Log:     [2] = <Function, defined in Game Engine at 0x18813F70>,
Log:     n = 2,
Log: }

[/spoiler]

Dieser Beitrag wurde von Termor am 03.04.2016 um 21:41 editiert.

yoq
#2
03.04.2016 21:55
Beiträge: 91

Du kannst im Debugger in der gelben Zeile direkt Lua-Code eingeben

> string
{
    sub = <Function, defined in Game Engine at 0x18813EC0>,
    [...]
    lower = <Function, defined in Game Engine at 0x18813F70>,
}
> _VERSION
"Lua 5.0.2"


Mit _G kannst du so alles verfügbare "erkunden".

Die Lua-Version ist von 2004 aber soweit ich gesehen habe nicht verändert. Der Savegame-Mechanismus ist aber von Bluebyte selbstgeschrieben und speichert weder Metatables (hat schon mal jemand einen Fix gebaut) noch Upvalues.

____________________
S5 & S6: Lua Script Debugger (Thread) | bbaTool
S5: S5Hook (Thread) | S5 GUI Editor | S5 Grafikupgrade + nVidia fix | Win10 Creators Update

Zedeg
#3
03.04.2016 22:14
Beiträge: 428

Zitat von Termor:
Gibt es bereits einen Workaround zu letzterem? Was eigenes zu schreiben wäre möglich, jedoch sind Weak-References in Lua so ... unspaßig

=> http://www.siedler-maps.de/for...metatables-Savegames-19067.htm
mcb hat auch noch so einige andere nützliche Funktion geschrieben (s. sein Profil)

____________________
Journalisten erkundigen sich bei Wissenschaftlern meist nicht nach Grundlagen, sondern eher nach Ergebnissen und Folgerungen. Das erklärt womöglich auch, warum sich Forschungsberichte in den Medien so häufig als feststehende Erkenntnisse lesen, nicht aber als Ideen, Entdeckungen oder Indizien, um die es sich genau genommen in den meisten Fällen handelt. -Axel Bojowski

Termor
#4
03.04.2016 22:35
Beiträge: 9

Zitat von yoq:
> _VERSION



*sigh* danke. Natürlich, das offensichtlichste vergessen. Danke

Zitat von Zedeg:
mcb hat auch noch so einige andere nützliche Funktion geschrieben (s. sein Profil)



Danke! Ich werde mit der Version von MCB etwas rumtesten da mir das kopieren von Tables etwas Sorge bereitet.

mcb
#5
03.04.2016 22:53
Beiträge: 1472

Das kopieren ist normalerweise kein Problem, Lua hat ja automatische Speicherverwaltung. Und da die Metatables normalerweise nicht so groß sind, sollte das keinen allzu großen Einfluss haben. Ich kann so nur am einfachsten einen Rückverweis speichern.

Termor
#6
06.04.2016 20:40
Beiträge: 9

Sorry! Mir ist Deine Antwort nicht aufgefallen. Mir geht es nur darum dass mein Projekt sehr viele Objekte via Referenz vergleicht. Meine Furcht ist dass ich am Ende mehrere "verschiedene identische" tables habe. Ich bin noch nicht zum testen Deines metatable Systems gekommen da ich meinen Code erstmal auf Lua 5 und Siedler Lua anpassen muss, daher kann diese Furcht auch ungerechtfertigt sein

mcb
#7
06.04.2016 21:42
Beiträge: 1472

Also kopiert werden nur die metatables, das Original nicht. Solange du also keinen Vergleich auf die metatables ausführst, sollte alles funktionieren.

Termor
#8
08.04.2016 14:47
Beiträge: 9

Yep, der Vergleich wird auf den Metatables ausgeführt
Ich bin jedoch geneigt das ganze in die Tonne zu schmeißen, das Fehlen von Upvalues zum umgehen ist ein viel zu enormer Aufwand. Mal schauen ob ich in nächster Zeit noch nen Workaround finde, jedoch ist es sehr problematisch wenn das Spiel primäre Features der Sprache nicht richtig serialisieren kann.

mcb
#9
08.04.2016 15:46
Beiträge: 1472

Was ist das denn für ein Script? Upvalues funktionieren so lange, bis das Spiel gespeichert und wieder geladen wird. In manchen Situationen sollte das also kein Problem sein. (Notfalls kann man auch das Speichern für kurze Zeit unterbinden, wurde schon für die DelayFunc gemacht). Ansonsten kann man relativ einfach die Variablen in ein table auslagern und das als Argument übergeben. (So mache ich das immer bei Jobs).
Für die Vergleiche auf den Metatables könntest du einfach einen Vergleich auf ein bestimmtes Element in den metatables machen, mein metatable-Code macht nur eine flache Kopie.

Termor
#10
09.04.2016 13:07
Beiträge: 9

Kurze Version:[/u]

Mein Skript ist eine Objekt Orientierte Library für Lua(unveröffentlicht da sie noch größtenteils ungetestet ist) und ich hatte vor einen Wrapper um die Siedler API zu schreiben. Dies hätte größtenteils das Ziel gehabt meine Library zu testen.
Das Problem mit den Upsaves ist nun, dass meine API einige Codezeilen zu Laufzeit generiert (Meine API ist typesafe, daher werden Type-Checks via loadstring generiert um Iterationen durch Typlisten zu verhindern, etc.). Da diese ganzen Werte letztlich in Up Values landen(Man könnte überlegen es in Weak-Tables zu packen, jedoch ist das nur schwer zu verwalten) speichert Siedler diese natürlich nicht.

Zu Lange Version(Davor bitte kurze Version lesen!):[/u]

Das Klassenlayout der ursprünglichen Library sah so aus: (Hinweis: Verwendete Metamethods wie __index, __call, etc. werden nicht aufgelistet)

table {
  -- Die Klassen-Instanz mit welcher der Anwender direkt 
  -- Interagieren kann. Enthält alle "Were" der Klasse.
  m_wert1 = "hallo, ich bin ein wert der klassen-instanz!",

  metatable = {
    -- Das Klassen-Objekt welches einmal definiert ist und 
    -- dann unveränderlich ist. Enthält alle methoden der
    -- Klasse.
    ctor = function(meinString, meinBool) ... end,
    sagHallo = function(text) end,

    metatable = {
      -- Die Klassen-Metadata welche intern verwendet wird
      -- um Sachen zu speichern die der Benutzer nicht ohne
      -- weiteres direkt als Funktion/Methode braucht. (
      -- Implementierte Interfaces, Basetypen)
      __TYPE = "my_class",
      __CLASSDATA = 1,
      __BASE = {}
    }
  }
}



Das sieht nach viel aus, jedoch existieren die metables und ihre sub-metatables für jede Klasse nur ein mal, da sie nur via Referenz zugewiesen werden.
Nun da Siedler natürlich damit nicht zurecht gekommen ist habe ich mich für den Port für eine Art "Klassen-Registry" entschieden.

table {
  m_wert1 = "hallo, ich bin ein wert der klassen-instanz!",

  metatable = {
    -- Indexer für Klassen-Registry
    __TYPE = "my_class"
  }
}

table {
  -- Die Klassen-Registry. Klassen werden mit ihrem Typnamen 
  -- als Key gespeichert.
  "my_class" = {
    ctor = function(meinString, meinBool) ... end,
    sagHallo = function(text) end,

    metatable = {
      __TYPE = "my_class",
      __CLASSDATA = 1,
      __BASE = {}
    }
  }
}



Das ganze hat mir an sich letztlich besser gefallen, da es mir erlaubt auch meta-data auf einzelnen Klassen-Instanzen zu speichern und ich bin am überlegen dieses Layout in die Normale Library aufzunehmen obwohl ein gewisser overhead vorhanden ist(Der Klassenname wird gleich 3x gespeichert).

Soweit so gut. Jetzt zum Problem der Up-Values. Eine Typische Klassendeklaration sieht so aus:

MY_CLASS = class("my_class")

-- Methoden können ganz normal definiert werden.
function MY_CLASS:init()
  self:setvalue("hello hello!)
  self:sayhello()
end

function MY_CLASS:sayhello()
  print(self._value)
end

-- Oder eben via der speziellen expliziten (und 
-- bevorzugen) method Deklarationen welche Type-
-- Safe sind so wie Method Overloading erlauben:
MY_CLASS.setvalue = method(
  {"number *NotNil",
  function(value)
    self.m_value = "number: " .. value
  end},
  {"boolean *CanBeNil",
  function(value)
    value = value or false
    self.m_value = "boolean: " ..  tostring(value)
  end}
end
)



Die letzte Method wird nun zu folgendem übersetzt (Oder so ähnlich, ich hab den exakten Code nicht mehr im Kopf, jedoch bleibt das Prinzip gleich): ("self" wird auch gehandelt)

function MY_CLASS:setvalue(...)
  local t_arg1 = type(arg[1])
  if (t_arg1 == "number") then
    return func1(arg[1]) --> Up Value!
  end
  if (t_arg1 == "boolean" or t_arg1 == "nil") then
    return func2(arg[1]) --> Up Value!
  end
  assert(false, ... Fehleranalyse)
end



Das ganze ist ein sehr mächtiges Prinzip(Mir ist dabei durch aus bewusst das Lua nicht als typsichere Sprache designt wurde, jedoch bevorzuge ich für mich Typsicherheit(Wann möchte man denn für seine Vektor Klasse einen Table oder boolean als Wert für eine Dimension haben?) und das ganze innerhalb der eigentlichen Funktion zu schreiben ist repetetiv und erzeugt unnötig viel Boilerplate Code. Somit kommt letztlich der "Vorteil" dass Lua nicht Typsicher ist nazu nie zum Zug(Sollte man es doch mal brauchen lässt der Typ-Name "any" in einer Methodendeklaration einfach jeden Typen durch).
Ebenso steht es mit nil Werten. Nur selten möchte man nil Werte haben, und wenn man es haben möchte steht es meistens dafür dass ein Wert explizit fehlt. Und wenn man explizit nil haben möchte sollte dies auch explizit erlaubt sein. Das ganze Konzept in von den Annotationen für Java welche von JetBrains in IntelliJ-Compiler eingebaut wurden abgeschaut(bzw. ReSharper für VisualStudio), da diese dort auch direkt in den Code reinkopiert werden.

public void sayHello(@NotNull string text) {
  System.out.println("Text: " + text);
}

// Wird zu:

public void sayHello(string text) {
  if (text == null) {
     throw new NullPointerException("text");
  }
  System.out.println("Text: " + text);
}



Genug davon. So toll wie das feature auch sein möge(Mir ist bewusst, dass es nicht für alle ist, viele Leute mögen die freie und nicht strikte Natur von Lua - Das ganze ist ansichtssache und jeder mag andere Sprach/Framework Designs), um Up-Values kommt hier nur herum in dem man die ganzen Funktionen irgendwo in einem Table-Speichert(Es wäre wohl die Klassenmeta, dann wären wir aber wieder beim Poblem mit den metatables!). Und das ganze ist nur eine der unzähligen Fälle in denen diese nur schwer schwer vermieden werden können(Oder nur mit stark erhöhter Komplexität, ich möchte auch in 5 Jahren noch verstehen was mein Code macht).

Ich hoffe ich hab euch nicht zu sehr zugetextet und hoffe dass das ganze trotz der wohl vorhandenen Rechtschreibfehler verständlich ist(Habe gerade keine deutsche RS Prüfung zur Hand, sorry.). Ich halte mal davon ab noch mehr Beispiele auszuführen sonst liest den Post letztlich echt keiner.

Dieser Beitrag wurde von Termor am 09.04.2016 um 13:14 editiert.

mcb
#11
09.04.2016 14:03
Beiträge: 1472

Klingt auf jeden Fall interessant. Mal sehen wie wir das unter Siedler-Lua zum laufen kriegen

Als erstes funktioniert loadstring in Siedler nicht. Zum Glück gibts im S5Hook eine Alternative: S5Hook.Eval (Link zum Hook)

Für die eigentlichen Methoden gibt es sicher mehrere Möglichkeiten:
1) Über ein weiteres Metatable mit call ein table mit den Funktionen als zusätzlichen Parameter übergeben. Von der Ausführungsgeschwindigkeit sicher nicht Ideal, aber halbwegs Savegamesicher.

2) Das ganze nach jedem Laden neu übersetzen, nichts anderes macht mein metatable-fix. Das Problem dabei: Du musst auf den S5Hook warten, da du S5Hook.Eval brauchst, und der braucht ein paar Ticks zum laden.

Termor
#12
09.04.2016 22:02
Beiträge: 9

Uff. Ich glaube da bin ich am besten dran' das ganze für Siedler 5 komplett neu zu schreiben(mit den von Dir eben geschriebenen Voeschlägen) anstatt das Vorhandene versuchen zu "übersetzen".

Zu Deinem 1. Punkt: Ich verstehe nicht ganz wie man einen Savegame sicheren Ablauf hinbekommen würde. Mein Gedankendang(Vielleicht denke ich in die falsche Richtung, mache sowas gerne mal):

So würde ich es nun machen:
1. method()-Aufruf mit den angegeben Parametertypen und den zugeordneten Funktionen.
2. Typargumente sowie deren Funktionen(nur als Parameter übergeben existieren also nirgendwo anders!) werden in einer Registry gespeichert(Kann wie Du geschrieben hast im metatable sein).
3. Dieser Speicherfunktion generiert via Eval nun den Code(Wird nirgends gespeichert, da das die Registry unnötig zumüllt) und gibt die erstellte neue Funktion zurück(Diese kann nicht gespeichert werden da der Eval-Code als Up-Value drinnen ist).
4. Diese Funktion wird auch vom method Aufruf zurückgegebn und wird der Wert der Variable.

- Speichern, Spiel neustarten

Ohne was zu machen würde passieren: (?)
1. Funktion wird aufgerufen, ist tot aufgrund von Up-Values. -> nil Fehlermeldung

Also müssen wir den Code neugenerieren. Jedoch frage ich mich hier wie der Eintrag im Registry table "wiederzufinden" ist.
Meine Vorstellung für den Eintrag wäre aktuell so:

registry = {
  -- Wir können nicht die Namen der Funtionen als Key verwenden, da der
  -- method()-Aufruf keine Ahnung hat welcher Variable er am Ende
  -- zugeordnet wird, dadurch dass debug fehlt lässt sich da auch
  -- nichts zusammenhacken.
  [1] = {
     [1] = {
       types = { 
         -- Das hier kann man auch noch speizifischer aufdröseln, jedoch
         -- bleibt das Prinzip gleich.
         [1] = "string *NotNil"
         [2] = "number *CanBeNil"
         [3] = "boolean *NotNil"
       } 
       -- Wird hoffentlich gespeichert, auch wenn die Funktion
       -- ursprünglich nur in einem Parameter erstellt wurde. Ich
       -- vermute mal dass der Serializer beim Speichern einfach _G
       -- einmal durchgeht und alles speichert(Und metatables schön
       -- ignoriert).
       callback = //function//
     }
     [2] = -- Ne' andere Funktion mit anderen Typargumenten
  }
  [2] = -- Ne' andere methode
}



Im Savegame ist jedoch letztlich nur ein Eintrag vorhanden der auf eine defekte Funktion verweist die letztlich niemals auf den oberen Table Eintrag zurückgeführt werden kann.

mcb
#13
09.04.2016 23:19
Beiträge: 1472

Tja, Siedler-Lua ist manchmal echt seltsam...

Ich meinte das so:

function myMethod(...)
   local t = {} -- die "Funktion"
   t.arg = arg -- Beschreibung speichern
   t.func = method(unpack(arg)) -- so wie bei dir, nur anstatt der upvalues die entsprechenden Einträge aus dem ersten Parameter
   metatable.set(t, {
      __call = function(t, ...)
         return t.func(t.arg, unpack(arg)) -- schummelt den ersten Parameter dazu
      end
   }
   return t
end



Generell wird alles gespeichert was erreichbar ist, bis auf upvalues, coroutines und metatables. Wenn du in einem table irgendwas anderes außer Zahlen und Strings als Key verwendest crasht S5 sogar.

Seiten: 1

SiteEngine v1.5.0 by nevermind, ©2005-2007
Design by SpiderFive (www.siedler-games.de) - English translation by juja

Impressum