Unterschied getXMLController() <---> getController()
Moderator: PPS-Leaders
Unterschied getXMLController() <---> getController()
Hallo liebe Orxonoxler
Ich bin dabei endlich das Tower Defense Spiel zu überarbeiten. Neu ist es möglich über XML ein eigenes Tower Defense level zu beschreiben. Dabei beschreibt man für jedes der 16x16 Felder den Typ des Feldes:
- Freies Feld
- Strasse auf der die Gegner geradeaus laufen
- Strasse auf der die Gegner rechts/links abbiegen
- Start der Gegner
- Ziel der Gegner
- Ein Turm mit Level 1/2/3/4/5
Dies funktioniert auch und die Gegner folgen der im XML-Level-File definierten Route. Dazu ist es aber nötig im C++ Code auf die WaypointController der Gegner zuzugreifen und Waypoints hinzuzufügen. Allerdings funktioniert dies nur, wenn ich die Funktion ControllableEntity::getXMLController() verwende, nicht aber wenn ich ControllableEntity::getController() verwenden.
Was ist der Unterschied zwischen den beiden Funktionen? Warum ist die Unterscheidung nötig? Schliesslich interessiert es micht nicht, ob ein Controller per XML erstellt wurde oder im C++ Coder mit "new Contoller()".
Ich bin dabei endlich das Tower Defense Spiel zu überarbeiten. Neu ist es möglich über XML ein eigenes Tower Defense level zu beschreiben. Dabei beschreibt man für jedes der 16x16 Felder den Typ des Feldes:
- Freies Feld
- Strasse auf der die Gegner geradeaus laufen
- Strasse auf der die Gegner rechts/links abbiegen
- Start der Gegner
- Ziel der Gegner
- Ein Turm mit Level 1/2/3/4/5
Dies funktioniert auch und die Gegner folgen der im XML-Level-File definierten Route. Dazu ist es aber nötig im C++ Code auf die WaypointController der Gegner zuzugreifen und Waypoints hinzuzufügen. Allerdings funktioniert dies nur, wenn ich die Funktion ControllableEntity::getXMLController() verwende, nicht aber wenn ich ControllableEntity::getController() verwenden.
Was ist der Unterschied zwischen den beiden Funktionen? Warum ist die Unterscheidung nötig? Schliesslich interessiert es micht nicht, ob ein Controller per XML erstellt wurde oder im C++ Coder mit "new Contoller()".
Re: Unterschied getXMLController() <---> getController
Das ist ziemlich sicher einfach ein Hack
Ich kann mich nicht mehr an die Gründe erinnern. Aber wenn man sich setXMLController anschaut, dann macht diese Funktion viele Dinge, die setController so nicht macht. Irgendwas wird hier wohl der Auslöser gewesen sein... damit es einfacher über XML geht? Damit es netzwerktauglich ist? Damit es nicht mit dem normalen Controller interferiert, der vom Gametype gesetzt wird? Keine Ahnung... Vermutlich war es einfach die schnellste Lösung kurz vor der Präsentation
Grundsätzlich hast du aber natürlich völlig recht mit deiner Aussage: Es sollte dich nicht interessieren müssen, woher der Controller stammt.
Was ich allerdings nicht ganz verstehe, ist, warum das bei dir überhaupt ein Problem ist. So wie ich das verstehe, gibt es irgendwo im Code eine Stelle, wo die Tower Defense Enemies erzeugt werden. Dort kann man auch einen WaypointController (inkl. Waypoints) erzeugen und ihn dem Enemy als Controller zuweisen. Fortan solltest du nie wieder auf den WaypointController zugreifen müssen.
Btw: Im öffentlichen Forum wenn möglich auf Englisch schreiben.
Ich kann mich nicht mehr an die Gründe erinnern. Aber wenn man sich setXMLController anschaut, dann macht diese Funktion viele Dinge, die setController so nicht macht. Irgendwas wird hier wohl der Auslöser gewesen sein... damit es einfacher über XML geht? Damit es netzwerktauglich ist? Damit es nicht mit dem normalen Controller interferiert, der vom Gametype gesetzt wird? Keine Ahnung... Vermutlich war es einfach die schnellste Lösung kurz vor der Präsentation
Grundsätzlich hast du aber natürlich völlig recht mit deiner Aussage: Es sollte dich nicht interessieren müssen, woher der Controller stammt.
Was ich allerdings nicht ganz verstehe, ist, warum das bei dir überhaupt ein Problem ist. So wie ich das verstehe, gibt es irgendwo im Code eine Stelle, wo die Tower Defense Enemies erzeugt werden. Dort kann man auch einen WaypointController (inkl. Waypoints) erzeugen und ihn dem Enemy als Controller zuweisen. Fortan solltest du nie wieder auf den WaypointController zugreifen müssen.
Btw: Im öffentlichen Forum wenn möglich auf Englisch schreiben.
Fabian 'x3n' Landau, Orxonox developer
Re: Unterschied getXMLController() <---> getController
Danke für die Antwort! Mit Englisch hat du natürlich Recht.....
Ein wirkliches Problem für das Tower Defense Minispiel ist es nicht. Beim erstellen der Gegner wird ein Template auf die Gegner angewendet. Das Template definiert Leben, Modelle, Partikeleffekte, ..... und eben auch dass der Gegner einen WaypointController hat. Im C++ Code sollen nun die Waypoints gesetzt werden. Dazu rief ich getController() auf und erhielt immer NULL bis ich herausfand, dass ich stattdessen getXMLController() aufrufen sollte. Natürlich könnte ich auch den WaypointController per C++ erzeugen
Ich schlage vor, wir entfernen die unnötige unterscheidung bei Gelgenheit und fusionieren folgende Variablen:
Controller* xmlcontroller_;
WeakPtr<Controller> controller_
Wenn nicht einmal der Autor der Klasse weiss wozu die Unterscheidung nötig war, wird es wohl unwichtig sein.....
Ein wirkliches Problem für das Tower Defense Minispiel ist es nicht. Beim erstellen der Gegner wird ein Template auf die Gegner angewendet. Das Template definiert Leben, Modelle, Partikeleffekte, ..... und eben auch dass der Gegner einen WaypointController hat. Im C++ Code sollen nun die Waypoints gesetzt werden. Dazu rief ich getController() auf und erhielt immer NULL bis ich herausfand, dass ich stattdessen getXMLController() aufrufen sollte. Natürlich könnte ich auch den WaypointController per C++ erzeugen
Ich schlage vor, wir entfernen die unnötige unterscheidung bei Gelgenheit und fusionieren folgende Variablen:
Controller* xmlcontroller_;
WeakPtr<Controller> controller_
Wenn nicht einmal der Autor der Klasse weiss wozu die Unterscheidung nötig war, wird es wohl unwichtig sein.....
Re: Unterschied getXMLController() <---> getController
Ich denke, es macht mehr Sinn, den WaypointController in C++ zu erzeugen. In XML kann man sich künstlerisch austoben und das Aussehen und die Eigenschaften der Gegner definieren. Aber dass sie einen WaypointController haben, hat nichts mit künstlerischer Freiheit zu tun. Im Gegenteil - es wird vom Gametype sogar zwingendermassen vorausgesetzt. Daher macht es auch Sinn, wenn der Gametype den Controller zusammen mit den Waypoints selber erzeugt.
Wegen dem Fusionieren:
Grundsätzlich bin ich einverstanden, es sollte nur eine Variable geben. Aber es kann durchaus sein, dass das momentan gar nicht geht, weil es eben doch irgendwo eine Notwendigkeit dafür gibt. Das wäre aber ein guter Grund, den entsprechenden Code so zu refactoren, dass es anschliessend mit einer Variable funktioniert.
Wegen dem Fusionieren:
Grundsätzlich bin ich einverstanden, es sollte nur eine Variable geben. Aber es kann durchaus sein, dass das momentan gar nicht geht, weil es eben doch irgendwo eine Notwendigkeit dafür gibt. Das wäre aber ein guter Grund, den entsprechenden Code so zu refactoren, dass es anschliessend mit einer Variable funktioniert.
Fabian 'x3n' Landau, Orxonox developer
Re: Unterschied getXMLController() <---> getController
Da hast du Recht. Ich erzeuge den WaypointController jetz per C++.
Leider erzeugt das zerstören der Gegner immer noch folgende:
Error: Received signal SIGABRT
Error: Try to write backtrace to file orxonox_crash.log
Ich versuchte stundenlang mit orxout() herauszufinden welcher pointer auf ein gelöschtes Objekt oder auf NULL zeigt. Leider ohne Erfolg. Es wäre hilfreich, wenn ich das file orxonox_crash.log finden würde. Im Ordner build/log/ ist es jedenfalls nicht und die Suchfunktion findet auch nichts. Das File gibt es unter Windows nicht, aber ich verwende ja Debian.....
Leider erzeugt das zerstören der Gegner immer noch folgende:
Error: Received signal SIGABRT
Error: Try to write backtrace to file orxonox_crash.log
Ich versuchte stundenlang mit orxout() herauszufinden welcher pointer auf ein gelöschtes Objekt oder auf NULL zeigt. Leider ohne Erfolg. Es wäre hilfreich, wenn ich das file orxonox_crash.log finden würde. Im Ordner build/log/ ist es jedenfalls nicht und die Suchfunktion findet auch nichts. Das File gibt es unter Windows nicht, aber ich verwende ja Debian.....
Re: Unterschied getXMLController() <---> getController
Hm, das File müsste dort liegen, wo auch die anderen Logfiles sind.
Der Stacktrace müsste aber nicht nur ins File, sondern auch in die Konsole geschrieben werden. Wenn das nicht der Fall ist, ist der Debugger wohl etwas überfordert.
Kannst du deinen Code mal in einem Branch committen (oder mir einen Patch schicken/hochladen)? Dann kann ich mal einen Blick drauf werfen.
Der Stacktrace müsste aber nicht nur ins File, sondern auch in die Konsole geschrieben werden. Wenn das nicht der Fall ist, ist der Debugger wohl etwas überfordert.
Kannst du deinen Code mal in einem Branch committen (oder mir einen Patch schicken/hochladen)? Dann kann ich mal einen Blick drauf werfen.
Fabian 'x3n' Landau, Orxonox developer
Re: Unterschied getXMLController() <---> getController
Der ganze Code ist jetzt in folgendem branch: http://svn.orxonox.net/game/code/branch ... nseFabien/
Ich finde die Dateien orxonox.log, orxonox.log1, ... Eine Datei orxonox_crash.log gibt es aber nicht. Die Suchfunktion findet die Datei auch nicht.
Beim löschen der Gegner (durch abschiessen oder wenn sie das Ziel erreicht haben) wird folgender Fehler ausgageben.
Error: Received signal SIGABRT
Error: Try to write backtrace to file orxonox_crash.log
"Try to write" heisst ja nicht, dass das auch gelingt.....
Den WaypointContoller per C++ erzeugen funktioniert nicht wie erwartet. Kannst du mal ausprobieren, wenn du in TowerDefense.cc die Zeilen 194, 195, 196 auskommentierst und 198 löschst.
Ich finde die Dateien orxonox.log, orxonox.log1, ... Eine Datei orxonox_crash.log gibt es aber nicht. Die Suchfunktion findet die Datei auch nicht.
Beim löschen der Gegner (durch abschiessen oder wenn sie das Ziel erreicht haben) wird folgender Fehler ausgageben.
Error: Received signal SIGABRT
Error: Try to write backtrace to file orxonox_crash.log
"Try to write" heisst ja nicht, dass das auch gelingt.....
Den WaypointContoller per C++ erzeugen funktioniert nicht wie erwartet. Kannst du mal ausprobieren, wenn du in TowerDefense.cc die Zeilen 194, 195, 196 auskommentierst und 198 löschst.
Re: Unterschied getXMLController() <---> getController
Wegen dem Crash-Log: Das kann schon sein, dass der Stacktrace nicht erzeugt werden kann. Du kannst dir den Code in SignalHandler.cc anschauen (in der util Library). Es wird versucht, eine GDB-Instanz an den laufenden Orxonox-Prozess zu attachen, um den Stacktrace zu erzeugen (unter Linux). Dabei kann natürlich einiges schief gehen.
Zu deinem eigentlichen Problem:
Ich habe das Spiel unter Windows mit Visual Studio debugged und ein paar Dinge gefixt und committed.
1) std::list statt std::vector
Das erste, worüber sich der Debugger beschwert hat, war ein kaputter Iterator in TowerDefense::tick und zwar in jenem Loop, in dem die Enemies zerstört werden, wenn sie am Ende des Pfades angelangt sind. Grundsätzlich war es dort schon einmal komisch, dass zuerst vector.erase(it) aufgerufen wurde und danach it->destroy(). Im Grunde wurde also auf ein ungültiger Iterator zugegriffen. Wenn man das gefixt hat, war der Debugger immer noch nicht glücklich damit, dass mitten im Loop Elemente aus dem Vector gelöscht wurden. Generell ist es etwas heikel, wenn man mit std::vector arbeitet und Elemente entfernt. Der vector ist vor allem dann geeignet, wenn man eine fixe Anzahl Elemente hat. Beispielsweise immer 20 Gegner, dann kann man von enemies[0] bis enemies[19] direkt darauf zugreifen. Wenn man aber einzelne Elemente hinzufügen oder entfernen muss, dann ist std::list besser geeignet.
Ich habe deshalb enemies_ so angepasst, dass es jetzt std::list statt std::vector ist. Damit war der Debugger erstmal zufrieden.
Übrigens: Es kann gut sein, dass das bei dir gar kein Problem war. Zum Beispiel dann, wenn der Iterator zwar eigentlich ungültig ist, aber in seinem Speicherbereich immer noch gültige Pointer existieren. Visual Studio hat im Debug-Modus aber ein paar zusätzliche Checks, die in solchen Fällen Alarm schlagen.
Commit: http://www.orxonox.net/changeset/10587
2) Zerstörte Waypoints
Sobald ein Gegner am Ende des Pfades angelangt ist und zerstört wurde, trat der nächste Fehler auf. Der Grund ist, dass (vermutlich aufgrund eines Flüchtigkeitsfehlers) alle WaypointController dieselben Waypoints hatten. Da aber jeder WaypointController, wenn er zerstört wird, seine Waypoints ebenfalls zerstört, heisst das, dass alle anderen WaypointController plötzlich eine Liste von zerstörten Waypoints besassen, was unweigerlich zum Crash führt.
Ich habe dann gesehen, dass in TowerDefense::addTowerDefenseEnemy eigentlich bereits eigene Waypoints für jeden Controller erzeugt werden. Allerdings wurden diese gar nicht verwendet, die Funktion hat also bloss ein Memory-Leak erzeugt.
Ich habe den Code jetzt so angepasst, dass der neu erzeugte Waypoint auch tatsächlich verwendet wird. Somit funktioniert nun auch das Zerstören der Enemies am Ende des Pfades.
Commit: http://www.orxonox.net/changeset/10588
3) Tower schiesst Enemy ab
Das war aber noch nicht alles, denn wenn ein Tower ein Enemy abgeschossen hat, gabs direkt den nächsten Crash. Der Grund dafür ist, dass TowerDefense eine Liste von allen Enemies hat (die Liste aus Punkt 1 oben). Wenn einer abgeschossen wird, ist der Pointer in der Liste nicht mehr gültig. Da (praktischerweise) eine Liste von WeakPtr verwendet wird, kann man mit einem NULL-Check prüfen, ob der Enemy noch existiert (und falls nicht, kann man ihn aus der Liste entfernen).
Ich habe diesen Check dementsprechend hinzugefügt.
Commit: http://www.orxonox.net/changeset/10589
----
Und nun zum Problem mit dem Controller:
Lustigerweise funktioniert der Code, wenn man auf Zeile 196 anstelle von setController setXMLController aufruft. Der Unterschied liegt darin, dass setXMLController auch noch bHasLocalController_ auf true setzt. Und wenn du dir SpaceShip::tick anschaust, dann siehst du, dass sich ein SpaceShip nur dann bewegt, wenn diese Variable true ist (und die Towerdefense-Enemies erben von SpaceShip). Der Grund dafür ist die Netzwerktauglichkeit - ein Schiff ist zwar auf allen Clients sichtbar, aber nur ein Spieler (derjenige mit dem local controller) darf es steuern. (Oder, im Falle von Bots, darf es nur der Server steuern.)
Mein Vorschlag ist, dass du setXMLController aufrufst - oder einen public Setter für bHasLocalController_ machst (aber das ist vielleicht ein bisschen heikel).
Übrigens: Wegen dem Unterschied zwischen getController und getXMLController ist mir noch etwas eingefallen. Ein Controller ist in Orxonox normalerweise eine langlebige Sache. Beispielsweise hat der Spieler einen Controller (NewHumanController) oder jeder Bot (FormationController). Diese Controller bleiben das ganze Spiel über "am Leben". Der Controller steuert dabei manchmal ein Schiff, manchmal eine Specator-Kamera. Dann wird das Schiff zerstört, er kriegt ein neues, etc. Grundsätzlich kann man also sagen, dass der Controller unabhängig vom ControllableEntity existiert.
Beim XML-Controller ist das anders: Er wurde im Level-File erzeugt und hat nur eine Aufgabe, nämlich das ihm zugewiesene Schiff zu steuern. Wenn das Schiff zerstört wurde, kriegt der Controller kein neues Schiff mehr - er ist tot. Deshalb wird in diesem Fall der XML-Controller zerstört.
Du kannst dir das im Destructor von ControllableEntity anschauen: Der XML-Controller wird ebenfalls zerstört, der normale Controller hingegen nicht.
In meinen Augen ist das aber eine unglückliche Lösung. Es wäre trotz allem besser, nur einen Controller zu haben. Um den "XML-Controller" automatisch zu zerstören, wenn das Schiff zerstört wurde, gäbe es bessere Alternativen.
Zu deinem eigentlichen Problem:
Ich habe das Spiel unter Windows mit Visual Studio debugged und ein paar Dinge gefixt und committed.
1) std::list statt std::vector
Das erste, worüber sich der Debugger beschwert hat, war ein kaputter Iterator in TowerDefense::tick und zwar in jenem Loop, in dem die Enemies zerstört werden, wenn sie am Ende des Pfades angelangt sind. Grundsätzlich war es dort schon einmal komisch, dass zuerst vector.erase(it) aufgerufen wurde und danach it->destroy(). Im Grunde wurde also auf ein ungültiger Iterator zugegriffen. Wenn man das gefixt hat, war der Debugger immer noch nicht glücklich damit, dass mitten im Loop Elemente aus dem Vector gelöscht wurden. Generell ist es etwas heikel, wenn man mit std::vector arbeitet und Elemente entfernt. Der vector ist vor allem dann geeignet, wenn man eine fixe Anzahl Elemente hat. Beispielsweise immer 20 Gegner, dann kann man von enemies[0] bis enemies[19] direkt darauf zugreifen. Wenn man aber einzelne Elemente hinzufügen oder entfernen muss, dann ist std::list besser geeignet.
Ich habe deshalb enemies_ so angepasst, dass es jetzt std::list statt std::vector ist. Damit war der Debugger erstmal zufrieden.
Übrigens: Es kann gut sein, dass das bei dir gar kein Problem war. Zum Beispiel dann, wenn der Iterator zwar eigentlich ungültig ist, aber in seinem Speicherbereich immer noch gültige Pointer existieren. Visual Studio hat im Debug-Modus aber ein paar zusätzliche Checks, die in solchen Fällen Alarm schlagen.
Commit: http://www.orxonox.net/changeset/10587
2) Zerstörte Waypoints
Sobald ein Gegner am Ende des Pfades angelangt ist und zerstört wurde, trat der nächste Fehler auf. Der Grund ist, dass (vermutlich aufgrund eines Flüchtigkeitsfehlers) alle WaypointController dieselben Waypoints hatten. Da aber jeder WaypointController, wenn er zerstört wird, seine Waypoints ebenfalls zerstört, heisst das, dass alle anderen WaypointController plötzlich eine Liste von zerstörten Waypoints besassen, was unweigerlich zum Crash führt.
Ich habe dann gesehen, dass in TowerDefense::addTowerDefenseEnemy eigentlich bereits eigene Waypoints für jeden Controller erzeugt werden. Allerdings wurden diese gar nicht verwendet, die Funktion hat also bloss ein Memory-Leak erzeugt.
Ich habe den Code jetzt so angepasst, dass der neu erzeugte Waypoint auch tatsächlich verwendet wird. Somit funktioniert nun auch das Zerstören der Enemies am Ende des Pfades.
Commit: http://www.orxonox.net/changeset/10588
3) Tower schiesst Enemy ab
Das war aber noch nicht alles, denn wenn ein Tower ein Enemy abgeschossen hat, gabs direkt den nächsten Crash. Der Grund dafür ist, dass TowerDefense eine Liste von allen Enemies hat (die Liste aus Punkt 1 oben). Wenn einer abgeschossen wird, ist der Pointer in der Liste nicht mehr gültig. Da (praktischerweise) eine Liste von WeakPtr verwendet wird, kann man mit einem NULL-Check prüfen, ob der Enemy noch existiert (und falls nicht, kann man ihn aus der Liste entfernen).
Ich habe diesen Check dementsprechend hinzugefügt.
Commit: http://www.orxonox.net/changeset/10589
----
Und nun zum Problem mit dem Controller:
Lustigerweise funktioniert der Code, wenn man auf Zeile 196 anstelle von setController setXMLController aufruft. Der Unterschied liegt darin, dass setXMLController auch noch bHasLocalController_ auf true setzt. Und wenn du dir SpaceShip::tick anschaust, dann siehst du, dass sich ein SpaceShip nur dann bewegt, wenn diese Variable true ist (und die Towerdefense-Enemies erben von SpaceShip). Der Grund dafür ist die Netzwerktauglichkeit - ein Schiff ist zwar auf allen Clients sichtbar, aber nur ein Spieler (derjenige mit dem local controller) darf es steuern. (Oder, im Falle von Bots, darf es nur der Server steuern.)
Mein Vorschlag ist, dass du setXMLController aufrufst - oder einen public Setter für bHasLocalController_ machst (aber das ist vielleicht ein bisschen heikel).
Übrigens: Wegen dem Unterschied zwischen getController und getXMLController ist mir noch etwas eingefallen. Ein Controller ist in Orxonox normalerweise eine langlebige Sache. Beispielsweise hat der Spieler einen Controller (NewHumanController) oder jeder Bot (FormationController). Diese Controller bleiben das ganze Spiel über "am Leben". Der Controller steuert dabei manchmal ein Schiff, manchmal eine Specator-Kamera. Dann wird das Schiff zerstört, er kriegt ein neues, etc. Grundsätzlich kann man also sagen, dass der Controller unabhängig vom ControllableEntity existiert.
Beim XML-Controller ist das anders: Er wurde im Level-File erzeugt und hat nur eine Aufgabe, nämlich das ihm zugewiesene Schiff zu steuern. Wenn das Schiff zerstört wurde, kriegt der Controller kein neues Schiff mehr - er ist tot. Deshalb wird in diesem Fall der XML-Controller zerstört.
Du kannst dir das im Destructor von ControllableEntity anschauen: Der XML-Controller wird ebenfalls zerstört, der normale Controller hingegen nicht.
In meinen Augen ist das aber eine unglückliche Lösung. Es wäre trotz allem besser, nur einen Controller zu haben. Um den "XML-Controller" automatisch zu zerstören, wenn das Schiff zerstört wurde, gäbe es bessere Alternativen.
Fabian 'x3n' Landau, Orxonox developer
Re: Unterschied getXMLController() <---> getController
Vielen Dank für deine ausführliche Antwort!!
1.)
Ich sollte wohl doch wieder Visual Studio verwenden. Der gcc compiler ist leider nicht intelligent genug.
2.)
Das ist tatsächlich ein Flüchtigkeitsfehler. Als ich irgendwann verstand, dass ein WaypointController wenn er zerstört wird auch alle seine Waypoints zerstört, erzeugt ich für jeden Controller separate Waypoints, machte aber wie du erkennt hast einen blöden Fehler.....
Zum Controller-Problem: Deine Erklärung ergibt Sinn. Ich werde wohl oder übel setXMLController verwenden. Wir sollten dazu ein Ticket eröffnen.
1.)
Ich sollte wohl doch wieder Visual Studio verwenden. Der gcc compiler ist leider nicht intelligent genug.
2.)
Das ist tatsächlich ein Flüchtigkeitsfehler. Als ich irgendwann verstand, dass ein WaypointController wenn er zerstört wird auch alle seine Waypoints zerstört, erzeugt ich für jeden Controller separate Waypoints, machte aber wie du erkennt hast einen blöden Fehler.....
Zum Controller-Problem: Deine Erklärung ergibt Sinn. Ich werde wohl oder übel setXMLController verwenden. Wir sollten dazu ein Ticket eröffnen.
Who is online
Users browsing this forum: No registered users and 2 guests