Beiträge von Lead0b110010100

    Ein gut gemeinter Appell, lass es sein, hör auf. Nimm deine 7 Sachen und vereis weit weg. Merkst du nicht wie sehr dein Geisteszustand mittlerweile leidet und dein Gehirn bereits schaden genommen hat? Ich glaube der DDoS ist direkt in dein' Kopf vorgedrungen...

    Sehr unfreundlich von dir.

    Das sagst du zu einem Typen, der bereits als Kind die Freundlichkeit in seine Arschvene hineininjiziert bekommen hat.

    Genau, er ist unfreundlich. Bro, du könntest seine Freundin ficken und er würde sich als Schweizer zunächst bedanken und dich freundlich aus der Wohnung bitten + dein Fahrtgeld in die Hand drücken für ein Taxi wtf.

    Bitte melden Sie sich an, um diesen Link zu sehen.


    metho Du weißt, ich liebe dich. <3

    Ich sehe z.B auch ständig Funktionen von mir, die hier rumlungern. Ich bin aber zu faul nachzuvollziehen für welchen Kunden ich die geschrieben habe, ich müsse Ente den Schnuckel mal dafür beauftragen. Der macht das glaub ich :D


    Das dreiste ist dann nur, dass ich nicht Mal verlinkt werde oder so sondern das sie es als "Release" verkaufen :P

    Aber juckt, wenn ichs besser geschützt hätte mit Lizenzsystem bla bla wäre das nicht passiert - Da kann ich die Schuld nicht anderen zuschreiben. Ich bin Developer, mit sowas muss ich nun Mal rechnen und mich wappnen. Gibt's halt keinen Support mehr für diese Kunden.

    Wenn du heutzutage Systeme anbietest mit >= 150€ Preis, solltest du sowieso auf ein Lizenzsystem setzen welches das Reselling erschwert (wenn auch nicht verhindert).

    Stimme ich dir absolut zu. Aber generell haben für mich Systeme die andere besitzen keinen bis nur minimalen Wert.

    Da es das Alleinstellungsmerkmal verloren hat und dies ist für mich der Hauptgrund neue Systeme auf einem Server zur Verfügung zu stellen.

    Deswegen haben ja auch die großen Server ihre eigenen Devs die nur für den Server arbeiten.

    Aber glaubst du das sowas wie ein Offlineshop oder das Target Information System oder eine Rangliste wie ein Mountsystem noch ein alleinstellungsmerkmal darstellen?

    Sowas zu verkaufen würde doch absolut Sinn machen. Es gibt genug Käufer (jeder neue Server ist quasi ein neuer Käufer für dich) und damit auch genug Anfrage.

    Wenn beim Verkauf nichts festgelegt wird, kann der Käufer meiner Meinung nach das System so oft auf anderen Server nutzen indem er aktiv Teil des Teams ist . Sollten jedoch eine Reglung wie Lizenz pro Server verhandelt worden sein. Wäre eine Nutzung des Systems auf einem anderen Server nicht rechtens. Wer Teil eines Teams wird nur mit der Intention ein System weiterzugeben betreibt reselling.


    Aber ganz ehrlich all diese Gründe tragen dazu bei ,wieso ich nie verstehen werde, wieso DEVs überhaupt noch Systeme anbieten.

    Anstatt sich bei den großen Server anständig bezahlen zu lassen und nur für dieses zu arbeiten.

    Wenn du heutzutage Systeme anbietest mit >= 150€ Preis, solltest du sowieso auf ein Lizenzsystem setzen welches das Reselling erschwert (wenn auch nicht verhindert).

    Nur wenn es vorher abgesprochen wurde. Ich sage z.B explizit immer das ich die Systeme, die ich schreibe, auch für mich selbst verwende ABER nicht weiterverkaufe grundsätzlich.

    Zumindestens wenn das System etwas Spezifisches ist. Falls es komplett unique nur für diesen einen Kunden erstellt wird, Verdreifache ich einfach den Preis.


    So ist es meiner Meinung nach am fairsten.

    26.03.21


    Wir sind am Ende angekommen. Folgend sieht der Fund bei der db-Source aktuell aus:


    Bitte melden Sie sich an, um dieses Bild zu sehen.


    Die game-Source dagegen so:


    Bitte melden Sie sich an, um dieses Bild zu sehen.


    Dazu zunächst einige Informationen:

    - Ich habe nicht jeden Fehler und jede Meldung gelöst bzw. berücksichtigt. Solche Tools sind nicht frei von false positives, das steht auch in den Dokumentationen drin

    - Von etwa 1500 Meldungen bei game & db habe ich sie auf etwa 25 runterbrechen können

    - Wenn ihr das Tool bei euch laufen lasst, werdet ihr sehr viel weniger Meldungen haben. Der Sprung von 32 auf 64 Bit bei mir macht Einiges anders. z.B macht es nun einen Unterschied, wie man Elemente in einem struct ordnet. Wenn das größte Element oben steht, lässt sich die Größe der Klasse (bzw. der Struktur) verringern. Und damit insgesamt weniger Speicher in Zusammenhang damit nutzen


    Wichtig ist aber vor allem:

    - Wenn ich nicht wusste was zutun ist und die Dokumentation zum Fehlercode (erste Spalte) für mich keinen Sinn ergab, habe ich nichts getan. Am Ende des Tages ist es bei C++ wahrscheinlich besser, nichts zu tun - Wenn man keine Ahnung hat. Sonst unterscheidet uns als deutsches Forum nichts mehr von turkmmo.


    Ich habe überlegt ob ich die Client Source nun auch noch durchgehe und hier öffentlich im Tagebuch festhalte, aber.. die Meldungen sind 1:1 die Selben. Es hätte für euch also keinen Mehrwert diesen Part mitzuverfolgen. Trotzdem sollt ihr hier nicht leer ausgehen. Lasst mich euch zum Abschluss noch die 'Rule of three' erklären.


    Allgemein besagt C++, dass jede Klasse (Strukturen also struct's sind auch Klassen wo standartmäßig alle Member public sind) die einen Destruktor, einen Copy-Constructor oder einen Copy Assignment Operator besitzt, die jeweils anderen 2 Konstrukte besitzen sollte. Ich sehe schon die Fragezeichen in euren köpfen, aber so schwer ist es nicht, ich verspreche es.


    1) Der Destructor

    ~ Ganz easy: Du löschst ein Objekt mit

    Code
    1. delete obj;


    und der Destructor

    Code
    1. Object::~Object()
    2. {
    3. std::cout << "I am deleted, oh no!" << std::endl;
    4. }


    wird aufgerufen.


    2) Copy Constructor

    ~ Hatten wir oben bereits besprochen: Eine Klasse wird mit allen ihren Membern / Attributen und Templates kopiert und an einer anderen Stelle im Speicher aufgebaut.


    Code
    1. void Object::Object(const Object& other)
    2. {
    3. m_member1 = other.m_member1;
    4. m_member2 = other.m_member2;
    5. m_member3 = other.m_member3;
    6. m_member4 = other.m_member4;
    7. // Hier könnte man auch das Copy und Swap Idiom anwenden, aber ich krieg jetzt schon Nachrichten das diese Texte hier keiner versteht also verzichte ich Mal darauf, ausnahmsweise.
    8. }


    3) Copy Assignment Operator

    ~ Auch easy, im Prinzip das Selbe wie der Copy Constructor, nur das diesmal der Assigment Operator (=) genutzt wird. Also statt

    Code
    1. Object *obj = new Obj(newObj);
    2. Object *newObj(obj);


    kann man jetzt auch folgendes machen:

    Code
    1. Object *obj = new Obj();
    2. Object *newObj;
    3. newObj = obj;


    Und definiert wird der Copy Assignment Operator folgendermaßen:

    Code
    1. Object& Object::operator=(const Object& other)
    2. {
    3. m_member1 = other.m_member1;
    4. m_member2 = other.m_member2;
    5. m_member3 = other.m_member3;
    6. m_member4 = other.m_member4;
    7. return *this;
    8. }


    Derart Strukturen werden in der STL überall genutzt, z.B: wenn ihr:

    Code
    1. std::string test = "hi"; // string = const char*
    2. bool b = 55; // bool = int


    macht. Das sind implizite Konvertierungen, die aber auch nicht aus Magie stattfinden. Das haben schlaue Köpfe mit den selben Mitteln in der STL programmiert wie ihr sie habt.


    Okay aber was hat PVS-Studio nun bemängelt? Es gab Klassen in Metin2, die z.B einen Copy assignment Operator definiert haben, aber keinen Copy Konstruktor. Üblicherweise machen diese Dinge das Selbe, das heißt man könnte nun versuchen den Konstruktor zu verwenden. Dann wird ein Default Konstruktor von C++ verwendet, den der Compiler dazudichtet. Das führt zu undefined behaviour. Um meinen Code nicht zu duplizieren, mache ich, wo nötig, eine Hilfsfunktion "Copy" die in beiden Fällen ausgeführt wird. So wird aus:


    Code
    1. CShop::CShop(const CShop &rCopy)
    2. {
    3. CopyContainer(m_listGuests, *rCopy.GetGuests());
    4. CopyContainer(m_vecItems, *rCopy.GetItems());
    5. CopyContainer(m_vecOffers, *rCopy.GetOffers());
    6. }


    folgendes, wenn man die Rule of Three beachtet:



    Es gibt noch eine 'Rule of five' aber die könnt ihr ergoogeln und euch selbst schlau machen. Von mir soll es für dieses Thema nun genug sein.

    Ich hoffe ihr habt einiges mitnehmen können und Danke euch für das durchlesen und das Folgen auf meinem Weg. Ich denke es wird noch ein paar mehr Tagebücher geben, aber dazu muss ich zunächst ein schönes Thema finden. Vllt. werden es ja 'Memory Leaks' oder die 'Gui Programmierung'? Vllt. auch mal ne ganz andere Programmiersprache, die wir zusammen erlernen könnten?


    Ich weiß es noch nicht, was ich weiß ist: Der Weg ist noch echt lang und ich sehe kein Ende. Solche Tagebücher helfen mir aufjedenfall weiter, da sie mich zwingen mich mit meinem eigenen Code außeinanderzusetzen und dazuzulernen und dabei stetig besser zu werden.


    Vielen Dank für eure Aufmerksamkeit.

    ~ Lead

    24.03.21


    Na mal sehen ob ich noch was finde was nicht erklärt wurde...

    Ich mach nur Spaß, wir könnten das Ganze hier wahrscheinlich unendlich lange machen.


    Das Thema 'unnötige Prüfungen' wird nie alt. z.B: wieder:


    Code
    1. if (item)
    2. sys_log(0, "ShopEx: BUY: name %s %s(x %d):%u price %u", ch->GetName(), item->GetName(), item->GetCount(), item->GetID(), dwPrice);


    Obwohl ein paar Zeilen dadrüber noch steht:

    Code
    1. if (!item)
    2. return SHOP_SUBHEADER_GC_SOLD_OUT;


    Ergo der Pointer ist an dieser Stelle sehr sicher valide. Oder naja, fast sicher könnte man sagen. Denn wenn dieser Speicherblock, auf den der Pointer zeigt in einem Event gelöscht (bzw. freigegeben) wird, das zufällig genau nach der Prüfung auf Invalidität ausgeführt wird -> Gibt es einen Corecrash.


    Ja, dieser Fehler ist selten und wenn dann ist es eher ein konzeptioneller Fehler. Ich will nur zeigen, das nur weil man mit dem Not-Operator (!) auf Invalidität geprüft hat, das Objekt bzw. der Access auf das Objekt über den Pointer (-> Ist eine Dereferenzierung btw.) nicht sicher ist.


    Machen wir das kurz anhand eines anderen Beispiels klar was ich meine. Angenommen wir purgen Monster in einem Run, indem wir über den Sectree iterieren. Dann prüfen wir ob die Instanz ein Item-Entity oder eine Charakter-Entity ist. Wenn es ein Charakter ist, prüfen wir ob es kein PC ist. Damit ist es ein Monster und Ähnliches. Aber das ist noch nicht so schlimm. Stellen wir uns vor, gleichzeitig rufe ich über eine Quest noch eine Purge Funktion auf, die genau diese Vid kurz nach dem Aufruf von d.purge mit M2_DESTROY_CHARACTER löscht. M2_DESTROY_CHARACTER ist btw. nur ein define für:


    delete (ch);


    Ergo hier ein Beispiel wo man "Heap use after free" auslöst. Man nutzt also Speicher im Heap, der bereits freigegeben wurde. Keine Ahnung warum das keyword 'delete' heißt, zum Glück wird mein Speicher nicht einfach so gelöscht sondern freigegeben. Aber das nur als Nebeninfo. In äußerst seltenen Fällen kommt es dabei zum Crash, aber dieses konzeptionelle Problem herrscht in Metin2 nunmal und man muss wissen das es existiert. Denn wenn mal etwas crasht und die Meldungen nicht so viel aussagen - Hilft euch nur noch die Erfahrung mit Fehlern.


    1) Wir purgen die Monster über den Sectree


    2) Man stelle sich eine Questfunktion vor die folgendes macht:



    Die Quest müsste im Dungeon ausgeführt werden, wenn z.B 1000 Monster gespawnt sind:

    23.03.21


    Hab gestern sehr vieles gemacht und geschafft. So viel, dass von den mittleren Punkten für die game-src nur noch etwa 30 Stück übrig sind. Wovon wahrscheinlich auch nur 15 von mir gefixxt werden müssen, der Rest sieht für mich aus wie false positives oder 'Mi Mi Mi's. Aber fangen wir mit den einfachen Dingen auf der Liste an (Es waren 5 Commits oder so, ich gehe einfach ein paar random Änderungen durch):


    1. Fehlendes return sorgt für Nutzung von Nullpointern

    Code
    1. if (!g1 || !g2)
    2. {
    3. luaL_error(L, "no such guild with id %d %d", gid1, gid2);
    4. // Hier fehlt das: return 0;
    5. }


    Da hier g1 und g2 auf not null geprüft werden, würde man erwarten das wenn eines der Beiden Pointer null ist, dass dann die Funktion endet. Denn im folgenden passiert:

    Code
    1. PIXEL_POSITION pos;
    2. uint32_t dwMapIndex = g1->GetGuildWarMapIndex(gid2);


    g1 könnt null sein und damit einen Corecrash hervorrufen. Dies wurde in einer vorherigen Tagebuchseite bereits erläutert. Also:

    Code
    1. if (!g1 || !g2)
    2. {
    3. luaL_error(L, "no such guild with id %d %d", gid1, gid2);
    4. return 0;
    5. }


    return hinzugefügt und möglichen Corecrash abgwendet. Natürlich nur unter der Bedingung: Der Server nutzt diese Funktion und man kann sie als Spieler ausführen ohne in einer Gilde zu sein.


    2. const references everywhere (Vor allem für große Klassen und Vektoren... Also.. bitte?)

    Code
    1. bool ITEM_MANAGER::SetMaxItemID(const TItemIDRangeTable& range)
    Code
    1. int32_t CDungeon::GetFlag(const std::string& name)
    Code
    1. void CSwitchbot::SetAttributes(uint8_t slot, const std::vector<TSwitchbotAttributeAlternativeTable>& vec_alternatives)


    Mehr muss ich glaub nicht mehr sagen, davon gabs wieder etwa 20 Stück, wo das Kopieren (copy constructor) von Objekten in den Stackframe (oder implizites allokieren im Heap z.B bei Strings) problematisch werden könnte bzw. an der Performance nagt.


    3. Entfernen von dead code


    Schonmal was von einem Yangspeicher bei Gilden, einem Partyskill namens "HEAL_ALL_MEMBERS" oder Lotto in Metin2 gehört? Wenn nicht, ist auch dead code. Wird nie benutzt, am Anfang der Funktion steht dann:

    Code
    1. return;


    Oder if-Anweisungen wie:

    Code
    1. if (0) // oder if (false) oder if (!!0) oder wie fancy man das auch schreiben mag


    Achso und das war kein Spaß, es gibt einen Heilskill bei Metin2 der einfach nie benutzt wird für Gruppen, hier ein Vorschaubild (rechts wäre der Heilskill), funktioniert aktuell nicht bzw. ist 'auskommentiert'.


    Bitte melden Sie sich an, um diesen Anhang zu sehen.


    4. iter++ ist böse

    Was ist das Problem mit folgendem Iterator?

    Code
    1. (; iter != m_member.end(); iter++)


    Es werden haufenweise Werte im Stack abgelegt. Glaubt ihr nicht? Dann schauen wir uns mal per Pseudocode den Unterschied zwischen i++ und ++i an.

    Was macht ++i? Ganz rudimentär: Ich bekomme einen Wert, so sei i jetzt 3 und inkrementiere ihn um 1 und gebe den neuen Wert nach der Inkrementation also 4 zurück.


    (i sei 3)

    ++i = 4


    Jetzt sagt ihr: Moment mal, i++ ist doch auch 4 am Ende der Zeile, was soll das denn jetzt? Dann nochmal das Beispiel mit i++. i++ inkrementiert den Wert, gibt aber den alten Wert, hier also die 3 zurück. Erst am Ende der Zeile bzw. zum Anfang der nächsten Zeile wäre i dann 4.


    i++ = 3

    // Erst hier ist i = 4


    Technisch gesehen könnten die Funktionen also so aussehen:


    Diese Variable tmp wird bei jedem 'i++' erstellt bzw. muss es auch. Ja, alle Compiler die ich kenne werden das on the fly optimieren, aber darauf kann man sich nun Mal nicht immer verlassen. Besser ist, direkt als Entwickler auf die richtigen Operatoren zu achten. Hier noch ein kleines Ratespiel was ich niemals auflösen werde:

    Code
    1. int i = 1;
    2. int new = i++ + i-- + ++i - ++i + i++ - i + i++;


    wie ist der Wert der Variable new am Ende der Zeile?


    5. Vermeiden, strings zu kopieren


    Eig. ganzt einfach: Ihr erstellt einen String, sagen wir "oldname" und gibt ihm einen Wert. Danach wollt ihr dass der String "newname" den Wert von "oldname" bekommt. Welchen Operator nutzt man instinktiv? "=" - Das mindert die Performance aber. Ihr habt jetzt, wissentlich oder unwissentlich den String von newname komplett neu erstellt, wobei ihr eig. nur den Inhalt von oldname nutzen wolltet. Beispiel gefällig?


    Code
    1. std::string oldname = "Albert";
    2. std::string newname;


    Jetzt gibt es verschiedene Lösungen wie man eine Kopie verhindert. Hier einige davon, die ich nicht näher erläutern möchte weil kb mehr zu schreiben und so:

    Code
    1. newname.swap(oldname);
    Code
    1. newname.assign(oldname);
    Code
    1. newname = std::move(oldname);
    Code
    1. std::swap(newname, oldname);


    Jeder dieser Funktionen liefert am Ende das gewünschte Ergebnis, was schneller ist dürft ihr mit dem CPP Bench Tool von der Seite hiervor gern selbst prüfen. Das was sie gemeinsam haben ist:


    Nach dieser Zeile, ist oldname eine womöglich leere oder ungülltige Variable. Bei std::move z.B darf man diese Variable danach (ohne sie erneut zu initialisieren) auf keinen Fall benutzen. Der Speicher dieses Objektes wurde 'gemoved'. Man greift damit auf ungülltigen Speicher zu. Bei Assign und Swap ist es ähnlich. Das heißt: Dieser Punkt ist nur relevant, wenn der alte String 'oldname' in diesem Beispiel im Folgenden nicht mehr gebraucht wird.


    Puh ich hab doch noch so viel zu erklären, ich hoffe ich hab mich bis jetzt nicht widerholt. Das soll es für heute aber sein, viel Spaß weiterhin mit diesem Thread und statischer Codeanalyse!

    Suche ebenfalls ein Raup

    Raupy findest du in Pokemon Rot auf Route 25 und dem Vertania Wald.

    Bei Pokemon Blau sieht das Ganze etwas anders aus, da ist es auf Route 2, 24 und 25 soweit ich weiß.

    Bei Gelb (das mit Pikachu) nur im Vertania Wald.


    Ansonsten bei der 2. Gen im Käfterturnier fangen, ist meistens dann schon ausgelevelt, wird sich aber nicht evolutionieren glaub ich.

    Ab Gen 3 kannst dus eintauschen oder auch im Musterbusch sowie im Vertania-Wald.

    Bei Gen 4 gabs da noch Einschränkungen für Pokemon Platin, da gibts den z.B auch im Ewigwald. Das ist die Version mit dem Gameboy Advance. Und naja, Route 204 wie gewöhnt.

    Ab Gen 5 kannst dus durch Zuckt, Tausch und allgemein im Wald finden.


    Hier noch ein Link mit mehr Informationen: Bitte melden Sie sich an, um diesen Link zu sehen.

    22.03.21


    Heute hab ich mir mal die "Middle" - Meldungen für die game angesehen und meistens war es eher eine Codeduplication Angelegenheit. Beispiel gefällig?



    Und wem ist es aufgefallen? Der case DAMAGE_TYPE_NOMRAL_RANGE und DAMAGE_TYPE_MAGIC haben genau den selben Inhalt bzw. die selben Instruktionen, deshalb kann diese Switchanweisung folgendermaßen aussehen:



    Ansonsten habe ich noch Prüfungen gesehen, die einfach unnötig sind. z.B: zu prüfen ob ein Pointer != nullptr ist über den '!' - Operator, obwohl zwei Zeilen zuvor bereits die Gleiche Prüfung nur anders geschrieben steht. Beispiel?


    Code
    1. if (!ch)
    2. return m_iMobGoldAmountRate;
    3. if (ch && ch->GetPremiumRemainSeconds(PREMIUM_GOLD) > 0)
    4. return m_iMobGoldAmountRatePremium;


    Erst zu prüfen ob ch == nullptr ist über den ! Operator und dann ch && ch ... zu prüfen ist redundant. In der Zeile 3 ist ch immer 'true' bzw. ein valider Pointer (Ja, es gibt 'Heap use After free' Fehler für die Erfahrenen, das könnte durch Multithreading oder Events / Iterieren über Sectree in Metin2 passieren, aber der Einfachheit halber lass ich den Satz so stehen.). Richtig wäre es dann natürlich so:


    Code
    1. if (!ch)
    2. return m_iMobGoldAmountRate;
    3. if (ch->GetPremiumRemainSeconds(PREMIUM_GOLD) > 0)
    4. return m_iMobGoldAmountRatePremium;


    Die restlichen 'Anmerkungen' kamen eher durch mich und meine Systeme, weshalb ich die hier nicht erwähnen muss. Die hättet ihr so sowieso nicht.


    War diesmal eine etwas kürzere Seite, aber viel bleibt auch nicht mehr. Ich denke, das Tagebuch nähert sich bereits dem Ende seiner Laufzeit. Sobald ich den größten Teil der Middle / Low Fehler gelöst habe, habe ich ein solides Grundwissen worauf ich in Zukunft alles achten sollte. Meine Erkenntnisse wollte ich als Zwischenstand hier bereits festhalten:


    1. Versuche deinen Code auf die Standards der neuen Generation zu updaten. Lambdas vereinfachen sehr viel so wie structured bindings und das auto keyword. std::get<xy>() war Mal, nutz das Auto keyword wie 'auto &[x, y] = ...'. Allgemein ist es wohl ratsam sich zunächst in der STL oder der BOOST Library umzusehen und nicht alles neu zu entwickeln. Sonst macht ihr noch sowas dummes wie die CTextFileLoader Klasse von Metin2 und das will nun wirklich keiner.
    2. Es gibt viele unnötige Prüfungen und unnötigen Code. Prüfe nicht mehrmals das Selbe, achte auf die Ausdrücke die du verbindest. Etwas was mit dem UND - Operator verbunden wird und an erster Stelle bereits sowas wie '(row = xy)' stehen hat, darf im Folgenden nicht nochmal row auf Gülltigkeit prüfen. Wenn es gülltig wäre, wäre deine Prüfung immer true und wenn es nicht gülltig wäre, würde dieser Ausdruck nie ausgewertet werden, weil ja das erste Element der Undverknüpfung bereits falsch ist.
    3. Manchmal bietet C++ Keywords, damit DU es als Entwickler leichter hast. Niemand kann von sich selbst behaupten perfekt zu sein, aber es lohnt sich sicher gewisse Funktionen als "override" zu markieren und anderen das "virtual" keyword wegzunehmen, wenn es sich nicht um tatsächlich virtuelle Funktionen handelt. Entwickle ein Gefühl dafür, welche Specifier (const, constexpr, static, inline, mutual, [dep: register]) du hast und verwende sie waise. Setze dich mit ihnen außeinander aber mach dich nicht kaputt, es gibt zu viele um alle Anwendungsmöglichkeiten auswendig zu können. Wenn du weißt wo es steht, musst dus nicht mehr lernen. Aber kenne deine Ankerpunkte, also jene Punkte wo du safe behaupten kannst es nachlesen zu können.
    4. Manchmal lohnt es sich, mit einem Online Benchmark Tool zu prüfen, ob gewisse Zeilen Code wirklich den Unterschied in punkto Performance machen können. Ich war sehr überrascht, dass std::string mit einem Character als Parameter sehr viel schneller ist als mit einem Array (const char* bzw. const char[]). Ich empfehle da jedem die Seite Bitte melden Sie sich an, um diesen Link zu sehen.. Hat mich auch Einiges gelehrt. z.B ist die Verwendung von std::count_if insgesamt langsamer als händisch durch ein Array zu iterieren weil vor der eigentlichen Iteration noch sehr viele Prüfungen stattfinden.
    5. Alles was du explizit machen kannst, solltest du auch explizit machen. Ergo wenn eine Funktion einen Integer erwartet, dann gib keinen Boolean rein und andersherum genauso. Das schafft Klarheit über deinen Code und eliminiert Redundanzen. Wenn Funktionen z.B nur booleans zurückwerfen, sollte der Rückgabewert auch ein boolean sein und kein Integer, das ist unnötig. Andersherum auch, wenn ich mehr als 2 States brauche, nehm ich halt einen Integer. Aber warum immer int (int32_t)? Warum nicht manchmal uint8_t -> 0-256 oder kleinere Datentypen?
    6. konstante Referenzen, ÜBERALL WO ES GEHT. Man will eig. nie Strings oder Vektoren oder Strukturen kopieren wenn man sie in Funktionen gibt. Es gibt vllt. einen von 1000 Anwendungsfällen wo man das explizit möchte. Ansonsten sollten alle Parameter die nicht verändert werden als konstante Referenzen übergeben werden. Also const type& xy, so kann man dann auch R-Values in die Funktion hineingeben wie (i + 4). i ist bekannt, der Wert von 'i + 4' ist ein R-Value und ist keiner Variable zugewiesen (Die einfach gesagt ein L-Value wäre). Auf diese Unterscheidung wollte ich hier im Thread nicht eingehen, das ginge über diesen Codeanalyse Part hinaus.


    Ich denke so lassen sich meine bisherigen Erfahrungen zusammenfassen und hoffe, dass diese Texte dem einen oder Anderen ein Bild davon geben können, wie ich und andere Developer arbeiten. Wir sind auch nur Menschen, die Dokumentationen lesen und sich für gewisse Kleinigkeiten begeistern können. Der eine für Webseiten, der Andere für grafische Oberflächen und ich für veraltete MMO's und Coderefactoring. Jeder hat seine Stärken, versucht nicht perfekt zu sein aber bildet euch fort. Erlernt so viel ihr könnt, macht euer eigenes Tagebuch, dreht YouTube Videos und gewöhnt euch an jeden Tag ein bisschen zu machen statt 1x die Woche ganz viel.


    Dann wird das auch schon, ich wünsche euch einen wundervollen Abend. Bleibt gesund!


    ~ Lead

    Ich durfte heute Lead0b110010100 beim Arbeiten zusehen. Ich hab noch nie jemanden wie ihn gesehen ... jemanden der so verdammt schnell arbeitet, dass du nicht einmal mit deinen Augen richtig hinterherkommst. Er kennt sich super in der Materie aus und ist zudem ein sehr entspannter und witziger Dude. Seine Preise sind ebenfalls mehr als Fair. Er nimmt sich wirklich genug Zeit für seinen Kunden. Ich kann wirklich nichts bemängeln!

    Puh, auf soviel Lob erst ein Mal eine weitere Folge Suits ansehen. <3

    20.03.21


    Neuer Tag und wir wollen den Drive ja nicht verlieren. Das erstellen eines Beitrags hier sollte zur Gewohnheit werden, damit das Lernen nicht schwer fällt. Fassen wir mal zusammen was ich heute gemacht habe..


    Bitte melden Sie sich an, um dieses Bild zu sehen.+


    Heilige Scheiße, wie fasse ich das jetzt wieder zusammen? Na gar nicht! Ich habe heute die letzten Punkte auf der "Low" Liste für die db - Source abgehakt. Einige habe ich auch einfach ausgeblendet, wie die Warnung, dass ein memsize type (size_t) in einen 32 Bit Datentypen umgewandelt wird. Also in meinem Beispiel:

    Code
    1. uint32_t x = m_vec.size();


    Klar kann das Probleme machen, weil size_t unter 64 Bit nunmal größer ist als uint32_t aber das sollte bei den Datenmengen die ich erwarte kein Problem sein. Ich wollte jetzt nicht an tausenden Stellen Variablen ersetzen und die Zeit zum Analysieren jeder einzelnen Warnung fehlt mir.


    Also weiter geht es:

    Code
    1. fdwatch_add_fd(m_fdWatcher, m_fd, this, FDW_READ, false);


    Ich habe ganz viele Warnung zu Zeilen wie dieser gefunden, wo ein Integer implizit zu bool gecastet wird oder anders herum. In diesem Falle werden die Argumente folgendermaßen eigentlich in die Funktion hineingegeben beim Ausführen:


    Code
    1. fdwatch_add_fd(m_fdWatcher, m_fd, this, FDW_READ, 0);


    Das selbe Spiel hier auch:


    Code
    1. memset(&active, false, sizeof(active));
    2. memset(&finished, false, sizeof(finished));
    3.         // false wird jeweils in 0 implizit konvertiert, also in einen Integer


    Hab ich dann an einigen Stellen angepasst und das Tool war ruhig.


    Aufgefallen ist mir zusätzlich das Funktionen wie:

    Code
    1. int32_t Connect(int32_t iSlot, const char *host, int32_t port, const char *dbname, const char *user, const char *pass);
    2. int32_t Start();


    Nur boolean Werte zurückgeben, aber als Rückgabewert 'int32_t' stehen hatten. Klar funktioniert alles, aber ganz sauber ist es nicht. Also ganz easy:


    Code
    1. bool Connect(int32_t iSlot, const char *host, int32_t port, const char *dbname, const char *user, const char *pass);
    2. bool Start();

    Achtung: Das ist nur die Deklaration, vergesst nicht das Selbe in der Definition bei euch zu machen. Achtet außerm darauf, dass diese Funktionen bei euch nicht irgendwo als integer genutzt werden. Wenn also irgendwo steht:


    Code
    1. if (Start() == 3) { ... }


    solltet ihr das natürlich nicht ändern.


    Habe noch ein paar mögliche Nullpointer gefixxt wie oben bereits erklärt. Damit sind alle Punkte für die db abgehakt und ich kann mich nun auf die Middle und Low Punkte für die game kümmern. Dazu aber dann mehr morgen, gute Nacht liebes Forum.


    Bleibt gesund!

    Nur damit wir beide uns verstehen:

    Ein RNG pickt eine zufällige Zahl aus einer Sequenz aus Zahlen. Der Seed bestimmt, an welcher Stelle der RNG diese Zahlenreihe beginnt und sich von dort die Zahlen pickt.

    Bei gleichem Seed ist demnach die Wahrscheinlichkeit die selbe Zahlenreihe zu bekommen recht groß, wenn nicht bedingt wahr.


    Das resultiert doch gerade darin, dass vor dem Aufruf von rand() der Seed neu gesetzt werden sollte, um die Zufälligkeit der gewählten Zahlen zu stützen. Hab ich hier einen Denkfehler?


    Bitte melden Sie sich an, um dieses Bild zu sehen.


    Also ist unser Punkt wo wir uns noch nicht einig sind nur der, dass du das Seeden nur 1x bei Programmstart machen würdest und ich eben jedes Mal bevor ich die zufällige Zahl mit rand() bekommen will. Ich weiß halt auch nicht, wie groß diese Sequenz ist. number() wird ziemlich häufig gecalled, bei nur 100 zufälligen Zahlen in der Sequenz ist man da ganz schnell wieder am Anfang und bekommt die selbe Zahlenreihe zurück.

    Danke für die Aufklärung. Aber erklärst du damit nicht genau den Fehler von YMIR und warum das erneute seeden nötig ist?

    Die Funktion die du zeigst, "thecore_init" wird nämlich nur 1x beim Serverstart aufgerufen. Das heißt jeder Aufruf von "thecore_random" erzeugt im folgenden auch nur pseudozufällige Zahlen, der Seed ist ja schließlich der Selbe.

    19.03.21


    Heute wollte ich eine Sache besprechen, die zum Einen das Tool mir gemeldet hat und die ich auch sonst überall sehe.

    Es wird nicht nur euren Code vereinfachen, es macht eure Anwendung / Funktion insgesamt auch noch performanter.


    Das Thema sind die Funktionen 'emplace' und 'emplace_back' je nach Container. (std::map hat z.B nur .emplace, std::vector dagegen .emplace_back).

    Schauen wir uns dazu folgende Zeile mal an:

    Code
    1. m_mapSymbol.insert(std::make_pair(guildID, gs));


    Was passiert hier? Gehen wir es mal Schritt für Schritt durch (so wie der Codefluss natürlich verläuft):

    1) Mit std::make_pair wird ein Objekt aus dem Datentypen von guildID und gs erstellt was dann in etwa so aussieht: std::pair<TypA, TypB>(guildID, gs). Wie genau da nun passiert ist hier erst Mal egal. Man erstellt also ein Objekt.

    2) Das Objekt wird über den .insert Operator an die entsprechende Stelle im Speicher kopiert. Man erstellt also eine tatsächliche Kopie des bereits erstellten Objektes und schiebt es in die Map.


    Das ist im Prinzip, simplifiziert, auch schon die 'Problematik' die man hier sieht. Das ist bei einer Ausführung sicher unproblematisch, bei 100.000 Objekten kann das aber Unterschiede machen. Dafür können wir z.B eine Entity Klasse erstellen und ihm einen Copy Konstruktor geben, dieser wird immer ausgeführt, wenn diese Klasse kopiert wird. Dann können wir diese Zeile mit .emplace schreiben und erklären uns dann gemeinsam den Unterschied bzw. leiten uns den her. Denn nur zu Wissen, dass es so 'performanter' ist macht euch nicht wirklich schlauer. Ihr müsst (so wie ich) die Hintergründe davon verstehen und dazulernen.



    So, zunächst versuchen wir es über die 'alte' Variante mit push_back:


    Code
    1. std::vector<Entity> vec;
    2. vec.push_back(Entity());
    3. vec.push_back(Entity());


    Was passiert hier? Es werden jeweils 2 Entity Objekte erstellt und dann nochmal für das .push_back im Speicher kopiert. Mit unserer oberen Klasse werden wir im Code nachverfolgen können, was in diesen drei Zeilen passiert.


    Ich hoffe die nächsten Sätze ergeben nun Sinn, machen wir das ganze Beispiel nun mit emplace_back:


    Code
    1. std::vector<Entity> vec;
    2. vec.emplace_back();
    3. vec.emplace_back();


    emplace_back erstellt das Objekt Entity direkt an der Stelle im Vektor, wo es am Ende hinkopiert werden würde. Im Stackframe der Mainfunktion landet dieses Objekt gar nicht erst. Wie sieht unser Output nun aus?



    Jetzt hört sich das vielleicht so an wie Meckern auf hohem Niveau, aber das kann einen extremen Unterschied machen. Ich kenne auch keinen Compiler, der das von sich aus optimieren würde, da sind tatsächlich wir als Entwickler gefragt.


    Hier noch ein kleiner Bonus damit das Thema "Vector Optimizing" vollständig ist:


    mit


    Code
    1. std::vector<Entity> vec;
    2. vec.reserve(2);
    3. ...


    kann ich mir das eine Objekt was durchs Rearranging entstand auch noch sparen. Ich sage dem Vektor bevor irgendetwas hinzugefügt wird einfach, wie viele Elemente ich erwarte. Dadurch kann er den Vektor direkt groß genug machen. So sieht dann unser Output aus, den ihr euch nun mit dem erlernten Wissen selbst erklären könnt:


    Code
    1. Created entity!
    2. Created entity!
    3. Destroyed entity!
    4. Destroyed entity!

    Ich denke, für jeden der hier auf Fehlersuche gehen wird, dir LUA Parts komplett neu schreiben wird + die Server Part Geschichte für dich wieder in Ordnung bringt, kannst du den Itemshop auch komplett neu erwerben beim Creator.

    Seh ich auch so.