Morgengrauner Dokumentation
Dateipfad: /home/mud/mudlib/doc//std/hooksDas neue Hooksystem
(Implementierung von Muadib, ueberarbeitet von Zesstra)
EINLEITUNG
==========
Das Hook-System stellt folgende Funktionalitaet zur Verfuegung:
Ein Objekt A kann sich von einem anderen Objekt B ueber den Eintritt von
bestimmten, Objekt B betreffenden Ereignissen informieren lassen. Hierzu
meldet Objekt A sich bei Objekt B fuer das interessierende Ereignis an,
dies wird als "registrieren" bezeichnet. Bei der Registrierung gibt
Objekt A an, welche Funktion bei Eintritt des Ereignisses durch Objekt B
gerufen werden soll.
Die Verbindung von Objekt A nach B nennen wir "Hook" (Objekt A "hakt" sich
sozusagen bei B ein, um wie beim Angeln das Zappeln des Fisches am Haken
mitzubekommen.) Der Funktionsaufruf von B bei Eintritt des Ereignisses
wird "Callback" genannt (Objekt B ruft gewissermassen bei A an, um ihm
mitzuteilen, dass das Ereignis eingetreten ist.)
Das registrierende Objekt A heisst Consumer (Konsument), Objekt B heisst
Hook-Provider (Anbieter).
Im Grunde funktionieren Hooks aehnlich wie Events, jedoch sind Hooks
zusaetzlich dadurch gekennzeichnet, dass sie auch den Abbruch des Ereignisses
oder die Veraenderung der ereignisrelevanten Daten bewirken koennen.
Zudem sind Hooks in ihrer Anzahl pro Provider limitiert.
Anmerkung: Das neue Hooksystem baut nicht mehr auf der Eintragung eines
Hooks in einer Property auf. Dadurch wird es moeglich, dass sich mehrere
Objekte als Consumer eintragen.
Consumer-Typen
--------------
Hook-Consumer koennen sich fuer verschiedene Rollen beim Hook-Provider
registrieren, die darueber entscheiden, was der Consumer innerhalb der
Callback-Funktion tun darf. Es gibt reine Zuhoerer (Listener),
Datenmodifizierer (Data-Modifier), Hook-Modifizierer (Hook-Modifier) und
sogenannte Surveyor-Hooks. Nachfolgend die Liste dieser Hook-Typen und
die Moeglichkeiten, die ihnen zur Verfuegung stehen:
* Listener (H_LISTENER, max. Anzahl 5)
werden ueber ein Ereignis nur informiert, koennen aber nicht in den
Ablauf eingreifen oder Daten aendern.
* Data-Modifier (H_DATA_MODIFICATOR, max. Anzahl 3)
duerfen die Daten eines Ereignisses aendern.
* Hook-Modifier (H_HOOK_MODIFICATOR, max. Anzahl 2)
duerfen die Daten des Ereignisses aendern und zusaetzlich das Ereignis
auch abbrechen.
* Surveyor (H_HOOK_SURVEYOR, max. Anzahl 1)
duerfen alles oben beschriebene. Zusaetzlich werden sie gefragt, wenn
andere Objekte einen Hook eintragen wollen, einen Hook abbrechen
wollen oder Daten aendern wollen.
Anders ausgedrueck: Surveyorhooks entscheiden, was andere duerfen.
Kein normales Objekte sollte diese Art von Hook eintragen. Der RM
muss die Verwendung eines Surveyors genehmigen.
Die angegebene Limitierung bezieht sich auf die Anzahl der beim jeweiligen
Provider registrierten Hook-Objekte.
Hook-Typen
----------
Es koennen grundsaetzlich nur die Hooks registriert werden, die ein Objekt
von sich aus aktiv anbietet. Ein Objekt, das Hooks zur Registrierung
anbieten will, muss /std/hook_provider erben. Die meisten Mudlib-
Basisobjekte tun dies bereits und bieten entsprechend die passenden Hooks
an. Objekte, die Surveyor-Hooks registrieren wollen, muessen
/std/hook_surveyor erben.
Zum Verstaendnis: Objekte, die sich bei einem Provider registrieren wollen,
muessen keines dieser Objekte erben. Lediglich muss inkludiert
werden, damit die Defines zur Verfuegung stehen.
Folgende Hooks gibt es zur Zeit in der Basis-Mudlib:
* H_HOOK_MOVE
Vor Bewegung eines Lebewesens ausgeloest.
Datenveraenderung und Abbruch moeglich.
Daten: ({dest,method,direction,textout,textin}), siehe move()
* H_HOOK_DIE
Beim Tod eines Lebewesens ausgeloest. Kann den Tod abbrechen oder
abaendern.
Daten: int poisondeath, siehe die()
* H_HOOK_DEFEND
Im Defend() eines Lebenwesens ausgeloest. Kann das Defend() abbrechen
und Daten des Defend() aendern. Dieser Hook kommt nach einem eventuell
in P_TMP_DEFEND_HOOK registrierten Legacy-Hook zum Zuge. Dessen Daten
sind in den EINFO-Daten enthalten und werden durch die Rueckgabedaten
dieses Hooks ersetzt.
Daten: ({dam, dam_type, spell, enemy}), siehe Defend()
* H_HOOK_ATTACK
Im Attack() eines Lebenwesens ausgeloest. Kann das Attack() abbrechen.
Daten: object* enemy (enthaelt nur enemy als einziges Element)
* H_HOOK_ATTACK_MOD
Wird im Attack() ausgeloest, nachdem die fuer den Angriff wichtigen Daten
ermittelt und berechnet wurden. Diese koennen dann vom Hook-Consumer
nochmal geaendert werden. Auch ein Abbruch des Attack() ist moeglich.
Dieser Hook kommt nach Auswertung eines ggf. mittels P_TMP_ATTACK_HOOK
eingetragenen Legacy-Hooks zum Zuge.
Daten: deep_copy(ainfo), siehe Defendinfo, Abschnitt ORIGINAL_AINFO
* H_HOOK_HP
* H_HOOK_SP
Bei Veraenderung der Property eines Spielers gerufen. Falls eine
Setmethode auf der Property liegt, wird der Hook wahrscheinlich meist
doch nicht gerufen. Wenn sich der Wert nicht geaendert hat, wird er auch
nicht gerufen: der Hook reagiert auf Veraenderung des Wertes, nicht
auf den Aufruf von SetProp().
Keine Datenveraenderung und kein Abbruch moeglich.
Daten: der neue Wert der Property
* H_HOOK_ALCOHOL
* H_HOOK_FOOD
* H_HOOK_DRINK
* H_HOOK_POISON
Bei Veraenderung der Property des Lebewesens gerufen. Falls eine
Setmethode auf der Property liegt, wird der Hook wahrscheinlich meist
doch nicht gerufen. Wenn sich der Wert nicht geaendert hat, wird er auch
nicht gerufen: der Hook reagiert auf Veraenderung des Wertes, nicht
auf den Aufruf von SetProp().
Datenveraenderung und Abbruch moeglich.
Daten: neuer Wert der Property
* H_HOOK_CONSUME
Wird gerufen, wenn ein Lebewesen Speisen oder Getraenke in Kneipen
konsumiert.
Datenveraenderung von und Abbruch moeglich. Aenderung von
nicht moeglich.
Daten: ({cinfo, testonly}), siehe consume()
* H_HOOK_TEAMROWCHANGE
Bei Teamreihenwechsel eines Lebewesens ausgeloest.
Keine Datenveraenderung und kein Abbruch moeglich.
Daten: int* ({alteTeamreihe, neueTeamreihe})
* H_HOOK_INSERT
Wird von Spielerobjekten ausgeloest, nachdem ein Objekt ins
Spielerinventar bewegt wurde.
Keine Datenveraenderung und kein Abbruch moeglich.
Daten: object, das ins Inventar bewegte Objekt.
* H_HOOK_EXIT_USE
Wird von einem Raum ausgeloest, wenn ein Lebewesen einen Ausgang benutzt.
Datenveraenderung und Abbruch moeglich.
Daten: ({string verb, string|closure destroom, string message})
* H_HOOK_INIT
Wird von einem Raum ausgeloest, wenn init() gerufen wird (d.h. ein
Lebewesen den Raum betritt).
Abbruch moeglich.
ACHTUNG: bei Abbruch von init() sind schwere Bugs wahrscheinlich!
Daten: keine.
Hook-Prioritaeten
-----------------
Hooks lassen sich darueber hinaus noch mit unterschiedlicher Prioritaet
eintragen, so dass bei Registrierung mehrerer Hooks am selben Provider diese
dann in der entsprechenden Reihenfolge abgearbeitet werden. Wenn ein neuer
Hook eingetragen wird, aber die max. Anzahl vorher schon erreicht war, wird
der Konsument mit der niedrigsten Prioritaet geloescht. In diesem Fall wird
der verdraengte Consumer durch Aufruf von superseededHook() darueber
informiert, dass seine Verbindung getrennt wurde.
HOOK-CONSUMER
=============
Um sich bei einem Provider zu registrieren, ruft man in diesem die Funktion
HRegisterToHook() auf (Beschreibung siehe unten bei den Hook-Providern).
Wenn die Registrierung erfolgreich war, ruft der Hook-Provider bei Eintritt
des Ereignisses in allen Konsumenten eine bestimmte Funktion auf. Wenn bei
der Registrierung ein Hook-Objekt angegeben wurde, wird standardmaessig die
Funktion HookCallback() gerufen. Registriert man stattdessen eine Closure,
wird diese mit denselben Daten wie die Lfun gerufen. Nachfolgend ist die
Lfun beschrieben, alles dort gesagte gilt aber genauso fuer Closures.
* mixed HookCallback(object hookSource, int hookid, mixed hookData)
Diese Methode wird in jedem Hook-Konsumenten eines Hook-Providers
aufgerufen, solange die Verarbeitung nicht vorher abgebrochen wurde.
Die Reihenfolge des Aufrufs ist Surveyor, Hook-Modifikator,
Data-Modifikator, Listener. Innerhalb der Gruppen wird nach Prioritaet
abgearbeitet.
Ein Surveyor-Hook kann verhindern, dass Hooks bestimmte Aenderungen
durchfuehren.
Der Funktion wird der Hook-Provider als Objekt hookSource, der Hook-Typ
sowie dessen Daten uebergeben. Das bedeutet, man kann diese Funktion
fuer die Bearbeitung verschiedener Hook-Typen registrieren.
Als Rueckgabewert wird immer ein Array aus zwei Elementen erwartet, das
die folgenden Angaben beinhalten muss:
Element 0 (H_RETCODE) gibt an, welche Aktion die Callback-Funktion
ausgefuehrt hat:
H_NO_MOD => keine Aenderungen
H_ALTERED => Daten wurden veraendert
H_CANCELLED => Hook-Kette soll abgebrochen werden, d.h. nach
Prioritaet spaeter aufzurufende Hooks kommen nicht mehr zum Zuge.
=> Ausserdem soll die Hook-ausloesende Stelle abgebrochen werden.
Beispielsweise wird das Defend() abgebrochen, wenn ein
H_HOOK_DEFEND mit H_CANCELLED beantwortet wird.
Element 1 (H_RETDATA) gibt die (evtl. geaenderten) Daten an
mixed-Objekt, das wie der Parameter hookData aufgebaut ist.
Hinweis: auch reine Listener-Objekte muessen ein Array zurueckgeben, das
dann als erstes Element H_NO_MOD enthaelt.
Ein Objekt darf sich mehrfach fuer den gleichen Hook registrieren.
Allerdings ist dann fuer jede Registrierung eine andere Closure noetig.
* void superseededHook(int hookid, object hookprovider)
Wird einmalig gerufen, um dem Konsumenten zu signalisieren, dass er von
einem anderen mit hoeherer Prioritaet verdraengt wurde.
HOOK-PROVIDER
=============
Der Hook-Provider bietet eine Menge von Methoden an, die eine Konfiguration
ermoeglichen und die Eintragung von Hook-Konsumenten erlauben. Im
Normalfall sollte er geerbt und muss nicht modifiziert werden. Die einzige
Konfiguration, die man typischerweise vornehmen muss, ist, die vom Objekt
bereitgestellten Hooks zu benennen.
* int* HListHooks();
Diese Methode liefert eine Liste von Hooktypen, fuer die das Objekt
Registrierungen akzeptiert. Standardmaessig bieten die Mudlib-Basis-
objekte folgende Hooks an:
Spielerobjekte: H_HOOK_MOVE, H_HOOK_DIE, H_HOOK_DEFEND, H_HOOK_ATTACK,
H_HOOK_HP, H_HOOK_SP, H_HOOK_ATTACK_MOD, H_HOOK_ALCOHOL
H_HOOK_FOOD, H_HOOK_DRINK, H_HOOK_POISON, H_HOOK_CONSUME,
H_HOOK_TEAMROWCHANGE ,H_HOOK_INSERT
NPCs: H_HOOK_MOVE, H_HOOK_DIE, H_HOOK_DEFEND, H_HOOK_ATTACK,
H_HOOK_ATTACK_MOD, H_HOOK_ALCOHOL, H_HOOK_FOOD, H_HOOK_DRINK,
H_HOOK_POISON, H_HOOK_CONSUME, H_HOOK_TEAMROWCHANGE
Raeume: H_HOOK_EXIT_USE, H_HOOK_INIT
Dinge: keine
* protected void offerHook(int hookid, int offerstate);
Diese Methode dient dazu, einen bestimmten Hook anzubieten. Nur Hooks,
die hiermit angeboten wurden, stehen zur Registrierung zur Verfuegung
und werden im Rueckgabewert von HListHooks() aufgefuehrt.
'offerstate': 0 (nicht verfuegbar), 1 (verfuegbar)
* int HRegisterToHook(int hookid, object|closure consumer, int hookprio,
int consumertype, int timeInSeconds);
Registriert ein Objekt oder eine Closure als Hook-Konsument.
Argumente:
'hookid' gibt den Hook-Typ an, s.o.
Man kann sich nur fuer Hooktypen eintragen, die die Methode
HListHooks() angeboten hat.
'consumer' Objekt oder Closure. Wenn ein Objekt uebergeben wird,
wird dieses eingetragen und spaeter HookCallback() an
diesem Objekt gerufen.
Wenn eine Closure uebergeben wird, wird das Objekt der
Closure eingetragen und spaeter diese Closure gerufen.
'hookprio' Gibt die Prioritaet an, mit der der Hook laufen soll.
Diese Angabe bestimmt die Reihenfolge, in der die Hooks
in der Liste der Hooks eingetragen werden. Die moeglichen
Prioritaeten sind:
- H_HOOK_LIBPRIO(x)
- H_HOOK_GUILDPRIO(x) oder
- H_HOOK_OTHERPRIO(x).
x darf 0, 1 oder 2 sein (je niedriger, desto hoeher die
Prioritaet).
'consumertype' Gibt an, um welche Art von Consumer es sich handelt.
Es gibt vier festgelegten Arten, die fuer alle Hooks
existieren koennen, aber nicht muessen. Die Methode
HConsumerTypeIsAllowed() gibt Aufschluss darueber, welche
Consumer-Typen tatsaechlich freigegeben sind (s.u.)
'timeInSeconds' gibt die Laufzeit des Hooks an. Falls 0 eingetragen wird,
laeuft der Hook ewig.
Rueckgabewerte:
1 - Registrierung erfolgreich
<=0 - Registrierung fehlgeschlagen mit folgendem Ergebnis:
-1 : Hook unbekannt
-2 : consumer ist keine closure und es konnte kein Callback auf
HookCallback im consumer erstellt werden.
-3 : Consumer ist bereits registriert
-4 : Consumer-Typ ist nicht erlaubt
-5 : hookprio ist nicht erlaubt
-6 : Surveyor hat Registrierung nicht erlaubt
-7 : zuviele Hooks registriert / kein Hookeintrag frei
* int HUnregisterFromHook(int hookid, object|closure consumer);
Hebt die Registrierung von fuer einen bestimmten Hook-Typ
wieder auf.
Argumente:
'hookid' der Hook-Typ (s.o.)
'consumer' Das Objekt oder die Closure, dessen/deren Registrierung
aufgehoben werden soll. Bei einer Closure wird genau diese
ausgetragen. Bei der Angabe eines Objekts wird versucht, die
Closure auf HookCallback() in diesem Objekt auszutragen.
Rueckgabewerte:
0 - 'consumer' nicht als Konsument gefunden
1 - Austragen erfolgreich
* int HConsumerTypeIsAllowed(int type, object consumer);
Diese Methode liefert 1 zurueck, wenn ein bestimmter Consumer-Typ
(fuer diesen Konsumenten) erlaubt wird.
Die Standardmethode liefert immer 1 (true) zurueck. Erbende Objekte
koennen diese Methode ueberschreiben, wenn sie nicht alle Consumer-Typen
anbieten.
Wenn man diese Methode in einem eigenen Hook-Provider ueberschreibt,
kann man Consumer-Typen nur global abschalten, aber nicht selektiv
pro Hook-Typ.
Alle Mudlib-Basisobjekte, die Hooks anbieten, geben hier zur Zeit immer
1 zurueck, auch wenn die Hook-Typen die gewuenschte Funktionalitaet nicht
auswerten. Beispielsweise kann man einen Hook-Modifikator fuer den
Insert-Hook registrieren, damit aber nicht verhindern, dass das Objekt
ins Spielerinventar bewegt wird.
* int HPriorityIsAllowed(int prio, object consumer);
Diese Methode gibt an, ob eine bestimmte Prioritaet (fuer den angegebenen
Konsumenten) erlaubt ist. Die Standardmethode liefert immer 1 (true)
zurueck. Erbende Objekte koennen diese Methode ueberschreiben, wenn
sie die verfuegbaren Hook-Prioritaeten einschraenken wollen.
Wenn man diese Methode in einem eigenen Hook-Provider ueberschreibt,
kann man Prioritaeten nur global abschalten, aber nicht selektiv
pro Hook-Typ.
Alle Mudlib-Basisobjekte, die Hooks anbieten, geben hier zur Zeit immer
1 zurueck.
* int HIsHookConsumer(int hookid, object|closure consumer);
Ist ein Objekt, liefert die Methode die Anzahl, wie oft dieses
Objekt (mit verschiedenen Closures) fuer den Hook eingetragen
ist.
Ist eine Closure, liefert diese Methode 1, wenn diese
Closure fuer den Hook eingetragen ist.
* protected mapping HCopyHookMapping();
Diese Methode liefert eine Kopie des Hook-Mappings.
ACHTUNG: diese Daten sollten das Objekt ausser fuer Debugzwecke
NIEMALS verlassen.
* protected mapping HCopyHookConsumers(int hookid);
Dieser Methode liefert eine Kopie der Hook-Consumer des Objektes.
ACHTUNG: diese Daten sollten das Objekt ausser fuer Debugzwecke
NIEMALS verlassen.
HOOK-SURVEYOR
=============
Objekte mit Surveyorhooks muessen eine Menge von Methoden definieren, die
der Hookprovider aufruft:
* status HookRegistrationCallback(
object registringObject,
int hookid,
object hookSource,
int registringObjectsPriority,
int registringObjectsType)
Diese Methode wird vom Hook-Provider aufgerufen, wenn der Hook-Konsument
als Surveyor eingetragen ist und ein weiterer Hook eingetragen werden
soll.
Gibt diese Methode 0 zurueck, dann verbietet der Surveyor, dass der
andere Konsument als Hook eingetragen wird.
* int HookCancelAllowanceCallback(
object cancellingObject,
int hookid,
object hookSource,
int cancellingObjectsPriority,
mixed hookData)
Diese Methode wird aufgerufen, um herauszufinden, ob ein bestimmter
anderer Hook die Ausfuehrung der Hook-Kette unterbrechen darf.
Nur Hooks des Consumer-Typs H_HOOK_MODIFICATOR werden der Methode
uebergeben, weil nur diese neben dem Surveyor selbst ueberhaupt die
Berechtigung haben, die Hook-Kette abzubrechen.
* int HookModificationAllowanceCallback(
object modifyingObject,
int hookid,
object hookSource,
int modifyingObjectsPriority,
mixed hookData)
Diese Methode wird aufgerufen, um herauszufinden, ob ein bestimmter
anderer Hook die Daten des Hooks veraendern darf oder nicht.
Es werden die Hooks der Consumer-Typen H_HOOK_MODIFICATOR und
H_DATA_MODIFICATOR (in dieser Reihenfolge) aufgerufen.
WAS KOSTET DAS?
Das Ausloesen eines Hooks per HookFlow() kostet 111 Ticks und ca. 7 us,
wenn es gar keinen gibt, der drauf lauscht (sozusagen Fixkosten).
Pro H_LISTENER kommen dann 31 Ticks und ca. 2 us dazu.
Gibts einen Surveyor-Hook (der wird dann gefragt, ob andere Objekte die
Daten des Hooks aendern oder die Hookverarbeitung abbrechen duerfen):
Fixkosten: 155 Ticks, 11 us.
Plus pro Data-Modifier:
106 Ticks, 5.6 us
Plus pro Hook-Modifier, der aber nur Daten aendert:
112 Ticks, 6.4 us
Und ein Hook-Modifier, der den Hook abbricht:
76 Ticks, 4 us
(Macht der Surveyor irgendwas anderes als 'return 1;', wirds natuerlich
entsprechend teurer.)
zurück zur Übersicht