• Anmelden

Evrey

Oberschurke im Ruhestand

  • »Evrey« ist der Autor dieses Themas

Motto: "Satzzeichen sind keine Rudeltiere." - Chesra

  • Nachricht senden

1

Dienstag, 26. April 2011, 19:24

(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))
Sehr dreiste Leute aliasen dann alias_method, um sich das blöde if danach zu ersparen, doch irgendwie ist all' das nicht so recht das Wahre. Das Prinzip ist stets das selbe:
  1. Sich einen möglichst bescheuerten Namen für die kopierte Methode ausdenken, damit es zu keiner Kollision kommt, die in einem StackError endet.
  2. (Prüfen, ob nicht doch irgendwann bereits eine solche Kopie angelegt wurde. Falls ja, ist es damit meist... ja, dumm gelaufen.)
  3. 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.
  • :medal: Werbung

    Bild

    Cpp Quellcode

    1
    
    #define TRUE FALSE //Happy debugging suckers
    (Einfach nur wundervoll.)
  • :palette: 1plus3 :cake:

    Bild
  • :fires: Nuuuhminaaah

    Bild
  • :medal: compétences

    mes compétences
    :heart_full: :heart_full: :heart_full: :heart_full: :heart_full: max.
    :ruler-triangle: Maps machen :heart_full: :heart-empty: :heart-empty: :heart-empty: :heart-empty:
    :media-player: Musik machen :heart_full: :heart-half: :heart-empty: :heart-empty: :heart-empty:
    :cup: Scripts machen :heart_full: :heart_full: :heart_full: :heart_full: :heart-break:
    :paper: Story ausdenken :heart_full: :heart_full: :heart_full: :heart-empty: :heart-empty:
    :cut: Pixeln und so :heart-empty: :heart-empty: :heart-empty: :heart-empty: :heart-empty:
    :game: Events proggen :heart_full: :heart_full: :heart_full: :heart_full: :heart_full:
    (Dieser Tab ist rein satirisch.)
  • :folder-open: mes projets

    • :addressbook: Silentium
      :book: Name: Silentium
      :rmxp: Maker: Eigenbau (C++, x86-SSE/AVX-Assembly, Ruby/Lua)

      :paper: Story
      :game: NPCs
      :cup: Scripts
      :drill: Ressis
      :ruler-triangle: Maps
      :compile: Gesamt
      (3+4)% 42 69% 0815 -17.438 103.38% ± 6.3mm²

      (Die Tabelle erfüllt lediglich satirische Zwecke.)
    • :compile: Onyx
      Eine 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.

      :exclamation: 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).
  • :locale: enfin

    Je ne peux pas parler français.
    C'est tout ce que Goodle et les restes de cours de français.
Signaturstand: 24.07.2013

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Evrey« (26. April 2011, 21:15)


2

Dienstag, 26. April 2011, 20:33

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
Dann solltest du auf ObjectSpace verzichten. Das ist in JRuby nämlich extremst langsam.

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.
Ich verstehe nicht wie du das meinst. Wie willst du Methoden an sich selbst binden?

Zitat

Empfohlen werden Procs, da diese - Im Gegensatz zu Lambdas - vollständig mit Methoden kompatibel sind.
Ist es nicht genau anders herum, oder verstehe ich dich nur falsch?

Zitat

Verbesserungen, Kritik, Lob und Preis hingegen sind gern gesehen.
Also die Grundidee finde ich zwar gut, der Ansatz ist aber imo unnötig kompliziert.

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.
Bild
RMXP Grundkurs
1 2 3
Ruby/RGSS-Kurs

Evrey

Oberschurke im Ruhestand

  • »Evrey« ist der Autor dieses Themas

Motto: "Satzzeichen sind keine Rudeltiere." - Chesra

  • Nachricht senden

3

Dienstag, 26. April 2011, 21:14

Zitat

Das ist in JRuby nämlich extremst langsam.
Gut, war mir als Nicht-JRuby-Nutzer nicht bekannt.

Zitat

Wie willst du Methoden an sich selbst binden?
Da hab' ich einiges versucht, angefangen bei:

Ruby Quellcode

1
method(:foo).unbind.bind(method(:foo))

Zitat

Ist es nicht genau anders herum, oder verstehe ich dich nur falsch?
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

2.) Was für einen Sinn hat der ObjectSpace überhaupt? [...] Erstmal ist der Hash-Wert nicht eindeutig [...]
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

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.
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

Aber es sollte hinterher auch möglichst einfacher zu bedienen sein als vorher, sonst bringt das doch nichts.
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

Ruby Quellcode

1
2
3
  wrap :update do |old_update, *args, &block|
    # mache was vorher
    old_update.call(*args, &block)
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.

Vorgeschlagene Änderungen sind drin.

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.
Auch eine durch und durch schicke Idee, zumal Aspektorientierung eh' zu kurz kommt. Schade, dass es nicht fertig ist.
  • :medal: Werbung

    Bild

    Cpp Quellcode

    1
    
    #define TRUE FALSE //Happy debugging suckers
    (Einfach nur wundervoll.)
  • :palette: 1plus3 :cake:

    Bild
  • :fires: Nuuuhminaaah

    Bild
  • :medal: compétences

    mes compétences
    :heart_full: :heart_full: :heart_full: :heart_full: :heart_full: max.
    :ruler-triangle: Maps machen :heart_full: :heart-empty: :heart-empty: :heart-empty: :heart-empty:
    :media-player: Musik machen :heart_full: :heart-half: :heart-empty: :heart-empty: :heart-empty:
    :cup: Scripts machen :heart_full: :heart_full: :heart_full: :heart_full: :heart-break:
    :paper: Story ausdenken :heart_full: :heart_full: :heart_full: :heart-empty: :heart-empty:
    :cut: Pixeln und so :heart-empty: :heart-empty: :heart-empty: :heart-empty: :heart-empty:
    :game: Events proggen :heart_full: :heart_full: :heart_full: :heart_full: :heart_full:
    (Dieser Tab ist rein satirisch.)
  • :folder-open: mes projets

    • :addressbook: Silentium
      :book: Name: Silentium
      :rmxp: Maker: Eigenbau (C++, x86-SSE/AVX-Assembly, Ruby/Lua)

      :paper: Story
      :game: NPCs
      :cup: Scripts
      :drill: Ressis
      :ruler-triangle: Maps
      :compile: Gesamt
      (3+4)% 42 69% 0815 -17.438 103.38% ± 6.3mm²

      (Die Tabelle erfüllt lediglich satirische Zwecke.)
    • :compile: Onyx
      Eine 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.

      :exclamation: 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).
  • :locale: enfin

    Je ne peux pas parler français.
    C'est tout ce que Goodle et les restes de cours de français.
Signaturstand: 24.07.2013

4

Dienstag, 26. April 2011, 21:37


Zitat

Wie willst du Methoden an sich selbst binden?
Da hab' ich einiges versucht, angefangen bei:

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





Zitat

Lambdas dürfen kein return() enthalten, verwenden statt dessen next(), wobei ein next() beim Konvertieren zu einer Methode die VM zerschießt.
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

Außerdem können Proc-Objekte Blöcke annehmen, Lambdas nicht. Ist mir erst mitten in der Bastelei aufgefallen.
Zumindest ab Ruby 1.87 funktioniert das mit beidem. Obs vorher nicht so war, weiß ich nicht.

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.
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

Ruby Quellcode

1
2
3
  wrap :update do |old_update, *args, &block|
    # mache was vorher
    old_update.call(*args, &block)
old_update scheint ein Method-Objekt zu sein?
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

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.
Bild
RMXP Grundkurs
1 2 3
Ruby/RGSS-Kurs

Evrey

Oberschurke im Ruhestand

  • »Evrey« ist der Autor dieses Themas

Motto: "Satzzeichen sind keine Rudeltiere." - Chesra

  • Nachricht senden

5

Dienstag, 26. April 2011, 21:58

Zitat

Wenn du in einer Methode eine Methode definierst, wird lediglich eine Instanzmethode definiert, nicht eine Methode in einer Methode.
Ich korrigiere:

Ruby Quellcode

1
2
method(:foo).send(:define_method,:bar) {|| 'hi'}
method(:foo).bar() #=> 'hi'

Zitat

Kann ich nicht rekonstruieren.
Eigenartig ôO Naja, wenn's klappt, klappt's. Ich nutz' momentan 1.9.2.136 x64 auf Win7.

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.
Vielleicht ruft ja irgendwann die Langeweile ;3
  • :medal: Werbung

    Bild

    Cpp Quellcode

    1
    
    #define TRUE FALSE //Happy debugging suckers
    (Einfach nur wundervoll.)
  • :palette: 1plus3 :cake:

    Bild
  • :fires: Nuuuhminaaah

    Bild
  • :medal: compétences

    mes compétences
    :heart_full: :heart_full: :heart_full: :heart_full: :heart_full: max.
    :ruler-triangle: Maps machen :heart_full: :heart-empty: :heart-empty: :heart-empty: :heart-empty:
    :media-player: Musik machen :heart_full: :heart-half: :heart-empty: :heart-empty: :heart-empty:
    :cup: Scripts machen :heart_full: :heart_full: :heart_full: :heart_full: :heart-break:
    :paper: Story ausdenken :heart_full: :heart_full: :heart_full: :heart-empty: :heart-empty:
    :cut: Pixeln und so :heart-empty: :heart-empty: :heart-empty: :heart-empty: :heart-empty:
    :game: Events proggen :heart_full: :heart_full: :heart_full: :heart_full: :heart_full:
    (Dieser Tab ist rein satirisch.)
  • :folder-open: mes projets

    • :addressbook: Silentium
      :book: Name: Silentium
      :rmxp: Maker: Eigenbau (C++, x86-SSE/AVX-Assembly, Ruby/Lua)

      :paper: Story
      :game: NPCs
      :cup: Scripts
      :drill: Ressis
      :ruler-triangle: Maps
      :compile: Gesamt
      (3+4)% 42 69% 0815 -17.438 103.38% ± 6.3mm²

      (Die Tabelle erfüllt lediglich satirische Zwecke.)
    • :compile: Onyx
      Eine 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.

      :exclamation: 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).
  • :locale: enfin

    Je ne peux pas parler français.
    C'est tout ce que Goodle et les restes de cours de français.
Signaturstand: 24.07.2013

6

Dienstag, 26. April 2011, 22:07

@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

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.

7

Freitag, 29. April 2011, 14:25

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.
Bild
RMXP Grundkurs
1 2 3
Ruby/RGSS-Kurs

8

Freitag, 29. April 2011, 14:45

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

Edit

Zitat

Ich finde es schöner wenn man das explizit über einen Methodenaufruf macht

wie genau meinst du das?
Realität ist nur eine subjektive Wahrnehmungsstörung.

Alles ist wahr, wenn man für wahr einen bestimmten Wert annimmt.

9

Freitag, 29. April 2011, 16:17

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...
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

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:
Spoiler

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
Bild
RMXP Grundkurs
1 2 3
Ruby/RGSS-Kurs

10

Freitag, 29. April 2011, 16:31

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)
Realität ist nur eine subjektive Wahrnehmungsstörung.

Alles ist wahr, wenn man für wahr einen bestimmten Wert annimmt.

11

Freitag, 29. April 2011, 16:54

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:
Spoiler

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.
Bild
RMXP Grundkurs
1 2 3
Ruby/RGSS-Kurs

12

Freitag, 29. April 2011, 17:37

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:

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.

13

Freitag, 29. April 2011, 17:45

Zitat

und ja man könnte das auch als c extension bauen
Das habe ich doch gar nicht gesagt. Ganz im Gegenteil: Ich finde eine reine Rubylösung ja auch sinnvoller.

Zu deinem Beispiel: Wie wäre es denn damit:
Spoiler

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
Bild
RMXP Grundkurs
1 2 3
Ruby/RGSS-Kurs

14

Freitag, 29. April 2011, 18:50

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.

Social Bookmarks