LUA - Script in PB-Programmen

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
GPI
Beiträge: 1511
Registriert: 29.08.2004 13:18
Kontaktdaten:

LUA - Script in PB-Programmen

Beitrag von GPI »

Ich hab die C-Headers für PB übersetzt und auch ein paar Beispiele geschrieben:
http://game.gpihome.eu/PureBasic/lua/

Wozu braucht man LUA?
Lua ist wie gesagt eine Scriptsprache. World of Warcraft nutzt diese bspw. um die Addonschnittstelle zu realisieren. Wenn man teile des Programmcodes in LUA realisiert oder "Hook"-Schnittstellen schafft, kann so jeder ein Spiel/Programm in verhalten verändern und erweitern. Eine andere Anwendungsmöglichkeit wären bspw. Konfigurationsdateien, die auch Rechnungen und Bedingungen enthalten können. Gibt sicherlich viele Anwendungsmöglichkeiten. Man stelle sich vor, man könnte den PureBasic-Compiler sagen: alles was zwischen den Codewörtern "Class" und "EndClass" steht soll erstmal an ein LUA-Script weitergegeben werden....
Zudem ist LUA für fast alle Plattformen erhältlich - naja in der Theorie, dazu später mehr :)

Bitte beachten, vollständig getestet ist das ganze nicht, ein Fehler kann immer irgendwo sein!
Ich hab auch gleich einen unschönen Bug in PureBasic gefunden:
Wenn man PrototypeC mit Quads verwendet:
PrototypeC.q function()
wird nur ein 16Bit-Wert zurückgegeben, der rest wird verworfen. Bei 64Bit ist das noch kein Problem, hier nimmt man einfach Integer (ist ja gleich groß), da gehts. Bei 32Bit ist das ganze nicht so schön.

Die Routinen sind für Windows geschrieben und von der LUA.DLL liegt auch eine 32Bit und 64Bit Version vor. Für Linux liegt eine 64Bit-Bibliothek in so-Format vor, aber da ich kein Linux habe, komplett ungetestet! Für Macs hab ich überhaupt nichts gefunden. Gedownloaded wurden die DLL/SO von http://luabinaries.sourceforge.net/index.html

In Example-Ordner gibt es ein paar Beispiele:
Example1:
Einfaches Beispiel wie man eine Lua-Datei aus den Datenblock lädt und aufruft. Und wie man eine Funktion aus den LUA-Script nachträglich ausführt.

Example2:
Wie man mit Metatabellen schreibt und so eine eigene Bibliothek schreibt und mit Userdata eigene Daten in LUA einspeisen kann.

Example3:
Leider ist es etwas Trickreich, wenn man die Standard-Bibliotheken von LUA sperren will. Gerade die io und os Bibliothek ermöglichen den Zugriff auf jede Datei auf den Rechner. Das kann ungewünscht sein, bspw. wenn man die LUA-Scripte zwingen will, in einem bestimmten Verzeichnis Daten abzulegen. Die Methode lässt sich auch durch ein "os = require('os')" in LUA-Script nicht mehr aushebeln. Alternativ müsste man LUA selbst compilen und die Bibliotheken kürzen. Da ich aber zukünftig einfach eine Binary downloaden will, ist mir die Methode lieber :)
Zudem werden hier drei Lua-Scripte in die gleiche VM geladen und ausgeführt, dabei werden die Script-Adressen zum ausführen in einer LUA-Tabelle gespeichert - eine bessere Methode gibts in Example5. Ich hab die Methode nur mal drin gelassen, falls jemand mal mit Tabellen handtieren will.

Example4:
Lädt eine LUA-Datei und speichert diese dann in binär-form ab. Anschließend wird in einer neuen VM die binärdatei eingelesen und ausgeführt. LUA benutzt (wie java) einen Pseudobinärcode. Durch diese Methode erreicht man zweierlei: Erstes muss der Quellcode nicht mehr compiled werden und zweitens er ist nicht mehr so leicht änderbar.

Example5:
Lädt mehrere LUA-Scripte in eine "VM" und führt diese aus. Zu beachten ist, das jedes Script in einen Chunk geladen wird und man nur einen Chunk einzeln starten kann. Die Adressen des Chunks (in Prinzip ist das wie eine Adresse einer Funktion) wird dabei in der #LUA_REGISTRYINDEX hinterlegt. Das Problem bei der Kommunikation mit LUA ist, das es Datenformate in LUA gibt, die es so nicht in PB gibt. Bspw eine Adresse zu einer LUA-Funktion. Man kann aber diese Adresse in der #LUA_REGISTRYINDEX speichern (hat nichts mit der Windows-Registry zu tun!) und bekommt einen Handle zurück - ähnlich wie bei Purebasic mit #pb_any. Das kann man dann verwenden und in PB speichern.

Example6:
Eine simple grafische Demo. Ich hab die Beispielbilder von PB genommen, so das ich keine reinkopieren brauchte :) Kann man sicher noch ausbauen, bspw. das LUA-Script in einen eigenen Task endlos laufen lassen.

In der Zip sind zwei pbi:
module_lua.pbi

diese muss man einfach einbinden und mit UseModule "anmelden" und mittels lua_initalize() starten:

Code: Alles auswählen

XIncludeFile "module_lua.pbi"
UseModule lua
If Lua_Initialize()=#False
  End
EndIf
....
Lua_Dispose()
lua_initalize() lädt dann die DLL und initaliziert sämtliche Funktionen, dispose entlädt LUA.dll. Der Aufruf ist nötig, damit man auch in einer DLL LUA nutzen kann.

module_lua_extern.pbi

die module_lua_extern.pbi ist ein kleiner Trick, der mir sehr viel Schreibarbeit abnimmt. Die DLL wird mittels Prototypen in PureBasic eingebunden. Der Nachteil ist, das man die Prototypen und Global - deklarationen in DeclareModule unterbringen muss, aber die Zuweisung in Module-Block. Zudem wollte ich das bei lua_dispose() die ganzen funktionen auf die Adresse 0 verweisen und nicht irgendwo in Speicher, wo die DLL war. Lieber ein sauberer Absturz, als wenn willkürlich eine Funktion versehentlich aufgerufen wird. In schlimmsten Fall funktioniert das ganze sogar noch...

So, mehr fällt mir jetzt gerade nicht ein :) Sorry fürs etwas wirr schreiben, bei Fragen einfach fragen.

Hier noch ein paar Links zum einlesen:
https://www.lua.org/
http://www.fh-wedel.de/~si/seminare/ws0 ... a/lua0.htm
CodeArchiv Rebirth: Deutsches Forum Github Hilfe ist immer gern gesehen!
Benutzeravatar
HeX0R
Beiträge: 2960
Registriert: 10.09.2004 09:59
Computerausstattung: AMD Ryzen 7 5800X
96Gig Ram
NVIDIA GEFORCE RTX 3060TI/8Gig
Win10 64Bit
G19 Tastatur
2x 24" + 1x27" Monitore
Glorious O Wireless Maus
PB 3.x-PB 6.x
Oculus Quest 2
Kontaktdaten:

Re: LUA - Script in PB-Programmen

Beitrag von HeX0R »

Wenn ich das gewusst hätte...
Bin selbst seit ein paar Tagen an LUA, allerdings weniger intensiv, als ich das gerne gehabt hätte.

Das kommt jetzt echt gut, lieber meine halben Sachen wegwerfen, als weiter kämpfen.

Vielen Dank!!
GPI
Beiträge: 1511
Registriert: 29.08.2004 13:18
Kontaktdaten:

Re: LUA - Script in PB-Programmen

Beitrag von GPI »

ich hab auch erst vor ein paar Tagen begonnen. Anscheinend gabs mal eine Userlibrary und ich hab eine alte (und lücken und fehlerhafte) Include-Datei gefunden :)

Du hattest aber Glück, eigentlich wollte ich Ruby umsetzen, aber da ist die Dokumentation irgendwie schlechter. Ich hab auch nicht so eine einfache dll-Datei für Windows gefunden.
CodeArchiv Rebirth: Deutsches Forum Github Hilfe ist immer gern gesehen!
Benutzeravatar
TroaX
Beiträge: 662
Registriert: 08.03.2013 14:27
Computerausstattung: PC: Ryzen 9 3950X, 96 GB RAM, RX6800XT, 2.5 TB SSD, 21:9 Display, Pop_OS! | Lappi: Ryzen 7 5800H, 16 GB RAM, 1 TB SSD, Pop_OS!
Wohnort: NRW
Kontaktdaten:

Re: LUA - Script in PB-Programmen

Beitrag von TroaX »

Danke für die Mühe.

Ich würde mich eher für ein ECMAScript Interpreter interessieren. Nur leider lassen sich die nur schwer einbinden, da sie meistens in C++ geschrieben sind oder als Lib erst kompilliert werden müssen.

Aber bis dahin hilft das hier auch :)
PC: Ryzen 9 3950X | 96 GB RAM | RX6800XT | 2,5 TB NVMe | Pop_OS!
Notebook: 16" 3:2 | Ryzen 7 5800H | 16 GB RAM | Radeon Vega | 1TB NVMe | Pop_OS!
NAS: Fritz.Box :lol:
Coding: Purebasic 6.04 | PHP | HTML | CSS | Javascript
Benutzeravatar
HeX0R
Beiträge: 2960
Registriert: 10.09.2004 09:59
Computerausstattung: AMD Ryzen 7 5800X
96Gig Ram
NVIDIA GEFORCE RTX 3060TI/8Gig
Win10 64Bit
G19 Tastatur
2x 24" + 1x27" Monitore
Glorious O Wireless Maus
PB 3.x-PB 6.x
Oculus Quest 2
Kontaktdaten:

Re: LUA - Script in PB-Programmen

Beitrag von HeX0R »

So, das lässt sich schonmal gut an.
Deine Macro Technik ist übrigens... ähm, sagen wir: :freak:

Aber egal.
Was mich jetzt doch etwas Arbeit gekostet hat:
Strings in LUA dürfen auch Chr(0) beinhalten.
Kommt vermutlich eh nie vor?
Naja, in meinem Fall schon, diese Tatsache hat nämlich einen ganz tollen Nebeneffekt:
Man kann Memorypuffer von/zu LUA als Strings schicken.

Naja, jedenfalls ist das so wie es im Moment ist etwas doof, weil PB ja bekanntlich Strings nach Chr(0) einfach abschneidet.

Daher habe ich folgende Erweiterungen hinzugefügt (alles in module_lua.pbi):
In DeclareModule:

Code: Alles auswählen

PrototypeC.i lua_pushBuffer(lua_State.i, *Buffer, Size.i)
Global lua_pushBuffer.lua_pushBuffer
Declare lua_tobuffer(state, i, *Size.INTEGER = 0)
(mit dem Macro-Irrsinn geht das vermutlich nicht ohne Hand anzulegen, schliesslich wird dieser Prototyp als Kopie an lua_pushlstring geheftet)
im Module:

Code: Alles auswählen

Procedure.i lua_tobuffer(state, i, *Size.INTEGER = 0)
	Protected *str = lua_tolstring(state, i, #Null)
	If *Size
		*Size\i = lua_rawlen(state, i)
	EndIf
	ProcedureReturn *str
EndProcedure
und unter "UndefineMacro c34"

Code: Alles auswählen

lua_pushBuffer = GetFunction(DLL_LUA, "lua_pushlstring")
Man muss den Pointer, der von lua_tobuffer zurückkommt, allerdings zur weiteren Verwendung in einen eigenen Memorybereich kopieren.
Weil der nicht ewig gültig ist.

Eins ist mir auch aufgefallen, da bin ich noch nicht dahinter gekommen:
Ohne UseModule funktioniert es nicht zuverlässig, da kommen wirre Fehlermeldungen (Macro nicht definiert, und so was).
GPI
Beiträge: 1511
Registriert: 29.08.2004 13:18
Kontaktdaten:

Re: LUA - Script in PB-Programmen

Beitrag von GPI »

Ja null ist da ein reguläres Zeichen - und verursacht eine Fehlermeldung, wenn man ihn in lua-code hat!

die ganzen Macros (bis auf die Extern) sind 1:1 aus den c-Header übernommen. Die erleichtern viele Aufrufe. Die ganzen macros beachten nicht, das sie in Modulen sind, deshalb geht ohne usemodule sehr wenig. Wobei das von mir durchaus gewollt ist, weil die API für C genauso designed ist und eben keine API für C++ ist. Ich wollte, das man die ganzen C-Beispiele nahezu 1:1 übernehmen kann. sonst würdest du lua::pushlstring schreiben. In ein Modul habe ich das ganze nur gepackt, damit man es auch von einen Modul aus aufrufen kann. Es gibt ja kein "shared" für Module.

dein lua_pushbuffer ist doch identisch mit lua_pushlstring?
ich würde das mittels Macro lösen

Code: Alles auswählen

Macro lua_pushBuffer(state,buffer,size):lua_pushlstring(state,buffer,size):endmacro
dein lua_toBuffer() ist auch etwas umständlicher als nötig:
die Funktion ist so in C deklariert:
const char *lua_tolstring (lua_State *L, int index, size_t *len); - https://www.lua.org/manual/5.3/manual.h ... _tolstring
du musst als die Länge nicht mit rawlen auslesen, sondern als dritten Parameter einfach die Adresse zu einer Integervariable übergeben.

damit würde ein einfaches Macro reichen:

Code: Alles auswählen

macro lua_tobuffer(a,b,c=0):lua_tolstring(a,b,c):endmacro
Einfach beide vor EndDeclareModul einfügen.

das mit "Extern" und Includefile spart mir ein haufen arbeit. Man kann in den Macro extern() sehen, was grob gemacht wird.
extern (lua_copy , (lua_State.i, fromidx.i, toidx.i) )
erster Parameter ist hier die Funktion aus der DLL und zeitgleich der Name. der zweite paramater ist die Parameterteil
beim ersten include wird der Prototyp und die globale "variable" erstellt:

Code: Alles auswählen

; extern (lua_copy   ,(lua_State.i, fromidx.i, toidx.i))
PrototypeC.i __proto_lua_copy (lua_State.i, fromidx.i, toidx.i)
Global lua_copy.__proto_lua_copy
beim zweiten wird die Function aus der DLL geklaubt

Code: Alles auswählen

; extern (lua_copy   ,(lua_State.i, fromidx.i, toidx.i))
lua_copy = GetFunction(DLL_LUA, "lua_copy")
und beim dritten mal (die DLL wurde entladen und ich setzt hier alle funktionen auf 0. falls man jetzt versehentlich eine function aufruft, schmiert das Programm sauber ab und macht nicht irgendwas jetzt zufällig an der Position der DLL gelandet ist)

Code: Alles auswählen

; extern (lua_copy   ,(lua_State.i, fromidx.i, toidx.i))
lua_copy = 0
Ich muss also nur noch an einer Stelle das ganze bearbeiten - wie es auch in C geht. PB ist da leider etwas umständlich geraten.

Edit: Was ganz wichtiges vergessen:
lua macht von den Buffer immer eine Kopie! und selbiges sollte man auch immer selber machen, der buffer von lua_tolstring wird von lua verwaltet und gnadenlos irgendwann freigeben. Er wurde nur für den C-Aufruf erstellt und kann somit schon beim nächsten API-Aufruf zerstört werden!
CodeArchiv Rebirth: Deutsches Forum Github Hilfe ist immer gern gesehen!
Benutzeravatar
HeX0R
Beiträge: 2960
Registriert: 10.09.2004 09:59
Computerausstattung: AMD Ryzen 7 5800X
96Gig Ram
NVIDIA GEFORCE RTX 3060TI/8Gig
Win10 64Bit
G19 Tastatur
2x 24" + 1x27" Monitore
Glorious O Wireless Maus
PB 3.x-PB 6.x
Oculus Quest 2
Kontaktdaten:

Re: LUA - Script in PB-Programmen

Beitrag von HeX0R »

GPI hat geschrieben:dein lua_pushbuffer ist doch identisch mit lua_pushlstring?
ich würde das mittels Macro lösen
Jein, man beachte den anders deklarierten Prototype.
Mit Deinem Makro bekomme ich immernoch eine Compilerwarnung, wenn ich einen Puffer an lua_pushlstring übergebe.

Ich wollte die ursprüngliche Funktion beibehalten, weil es ja in aller Regel funktioniert, wer hat schon chr(0) in einem String...
das mit "Extern" und Includefile spart mir ein haufen arbeit.
War mir schon klar, aber übersichtlich ist anders.
Und ehrlich gesagt habe ich nur 10 Minuten gebraucht, um den Code entsprechend umzuändern, also ohne die extern-Makros.
Das macht man ja nicht per Hand, sondern per Regex.


Das mit dem lua_toBuffer() ist allerdings richtig, das ist natürlich wesentlich einfacher!
GPI
Beiträge: 1511
Registriert: 29.08.2004 13:18
Kontaktdaten:

Re: LUA - Script in PB-Programmen

Beitrag von GPI »

damit wäre es vermutlich schneller gegangen:
http://www.purebasic.fr/german/viewtopi ... preprocess
Das Tool löst alle macros vollständig auf. allerdings sind dann auch die ganzen hilfsmacros weg, die sollte man wieder einfügen. Aber um den Teil bei include rauszuklauben gehts schneller.

Übersichtlich ist so eine Sache. Ich finde es erhöht die übersichtlichkeit enorm. Vorallen ist es leichter zu pflegen. Wenn jetzt 5.3.4 kommt, muss ich neue Funktionen nur einmal an einer stelle (in der extern.pbi) einfügen und das wars dann. Genauso wenn ich fehler find. Ansonsten müsste ich immer an drei Stellen suchen, nicht so ideal.
CodeArchiv Rebirth: Deutsches Forum Github Hilfe ist immer gern gesehen!
Antworten