Startseite »
Forum »
RPG-Studio.org - Skripte, Ressourcen & Tutorials »
Tutorial-Bereich »
RGSS / RGSS 2 / RGSS 3 / jsPlugins »
F12 - Neustarten des Projekts ohne Komplikationen
Von dem Problem hört man ja oft. Man drückt in seinem Projekt die F12 Taste und bekommt prompt folgende Fehlermeldung:
Auch bekannt als Alias-Bug. Die Bezeichnung finde ich etwas unpassend, weil es eigentlich überhaupt kein Bug ist. Aliasing verhält sich genau so wie es sollte. Das Problem liegt woanders. Ein Drücken der F12 Taste löst den Neustart des Spiels aus. Dies geschieht, in dem der Scripteditor als Ganzes neu eingelesen und ausgeführt wird. Das wäre alles nicht weiter tragisch, würde auch der Interpreter neu gestartet werden. Das passiert aber nicht. Das heißt: Der gesamte Inhalt des Programms, alle Variablen, Objekte, Klassen, bleibt erhalten. Nur der Programmcode wird neu ausgeführt. Das führt zu unzähligen möglichen Problemen. Der SystemStackError ist nur einer davon, aber vermutlich der häufigste.
Angenommen man hat eine Methode foo. Man aliast diese unter einem neuen Namen foo_copy. Nun ersetzt man foo und fügt dort einen eigenen Code plus einen Aufruf von foo_copy ein. Dies ist eine typische Vorgehensweise beim Schreiben von Scripten, da man dadurch eigenen Code in Methoden einfügen kann, ohne dabei den Code anderer Scripte (die hoffentlich auch alias nutzen) zu überschreiben. Das Problem: Führt man noch einmal einen alias auf foo zu foo_copy aus, so erhält man eine Methode foo_copy, welche denselben Programmcode wie das zuvor veränderte foo enthält, vor allem den Aufruf von foo_copy. Man erhält also eine Methode die sich selbst aufruft. Das führt zu einer Endlosrekursion, bis irgendwann der Interpreter die Schnauze voll hat und das Programm mit einem SystemStackError beendet.
Wie verhindert man das? Am häufigsten trifft man folgendes Programmkonstrukt:
Noch hässlicher:
Mir gefallen beide Konstrukte nicht. Warum? Weil sie das Problem an der falschen Stelle angehen. Wenn es ins Haus reinregnet, weil das Dach kaputt ist, kann man zwar einen Eimer hinstellen. Das hilft für's erste. Aber besser ist natürlich, man lässt das Dach reparieren. Kurzum: Das Problem an der Wurzel packen.
Was ist die Wurzel? Na, das Neuladen des Scripteditors! Als erstes sollte man sich die Frage stellen, wie der Rubyinterpreter das überhaupt macht. Das Neustarten des Scripteditors ohne den Interpreter selbst neuzustarten. Das ist nämlich keinesfalls trivial. Er muss praktisch aus dem Script rausspringen. Das ließe sich über Threads lösen (auf eine sehr hässliche Art und Weise), aber die naheliegenste Lösung ist ein Sprungbefehl, der aus dem Programmcode rausspringt. Nun gibt es in Ruby nur einen einzigen Sprungbefehl, der sowas vermag: Die Exceptions! Also habe ich einfach mal geprüft ob beim Drücken der F12 Taste eine Exception geworfen wird. Zu diesem Zweck muss man einfach dem Main-Script sagen, es soll alle Exceptions abfangen, und beim Wurf einer Exception dessen Namen ausgeben. Und tatsächlich: Das Drücken der F12-Taste wirft die Exception Reset. Nun kann man sich darüber ärgern, dass dieser Umstand wie so vieles nicht in der Dokumentation des Makers zu finden ist. Man kann sich aber auch freuen, dass das Problem nun so einfach zu lösen ist.
Edit: Oder auch nicht ^^ Die Reset-Exception Klasse wird erst erzeugt, wenn sie gebraucht wird. Darum kommt es zum NameError wenn man das Programm über eine andere Exception verlässt. Scheinbar macht es aber auch nichts aus, wenn man sie vorher selbst manuell erzeugt. Das zweite Problem ist eher kosmetischer Natur: Die Sprites werden nicht disposed -_- Hier muss man manuell nachhelfen und alle Sprites per Hand löschen. Das habe ich, etwas unsauber, über den ObjectSpace gelöst. Auch hier wieder was neues gelernt: Obwohl in der RGSS-Doku steht, dass Viewport die Methode disposed? enthält, existiert diese Methode nicht ^^
Hier also die korrigierte, jetzt nicht mehr so hübsch aussehende Version. Sie sollte jetzt aber hoffentlich ohne Komplikationen funktionieren:
Damit müsste die F12 Funktion jetzt wieder einwandfrei funktionieren, ohne dass es zu Komplikationen mit den Scripten kommt. Wer die F12 Taste nicht braucht, kann natürlich diesen unschönen Hack mit dem ObjektSpace weglassen und das retry entfernen. Dann wird das Programm beim Drücken von F12 ganz normal beendet.
Ich weiß nicht ob das jedem bekannt ist (eventuell gähnt der eine oder andere beim Lesen des Threads) - Anlass für den Thread war eine entsprechende Anfrage im Quartier, bei der ich der Sache nachgegangen bin. Eventuell interessiert die Lösung ja auch hier den einen oder anderen.
|
|
Quellcode |
1 |
SystemStackError: stack level too deep |
Auch bekannt als Alias-Bug. Die Bezeichnung finde ich etwas unpassend, weil es eigentlich überhaupt kein Bug ist. Aliasing verhält sich genau so wie es sollte. Das Problem liegt woanders. Ein Drücken der F12 Taste löst den Neustart des Spiels aus. Dies geschieht, in dem der Scripteditor als Ganzes neu eingelesen und ausgeführt wird. Das wäre alles nicht weiter tragisch, würde auch der Interpreter neu gestartet werden. Das passiert aber nicht. Das heißt: Der gesamte Inhalt des Programms, alle Variablen, Objekte, Klassen, bleibt erhalten. Nur der Programmcode wird neu ausgeführt. Das führt zu unzähligen möglichen Problemen. Der SystemStackError ist nur einer davon, aber vermutlich der häufigste.
Angenommen man hat eine Methode foo. Man aliast diese unter einem neuen Namen foo_copy. Nun ersetzt man foo und fügt dort einen eigenen Code plus einen Aufruf von foo_copy ein. Dies ist eine typische Vorgehensweise beim Schreiben von Scripten, da man dadurch eigenen Code in Methoden einfügen kann, ohne dabei den Code anderer Scripte (die hoffentlich auch alias nutzen) zu überschreiben. Das Problem: Führt man noch einmal einen alias auf foo zu foo_copy aus, so erhält man eine Methode foo_copy, welche denselben Programmcode wie das zuvor veränderte foo enthält, vor allem den Aufruf von foo_copy. Man erhält also eine Methode die sich selbst aufruft. Das führt zu einer Endlosrekursion, bis irgendwann der Interpreter die Schnauze voll hat und das Programm mit einem SystemStackError beendet.
Wie verhindert man das? Am häufigsten trifft man folgendes Programmkonstrukt:
|
|
Ruby Quellcode |
1 2 3 |
class MeineKlasse alias eine_methode_copy eine_methode unless method_defined? :eine_methode_copy end |
Noch hässlicher:
|
|
Ruby Quellcode |
1 2 3 4 |
class MeineKlasse alias eine_methode_copy eine_methode unless $eine_methode_copy_defined $eine_methode_copy_defined = true end |
Mir gefallen beide Konstrukte nicht. Warum? Weil sie das Problem an der falschen Stelle angehen. Wenn es ins Haus reinregnet, weil das Dach kaputt ist, kann man zwar einen Eimer hinstellen. Das hilft für's erste. Aber besser ist natürlich, man lässt das Dach reparieren. Kurzum: Das Problem an der Wurzel packen.
Was ist die Wurzel? Na, das Neuladen des Scripteditors! Als erstes sollte man sich die Frage stellen, wie der Rubyinterpreter das überhaupt macht. Das Neustarten des Scripteditors ohne den Interpreter selbst neuzustarten. Das ist nämlich keinesfalls trivial. Er muss praktisch aus dem Script rausspringen. Das ließe sich über Threads lösen (auf eine sehr hässliche Art und Weise), aber die naheliegenste Lösung ist ein Sprungbefehl, der aus dem Programmcode rausspringt. Nun gibt es in Ruby nur einen einzigen Sprungbefehl, der sowas vermag: Die Exceptions! Also habe ich einfach mal geprüft ob beim Drücken der F12 Taste eine Exception geworfen wird. Zu diesem Zweck muss man einfach dem Main-Script sagen, es soll alle Exceptions abfangen, und beim Wurf einer Exception dessen Namen ausgeben. Und tatsächlich: Das Drücken der F12-Taste wirft die Exception Reset. Nun kann man sich darüber ärgern, dass dieser Umstand wie so vieles nicht in der Dokumentation des Makers zu finden ist. Man kann sich aber auch freuen, dass das Problem nun so einfach zu lösen ist.
Edit: Oder auch nicht ^^ Die Reset-Exception Klasse wird erst erzeugt, wenn sie gebraucht wird. Darum kommt es zum NameError wenn man das Programm über eine andere Exception verlässt. Scheinbar macht es aber auch nichts aus, wenn man sie vorher selbst manuell erzeugt. Das zweite Problem ist eher kosmetischer Natur: Die Sprites werden nicht disposed -_- Hier muss man manuell nachhelfen und alle Sprites per Hand löschen. Das habe ich, etwas unsauber, über den ObjectSpace gelöst. Auch hier wieder was neues gelernt: Obwohl in der RGSS-Doku steht, dass Viewport die Methode disposed? enthält, existiert diese Methode nicht ^^
Hier also die korrigierte, jetzt nicht mehr so hübsch aussehende Version. Sie sollte jetzt aber hoffentlich ohne Komplikationen funktionieren:
|
|
Ruby Quellcode |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#============================================================================== # ** Main #------------------------------------------------------------------------------ # After defining each class, actual processing begins here. #============================================================================== class Reset < Exception end module RGSSGraphicsObject end [Viewport, Sprite, Plane, Window, Tilemap].each() {|klass| klass.send(:include, RGSSGraphicsObject) } begin # Prepare for transition Graphics.freeze # Make scene object (title screen) $scene = Scene_Title.new # Call main method as long as $scene is effective while $scene != nil $scene.main end # Fade out Graphics.transition(20) rescue Errno::ENOENT # Supplement Errno::ENOENT exception # If unable to open file, display message and end filename = $!.message.sub("No such file or directory - ", "") print("Unable to find file #{filename}.") rescue Reset Graphics.freeze ObjectSpace.each_object(RGSSGraphicsObject) {|v| v.dispose if v.respond_to?(:dispose) && (!v.respond_to?(:disposed?) || !v.disposed?) } RPG::Cache.clear GC.start retry end |
Damit müsste die F12 Funktion jetzt wieder einwandfrei funktionieren, ohne dass es zu Komplikationen mit den Scripten kommt. Wer die F12 Taste nicht braucht, kann natürlich diesen unschönen Hack mit dem ObjektSpace weglassen und das retry entfernen. Dann wird das Programm beim Drücken von F12 ganz normal beendet.
Ich weiß nicht ob das jedem bekannt ist (eventuell gähnt der eine oder andere beim Lesen des Threads) - Anlass für den Thread war eine entsprechende Anfrage im Quartier, bei der ich der Sache nachgegangen bin. Eventuell interessiert die Lösung ja auch hier den einen oder anderen.
Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von »=Kai=« (23. Dezember 2010, 15:26) aus folgendem Grund: Blöde verbuggte RGSS ^^
Benutzerinformationen überspringen
Motto: Wer anderen eine Bratwurst brät, der hat ein Bratwurstbratgerät.
Das ist natürlich interessant :o
Allerdings hätte ich das hier:
anders gelöst, nämlich einfach bei der Erstellung eines solchen Objekts zu einem Array hinzufügen und beim disposen wieder daraus entfernen.
Weil so werden Viewports (und Tilemaps ?) doch nicht gelöscht, oder?
Übrigens könnte es auch sinnvoll sein, vorher noch RPG::Cache.clear aufzurufen und Bitmap zu der Liste hinzuzufügen.
Es scheint nämlich so, dass ungelöschte Bitmaps Memory Leaks verursachen.
Allerdings hätte ich das hier:
|
|
Ruby Quellcode |
1 2 3 4 5 |
module RGSSGraphicsObject end [Viewport, Sprite, Plane, Window, Tilemap].each() {|klass| klass.send(:include, RGSSGraphicsObject) } |
anders gelöst, nämlich einfach bei der Erstellung eines solchen Objekts zu einem Array hinzufügen und beim disposen wieder daraus entfernen.
Weil so werden Viewports (und Tilemaps ?) doch nicht gelöscht, oder?
Übrigens könnte es auch sinnvoll sein, vorher noch RPG::Cache.clear aufzurufen und Bitmap zu der Liste hinzuzufügen.
Es scheint nämlich so, dass ungelöschte Bitmaps Memory Leaks verursachen.
Interessant .-.
Das der Druck auf F12 die Reset-Exception wirft, war mir schon bekannt, aus dem RGSS-Player Modul Script, wo man F12 und F1 deaktivieren kann.
Neos Einwand zu den Bitmaps teile ich, wieso hast Du die nicht auch in deine Verwaltung aufgenommen?
Aber der Text ist gut geschrieben und verständlich, aber für ein Tutorial hätte ich mir ein bisschen mehr Hintergrundwissen und Erklärung deiner Korrektur gewünscht.
Das der Druck auf F12 die Reset-Exception wirft, war mir schon bekannt, aus dem RGSS-Player Modul Script, wo man F12 und F1 deaktivieren kann.
Neos Einwand zu den Bitmaps teile ich, wieso hast Du die nicht auch in deine Verwaltung aufgenommen?
Aber der Text ist gut geschrieben und verständlich, aber für ein Tutorial hätte ich mir ein bisschen mehr Hintergrundwissen und Erklärung deiner Korrektur gewünscht.
Das große Scientia Wiki zur Spielentwicklung 
Was ist das RGSS ? RGSS-Dokumentation auf Sc
Kyoshiros Makerkurs
Musik von Shabraxxx für euch
Guide zu den Audioformaten
Skripte von mir (Auswahl):
Atmungssystem
| Streichholzsystem
| Animiert durch Bücher blättern
Random : Marktsystem für Kardor
| Staterelated Battlergraphic
| Hinweis auf mögliche Aktionen
SelfSwitchExpirationtimer Skript - Gameplayerweiterung für Pilzesammler und Farmspiele
Meine Skripte werden gerade hier gesammelt.

Was ist das RGSS ? RGSS-Dokumentation auf Sc
Kyoshiros Makerkurs

Musik von Shabraxxx für euch
Guide zu den Audioformaten

Skripte von mir (Auswahl):
Atmungssystem
| Streichholzsystem
| Animiert durch Bücher blättern
Random : Marktsystem für Kardor
| Staterelated Battlergraphic
| Hinweis auf mögliche Aktionen
SelfSwitchExpirationtimer Skript - Gameplayerweiterung für Pilzesammler und Farmspiele
Meine Skripte werden gerade hier gesammelt.Äh, nee, der Code fügt einfach nur ein leeres Modul in alle RGSS-Grafikklassen ein, so dass man sie leicht über den ObjectSpace iterieren kann. Im ObjektSpace rufe ich dann die dispose Methode auf. Die Objekte werden ja sowieso gelöscht (gibt ja keine Referenz mehr darauf). Nur das dauert eben, bis der GC sich dazu bequemt.
Zitat
nämlich einfach bei der Erstellung eines solchen Objekts zu einem Array hinzufügen und beim disposen wieder daraus entfernen.
Das mit dem Array ist nur bedingt eine Alternative. Denn der Array würde zugleich dafür sorgen, dass die Objekte vom GC nicht mehr gelöscht werden. Das wäre dann ein fataler MemoryLeak. An der Stelle könnte man mit WeakRef arbeiten (Pointer, die vom GC nicht berücksichtigt werden und so Objekte referenzieren können, ohne diese am Leben zu halten). Aber ich finde da die Variante mit dem ObjectSpace ehrlich gesagt leichter (elegant sind sie beide nicht).
Gute Idee. Das sollte man hinzufügen.
Zitat
Übrigens könnte es auch sinnvoll sein, vorher noch RPG::Cache.clear aufzurufen und Bitmap zu der Liste hinzuzufügen.
Es scheint nämlich so, dass ungelöschte Bitmaps Memory Leaks verursachen.
Weil es dann passieren kann, dass Bitmaps vor ihren Eigentümern gelöscht werden. Wenn die Eigentümer in ihrer dispose Methode dann auf das Bitmap zugreifen (warum sollten sie sowas bescheuertes machen? Na das fragt mal die Macher der RGSS -_-) gibt es eine Exception. Man kann natürlich nach dem Löschen der Viewports und Sprites immer noch die Bitmaps löschen. Aber wie bereits gesagt: Die werden vom GC irgendwann eh gelöscht. Der ganze Sinn des Löschens der Grafikobjekte war nicht den Speicher freizuräumen, sondern lediglich die Grafikartefakte zu entfernen, die erscheinen, wenn man im Spiel oft F12 drückt.
Zitat
Neos Einwand zu den Bitmaps teile ich, wieso hast Du die nicht auch in deine Verwaltung aufgenommen?
Das ist richtig. Eigentlich isses auch weniger ein Tutorial, sondern eher ein Hinweis. Ich wusste nur nicht, wohin mit dem Thread. Eventuell kann ja ein Mod den in den Alias-Thread reinschieben.
Zitat
aber für ein Tutorial hätte ich mir ein bisschen mehr Hintergrundwissen und Erklärung deiner Korrektur gewünscht.
btw. noch eine alternative Lösung, die weniger hacky ist:
Man fügt am Anfang des Scripteditors folgenden Code ein:
|
|
Ruby Quellcode |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
Main.start if defined? Main module Main def self.start # Prepare for transition Graphics.freeze # Make scene object (title screen) $scene = Scene_Title.new # Call main method as long as $scene is effective while $scene != nil $scene.main end # Fade out Graphics.transition(20) # exit program exit() rescue Errno::ENOENT # Supplement Errno::ENOENT exception # If unable to open file, display message and end filename = $!.message.sub("No such file or directory - ", "") print("Unable to find file #{filename}.") end end |
Und nun fügt man an Stelle seines Main-Scripts einfach den Code
|
|
Ruby Quellcode |
1 |
Main.start |
ein. Dann wird der Scripteditor ebenfalls nicht neu eingelesen, weil schon im obersten Script das Spiel gestartet wird. Dafür hat der Interpreter die Gelegenheit alle Grafiken auf saubere Art und Weise zu löschen.
Ähnliche Themen
-
Computer & Technik »-
Rpg Maker VX Lädt zu lange.
(16. Dezember 2010, 14:30)
-
Computer & Technik »-
Rpg Maker VX Lädt zu lange.
(16. Dezember 2010, 14:30)
-
Skript-Anfragen »-
Name selbst schreiben
(19. Dezember 2010, 18:43)
-
Maker-Talk »-
Keine Schrift mehr beim Rm2k?!
(26. Juni 2009, 13:58)
-
(Alt) Smalltalk »-
2 probs mit pc
(25. Mai 2005, 10:34)


