(Symbol) Object::overwrite_method(sym,&b)
Das Problem der Verdammnis
Man kennt es besonders aus der Maker-Szene:
Ständig muss man beim Scripten bestehende Methoden der Klassen Scene_SchlagMichTot erweitern und überschreiben und wasnichtnochalles. Oft findet man hier diese Lösungsansätze:
|
|
Ruby Quellcode |
1 2 |
alias asdfjklö_muhaha_1234567890_foo foo def foo() |
|
|
Ruby Quellcode |
1 |
alias_method(:asdfjklö_muhaha_1234567890_foo,:foo) if (!method_defined?(:asdfjklö_muhaha_1234567890_foo)) |
- Sich einen möglichst bescheuerten Namen für die kopierte Methode ausdenken, damit es zu keiner Kollision kommt, die in einem StackError endet.
- (Prüfen, ob nicht doch irgendwann bereits eine solche Kopie angelegt wurde. Falls ja, ist es damit meist... ja, dumm gelaufen.)
- Sich den ewiglangen, bescheuerten Namen merken oder zwischenspeichern, um diese Methode irgendwo ggf. wieder aufzurufen.
alias und alias_method können durchaus zu sinnvollen Methoden werden, z.B. dann, wenn man möglichst effizient Synonyme für Methoden-Namen erstellen will. Array#map() und Array#collect() z.B. sind völlig identisch. Hier würde es genügen, Array#map() zu implementieren, und dann alias_method(:collect,:map) aufzurufen.
Wenn es jedoch bloß darum geht, Methoden zu erweitern, interessiert vor allem eines nicht: Der Name der Methoden-Kopie, zumal dieser schlimmsten Falls in einer Kollision endet. Und das Bearbeiten von alias_method wäre im schlimmsten Fall mehrmals vorhanden - Man weiß halt nicht immer, was die Scripter der geladenen Scripts so alles verbrochen haben (z.B. im SDK).
Genug getrauert!
Mir war langweilig, weshalb ich eine Lösung für das Problem gesucht und implementiert habe. Es ist nicht so schön, wie es geplant war, jedoch funktioniert es ebenso gut, wie es sein sollte. Bevor ich euch mit Quellcode und Erklärungen erschlage, präsentiere ich zunächst zwei Beispiel-Codes für die Verwendung der neuen Methode (die absolut nichts Bestehendes in Ruby verändert).
|
|
Ruby Quellcode |
1 2 3 4 5 6 7 |
# So war es geplant: overwrite_method(:foo) \ {|| neuer_code() old_method() # <== das sollte eine Art neues super() werden, das ebenfalls in jeder Methode gleich heißt neuer_code() } |
|
|
Ruby Quellcode |
1 2 3 4 5 6 7 8 9 |
# So ist es geworden: overwrite_method(:foo) \ {|old| # <== dieser Name entspricht nicht der Methoden-Kopie, wird jedoch behandelt, als wäre dem so proc {|| neuer_code() send(old) # <== leider kein Pseudo-Schlüsselwort neuer_code() } } |
Ich denke, dass dies die weit elegantere Lösung für diese Problematik ist. Wen es interessiert, wie die Methoden-Kopie genannt wurde (intern wird nach wie vor alias_method verwendet), der kann sich freuen, dass ich an ihn/sie gedacht habe: Der Rückgabewert von overwrite_method ist ein Symbol, das dem Namen der Methoden-Kopie entspricht.
Wie funktioniert der Kram?
Der Parameter von overwrite_method ist ein Symbol, das dem Namen der zu überschreibenden Methode entspricht. Intern wird dann ein kollisionssicherer Ersatzname generiert und die Methode als private Methode kopiert. Man beachte: Die Kopie ist privat! overwrite_method wird zudem ein Block mit genau einem Parameter übergeben. Der Name wird als Symbol diesem Block übergeben. Der Rückgabewert des Blocks ist ein Lambda oder Proc -Objekt. Empfohlen werden Procs, da diese - Im Gegensatz zu Lambdas - vollständig mit Methoden kompatibel sind. In die Proc/den Lambda kommt dann der Code der neuen Methode hinein. Über send(<parameter_des_blocks>,*args,&block) wird dann die alte Version der Methode aufgerufen. Das Proc-/Lambda-Objekt wird anschließend zu einer Methode konvertiert.
Ein - halbwegs "brauchbares" - Anwendungsbeispiel:
|
|
Ruby Quellcode |
1 2 3 4 5 6 7 8 9 |
module Kernel class << self overwrite_method(:print) {|old| proc {|*args,&b| args.push(b.call()) if (b) send(old,args.join("\n").concat("\n")) }} end # eof meta Kernel end # eof module Kernel Kernel.print(3,4){7} # prints "3\n4\n7\n" instead of "34" |
Wem das jetzt Zucker genug ist, hier der Code. Er ist sowohl 1.9 als auch 1.8 -kompatibel, wobei ich bei 1.9 - unabhängig von der VM - höhere Geschwindigkeiten erwarte, die jedoch im Normalfall nie zu Tragen kämen.
Abschließend
Der Code: overwrite_method.rb (v2)
Credits sind mir übrigens egal, Verbesserungen, Kritik, Lob und Preis hingegen sind gern gesehen.
Gruß und Segen,
Ich
P.S: An die potenziellen Verbesserer: Ich habe gar versucht, Methoden in Methoden zu definieren und anschließend die Methoden an sich selbst zu binden. Methoden können auch Instanz-Variablen haben, ignorieren diese jedoch gekonnt.
-
Werbung -
1plus3
-
Nuuuhminaaah
-
compétences(Dieser Tab ist rein satirisch.)mes compétences
max.
Maps machen
Musik machen
Scripts machen
Story ausdenken
Pixeln und so
Events proggen
-
mes projets-
Silentium
Name: Silentium
Maker: Eigenbau (C++, x86-SSE/AVX-Assembly, Ruby/Lua)
Story
NPCs
Scripts
Ressis
Maps
Gesamt(3+4)% 42 69% 0815 -17.438 103.38% ± 6.3mm²
(Die Tabelle erfüllt lediglich satirische Zwecke.) -
OnyxEine in C++ implementierte, modulare, plattformunabhängige, virtuelle Maschine. Die Test-Version ist bereits halb fertig. Ab dann gibt es vielleicht mehr Infos. Sie soll die auf dem ersten Blick LISP-artige und eigens dafür konstruierte Sprache Obsidian ausführen können. Experimentell wird auch ein Lua-Compiler für Onyx gebaut. Ziel ist eine leistungsfähige, virtuelle Maschine für beliebige Scriptsprachen. Theoretisch gesehen müsste man bloß noch einen kompatiblen Compiler schreiben, der Quellcode jener Sprache in Onyx-Assembly, oder direkt in Onyx-Bytecode übersetzt. Ob die jemand nutzen wird, ist eine andere Frage und nur ein sekundäres... nein, eher tertiäres Ziel dieser VM. Primär dient es mir lediglich dazu, mein Verständnis von Hardware, ISA, und Assembly zu vertiefen, sowie eigene Grenzen auszutesten.
Warnung!
Das Entwickeln einer virtuellen Maschine oder Programmiersprache (im wahnsinnigsten Fall beides) ist eine höchst komplizierte Tätigkeit, aus der viel Frust und Hirnmatsche hervor gehen. Sollte sich dennoch ein ähnlich wahnsinniger finden, der sowas zusammen schustern will, so lege ich ihm/ihr die folgenden Bücher ans Herz:- Compiler - Das Drachenbuch [978-3-8273-7097-6]
Dieses Buch schlachtet ausführlich und leicht verständlich die Grundlagen bis hoch zu den Experten-Techniken des Compilerbaus aus. Es fängt mit der Automaten-Theorie und formalen Sprachen an, arbeitet sich durch Analysetechniken vor, und landet schließlich bei Techniken wie Optimierung und Register-Zuweisung. Das Buch wiegt 3Kg oder 4Kg. Hab's mal gewogen. Ist also nicht gerade die Lektüre für unterwegs.
- Computerarchitektur [3-8273-7016-7]
Hier werden leicht verständlich die wichtigsten Entwicklungen der Rechnerarchitekturen erklärt (Gut, das Buch ist in die Jahre gekommen, aber der Weg zu heute ist ein winziger Schritt, den man sich nach diesem Buch selbst erdenken kann). Hauptbestandteil des Buchs ist eine relativ umfassende Betrachtung der Funktionsweise dreier gänzlich unterschiedlicher, aber dominierender Prozessor-Typen am Beispiel des Pentium II, UltraSPARC II, sowie picoJava. Die meisten Elemente dieses Buchs sind zwar für die Konstruktion einer virtuellen Maschine irrelevant, oder aufgrund der Tatsache, dass die VM Software ist und z.B. Byte-Grenzen hat, sogar zu Leistungseinbußen führen kann, doch ist ein hinreichendes Verständnis dieser Maschinen, mit denen wir arbeiten, äußerst hilfreich für die Überlegungen, wie die VM arbeiten soll.
Es kann sehr hilfreich und inspirierend sein, den Code quelloffener, virtueller Maschinen anderer Sprachen zu überfliegen. Meine Lieblings-Quelle war und ist stets die VM von Lua. Sie ist schlank, verständlich, in C implementiert, und basiert im Gegensatz zu vielen anderen Scriptsprachen-VMs auf einer Register-Maschine statt einer Stapelmaschine. Es wäre natürlich vorteilhaft, die entsprechende Sprache zu verstehen, in der man auch die eigene VM implementieren will. Weiterhin ist es äußerst vorteilhaft, eine leistungsstarke und bequeme Sprache wie C++ zu beherrschen, um die VM zu implementieren. Und bevor irgendwer auf die Idee kommt: Assembly ist NICHT als dominierende Sprache für den Bau einer VM geeignet. Wer die Frage des "Warum?" nicht beantworten kann, sollte zunächst die gewählte Sprache und Assembly hinreichend verstehen lernen, und es dann erneut mit der Frage versuchen. Es lohnt sich dennoch, Assembly zu lernen. Allein schon, um erneut das Verständnis zu vertiefen, zumal ihr mehr oder weniger gezwungen seid, auch für eure VM eine Assembler-Sprache zu entwickeln (Außer natürlich ihr schreibt eure Test-Programme Bit für Bit ;3). - Compiler - Das Drachenbuch [978-3-8273-7097-6]
-
-
enfinJe ne peux pas parler français.
C'est tout ce que Goodle et les restes de cours de français.
Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Evrey« (26. April 2011, 21:15)
Dann solltest du auf ObjectSpace verzichten. Das ist in JRuby nämlich extremst langsam.
Zitat
Er ist sowohl 1.9 als auch 1.8 -kompatibel, wobei ich bei 1.9 - unabhängig von der VM - höhere Geschwindigkeiten erwarte
Ich verstehe nicht wie du das meinst. Wie willst du Methoden an sich selbst binden?
Zitat
An die potenziellen Verbesserer: Ich habe gar versucht, Methoden in Methoden zu definieren und anschließend die Methoden an sich selbst zu binden. Methoden können auch Instanz-Variablen haben, ignorieren diese jedoch gekonnt.
Ist es nicht genau anders herum, oder verstehe ich dich nur falsch?
Zitat
Empfohlen werden Procs, da diese - Im Gegensatz zu Lambdas - vollständig mit Methoden kompatibel sind.
Also die Grundidee finde ich zwar gut, der Ansatz ist aber imo unnötig kompliziert.
Zitat
Verbesserungen, Kritik, Lob und Preis hingegen sind gern gesehen.
1.) Die Unterscheidung zwischen Ruby 1.9 und Ruby 1.8 macht nur bedingt Sinn. Du nutzt sie nur um hash() auf Enumerator (der in 1.8 noch nicht von each_object zurückgegeben wird) anzuwenden. hash() ist aber eine Methode von Object und wird von Enumerator nicht überschrieben. Das ganze ist also so sinnvoll als hättest du einfach Object.new.hash geschrieben - mit dem hübschen Nebeneffekt, dass du dir dann auch den ObjectSpace gespart hättest.
2.) Was für einen Sinn hat der ObjectSpace überhaupt? Du baust dir irgendeinen Hash-Wert zusammen und iterierst dabei über ALLE Objekte oO Erstmal ist der Hash-Wert nicht eindeutig, weshalb der ganze Aufwand nicht mehr bringt als sich eine Zufallszahl auszudenken, zum anderen ist das einfach nur extrem aufwendig. Wie gesagt, du könntest genauso gut dir eine Zufallszahl ausgeben lassen. Die Wahrscheinlichkeit, dass bereits eine Methode mit dieser Zahl definiert ist, dürfte gegen 0 tendieren (mit gaaaaanz vielen 0en hinter dem Komma). Und die Performance dürfte um einiges besser sein. Die Alternative ist: Du lässt einfach einen Zähler mitlaufen der bei jedem Aufruf von overwrite um 1 erhöht wird. Dürfte genauso funktionieren und ist sogar noch performanter.
3.) Ganz ehrlich: Die Lösung, die letztlich rausgekommen ist, sieht nicht wirklich hübscher aus als ein alias davor. Der Schreibaufwand dürfte sogar höher als ein alias sein (und die Gefahr vor Kollisionen dürfte bei aliases auch gegen 0 gehen, wenn man seinen Namen und z.B. das Datum davorsetzt).
Prinzipiell finde ich es sinnvoll, ein besseres alias/plugin-System zu entwickeln. Aber es sollte hinterher auch möglichst einfacher zu bedienen sein als vorher, sonst bringt das doch nichts. Ich hätte an der Stelle auch einen Vorschlag zu machen: Klick mich. Das ist ein Aspektorientierungs-Framework, welches ich mal geschrieben habe. Die Benutzung läuft etwa so ab:
|
|
Ruby Quellcode |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Scene_Map after :update def update_mein_script mein_script.update() end # oder was das Äquivalent zu deinem Script ist wrap :update do |old_update, *args, &block| # mache was vorher old_update.call(*args, &block) # mache was hinterher end end |
Ich weiß allerdings, dass dieses Script 'ne ganz lange Liste an Problemen und TODO's enthält. Insbesondere im Zusammenhang mit Singletonklassen, Mixins, Superklassen etc. Durch Vererbung wird das ganze nämlich noch viel komplizierter. Leider weiß ich nicht mehr was an dem Script alles ausgebessert werden müsste, da ich es schon länger nicht mehr benutzt habe.
Gut, war mir als Nicht-JRuby-Nutzer nicht bekannt.
Zitat
Das ist in JRuby nämlich extremst langsam.
Da hab' ich einiges versucht, angefangen bei:
Zitat
Wie willst du Methoden an sich selbst binden?
|
|
Ruby Quellcode |
1 |
method(:foo).unbind.bind(method(:foo)) |
Lambdas dürfen kein return() enthalten, verwenden statt dessen next(), wobei ein next() beim Konvertieren zu einer Methode die VM zerschießt. Außerdem können Proc-Objekte Blöcke annehmen, Lambdas nicht. Ist mir erst mitten in der Bastelei aufgefallen.
Zitat
Ist es nicht genau anders herum, oder verstehe ich dich nur falsch?
Der Grundgedanke war der, dass Object#hash() für ein und das selbe Objekt des selben Zustands identisch sei und ObjectSpace mit jedem neuen Objekt einen neuen Zustand erhielt, wodurch es unter Garantie stets andere Werte geben soll.
Zitat
2.) Was für einen Sinn hat der ObjectSpace überhaupt? [...] Erstmal ist der Hash-Wert nicht eindeutig [...]
Eine so simple Idee, dass ich nicht darauf gekommen bin. Werd' ich einbauen, wobei der Zähler nur temporär sein wird. Ich nutze nicht gerne unnötige Instanz- und globale Variablen, an denen man potenziell zu eigenem Nachteil rumspielen könnte.
Zitat
Du lässt einfach einen Zähler mitlaufen der bei jedem Aufruf von overwrite um 1 erhöht wird. Dürfte genauso funktionieren und ist sogar noch performanter.
Ursprünglich war halt ein künstlicher super()-Klon geplant, den ich über das Pfuschen an den Scopes realisieren wollte. Da es allerdings nicht so recht geklappt hat, war der Block im Block die einfachste Lösung, die mir in den Sinn kam.
Zitat
Aber es sollte hinterher auch möglichst einfacher zu bedienen sein als vorher, sonst bringt das doch nichts.
old_update scheint ein Method-Objekt zu sein? So war ursprünglich der Parameter des ersten Blocks, wobei es einen bösen Haken hatte: Die Methode ist in diesem Moment an Class gebunden, in dem ich sie über method(sym) fische. Wenn sie so in den Block gerät, ist der Scope falsch und nichts läuft, wenn ich die Methode unbound mache, müsste der Nutzer die Methode im neuen Block manuell binden. Aus diesem Grund bin ich auf call() ausgewichen.
Zitat
![]()
Ruby Quellcode
1 2 3 wrap :update do |old_update, *args, &block| # mache was vorher old_update.call(*args, &block)
Vorgeschlagene Änderungen sind drin.
Auch eine durch und durch schicke Idee, zumal Aspektorientierung eh' zu kurz kommt. Schade, dass es nicht fertig ist.
Zitat
Das ist ein Aspektorientierungs-Framework, welches ich mal geschrieben habe. [...] Ich weiß allerdings, dass dieses Script 'ne ganz lange Liste an Problemen und TODO's enthält.
-
Werbung -
1plus3
-
Nuuuhminaaah
-
compétences(Dieser Tab ist rein satirisch.)mes compétences
max.
Maps machen
Musik machen
Scripts machen
Story ausdenken
Pixeln und so
Events proggen
-
mes projets-
Silentium
Name: Silentium
Maker: Eigenbau (C++, x86-SSE/AVX-Assembly, Ruby/Lua)
Story
NPCs
Scripts
Ressis
Maps
Gesamt(3+4)% 42 69% 0815 -17.438 103.38% ± 6.3mm²
(Die Tabelle erfüllt lediglich satirische Zwecke.) -
OnyxEine in C++ implementierte, modulare, plattformunabhängige, virtuelle Maschine. Die Test-Version ist bereits halb fertig. Ab dann gibt es vielleicht mehr Infos. Sie soll die auf dem ersten Blick LISP-artige und eigens dafür konstruierte Sprache Obsidian ausführen können. Experimentell wird auch ein Lua-Compiler für Onyx gebaut. Ziel ist eine leistungsfähige, virtuelle Maschine für beliebige Scriptsprachen. Theoretisch gesehen müsste man bloß noch einen kompatiblen Compiler schreiben, der Quellcode jener Sprache in Onyx-Assembly, oder direkt in Onyx-Bytecode übersetzt. Ob die jemand nutzen wird, ist eine andere Frage und nur ein sekundäres... nein, eher tertiäres Ziel dieser VM. Primär dient es mir lediglich dazu, mein Verständnis von Hardware, ISA, und Assembly zu vertiefen, sowie eigene Grenzen auszutesten.
Warnung!
Das Entwickeln einer virtuellen Maschine oder Programmiersprache (im wahnsinnigsten Fall beides) ist eine höchst komplizierte Tätigkeit, aus der viel Frust und Hirnmatsche hervor gehen. Sollte sich dennoch ein ähnlich wahnsinniger finden, der sowas zusammen schustern will, so lege ich ihm/ihr die folgenden Bücher ans Herz:- Compiler - Das Drachenbuch [978-3-8273-7097-6]
Dieses Buch schlachtet ausführlich und leicht verständlich die Grundlagen bis hoch zu den Experten-Techniken des Compilerbaus aus. Es fängt mit der Automaten-Theorie und formalen Sprachen an, arbeitet sich durch Analysetechniken vor, und landet schließlich bei Techniken wie Optimierung und Register-Zuweisung. Das Buch wiegt 3Kg oder 4Kg. Hab's mal gewogen. Ist also nicht gerade die Lektüre für unterwegs.
- Computerarchitektur [3-8273-7016-7]
Hier werden leicht verständlich die wichtigsten Entwicklungen der Rechnerarchitekturen erklärt (Gut, das Buch ist in die Jahre gekommen, aber der Weg zu heute ist ein winziger Schritt, den man sich nach diesem Buch selbst erdenken kann). Hauptbestandteil des Buchs ist eine relativ umfassende Betrachtung der Funktionsweise dreier gänzlich unterschiedlicher, aber dominierender Prozessor-Typen am Beispiel des Pentium II, UltraSPARC II, sowie picoJava. Die meisten Elemente dieses Buchs sind zwar für die Konstruktion einer virtuellen Maschine irrelevant, oder aufgrund der Tatsache, dass die VM Software ist und z.B. Byte-Grenzen hat, sogar zu Leistungseinbußen führen kann, doch ist ein hinreichendes Verständnis dieser Maschinen, mit denen wir arbeiten, äußerst hilfreich für die Überlegungen, wie die VM arbeiten soll.
Es kann sehr hilfreich und inspirierend sein, den Code quelloffener, virtueller Maschinen anderer Sprachen zu überfliegen. Meine Lieblings-Quelle war und ist stets die VM von Lua. Sie ist schlank, verständlich, in C implementiert, und basiert im Gegensatz zu vielen anderen Scriptsprachen-VMs auf einer Register-Maschine statt einer Stapelmaschine. Es wäre natürlich vorteilhaft, die entsprechende Sprache zu verstehen, in der man auch die eigene VM implementieren will. Weiterhin ist es äußerst vorteilhaft, eine leistungsstarke und bequeme Sprache wie C++ zu beherrschen, um die VM zu implementieren. Und bevor irgendwer auf die Idee kommt: Assembly ist NICHT als dominierende Sprache für den Bau einer VM geeignet. Wer die Frage des "Warum?" nicht beantworten kann, sollte zunächst die gewählte Sprache und Assembly hinreichend verstehen lernen, und es dann erneut mit der Frage versuchen. Es lohnt sich dennoch, Assembly zu lernen. Allein schon, um erneut das Verständnis zu vertiefen, zumal ihr mehr oder weniger gezwungen seid, auch für eure VM eine Assembler-Sprache zu entwickeln (Außer natürlich ihr schreibt eure Test-Programme Bit für Bit ;3). - Compiler - Das Drachenbuch [978-3-8273-7097-6]
-
-
enfinJe ne peux pas parler français.
C'est tout ce que Goodle et les restes de cours de français.
Da hab' ich einiges versucht, angefangen bei:
Zitat
Wie willst du Methoden an sich selbst binden?
![]()
Ruby Quellcode
1 method(:foo).unbind.bind(method(:foo))
Aber was willst du damit erreichen? Wenn du in einer Methode eine Methode definierst, wird lediglich eine Instanzmethode definiert, nicht eine Methode in einer Methode.
|
|
Ruby Quellcode |
1 2 3 4 5 6 7 8 9 10 |
class A def foo def foo puts "bla" end end end a = A.new a.foo #=> ruft def foo; ...; end auf und fügt damit A eine neue Methode hinzu a.foo #=> bla |
Kann ich nicht rekonstruieren. Bei mir funktioniert es egal ob ich next, return, proc oder lambda verwende. Aber wie auch immer: Es ist genau anders herum. Lambda verwenden return statt next. Proc verwendet next.
Zitat
Lambdas dürfen kein return() enthalten, verwenden statt dessen next(), wobei ein next() beim Konvertieren zu einer Methode die VM zerschießt.
Zumindest ab Ruby 1.87 funktioniert das mit beidem. Obs vorher nicht so war, weiß ich nicht.
Zitat
Außerdem können Proc-Objekte Blöcke annehmen, Lambdas nicht. Ist mir erst mitten in der Bastelei aufgefallen.
Ein hash ist aber nicht der Zustand eines Objektes. Es ist genau andersrum: Objekte verschiedener Zustände sollten möglichst unterschiedliche hashs haben, sie müssen aber nicht (und können dies auch nicht garantieren). Das ist klar, denn die meisten Objekte können unendlich viele Zustände annehmen, es gibt aber nur endlich viele Hashwerte. Aus dem Grund kannst du genauso gut eine Zufallszahl ziehen. Die Wahrscheinlichkeit, dass du zwei mal dieselbe Zufallszahl ziehst, ist nicht größer als die Wahrscheinlichkeit, dass die Summe aller Hashwerte im ObjectSpace zweimal gleich ist, obwohl der ObjektSpace unterschiedliche Objekte enthält.
Zitat
Der Grundgedanke war der, dass Object#hash() für ein und das selbe Objekt des selben Zustands identisch sei und ObjectSpace mit jedem neuen Objekt einen neuen Zustand erhielt, wodurch es unter Garantie stets andere Werte geben soll.
Nee, ist eine Instanz einer eigenen Message-Klasse, die wiederum einfach nur ein Objekt und ein Symbol wrappt. call() ruft dann einfach objekt.send(symbol) auf. Method-Objekte haben den Haken, dass sie extrem ineffizient sind. Es gibt noch weitere Nachteile bei Method-Objekten: Sie beziehen sich nur auf konkrete Methoden, ignorieren also dynamische Methoden, die per method_missing "konstruiert" werden. Aber method_missing wird bei mir afair eh noch nicht beachtet, das wäre z.B. so ein Punkt in der Todo-Liste ^^
Zitat
old_update scheint ein Method-Objekt zu sein?
![]()
Ruby Quellcode
1 2 3 wrap :update do |old_update, *args, &block| # mache was vorher old_update.call(*args, &block)
Zitat
Schade, dass es nicht fertig ist.
Naja, es ist schon einsatzfähig. Hab es auch früher in der Praxis schon eingesetzt. Aber der Teufel steckt halt immer im Detail. Es müsste noch viel getestet werden.
Ich korrigiere:
Zitat
Wenn du in einer Methode eine Methode definierst, wird lediglich eine Instanzmethode definiert, nicht eine Methode in einer Methode.
|
|
Ruby Quellcode |
1 2 |
method(:foo).send(:define_method,:bar) {|| 'hi'} method(:foo).bar() #=> 'hi' |
Eigenartig ôO Naja, wenn's klappt, klappt's. Ich nutz' momentan 1.9.2.136 x64 auf Win7.
Zitat
Kann ich nicht rekonstruieren.
Vielleicht ruft ja irgendwann die Langeweile ;3
Zitat
Naja, es ist schon einsatzfähig. Hab es auch früher in der Praxis schon eingesetzt. Aber der Teufel steckt halt immer im Detail. Es müsste noch viel getestet werden.
-
Werbung -
1plus3
-
Nuuuhminaaah
-
compétences(Dieser Tab ist rein satirisch.)mes compétences
max.
Maps machen
Musik machen
Scripts machen
Story ausdenken
Pixeln und so
Events proggen
-
mes projets-
Silentium
Name: Silentium
Maker: Eigenbau (C++, x86-SSE/AVX-Assembly, Ruby/Lua)
Story
NPCs
Scripts
Ressis
Maps
Gesamt(3+4)% 42 69% 0815 -17.438 103.38% ± 6.3mm²
(Die Tabelle erfüllt lediglich satirische Zwecke.) -
OnyxEine in C++ implementierte, modulare, plattformunabhängige, virtuelle Maschine. Die Test-Version ist bereits halb fertig. Ab dann gibt es vielleicht mehr Infos. Sie soll die auf dem ersten Blick LISP-artige und eigens dafür konstruierte Sprache Obsidian ausführen können. Experimentell wird auch ein Lua-Compiler für Onyx gebaut. Ziel ist eine leistungsfähige, virtuelle Maschine für beliebige Scriptsprachen. Theoretisch gesehen müsste man bloß noch einen kompatiblen Compiler schreiben, der Quellcode jener Sprache in Onyx-Assembly, oder direkt in Onyx-Bytecode übersetzt. Ob die jemand nutzen wird, ist eine andere Frage und nur ein sekundäres... nein, eher tertiäres Ziel dieser VM. Primär dient es mir lediglich dazu, mein Verständnis von Hardware, ISA, und Assembly zu vertiefen, sowie eigene Grenzen auszutesten.
Warnung!
Das Entwickeln einer virtuellen Maschine oder Programmiersprache (im wahnsinnigsten Fall beides) ist eine höchst komplizierte Tätigkeit, aus der viel Frust und Hirnmatsche hervor gehen. Sollte sich dennoch ein ähnlich wahnsinniger finden, der sowas zusammen schustern will, so lege ich ihm/ihr die folgenden Bücher ans Herz:- Compiler - Das Drachenbuch [978-3-8273-7097-6]
Dieses Buch schlachtet ausführlich und leicht verständlich die Grundlagen bis hoch zu den Experten-Techniken des Compilerbaus aus. Es fängt mit der Automaten-Theorie und formalen Sprachen an, arbeitet sich durch Analysetechniken vor, und landet schließlich bei Techniken wie Optimierung und Register-Zuweisung. Das Buch wiegt 3Kg oder 4Kg. Hab's mal gewogen. Ist also nicht gerade die Lektüre für unterwegs.
- Computerarchitektur [3-8273-7016-7]
Hier werden leicht verständlich die wichtigsten Entwicklungen der Rechnerarchitekturen erklärt (Gut, das Buch ist in die Jahre gekommen, aber der Weg zu heute ist ein winziger Schritt, den man sich nach diesem Buch selbst erdenken kann). Hauptbestandteil des Buchs ist eine relativ umfassende Betrachtung der Funktionsweise dreier gänzlich unterschiedlicher, aber dominierender Prozessor-Typen am Beispiel des Pentium II, UltraSPARC II, sowie picoJava. Die meisten Elemente dieses Buchs sind zwar für die Konstruktion einer virtuellen Maschine irrelevant, oder aufgrund der Tatsache, dass die VM Software ist und z.B. Byte-Grenzen hat, sogar zu Leistungseinbußen führen kann, doch ist ein hinreichendes Verständnis dieser Maschinen, mit denen wir arbeiten, äußerst hilfreich für die Überlegungen, wie die VM arbeiten soll.
Es kann sehr hilfreich und inspirierend sein, den Code quelloffener, virtueller Maschinen anderer Sprachen zu überfliegen. Meine Lieblings-Quelle war und ist stets die VM von Lua. Sie ist schlank, verständlich, in C implementiert, und basiert im Gegensatz zu vielen anderen Scriptsprachen-VMs auf einer Register-Maschine statt einer Stapelmaschine. Es wäre natürlich vorteilhaft, die entsprechende Sprache zu verstehen, in der man auch die eigene VM implementieren will. Weiterhin ist es äußerst vorteilhaft, eine leistungsstarke und bequeme Sprache wie C++ zu beherrschen, um die VM zu implementieren. Und bevor irgendwer auf die Idee kommt: Assembly ist NICHT als dominierende Sprache für den Bau einer VM geeignet. Wer die Frage des "Warum?" nicht beantworten kann, sollte zunächst die gewählte Sprache und Assembly hinreichend verstehen lernen, und es dann erneut mit der Frage versuchen. Es lohnt sich dennoch, Assembly zu lernen. Allein schon, um erneut das Verständnis zu vertiefen, zumal ihr mehr oder weniger gezwungen seid, auch für eure VM eine Assembler-Sprache zu entwickeln (Außer natürlich ihr schreibt eure Test-Programme Bit für Bit ;3). - Compiler - Das Drachenbuch [978-3-8273-7097-6]
-
-
enfinJe ne peux pas parler français.
C'est tout ce que Goodle et les restes de cours de français.
@Pluginsystem:
äh kai da hatte ich mal was feines gebaut:
das hatte ich aus dem APD raus geholt. nähmlich die CodeStellen .. oder wie ich sie gern nenne, meine einsprungpunkte
das sorgt dafür das aufrufe von methoden die mit cs_(name) anfangen alle methoden aufrufen die *_cs_(name) enthalten
will man was erweitern definiert man nur eine methode die abc_cs_name heist und die wird automatisch aufgerufen
.. comment?
äh kai da hatte ich mal was feines gebaut:
das hatte ich aus dem APD raus geholt. nähmlich die CodeStellen .. oder wie ich sie gern nenne, meine einsprungpunkte
|
|
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 |
class Object def method_missing(methId, *args) if /^cs_/.match(methId) @__cs_cache ||={} unless @__cs_cache.has_key?(methId) req = /_#{methId}$/ @__cs_cache[methId] = (methods | protected_methods | private_methods ).find_all { | m | req.match(m) } end result = @__cs_cache[methId].map { |m| send(m, *args) } if block_given? return result.each {|s| yield s} else return result end else super end end def cs_cache_clear(key=nil) return if @__cs_cache.nil? if key.nil? @__cs_cache.clear else @__cs_cache.delete(key) end end end |
das sorgt dafür das aufrufe von methoden die mit cs_(name) anfangen alle methoden aufrufen die *_cs_(name) enthalten
will man was erweitern definiert man nur eine methode die abc_cs_name heist und die wird automatisch aufgerufen
.. comment?
Realität ist nur eine subjektive Wahrnehmungsstörung.
Alles ist wahr, wenn man für wahr einen bestimmten Wert annimmt.
Alles ist wahr, wenn man für wahr einen bestimmten Wert annimmt.
Zwei Sachen finde ich weniger schön: Zum einen das Verwenden von method_missing, was weniger effizient ist, zum anderen das Verwenden einer Instanzvariable. Die ist zwar nötig, denn ohne Caching wäre das ja extrem ineffizient, aber diese Variable wird ja beim Serialisieren auch mit abgespeichert. Eine Möglichkeit wäre die _dump Methode des Hashs zu überschreiben, so dass nur ein leerer Hash abgespeichert wird. Generell bedeutet das aber, dass Einsprungspunkte in jedem Objekt zusätzlichen Speicher kosten. Sauberer wäre es imo, die Einsprungspunkte über die Klasse zu definieren (= in deinem Fall statt method_missing eben method_added überschreiben) und nicht über die Instanz selbst.
Die Einsprungspunkte implizit über den Namen zu bestimmen ist wohl Geschmackssache. Ich finde es schöner wenn man das explizit über einen Methodenaufruf macht, aber das ist dann halt ein paar Zeichen mehr Schreibaufwand.
Die Einsprungspunkte implizit über den Namen zu bestimmen ist wohl Geschmackssache. Ich finde es schöner wenn man das explizit über einen Methodenaufruf macht, aber das ist dann halt ein paar Zeichen mehr Schreibaufwand.
das muss irgendwie am object hängen wenn wenn ich ein modul hab was neue xyz_cs_abc in ein object extendiert muss das object auch damit umgehen können...
Hooks die ich anstelle von method_missing nutzen werde sind:
method_added im Module
singleton_method_added in Object (welche NICHT in der doku ist)
und für modul support muss ich glaub ich auch extend_object und included
aber method_missing bekomm ich noch nicht ganz weg ...
es sei denn ich bau eine funktion: cs_call(id,*args) welch in etwa so wie method_missing wäre
Hooks die ich anstelle von method_missing nutzen werde sind:
method_added im Module
singleton_method_added in Object (welche NICHT in der doku ist)
und für modul support muss ich glaub ich auch extend_object und included
aber method_missing bekomm ich noch nicht ganz weg ...
es sei denn ich bau eine funktion: cs_call(id,*args) welch in etwa so wie method_missing wäre
Realität ist nur eine subjektive Wahrnehmungsstörung.
Alles ist wahr, wenn man für wahr einen bestimmten Wert annimmt.
Alles ist wahr, wenn man für wahr einen bestimmten Wert annimmt.
In dem Fall wird das Modul ja in die Metaklasse des Objekts eingefügt. Also kann die Metaklasse das handhaben.Vermutlich müsste man dann aber die extend Methode überschreiben um so ein Ereignis abfangen zu können.
Zitat
das muss irgendwie am object hängen wenn wenn ich ein modul hab was neue xyz_cs_abc in ein object extendiert muss das object auch damit umgehen können...
Zitat
Ich finde es schöner wenn man das explizit über einen Methodenaufruf macht
wie genau meinst du das?
Naja, in meinem Script definierst du solche Hooks mit der Syntax:
|
|
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 |
class A def foo puts "foo" end end # jetzt das Plugin class A after :foo def mache_was_nach_foo puts "Dieser Code wird nach foo ausgeführt" end before :foo def mache_was_vor_foo puts "Dieser Code wird vor foo ausgeführt" end after :foo do puts "Namenlose Methode, um Namenskollissionen zu verhindern." puts "Ist, da es ein Proc ist, natürlich langsamer als die obigen Varianten" end end |
zum Lesen den Text mit der Maus markieren
Mit explizit meine ich eben, dass man da "after :originalmethode" vor der Methodendefinition schreibt. Damit ist auch Lesern, die mit dem Framework nicht vertraut sind, hoffentlich klar, das diese Methode nach :originalmethode ausgeführt werden soll. Der größte Vorteil liegt aber in der Umsetzung: Der Aufruf von after mit anschließendem method_added macht der Klasse klar: Hier wird ein neuer Aspekt/Einsprungspunkt definiert. Und die Klasse weiß auch den Namen der Originalmethode. Mein Script aliased daraufhin die Originalmethode und überschreibt sie durch eine per eval erzeugte Methode, welche alle Einsprungspunkte plus die aliaste Originalmethode aufruft. Damit hat man sogar eine bessere Performance als wenn man selbst aliasen würde. Und es ist performanter als wenn man über method_missing geht. Nachteil des Ganzen ist natürlich, dass es kompliziert wird, wenn Mixins etc. ins Spiel kommen. Ich glaub mein Script kann derzeit noch nicht mit Mixins umgehen, die Einsprungspunkte enthalten. Allerdings ließe sich sowas durchaus einbauen. Man müsste included und extended überschreiben und alle Aspekte/Einsprungspunkte auf die Zielklasse übertragen.
Im obigen Beispiel wird letztlich per eval folgende Methode generiert:
|
|
Ruby Quellcode |
1 2 3 4 5 6 7 |
def foo(*args, &block) aop_extension_result = mache_was_vor_foo(*args, &block) aop_extension_result = aop_extension_method_alias_A_foo(*args, &block) # alias von foo aop_extension_result = mache_was_nach_foo(*args, &block) aop_extension_result = aop_extension_proc_A_foo_1(*args, &block) # ruft den proc auf aop_extension_result end |
hm jain meine haben nicht sowas wie after oder post, weil bei den einsprungpunkten die reihenfolge egal ist
wie du sehen kannst werden die blöß der reihe nach rausgeführt und deren rückgabewerte gesammet
wichtig ist aber das objekt mit cs_funktionen auch wenn die mit extend hinzukommen trotzdem marschal-bar sein muss (das getrifft die rpg und game objekte woran ich bau)
wie du sehen kannst werden die blöß der reihe nach rausgeführt und deren rückgabewerte gesammet
wichtig ist aber das objekt mit cs_funktionen auch wenn die mit extend hinzukommen trotzdem marschal-bar sein muss (das getrifft die rpg und game objekte woran ich bau)
Realität ist nur eine subjektive Wahrnehmungsstörung.
Alles ist wahr, wenn man für wahr einen bestimmten Wert annimmt.
Alles ist wahr, wenn man für wahr einen bestimmten Wert annimmt.
Obs after und before gibt ist ja egal. Mit explizit meinte ich halt, dass man die Zugehörigkeit eines Einsprungspunktes nicht vom Methodennamen abhängig macht, sondern über einen Methodenaufruf vorher festlegt, dass eine Methode ein Einsprungspunkt ist.
btw. ich weiß ja nicht wofür du das brauchst, aber afair arbeitest du doch auch an einem eigenen Maker, oder? In der RGSS hat man ja leider keine Möglichkeit ordentliche Plugins zu schreiben, ohne zu solchen Tricks zu greifen. Wenn man aber eine eigene Engine schreibt, würde ich gar nicht erst zu solchen Mitteln greifen. Viel zu kompliziert, fehleranfällig und oftmals auch nicht sonderlich performant. Es gibt einfache Rubylösungen für solche Fälle. Wenn ich z.B. eine Klasse GameObject mit einer Methode update habe und ich möchte die Möglichkeit bieten, diese Methode zu überschreiben, dann kann man das folgendermaßen machen:
Damit kann man über das Einfügen neuer Mixins ganz einfach neue Methoden in bestehende Einsetzen. Und das ganz ohne Magie.
btw. ich weiß ja nicht wofür du das brauchst, aber afair arbeitest du doch auch an einem eigenen Maker, oder? In der RGSS hat man ja leider keine Möglichkeit ordentliche Plugins zu schreiben, ohne zu solchen Tricks zu greifen. Wenn man aber eine eigene Engine schreibt, würde ich gar nicht erst zu solchen Mitteln greifen. Viel zu kompliziert, fehleranfällig und oftmals auch nicht sonderlich performant. Es gibt einfache Rubylösungen für solche Fälle. Wenn ich z.B. eine Klasse GameObject mit einer Methode update habe und ich möchte die Möglichkeit bieten, diese Methode zu überschreiben, dann kann man das folgendermaßen machen:
|
|
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 |
module Updateable def before_update end def after_update end end class GameObject include Updateable def update before_update mache_irgendwas_besonderes after_update end end # Und nun das Plugin module Logger def before_update logge("Jetzt wird geupdatet!") super end def after_update super logge("Update abgeschlossen") end end class GameObject include Logger end |
zum Lesen den Text mit der Maus markieren
Damit kann man über das Einfügen neuer Mixins ganz einfach neue Methoden in bestehende Einsetzen. Und das ganz ohne Magie.
jo ich arbeite wie quintus daran, und ja man könnte das auch als c extension bauen, aber wir wollten erstmal soviel wie möglich in ruby bauen
so die cs zb ganz praktisch sind:
wenn zb eine funktion eingebaut werden soll die je nach status atk mal 0.5 macht zb kann man die atk funktion nicht so einfach überladen .. ok es ginge mit anderen sachen aber trotzdem nicht so elegant
aber so ähnlich wie dus schreibst funktioniert das schon ... aber mitunter sind es keine neuen mixins sondern erweitern schon vorhandener
so die cs zb ganz praktisch sind:
|
|
Ruby Quellcode |
1 2 3 4 5 6 7 |
class Game::Actor def atk result = baseatk cs_stat_multi(:atk) {|i| result *= i } cs_stat_add(:atk) {|i| result += i } end end |
wenn zb eine funktion eingebaut werden soll die je nach status atk mal 0.5 macht zb kann man die atk funktion nicht so einfach überladen .. ok es ginge mit anderen sachen aber trotzdem nicht so elegant
aber so ähnlich wie dus schreibst funktioniert das schon ... aber mitunter sind es keine neuen mixins sondern erweitern schon vorhandener
Realität ist nur eine subjektive Wahrnehmungsstörung.
Alles ist wahr, wenn man für wahr einen bestimmten Wert annimmt.
Alles ist wahr, wenn man für wahr einen bestimmten Wert annimmt.
Das habe ich doch gar nicht gesagt. Ganz im Gegenteil: Ich finde eine reine Rubylösung ja auch sinnvoller.
Zitat
und ja man könnte das auch als c extension bauen
Zu deinem Beispiel: Wie wäre es denn damit:
|
|
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 |
class Game_Actor module ComputeStats def compute_atk old_value old_value end # ... end include ComputeStats def atk compute_atk(@atk) end end module SomeStatePlugin def compute_atk value if has_state?(:ill) then value * 0.5 else value end end end class Game_Actor include SomeStatePlugin end |
zum Lesen den Text mit der Maus markieren
jo sowas ginge nur ist es auch problematisch wenn plugins mit pluginsverschmeltzen sollen: für Actor zb gibts 2 plugins und ein weiteres plugin was die plugins verbindet ... und da für jedes ein extra modul zu machen ist auch irgendwie "blöd"
Realität ist nur eine subjektive Wahrnehmungsstörung.
Alles ist wahr, wenn man für wahr einen bestimmten Wert annimmt.
Alles ist wahr, wenn man für wahr einen bestimmten Wert annimmt.
Ähnliche Themen
-
Scientia Supportforum »-
Scientia-Revival - Vorschläge, Festlegungen, Denkanstöße
(9. Januar 2011, 12:07)
-
RGSS 1 Probleme & Talk »-
Klassen
(20. November 2009, 00:22)
-
Einsteigerhilfe »-
Charsets umstellen das es nur eine person insgesammt ist
(22. April 2009, 19:12)



