1 /** @file thinkers.cpp World map thinker management.
2 *
3 * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 * @authors Copyright © 2006-2015 Daniel Swanson <danij@dengine.net>
5 *
6 * @par License
7 * GPL: http://www.gnu.org/licenses/gpl.html
8 *
9 * <small>This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by the
11 * Free Software Foundation; either version 2 of the License, or (at your
12 * option) any later version. This program is distributed in the hope that it
13 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15 * Public License for more details. You should have received a copy of the GNU
16 * General Public License along with this program; if not, see:
17 * http://www.gnu.org/licenses</small>
18 */
19
20 #define DENG_NO_API_MACROS_THINKER
21
22 #include "de_base.h"
23 #include "world/thinkers.h"
24
25 #ifdef __CLIENT__
26 # include "client/cl_mobj.h"
27 # include "world/clientmobjthinkerdata.h"
28 #endif
29
30 #ifdef __SERVER__
31 # include "def_main.h"
32 # include "server/sv_pool.h"
33 # include <doomsday/world/mobjthinkerdata.h>
34 #endif
35
36 #include "world/map.h"
37 #include "world/p_object.h"
38
39 #include <de/memoryzone.h>
40 #include <QList>
41 #include <QtAlgorithms>
42
43 using namespace de;
44
Thinker_IsMobjFunc(thinkfunc_t func)45 bool Thinker_IsMobjFunc(thinkfunc_t func)
46 {
47 return (func && func == reinterpret_cast<thinkfunc_t>(gx.MobjThinker));
48 }
49
Thinker_IsMobj(thinker_t const * th)50 bool Thinker_IsMobj(thinker_t const *th)
51 {
52 return (th && Thinker_IsMobjFunc(th->function));
53 }
54
Thinker_Map(thinker_t const &)55 world::Map &Thinker_Map(thinker_t const & /*th*/)
56 {
57 /// @todo Do not assume the current map.
58 return App_World().map();
59 }
60
61 namespace world {
62
63 struct ThinkerList
64 {
65 bool isPublic; ///< All thinkers in this list are visible publically.
66
67 Thinker sentinel;
68
ThinkerListworld::ThinkerList69 ThinkerList(thinkfunc_t func, bool isPublic) : isPublic(isPublic)
70 {
71 sentinel.function = func;
72 sentinel.disable(); // Safety measure.
73
74 sentinel.prev = sentinel.next = sentinel;
75 }
76
reinitworld::ThinkerList77 void reinit()
78 {
79 sentinel.prev = sentinel.next = sentinel;
80 }
81
functionworld::ThinkerList82 thinkfunc_t function() const
83 {
84 return sentinel.function;
85 }
86
87 // Link the thinker to the list.
linkworld::ThinkerList88 void link(thinker_t &th)
89 {
90 sentinel.prev->next = &th;
91 th.next = sentinel;
92 th.prev = sentinel.prev;
93 sentinel.prev = &th;
94 }
95
countworld::ThinkerList96 dint count(dint *numInStasis) const
97 {
98 dint num = 0;
99 thinker_t *th = sentinel.next;
100 while (th != &sentinel.base() && th)
101 {
102 #ifdef LIBDENG_FAKE_MEMORY_ZONE
103 DENG2_ASSERT(th->next);
104 DENG2_ASSERT(th->prev);
105 #endif
106 num += 1;
107 if (numInStasis && Thinker_InStasis(th))
108 {
109 (*numInStasis) += 1;
110 }
111 th = th->next;
112 }
113 return num;
114 }
115
releaseAllworld::ThinkerList116 void releaseAll()
117 {
118 for (thinker_t *th = sentinel.next; th != &sentinel.base() && th; th = th->next)
119 {
120 Thinker::release(*th);
121 }
122 }
123 };
124
DENG2_PIMPL(Thinkers)125 DENG2_PIMPL(Thinkers)
126 {
127 dint idtable[2048]; ///< 65536 bits telling which IDs are in use.
128 dushort iddealer = 0;
129
130 QList<ThinkerList *> lists;
131 QHash<thid_t, mobj_t *> mobjIdLookup; ///< public only
132 QHash<thid_t, thinker_t *> thinkerIdLookup; ///< all thinkers with ID
133
134 bool inited = false;
135
136 Impl(Public *i) : Base(i)
137 {
138 clearMobjIds();
139 }
140
141 ~Impl()
142 {
143 // Make sure the private instances of thinkers are released.
144 releaseAllThinkers();
145
146 // Note that most thinkers are allocated from the memory zone
147 // so there is no memory leak here as this memory will be purged
148 // automatically when the map is "unloaded".
149 qDeleteAll(lists);
150 }
151
152 void releaseAllThinkers()
153 {
154 thinkerIdLookup.clear();
155 for (ThinkerList *list : lists)
156 {
157 list->releaseAll();
158 }
159 }
160
161 void clearMobjIds()
162 {
163 de::zap(idtable);
164 idtable[0] |= 1; // ID zero is always "used" (it's not a valid ID).
165
166 mobjIdLookup.clear();
167 thinkerIdLookup.clear();
168 }
169
170 thid_t newMobjId()
171 {
172 // Increment the ID dealer until a free ID is found.
173 /// @todo fixme: What if all IDs are in use? 65535 thinkers!?
174 while (self().isUsedMobjId(++iddealer)) {}
175
176 // Mark this ID as used.
177 self().setMobjId(iddealer);
178
179 return iddealer;
180 }
181
182 ThinkerList *listForThinkFunc(thinkfunc_t func, bool makePublic = true,
183 bool canCreate = false)
184 {
185 for (dint i = 0; i < lists.count(); ++i)
186 {
187 ThinkerList *list = lists[i];
188 if (list->function() == func && list->isPublic == makePublic)
189 return list;
190 }
191
192 if (!canCreate) return nullptr;
193
194 // A new thinker type.
195 lists.append(new ThinkerList(func, makePublic));
196 return lists.last();
197 }
198 };
199
Thinkers()200 Thinkers::Thinkers() : d(new Impl(this))
201 {}
202
isUsedMobjId(thid_t id)203 bool Thinkers::isUsedMobjId(thid_t id)
204 {
205 return d->idtable[id >> 5] & (1 << (id & 31) /*(id % 32) */ );
206 }
207
setMobjId(thid_t id,bool inUse)208 void Thinkers::setMobjId(thid_t id, bool inUse)
209 {
210 dint c = id >> 5, bit = 1 << (id & 31); //(id % 32);
211
212 if (inUse) d->idtable[c] |= bit;
213 else d->idtable[c] &= ~bit;
214 }
215
mobjById(dint id)216 struct mobj_s *Thinkers::mobjById(dint id)
217 {
218 auto found = d->mobjIdLookup.constFind(id);
219 if (found != d->mobjIdLookup.constEnd())
220 {
221 return found.value();
222 }
223 return nullptr;
224 }
225
find(thid_t id)226 thinker_t *Thinkers::find(thid_t id)
227 {
228 auto found = d->thinkerIdLookup.constFind(id);
229 if (found != d->thinkerIdLookup.constEnd())
230 {
231 return found.value();
232 }
233 return nullptr;
234 }
235
add(thinker_t & th,bool makePublic)236 void Thinkers::add(thinker_t &th, bool makePublic)
237 {
238 if (!th.function)
239 throw Error("Thinkers::add", "Invalid thinker function");
240
241 // Will it need an ID?
242 if (Thinker_IsMobj(&th))
243 {
244 // It is a mobj, give it an ID (not for client mobjs, though, they
245 // already have an id).
246 #ifdef __CLIENT__
247 if (!Cl_IsClientMobj(reinterpret_cast<mobj_t *>(&th)))
248 #endif
249 {
250 th.id = d->newMobjId();
251 }
252
253 if (makePublic && th.id)
254 {
255 d->mobjIdLookup.insert(th.id, reinterpret_cast<mobj_t *>(&th));
256 }
257 }
258 else
259 {
260 th.id = 0; // Zero is not a valid ID.
261 }
262
263 if (th.id)
264 {
265 d->thinkerIdLookup.insert(th.id, &th);
266 }
267
268 // Link the thinker to the thinker list.
269 ThinkerList *list = d->listForThinkFunc(th.function, makePublic, true /*can create*/);
270 list->link(th);
271 }
272
remove(thinker_t & th)273 void Thinkers::remove(thinker_t &th)
274 {
275 // Has got an ID?
276 if (th.id)
277 {
278 // Flag the identifier as free.
279 setMobjId(th.id, false);
280
281 d->mobjIdLookup.remove(th.id);
282 d->thinkerIdLookup.remove(th.id);
283
284 #ifdef __SERVER__
285 // Then it must be a mobj.
286 auto *mob = reinterpret_cast<mobj_t *>(&th);
287
288 // If the state of the mobj is the NULL state, this is a
289 // predictable mobj removal (result of animation reaching its
290 // end) and shouldn't be included in netGame deltas.
291 if (!mob->state || !runtimeDefs.states.indexOf(mob->state))
292 {
293 Sv_MobjRemoved(th.id);
294 }
295 #endif
296 }
297
298 th.function = (thinkfunc_t) -1;
299
300 Thinker::release(th);
301 }
302
initLists(dbyte flags)303 void Thinkers::initLists(dbyte flags)
304 {
305 if (!d->inited)
306 {
307 d->lists.clear();
308 }
309 else
310 {
311 for (dint i = 0; i < d->lists.count(); ++i)
312 {
313 ThinkerList *list = d->lists[i];
314
315 if (list->isPublic && !(flags & 0x1)) continue;
316 if (!list->isPublic && !(flags & 0x2)) continue;
317
318 list->reinit();
319 }
320 }
321
322 d->clearMobjIds();
323 d->inited = true;
324 }
325
isInited() const326 bool Thinkers::isInited() const
327 {
328 return d->inited;
329 }
330
forAll(dbyte flags,std::function<LoopResult (thinker_t *)> func) const331 LoopResult Thinkers::forAll(dbyte flags, std::function<LoopResult (thinker_t *)> func) const
332 {
333 if (!d->inited) return LoopContinue;
334
335 for (dint i = 0; i < d->lists.count(); ++i)
336 {
337 ThinkerList *list = d->lists[i];
338
339 if ( list->isPublic && !(flags & 0x1)) continue;
340 if (!list->isPublic && !(flags & 0x2)) continue;
341
342 thinker_t *th = list->sentinel.next;
343 while (th != &list->sentinel.base() && th)
344 {
345 #ifdef LIBDENG_FAKE_MEMORY_ZONE
346 DENG2_ASSERT(th->next);
347 DENG2_ASSERT(th->prev);
348 #endif
349 thinker_t *next = th->next;
350
351 if (auto result = func(th))
352 return result;
353
354 th = next;
355 }
356 }
357
358 return LoopContinue;
359 }
360
forAll(thinkfunc_t thinkFunc,dbyte flags,std::function<LoopResult (thinker_t *)> func) const361 LoopResult Thinkers::forAll(thinkfunc_t thinkFunc, dbyte flags, std::function<LoopResult (thinker_t *)> func) const
362 {
363 if (!d->inited) return LoopContinue;
364
365 if (!thinkFunc)
366 {
367 return forAll(flags, func);
368 }
369
370 if (flags & 0x1 /*public*/)
371 {
372 if (ThinkerList *list = d->listForThinkFunc(thinkFunc))
373 {
374 thinker_t *th = list->sentinel.next;
375 while (th != &list->sentinel.base() && th)
376 {
377 #ifdef LIBDENG_FAKE_MEMORY_ZONE
378 DENG2_ASSERT(th->next);
379 DENG2_ASSERT(th->prev);
380 #endif
381 thinker_t *next = th->next;
382
383 if (auto result = func(th))
384 return result;
385
386 th = next;
387 }
388 }
389 }
390 if (flags & 0x2 /*private*/)
391 {
392 if (ThinkerList *list = d->listForThinkFunc(thinkFunc, false /*private*/))
393 {
394 thinker_t *th = list->sentinel.next;
395 while (th != &list->sentinel.base() && th)
396 {
397 #ifdef LIBDENG_FAKE_MEMORY_ZONE
398 DENG2_ASSERT(th->next);
399 DENG2_ASSERT(th->prev);
400 #endif
401 thinker_t *next = th->next;
402
403 if (auto result = func(th))
404 return result;
405
406 th = next;
407 }
408 }
409 }
410
411 return LoopContinue;
412 }
413
count(dint * numInStasis) const414 dint Thinkers::count(dint *numInStasis) const
415 {
416 dint total = 0;
417 if (isInited())
418 {
419 for (dint i = 0; i < d->lists.count(); ++i)
420 {
421 ThinkerList *list = d->lists[i];
422 total += list->count(numInStasis);
423 }
424 }
425 return total;
426 }
427
unlinkThinkerFromList(thinker_t * th)428 static void unlinkThinkerFromList(thinker_t *th)
429 {
430 th->next->prev = th->prev;
431 th->prev->next = th->next;
432 }
433
434 } // namespace world
435 using namespace world;
436
Thinker_InitPrivateData(thinker_t * th,Id::Type knownId)437 void Thinker_InitPrivateData(thinker_t *th, Id::Type knownId)
438 {
439 //DENG2_ASSERT(th->d == nullptr);
440
441 /// @todo The game should be asked to create its own private data. -jk
442
443 if (th->d == nullptr)
444 {
445 Id const privateId = knownId? Id(knownId) : Id(/* get a new ID */);
446
447 if (Thinker_IsMobj(th))
448 {
449 #ifdef __CLIENT__
450 th->d = new ClientMobjThinkerData(privateId);
451 #else
452 th->d = new MobjThinkerData(privateId);
453 #endif
454 }
455 else
456 {
457 // Generic thinker data (Doomsday Script namespace, etc.).
458 th->d = new ThinkerData(privateId);
459 }
460
461 auto &thinkerData = THINKER_DATA(*th, ThinkerData);
462 thinkerData.setThinker(th);
463 thinkerData.initBindings();
464 }
465 else
466 {
467 DENG2_ASSERT(knownId != 0);
468
469 // Change the private identifier of the existing thinker data.
470 THINKER_DATA(*th, ThinkerData).setId(knownId);
471 }
472 }
473
474 /**
475 * Locates a mobj by it's unique identifier in the CURRENT map.
476 */
477 #undef Mobj_ById
Mobj_ById(dint id)478 DENG_EXTERN_C struct mobj_s *Mobj_ById(dint id)
479 {
480 /// @todo fixme: Do not assume the current map.
481 if (!App_World().hasMap()) return nullptr;
482 return App_World().map().thinkers().mobjById(id);
483 }
484
485 #undef Thinker_Init
Thinker_Init()486 void Thinker_Init()
487 {
488 /// @todo fixme: Do not assume the current map.
489 if (!App_World().hasMap()) return;
490 App_World().map().thinkers().initLists(0x1); // Init the public thinker lists.
491 }
492
493 #undef Thinker_Run
Thinker_Run()494 void Thinker_Run()
495 {
496 /// @todo fixme: Do not assume the current map.
497 if (!App_World().hasMap()) return;
498
499 App_World().map().thinkers().forAll(0x1 | 0x2, [] (thinker_t *th)
500 {
501 try
502 {
503 if (Thinker_InStasis(th)) return LoopContinue; // Skip.
504
505 // Time to remove it?
506 if (th->function == (thinkfunc_t) -1)
507 {
508 unlinkThinkerFromList(th);
509
510 if (th->id)
511 {
512 // Recycle for reduced allocation overhead.
513 P_MobjRecycle((mobj_t *) th);
514 }
515 else
516 {
517 // Non-mobjs are just deleted right away.
518 Thinker::destroy(th);
519 }
520 }
521 else if (th->function)
522 {
523 // Create a private data instance of appropriate type.
524 if (!th->d) Thinker_InitPrivateData(th);
525
526 // Public thinker callback.
527 th->function(th);
528
529 // Private thinking.
530 if (th->d) THINKER_DATA(*th, Thinker::IData).think();
531 }
532 }
533 catch (const Error &er)
534 {
535 LOG_MAP_WARNING("Thinker %i: %s") << th->id << er.asText();
536 }
537 return LoopContinue;
538 });
539 }
540
541 #undef Thinker_Add
Thinker_Add(thinker_t * th)542 void Thinker_Add(thinker_t *th)
543 {
544 if (!th) return;
545 Thinker_Map(*th).thinkers().add(*th);
546 }
547
548 #undef Thinker_Remove
Thinker_Remove(thinker_t * th)549 void Thinker_Remove(thinker_t *th)
550 {
551 if (!th) return;
552 Thinker_Map(*th).thinkers().remove(*th);
553 }
554
555 #undef Thinker_Iterate
Thinker_Iterate(thinkfunc_t func,dint (* callback)(thinker_t *,void *),void * context)556 dint Thinker_Iterate(thinkfunc_t func, dint (*callback) (thinker_t *, void *), void *context)
557 {
558 if (!App_World().hasMap()) return false; // Continue iteration.
559
560 return App_World().map().thinkers().forAll(func, 0x1, [&callback, &context] (thinker_t *th)
561 {
562 return callback(th, context);
563 });
564 }
565
566 DENG_DECLARE_API(Thinker) =
567 {
568 { DE_API_THINKER },
569 Thinker_Init,
570 Thinker_Run,
571 Thinker_Add,
572 Thinker_Remove,
573 Thinker_Iterate
574 };
575