Modularer Skripten

Wichtiger Hinweis: Bitte ändert nicht manuell die Schriftfarbe auf schwarz sondern belasst es bei der Standardeinstellung. Somit tragt ihr dazu bei dass euer Text auch bei Verwendung unseren dunklen Forenstils noch lesbar ist!

Tipp: Ihr wollt längere Codeausschnitte oder Logfiles bereitstellen? Benutzt unseren eigenen PasteBin-Dienst Link
  • Hallo Brotfische,


    oft sehe ich hier ein großes Missverständnis von Includes und Code-Trennungen.
    Deshalb möchte ich hier noch einmal versuchen ein wenig mit diesen Fehlern aufzuräumen.


    Was ist eigentlich ein Include?
    Viele wissen dies nicht. Viele denken, das ist etwas ganz anderes als eine .pwn Datei, schließlich steht da ja .inc.
    Allerdings ist das ein großes Missverständnis, denn im Endeffekt ist es identisch.
    Die Endung soll nur symbolisieren, dass es sich um ein Include handelt und kein eigenständiges Skript.


    Was ist der Unterschied?
    Ein eigenständiges Skript wird kompiliert. Ein Include wird nicht kompiliert.


    Wieso muss ein Include nicht kompiliert werden?
    Ganz einfach, stellen wir uns mal folgende Situation vor:



    Kompilieren wir jetzt den Gamemode, sieht das nach dem pre-compile Prozess so aus:


    C
    //Hier würde alles stehen, was in a_samp steht.
    stock GetMyFunction()
    {
    	return 1;
    }
    main() {
    	print("%d",GetMyFunction());
    }


    Wir sehen also, es wird "einfach nur" (soo leicht ist das auch nicht, aber dazu später mehr) in die Datei kopiert.


    Deshalb müssen wir Includes nicht kompilieren, da sie vom Gamemode kompiliert werden.


    Und deshalb müssen wir in den Includes z.B. auch nicht mehr #include <a_samp> schreiben, da ja das schon im Gamemode steht und das somit auch für das Include gilt.


    Was bringt das jetzt?
    Das wurde ja schon oft gesagt, man kann nun viele Sachen auslagern.
    Okay, aber was genau bedeutet das?


    Logikfehler
    Viele machen hier einen großen Logikfehler.
    Da viele sich die Struktur ihres Gamemodes bei einem Godfather abschauen, verstehen sie oft nicht, wieso diese so schlecht ist.
    Auf den Ersten Blick sieht das ja auch gut aus, ich meine, alle defines Oben, alle Variablen oben, alle Commands und publics in der Mitte und ganz unten alle "stock - Funktionen".
    Ja cool, jetzt wo wir also Includes nutzen können, packen wir alle Variablen in ein Include, alle Commands, alle publics und alle "stock - Funktionen" und voila. Skript aufgeräumt xD


    (No Joke, das machen sehr viele und sind fest von dieser Struktur überzeugt).


    Wieso ist das aber schlecht?
    Genau das versuche ich jetzt hiermit zu vermitteln, das ist NICHT der Sinn oder Vorteil eines Includes.
    Da könnte man sich diesen ganzen Prozess gleich schenken.


    Denn denken wir das weiter und es entsteht jetzt ein Fehler beim Haussystem.
    Wie finden wir in so einem System den Fehler?


    Ja Herzlichen Glückwunsch, jetzt sogar noch schlechter, da wir in 4 Dateien Suchen, alle Variablen dafür finden müssen, alle Funktionen, alle Befehle und überall in jedem public steht mal ein fitzelchen Code von dem System xD
    Kein Wunder also, dass soviele Skripte verbuggt sind, wie will man da auch irgendwas finden.


    Deshalb posten hier auch so viele Menschen 500 Zeilen Code und sagen, ja wo ist denn da der Fehler, bla bla funktioniert nicht.


    Was wäre ein besserer Ansatz?
    So, hier kommt jetzt das Wichtige.
    Man packt ein einzelnes System in ein Include.


    Und damit meine ich nicht nur den Hauptteil des Systems, sondern ALLES.


    Und um noch einen drauf zu setzen, macht sowas:



    Hier sehen wir sehr viel static.
    Das hat mehrere Gründe:


    • static ist nur im Include verfügbar. Das heißt es ist sichergestellt, dass diese Variable NUR dort verändert wird.
      Sprich, gibt es ein Fehler mit dem System, MUSS er dort zu finden sein.
    • Ein weiterer Vorteil ist, dass man einfache Namen verwenden kann und sich nicht darum kümmern muss, dass diese in anderen Include auch nochmal verwendet werden.
      Da die Variablen ja immer für jedes Include abgegrenzt sind. Selbiges gilt für Funktionen, die man als static deklariert.
      PS: Nur globale Variablen im Include static machen, nicht Lokale!! Das wäre etwas völlig Anderes.
    • Es sollte nur mit Callbacks und Hooks gearbeitet werden, die alle in dem Include landen und dort ALLES gesteuert wird, jede Logik.


    Denn wenn man dann einen Fehler hat, weiß man genau, wo man suchen muss.


    Zusammenfassung
    Weg von der Trennung von Bezeichnungen, wie new, public, stock...
    Diese Trennung bringt rein gar nichts, man muss die LOGIK in einem Gamemode trennen.


    Dafür eigenen sich Includes ideal.


    Es sind im Endeffekt auch nur .pwn Dateien, die ihr mit Pawno schreiben könnt.
    Und kompiliert, wird nur das Gamemode.


    Zusätzliche Informationen:
    Schneller & strukturierter skripten


    Es ist auch nicht notwendig, dass ihr ein Selfmade anfangt und das dann konsequent macht (obwohl das ser sinnig wäre).
    Ihr könnt auch anfangen neue Systeme, die ihr in ein GF implementiert, versucht so umzusetzen.


    In diesem Sinne, ich hoffe ich sehe bald mehr Leute die das nutzen und schönere Fragen in der Scripting Base.
    Mit freundlichen Grüßen
    Euer Kalle

    ast2ufdyxkb1.png


    Leute, lernt scripten und versucht mal lieber etwas selber zu schreiben, als es aus einem GF zu kopieren. :S

  • Eine wirklich schöne Erklärung, dass es eigentlich jeder kapiert was gemeint ist.
    Aber denke, dass viele dennoch die Struktur "alles in den Gamemode knallen" weiterhin verwenden, auch wenn denen klar ist, dass es eigentlich kurz gefasst scheiße ist. Sprichwort "Was der Bauer nicht kennt, frisst er nicht", dass kann man gut darauf beziehen ^^

  • Danke!!! :thumbup:
    Sowas habe ich mir schon viel früher gewünscht. Ich habe schon ein paar Mal diese Struktur in Gamemodes gesehen, allerdings die Logik dahinter nicht zu 100% verstanden, den Grundgedanken wiederum schon.

  • @Kaliber wie genau hooked man nun die Callbacks von den Includes zum Gamemode? Wenn man nämlich nur die Includes zum Gamemode inkludiert, werden die Callbacks des Includes ja nicht aufgerufen(?).


    Des Weiteren: Wäre die Verwendung des static's in dem Fall korrekt? Denn die Variable ist ja ebenfalls wichtig für andere Includes (also global). Wie allerdings definiert man dann lokale Variablen, die nur eine Rolle im Include selbst spielen, einfach mit new?

  • wie genau hooked man nun die Callbacks von den Includes zum Gamemode? Wenn man nämlich nur die Includes zum Gamemode inkludiert, werden die Callbacks des Includes ja nicht aufgerufen(?).

    Wie man richtig Hookt, wird hier gut erklärt:


    Includes richtig erstellen | Hooking ganz einfach


    Falls dir das zu umständlich ist, könntest du auch einfach eine Funktion in das Callback vom Gamemode packen und diese dann im Include aufrufen.


    Dann hätteste du auch immer eine chronologische Reihenfolge und eine direkte Übersicht über alle Includes im Gamemode.



    Wie allerdings definiert man dann lokale Variablen, die nur eine Rolle im Include selbst spielen, einfach mit new?

    Die Spieler Variable, ist ja quasi der Kern eines jeden Gamemodes.


    Diese kannst du bei new belassen.


    Und solltest du meiner Ansicht nach im Gamemode lassen und Include die darauf zugreifen, unter der Deklaration inkludieren, dann kannst du das dort überall verwenden.


    Aber kannst natürlich auch das in eine Include packen.


    Da hat jeder so seine Präferenzen.

    ast2ufdyxkb1.png


    Leute, lernt scripten und versucht mal lieber etwas selber zu schreiben, als es aus einem GF zu kopieren. :S

  • Und solltest du meiner Ansicht nach im Gamemode lassen und Include die darauf zugreifen

    Klingt plausibel. Also Kernelemente eher direkt im Gamemode belassen.


    Diese kannst du bei new belassen.

    Dann frage ich mich allerdings, wann genau man static benutzt. Aus dem Grund, dass es als Kernelement eher direkt ins Gamemode soll? Kannst du mir das bitte noch genauer erläutern?


    Die Variante finde ich tatsächlich besser, aus deinen genannten Gründen. Werde ich dann so machen. Allerdings habe ich dazu noch eine Frage. Hat es einen speziellen Grund, dass du stock statt public im Include verwendest?

  • Dann frage ich mich allerdings, wann genau man static benutzt. Aus dem Grund, dass es als Kernelement eher direkt ins Gamemode soll? Kannst du mir das bitte noch genauer erläutern?

    Habe ich doch genau erklärt in meinen 3 Punkten.


    Es ist quasi wie in der OOP als private zu verstehen.


    Es ist nur im Include veränderbar und der Name hat keinen Einfluss auf andere Systeme.
    Es ist quasi komplett isoliert und wenn etwas nicht funktioniert mit dem System, muss es an dem Include liegen.



    Die Variante finde ich tatsächlich besser, aus deinen genannten Gründen. Werde ich dann so machen. Allerdings habe ich dazu noch eine Frage. Hat es einen speziellen Grund, dass du stock statt public im Include verwendest?

    Dazu schau dir das hier mal an: Der Mythos "stock"


    Also im Endeffekt ist sowohl "public", als auch "stock" egal, man hat eine Funktion.


    Und in diesem Fall muss diese nicht public sein, da sie weder von einem Timer, noch "CallRemoteFunction" etc aufgerufen werden müsste.

    ast2ufdyxkb1.png


    Leute, lernt scripten und versucht mal lieber etwas selber zu schreiben, als es aus einem GF zu kopieren. :S

  • Habe ich doch genau erklärt in meinen 3 Punkten.

    Ja stimmt.


    Habe ich doch genau erklärt in meinen 3 Punkten.


    Es ist quasi wie in der OOP als private zu verstehen.


    Es ist nur im Include veränderbar und der Name hat keinen Einfluss auf andere Systeme.
    Es ist quasi komplett isoliert und wenn etwas nicht funktioniert mit dem System, muss es an dem Include liegen.

    Perfekt danke. :thumbup:

  • Was ist denn nun am Sinnvollsten, Hooken oder lieber die Funktion im Script aufrufen lassen ?


    Und, macht es z.B Sinn schon das Login / Registration System in einer Include zu Packen ?


    PS: Danke, schöne Anleitung und Gefällt mir sehr.

    Bestes Zitat des Jahres von Max Jackson!
    Vertrauen kann man mir auch, ich bin hier auf Breadfish schon sehr lange Aktiv und das sollte schon auf einer gewissen Weise, eine Vertrauensbasis schaffen. Ich meine, meine schlimmste Verwarnung war lediglich eine Morddrohung - Is ja nix.

  • do.de - Domain-Offensive - Domains für alle und zu super Preisen
  • Was ist denn nun am Sinnvollsten, Hooken oder lieber die Funktion im Script aufrufen lassen ?

    Das musst du für dich selbst entscheiden, wie du magst :)



    Und, macht es z.B Sinn schon das Login / Registration System in einer Include zu Packen ?

    Es macht Sinn das Speicher- und Ladesystem auszulagern :)


    So dass man quasi im Gamemode nur noch einzelne Funktionen hat und der ganze Rest durch das Include verarbeitet wird.

    ast2ufdyxkb1.png


    Leute, lernt scripten und versucht mal lieber etwas selber zu schreiben, als es aus einem GF zu kopieren. :S

  • Das musst du für dich selbst entscheiden, wie du magst

    Okay, gibt also kein Unterschied von der Schnelligkeit her, ob ich es nun Hooke oder es einfach via Script die Funktion aufrufen lasse.


    Vielen Dank, werde dann mal Anfangen umzubauen! :)

    Bestes Zitat des Jahres von Max Jackson!
    Vertrauen kann man mir auch, ich bin hier auf Breadfish schon sehr lange Aktiv und das sollte schon auf einer gewissen Weise, eine Vertrauensbasis schaffen. Ich meine, meine schlimmste Verwarnung war lediglich eine Morddrohung - Is ja nix.

  • Ich kann diese Methode nicht für gut heißen und auch nicht empfehlen.
    Was hier beschrieben wird, hat nichts mit echtem modularen Code-Aufbau zu tun, sondern verteilt den eigentlichen Code nur in unzählige Includes, was den Code nur verteilt und sonst keinen Mehrwert bringt, der die Nachteile davon überwiegen würde. Es wird ein falscher Eindruck von Modularem Coding vermittelt.


    Unter Modularem Coding versteht man nämlich im weitesten Sinne:

    • Einzelne Planung und Aufbau von Software-Paketen, die voneinander unabhängig funktionieren
    • Erstellung von universellen Modulen, die mehrfach wiederverwendet werden können
    • Die Nutzung wird in der Regel über Klassen/Libraries realisiert, die beliebig implementiert werden können

    Weitere Informationen zu echter modularer Programmierung: https://de.wikipedia.org/wiki/Modulare_Programmierung bzw. https://en.wikipedia.org/wiki/Modular_programming



    Keiner der oben genannten Aspekte trifft auf die hier beschriebene Logik zu.
    Es erzeugt deutlich mehr Verkomplizierungen, wenn man seine Systeme in Includes auslagert.


    Beispielsweise kann man in den Includes dann keine systemübergreifenden Funktionen einbauen, ohne dass diese im falschen Include stehen.
    Stellen wir uns mal vor, man hat ein Banksystem und ein Autohaussystem. Die Include des Banksystems steht vor der Include des Autohaussystems im Code. Dinge wie z.B. #define GetCarValue(%0) carInfo[%0][cValue] können dann nicht im Banksystem verwendet werden. Auf den ersten Blick mag dies auch nicht notwendig sein. Denkt man aber nur einen Schritt weiter, dann kann dies durchaus notwendig sein. Man muss sich nur vorstellen, man kann ein Fahrzeug Leasen und will die Bankrate entsprechend es Fahrzeug-Werts berechnen. In welches System (und damit in welche Include) gehört der Leasing-Vertrag dann, der mit der Bank aber im Autohaus abgeschlossen wird? Oder ein entsprechender Kreditvertrag, bei dem die Fahrzeug-Papiere bei der Bank hinterlegt werden? Natürlich kommt man über stock-Funktionen etc. an die Werte auch im Banksystem hin, darum geht es nicht. Man verkompliziert sich den Aufbau und wird niemals eine 100%'ige Trennung hinbekommen. In zig Threads zur Code-Optimierung wird gepredigt, keine unnötigen Funktionen aufzubauen, was man mit dieser Variante aber sehr wohl machen müsste.
    Zusätzlich, und das ist der springende Punkt an diesem Beispiel, vermischen sich die beiden Beispiele hier. Man wird daher nicht zwingend alle Punkte, die ein Autohaus betreffen, auch in der Autohaus-Include haben. Ich werde jetzt nicht dutzende Beispiele bringen, die durch diese Methode schlechter werden, das Beispiel genügt.


    Zusätzlich, stellen wir uns doch mal vor, wir haben ein Flughafen-System und ein Bahnhof-System. Hat man nun zum Beispiel in einem Timer eine Prüfung drin, die überprüft, ob der Spieler an einem Flughafen oder einem Bahnhof ist, und dann irgendetwas ausführt, dann geht dies hier nicht ohne weiteres. Normales Verhalten wäre ein 1-Sekunden-Timer (oder was auch immer), der prüft, ob der Spieler entweder am Bahnhof oder am Flughafen ist. Das klappt hier nicht, da man beides nicht in eine Include packen kann. Somit hat man zwei Timer und somit hat man auch erneut das Gegenteil von der hier als nicht vorhanden genannten Performanceverschlechterung erreicht.


    Es gibt natürlich noch deutlich mehr und deutlich komplexere Logiken, bei denen Systeme ineinandergreifen müssen, um korrekt zu funktionieren. Das ist immer vom jeweiligen Gamemode abhängig.
    Um nochmal auf die Ressourcen zurück zu kommen. Bisher hat man ein Callback im Code, dort steht alles drin. Hookt man das Callback jetzt in 10 Includes, dann muss in jedem Callback beispielsweise eine Variable "string[145]" zur Ausgabe von Nachrichten deklariert werden. Auch hier wird in unzähligen Threads geschrieben, dass man die mehrfache Deklaration von Variablen nach Möglichkeit vermeiden soll. Hier würde man dies explizit machen und sich daher den x-fachen Speicher anhäufen. Dies nur als einzelnes Beispiel.


    Wie man an den Beispielen aber sieht, ist diese hier sogenannte "modulare Programmierung" (was es nicht ist!) hier nur in der Theorie gegeben, sieht auf dem Papier schön aus, macht es in der Praxis aber nur unnötig kompliziert.


    Wofür sind Includes eigentlich gedacht? Includes sind dafür gemacht, wie oben in den Punkten genannt, fertige Funktionen, die unabhängig voneinander funktionieren, in den Code einzubinden, zum Beispiel einen Checkpoint-Streamer. Im Prinzip nichts anderes als Libraries in Java zum Beispiel. Niemand würde in Java auf die Idee kommen, seinen gesamten Code in Libraries auszulagern. So sollte auch in PAWN niemand auf die Idee kommen, seinen Code in Includes auszulagern.


    Der Ansatz, wie er hier als schlecht beschrieben wird, Funktionen in Includes zu trennen, bringt technisch gesehen zwar nichts, wird aber sehr häufig in der Praxis verwendet, um gigantische Code-Dateien zu vermeiden. Ein Beispiel aus meinem Alltag sind zum Beispiel SAP Reports, diese sind , bei deutlicher Komplexität, öfters auch so aufgebaut, dass es eine Include gibt (TOP-Include), welche alle Deklarationen beinhaltet, eine Include, welche die Selektions-GUI beinhaltet und diverse Includes, welche die jeweilig getrennten Funktionen beinhalten. Dies kann man machen, muss man aber nicht. Ich persönlich (meine Meinung) finde diesen Ansatz in der Regel trotzdem nicht sinnvoll, da im Falle von SAP z.B. eine Möglichkeit besteht, Code direkt in der Ausführung zu debuggen. Springt man ständig durch die Includes, fehlt oftmals die Verbindung zwischen den Funktionen und auch für Anpassungen muss man oftmals durch diverse Includes springen, um alle Anpassungen machen zu können.


    Fazit:
    Eine Trennung der Logik kann und sollte nicht über Includes realisiert werden, aus oben genannten Gründen, welche im Wesentlichen aus der unnötigen Verkomplizierung vom Aufbau des Codes bestehen und der nicht sauber vorhandenen Trennbarkeit von Systemen.
    Zudem hat der beschriebene Ansatz im Tutorial nichts mit der tatsächlichen Idee der Modularen Programmierung zu tun und vermittelt ein falsches Bild dessen.
    Includes sollten dafür genutzt werden, wofür sie auch gemacht sind. Um Funktionen unabhängig und in mehreren Codes einbinden zu können.


    Ich kann nur hoffen, niemand verbiegt sich hierdurch unnötigerweise seinen Code.

  • wofür sie auch gemacht sind. Um Funktionen unabhängig und in mehreren Codes einbinden zu können.

    Ja...genau das beschreibe ich doch?!


    Dir ist klar, wenn man ein System so wie beschrieben in einem Include hat, dann ist es unabhängig von dem Gamemode und kann in mehrere Codes eingebunden werden.


    Das ist ja der Sinn dahinter bestimmte Sub-Systeme auszulagern.


    Ich sage ja nicht, dass man ALLE Systeme auslagern soll, nur die, die in sich abgeschlossen sind. (Deshalb ja auch static)



    Ich glaube, du hast meinen Text nicht richtig gelesen und übertreibst mega mit deinem Text.


    Die ersten 3 Punkte die du beschreibst sind genau das, was ich in meinem Text auch beschrieben habe...


    Also so ein Käse.

    ast2ufdyxkb1.png


    Leute, lernt scripten und versucht mal lieber etwas selber zu schreiben, als es aus einem GF zu kopieren. :S

  • Zum einen übertreibe ich angeblich, und zum anderen sollen meine Punkte die gleichen sein, wie die deinen? Außerdem beschreibe ich angeblich was du sagst und zum anderen ist meine Meinung Käse? Da stimmt doch was nicht.


    Als Beispiel nennst du ein Haussystem. Ich hatte selbst jahrelang einen Server, mit einem solchen System. Es lässt sich auf gar keinen Fall komplett vom Rest trennen, und gehört damit auch nicht in eine Include.
    Außerdem sagst du explizit, dass die Logik (also die Integration in den Gamemode) in die Include soll. Genau das soll sie eben nicht. Das ist der springende Punkt. In Includes gehört nur das, was sich ohne integrative Probleme in jeden beliebigen anderen Gamemode hinzufügen lässt. Dein "Tutorial" stellt dies völlig falsch dar.



    Ich stehe zu dem, was ich geschrieben habe und betone nochmal, dass hoffentlich niemand seinen Code durch die falsche Darlegung der modularen Programmierung in diesem "Tutorial", um die es dem Titel nach ja gehen sollte, aber gar nicht geht, verunstaltet.

  • zum anderen ist meine Meinung Käse?

    Wo ist deine Meinung Käse?


    Deine Übertreibung ist Käse und wie du den Beitrag darstellst.



    Ich hatte selbst jahrelang einen Server, mit einem solchen System

    Herzlichen Glückwunsch, da sind wir nun schon 2.



    Es lässt sich auf gar keinen Fall komplett vom Rest trennen, und gehört damit auch nicht in eine Include.

    Aha...ja, okay...ich denke das kommt auf die Features an..
    Aber..ich habe das getan und konnte dies auch ohne Probleme in anderen Gamemodes von mir wieder verwenden.
    Sehr mysteriös.


    Außerdem sagst du explizit, dass die Logik (also die Integration in den Gamemode) in die Include soll.

    Ich sage, dass es Käse ist, die Trennung so wie im GF zu veranstalten.
    Mit Logik Trennung meine ich, dass man einzelne Sub-Systeme Trennen soll.
    Da man im vorherigen nicht das Trennt, sondern einfach nur die Anordnung von Funktionen etc pp



    Naja, wenn du meinst, ist alles Scheiße, deine Meinung.
    Ich finde es ziemlich gut und habe damit auch nur gute Erfahrungen gemacht.
    Gab es einen Fehler, wusste ich direkt, in welchem Include ich schauen musste, man kann viel Schneller Dinge erweitern ohne schauen zu müssen, wo jetzt alles steht.
    Man hat alles beieinander.


    Ist mir jetzt zu doof diese Diskussion, soll jeder machen wie er meint.

    ast2ufdyxkb1.png


    Leute, lernt scripten und versucht mal lieber etwas selber zu schreiben, als es aus einem GF zu kopieren. :S

  • Auf den Ersten Blick sieht das ja auch gut aus, ich meine, alle defines Oben, alle Variablen oben, alle Commands und publics in der Mitte und ganz unten alle "stock - Funktionen".

    Hey Kaliber ,


    ich hätte mal eine Frage zu "stock-Funktionen". Müssen sie alle unten angeordnet sein oder könnte ich auch alle oben zu den define's, enum's und co packen?

    Ein compailing Fehler bekomme ich zwar nicht, aber kann ja sein, dass es sich Performance-mäßig auswirkt.


    Grüße Kazu

  • Das wirkt sich prinzipiell erstmal nicht auf die Performance aus. Die Anordnung der Funktionen werden eigentlich erst dann relevant, wenn du anfangen solltest in externe Dateien (Includes) auszulagern