blob: 688c1b78b33058e9f988369a669286ada44c6611 [file] [log] [blame]
zesstra42fd8bf2016-06-27 22:04:14 +02001// (c) by Padreic (Padreic@mg.mud.de)
2
3/* 12 Juli 1998, Padreic
4 *
5 * Dieser Master dient zum einsparen von call_out Ketten bei Laufnpcs.
6 * Anmelden bei diesem Master geschieht ueber die Funktion RegisterWalker().
7 * varargs void RegisterWalker(int time, int random, closure walk_closure)
8 * time - in welchen Abstaenden soll das Objekt bewegt werden
9 * rand - dieser Wert wird als random immer beim bewegen zu time addiert
10 * walk_closure - die Closure die immer aufgerufen werden muss, wenn dieser
11 * Parameter weggelassen wird, wird statdessen die Funktion
12 * Walk() im NPC aufgerufen.
13 *
14 * Abgemeldet wird der NPC sobald die Closure bzw. die Walk-Funktion 0
15 * returned. Bei allen Werten !=0 bleibt der NPC aktiv.
16 *
17 * Hinweis: Der NPC muss sich mind. alle 180 sek. bewegen (time+random),
18 * ansonsten kann dieser Master nicht verwendet werden.
19 */
20
Zesstra90026882019-07-19 18:46:28 +020021#pragma strong_types,rtt_checks
22#pragma no_clone,no_inherit
23
Zesstra23019b92019-12-09 20:56:05 +010024#include "/p/service/padreic/mnpc/mnpc.h"
25
26#define MAX_DELAY_HBS (MAX_MASTER_TIME/__HEART_BEAT_INTERVAL__)
27#define DEFAULT_WALK_DELAY MAX_MASTER_TIME /* ist der billigste Wert :) */
28
Zesstra457693b2019-07-21 21:47:02 +020029/* Ab welcher Rest-Tickmenge wird die Verarbeitung von Walkers unterbrochen */
30#define MAX_JOB_COST 200000
zesstra42fd8bf2016-06-27 22:04:14 +020031
32// Funktionen zum vereinfachten Zugriff auf die Komprimierten Daten
33// im Walkerarray
34#define TIME(t) (t & 0x00ff) /* 8 Bit = 256 */
35#define RANDOM(r) ((r & 0xff00) >> 8) /* 8 Bit = 256 */
36#define WERT(t, r) ((t & 0x00ff)+((r << 8) & 0xff00)) /* 16 Bit */
37
Zesstra457693b2019-07-21 21:47:02 +020038// Indizes fuer clients
Zesstrabc91fa72019-07-21 22:01:58 +020039#define WALK_CLOSURE 0
40#define WALK_DELAY 1
41#define WALK_TIMESLOT 2
Zesstra1d54d2a2019-06-24 22:25:12 +020042
Zesstra90026882019-07-19 18:46:28 +020043nosave int counter; // Markiert aktuellen Zeitslot im Array walker
Zesstra457693b2019-07-21 21:47:02 +020044nosave < < closure >* >* walker;
Zesstrad41e6712019-07-19 18:35:37 +020045// Mapping mit allen registrierten MNPC (als Objekte) als Key und deren
Zesstrabc91fa72019-07-21 22:01:58 +020046// walk_closure (1. Wert), Zeitdaten (2. wert) und dem aktuellen
47// Zeitslot in <walker> (3. Wert).
48nosave mapping clients = m_allocate(0,3);
zesstra42fd8bf2016-06-27 22:04:14 +020049
Zesstra90026882019-07-19 18:46:28 +020050public int Registration();
Zesstrabc854872019-06-24 22:51:54 +020051
zesstra42fd8bf2016-06-27 22:04:14 +020052protected void create()
53{
Zesstra23019b92019-12-09 20:56:05 +010054 walker=map(allocate(MAX_DELAY_HBS+1), #'allocate);
zesstra42fd8bf2016-06-27 22:04:14 +020055}
56
57#define ERROR(x) raise_error(sprintf(x, previous_object()));
58
Zesstrab9a22a62019-09-26 19:36:52 +020059// Am besten nicht direkt sondern nur ueber einen Standardnpc benutzen.
Zesstra90026882019-07-19 18:46:28 +020060public varargs void RegisterWalker(int time, int rand, closure walk_closure)
zesstra42fd8bf2016-06-27 22:04:14 +020061{
zesstra42fd8bf2016-06-27 22:04:14 +020062 // pruefen ob die Paramter zulaessig sind...
63 if (time<0) ERROR("negative time to RegisterWalker() from %O.\n");
64 if (rand<0) ERROR("negative random to RegisterWalker() from %O.\n");
Zesstra260613d2019-12-09 21:02:58 +010065 // das max. Delay darf max. MAX_MASTER_TIME-1 sein, sonst landet der MNPC im
66 // HB evtl. wieder in dem Slot, der gerade bearbeitet wird und der direkt
67 // danach komplett genullt wird...
68 if ((time+rand) >= (MAX_MASTER_TIME))
zesstra42fd8bf2016-06-27 22:04:14 +020069 ERROR("Too long delaytime from %s to RegisterWalker().\n");
70
Zesstrae7956f32019-06-24 22:32:04 +020071 if (Registration())
Zesstrabc854872019-06-24 22:51:54 +020072 raise_error(sprintf("Mehrfachanmeldung nicht erlaubt. Objekt: %O\n",
73 previous_object()));
Zesstrae7956f32019-06-24 22:32:04 +020074
Zesstra90026882019-07-19 18:46:28 +020075 int wert=WERT(time, rand);
zesstra42fd8bf2016-06-27 22:04:14 +020076 if (!wert && !rand) wert=DEFAULT_WALK_DELAY;
Zesstra90026882019-07-19 18:46:28 +020077
78 closure func = walk_closure;
79 if (!closurep(func))
80 {
Bugfix8ecb43e2021-01-23 19:30:43 +010081 func=symbol_function("CheckWalk", previous_object());
zesstra42fd8bf2016-06-27 22:04:14 +020082 if (!func)
83 raise_error("RegisterWalker() call from Object without Walk() function.\n");
84 }
Zesstrab9a22a62019-09-26 19:36:52 +020085 else
86 {
87 // Closures auf fremde lfuns fuehren zu Inkonsistenzen/Fehlers und man
88 // kann sie auch nicht wieder abmelden. Daher abfangen.
89 if (get_type_info(func, 2) != previous_object())
90 raise_error(sprintf("Anmeldung von Closures auf fremde lfuns ist nicht "
91 "erlaubt. Closure: %O\n",func));
92 }
Zesstra8b66d692019-10-17 22:19:53 +020093 // Erster Client? -> HB einschalten.
Zesstra9d250de2019-07-19 18:59:54 +020094 if (!sizeof(clients)) {
zesstra42fd8bf2016-06-27 22:04:14 +020095 set_heart_beat(1);
zesstra42fd8bf2016-06-27 22:04:14 +020096 }
Zesstra90026882019-07-19 18:46:28 +020097 int next=counter;
Zesstra23019b92019-12-09 20:56:05 +010098 //min. 1 Heartbeat delay erzwingen, ab jetzt in Heartbeats
99 next += max(1, (time+random(rand))/__HEART_BEAT_INTERVAL__);
100 if (next>MAX_DELAY_HBS) next-=MAX_DELAY_HBS;
Zesstra457693b2019-07-21 21:47:02 +0200101 walker[next]+=({ func });
Zesstrabc91fa72019-07-21 22:01:58 +0200102 clients += ([ get_type_info(func, 2): func; wert; next ]);
zesstra42fd8bf2016-06-27 22:04:14 +0200103}
104
zesstra42fd8bf2016-06-27 22:04:14 +0200105// Aufruf nach Moeglichkeit bitte vermeiden, da recht aufwendig. Meist ist
106// es leicht im NPC "sauber Buch zu fuehren" und dann ggf. aus Walk()
107// 0 zu returnen.
Zesstra90026882019-07-19 18:46:28 +0200108public void RemoveWalker()
zesstra42fd8bf2016-06-27 22:04:14 +0200109{
Zesstrad41e6712019-07-19 18:35:37 +0200110 if (!member(clients, previous_object()))
111 return;
Zesstrabc91fa72019-07-21 22:01:58 +0200112 // Naechster Zeitslot und index in dem Slotarrays ermitteln
113 int next = clients[previous_object(), WALK_TIMESLOT];
114 closure func = clients[previous_object(), WALK_CLOSURE];
115 int idx = member(walker[next], func);
116 // Durch 0 ersetzen. Aber wir koennten gerade im heart_beat stecken... In
117 // dem Fall den Eintrag ersetzen durch was, was beim Abarbeiten ausgetragen
118 // wird.
119 if (next==counter)
120 walker[next][idx]=function () {return 0;};
121 else
122 walker[next][idx]=0;
Zesstrad41e6712019-07-19 18:35:37 +0200123
Zesstrabc91fa72019-07-21 22:01:58 +0200124 // 0-Eintraege entfernen, aber nur, wenn wir gerade nicht evtl. in dem HB
125 // stecken und den Slot abarbeiten.
126 if (next!=counter)
127 walker[next]-=({ 0 });
128 // und noch die Stammdaten entfernen
Zesstrad41e6712019-07-19 18:35:37 +0200129 m_delete(clients, previous_object());
zesstra42fd8bf2016-06-27 22:04:14 +0200130}
131
Zesstra90026882019-07-19 18:46:28 +0200132public int Registration()
zesstra42fd8bf2016-06-27 22:04:14 +0200133{
Zesstrad41e6712019-07-19 18:35:37 +0200134 return member(clients, previous_object());
zesstra42fd8bf2016-06-27 22:04:14 +0200135}
136
137void heart_beat()
138{
Zesstra9d250de2019-07-19 18:59:54 +0200139 int i = sizeof(walker[counter]);
140 if (i)
141 {
Zesstra9d250de2019-07-19 18:59:54 +0200142 for (i--;i>=0;i--)
143 {
144 if (get_eval_cost() < MAX_JOB_COST)
145 {
zesstra42fd8bf2016-06-27 22:04:14 +0200146 // nicht abgefertigte NPCs im naechsten heart_beat ausfuehren
147 walker[counter]=walker[counter][0..i];
zesstra42fd8bf2016-06-27 22:04:14 +0200148 return;
149 }
Zesstra9d250de2019-07-19 18:59:54 +0200150 else
151 {
Zesstra457693b2019-07-21 21:47:02 +0200152 closure func = walker[counter][i];
153 if (func)
Zesstra1d54d2a2019-06-24 22:25:12 +0200154 {
Zesstra457693b2019-07-21 21:47:02 +0200155 object mnpc = get_type_info(func, 2);
156 mixed res;
157 if (!catch(res=funcall(func);publish)
158 && intp(res) && res)
159 {
160 // Es gab keinen Fehler und das Objekt will weiterlaufen.
161 // Naechsten Zeitslot bestimmen und dort die closure eintragen.
162 int delay = clients[mnpc, WALK_DELAY];
Zesstra9ad5b112019-10-17 22:54:48 +0200163 // das delay muss min. 1 sein, sonst tragen wir den MNPC in
164 // diesen aktuellen zeitslot wieder ein (ja, Magier tun solche
165 // Dinge), der ja nach Abarbeiten des Slots genullt wird.
166 int next = counter
Zesstra23019b92019-12-09 20:56:05 +0100167 + max(1, (TIME(delay) + random(RANDOM(delay)))
168 /__HEART_BEAT_INTERVAL__);
169 if (next > MAX_DELAY_HBS)
170 next -= MAX_DELAY_HBS;
Zesstra457693b2019-07-21 21:47:02 +0200171 walker[next] += ({ func });
Zesstrabc91fa72019-07-21 22:01:58 +0200172 clients[mnpc, WALK_TIMESLOT] = next;
Zesstra457693b2019-07-21 21:47:02 +0200173 }
174 else // Fehler oder Objekt will abschalten
175 m_delete(clients, mnpc);
zesstra42fd8bf2016-06-27 22:04:14 +0200176 }
Zesstra457693b2019-07-21 21:47:02 +0200177 // else: Falls die closure nicht mehr existiert, existiert das Objekt
178 // nicht mehr, dann ist es ohnehin auch schon aus clients raus und es
179 // muss nix gemacht werden.
zesstra42fd8bf2016-06-27 22:04:14 +0200180 }
181 }
Zesstra457693b2019-07-21 21:47:02 +0200182 walker[counter]=({}); // fertiger Zeitslot, komplett leeren
zesstra42fd8bf2016-06-27 22:04:14 +0200183 }
Zesstra457693b2019-07-21 21:47:02 +0200184 // Wrap-around am Ende des Arrays.
Zesstra23019b92019-12-09 20:56:05 +0100185 if (counter == MAX_DELAY_HBS)
zesstra42fd8bf2016-06-27 22:04:14 +0200186 counter=0;
Zesstra457693b2019-07-21 21:47:02 +0200187 else
188 counter++;
189
190 // wenn keine Clients mehr uebrig, kann pausiert werden. Es kann sein, dass
191 // noch Dummy-Eintraege in walker enthalten sind, die stoeren nicht, bis
192 // wir das naechste Mal drueber laufen.
Zesstra9d250de2019-07-19 18:59:54 +0200193 if (!sizeof(clients)) {
zesstra42fd8bf2016-06-27 22:04:14 +0200194 set_heart_beat(0);
zesstra42fd8bf2016-06-27 22:04:14 +0200195 }
196}
197
Zesstra8b66d692019-10-17 22:19:53 +0200198// im reset zur Sicherheit mal den heart_beat ggf. einschalten.
zesstra42fd8bf2016-06-27 22:04:14 +0200199// dient zu einem wieder anwerfen im Falle eines Fehlers im heart_beat()
Zesstra8b66d692019-10-17 22:19:53 +0200200// wenn doch nix gemacht werden musss, schaltet sich der HB eh wieder aus.
201void reset()
zesstra42fd8bf2016-06-27 22:04:14 +0200202{
Zesstra8b66d692019-10-17 22:19:53 +0200203 // set_heart_beat() wird als query_heart_beat() 'missbraucht' und daher muss
204 // im else der HB in jedem Fall auch wieder eingeschaltet werden.
Zesstra9d250de2019-07-19 18:59:54 +0200205 if (set_heart_beat(0)<=0)
206 {
207 if (sizeof(clients) > 0)
208 {
zesstra42fd8bf2016-06-27 22:04:14 +0200209 write_file(object_name()+".err", sprintf(
210 "%s: Fehler im heart_beat(). %d aktive Prozesse.\n",
Zesstra9d250de2019-07-19 18:59:54 +0200211 dtime(time()), sizeof(clients)));
Zesstra8b66d692019-10-17 22:19:53 +0200212 set_heart_beat(1);
zesstra42fd8bf2016-06-27 22:04:14 +0200213 }
214 }
Zesstra9d250de2019-07-19 18:59:54 +0200215 else
216 set_heart_beat(1);
zesstra42fd8bf2016-06-27 22:04:14 +0200217}
218
Zesstra90026882019-07-19 18:46:28 +0200219// Bemerkung: damit kann jeder die Closures ermitteln und dann selber rufen.
zesstra42fd8bf2016-06-27 22:04:14 +0200220mixed *WalkerList() // nur fuer Debugzwecke
Zesstra9d250de2019-07-19 18:59:54 +0200221{ return ({ clients, walker, counter }); }