1 /*
2 ** dthinker.cpp
3 ** Implements the base class for almost anything in a level that might think
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 1998-2006 Randy Heit
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 **    notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 **    notice, this list of conditions and the following disclaimer in the
17 **    documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 **    derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 */
34 
35 #include "dthinker.h"
36 #include "stats.h"
37 #include "p_local.h"
38 #include "statnums.h"
39 #include "i_system.h"
40 #include "doomerrors.h"
41 #include "farchive.h"
42 
43 
44 static cycle_t ThinkCycles;
45 extern cycle_t BotSupportCycles;
46 extern int BotWTG;
47 
48 IMPLEMENT_CLASS (DThinker)
49 
50 DThinker *NextToThink;
51 
52 FThinkerList DThinker::Thinkers[MAX_STATNUM+2];
53 FThinkerList DThinker::FreshThinkers[MAX_STATNUM+1];
54 bool DThinker::bSerialOverride = false;
55 
AddTail(DThinker * thinker)56 void FThinkerList::AddTail(DThinker *thinker)
57 {
58 	assert(thinker->PrevThinker == NULL && thinker->NextThinker == NULL);
59 	assert(!(thinker->ObjectFlags & OF_EuthanizeMe));
60 	if (Sentinel == NULL)
61 	{
62 		Sentinel = new DThinker(DThinker::NO_LINK);
63 		Sentinel->ObjectFlags |= OF_Sentinel;
64 		Sentinel->NextThinker = Sentinel;
65 		Sentinel->PrevThinker = Sentinel;
66 		GC::WriteBarrier(Sentinel);
67 	}
68 	DThinker *tail = Sentinel->PrevThinker;
69 	assert(tail->NextThinker == Sentinel);
70 	thinker->PrevThinker = tail;
71 	thinker->NextThinker = Sentinel;
72 	tail->NextThinker = thinker;
73 	Sentinel->PrevThinker = thinker;
74 	GC::WriteBarrier(thinker, tail);
75 	GC::WriteBarrier(thinker, Sentinel);
76 	GC::WriteBarrier(tail, thinker);
77 	GC::WriteBarrier(Sentinel, thinker);
78 }
79 
GetHead() const80 DThinker *FThinkerList::GetHead() const
81 {
82 	if (Sentinel == NULL || Sentinel->NextThinker == Sentinel)
83 	{
84 		return NULL;
85 	}
86 	assert(Sentinel->NextThinker->PrevThinker == Sentinel);
87 	return Sentinel->NextThinker;
88 }
89 
GetTail() const90 DThinker *FThinkerList::GetTail() const
91 {
92 	if (Sentinel == NULL || Sentinel->PrevThinker == Sentinel)
93 	{
94 		return NULL;
95 	}
96 	return Sentinel->PrevThinker;
97 }
98 
IsEmpty() const99 bool FThinkerList::IsEmpty() const
100 {
101 	return Sentinel == NULL || Sentinel->NextThinker == NULL;
102 }
103 
SaveList(FArchive & arc,DThinker * node)104 void DThinker::SaveList(FArchive &arc, DThinker *node)
105 {
106 	if (node != NULL)
107 	{
108 		while (!(node->ObjectFlags & OF_Sentinel))
109 		{
110 			assert(node->NextThinker != NULL && !(node->NextThinker->ObjectFlags & OF_EuthanizeMe));
111 			arc << node;
112 			node = node->NextThinker;
113 		}
114 	}
115 }
116 
SerializeAll(FArchive & arc,bool hubLoad)117 void DThinker::SerializeAll(FArchive &arc, bool hubLoad)
118 {
119 	DThinker *thinker;
120 	BYTE stat;
121 	int statcount;
122 	int i;
123 
124 	// Save lists of thinkers, but not by storing the first one and letting
125 	// the archiver catch the rest. (Which leads to buttloads of recursion
126 	// and makes the file larger.) Instead, we explicitly save each thinker
127 	// in sequence. When restoring an archive, we also have to maintain
128 	// the thinker lists here instead of relying on the archiver to do it
129 	// for us.
130 
131 	if (arc.IsStoring())
132 	{
133 		for (statcount = i = 0; i <= MAX_STATNUM; i++)
134 		{
135 			statcount += (!Thinkers[i].IsEmpty() || !FreshThinkers[i].IsEmpty());
136 		}
137 		arc << statcount;
138 		for (i = 0; i <= MAX_STATNUM; i++)
139 		{
140 			if (!Thinkers[i].IsEmpty() || !FreshThinkers[i].IsEmpty())
141 			{
142 				stat = i;
143 				arc << stat;
144 				SaveList(arc, Thinkers[i].GetHead());
145 				SaveList(arc, FreshThinkers[i].GetHead());
146 				thinker = NULL;
147 				arc << thinker;		// Save a final NULL for this list
148 			}
149 		}
150 	}
151 	else
152 	{
153 		if (hubLoad)
154 			DestroyMostThinkers();
155 		else
156 			DestroyAllThinkers();
157 
158 		// Prevent the constructor from inserting thinkers into a list.
159 		bSerialOverride = true;
160 
161 		try
162 		{
163 			arc << statcount;
164 			while (statcount > 0)
165 			{
166 				arc << stat << thinker;
167 				while (thinker != NULL)
168 				{
169 					// This may be a player stored in their ancillary list. Remove
170 					// them first before inserting them into the new list.
171 					if (thinker->NextThinker != NULL)
172 					{
173 						thinker->Remove();
174 					}
175 					// Thinkers with the OF_JustSpawned flag set go in the FreshThinkers
176 					// list. Anything else goes in the regular Thinkers list.
177 					if (thinker->ObjectFlags & OF_EuthanizeMe)
178 					{
179 						// This thinker was destroyed during the loading process. Do
180 						// not link it in to any list.
181 					}
182 					else if (thinker->ObjectFlags & OF_JustSpawned)
183 					{
184 						FreshThinkers[stat].AddTail(thinker);
185 					}
186 					else
187 					{
188 						Thinkers[stat].AddTail(thinker);
189 					}
190 					arc << thinker;
191 				}
192 				statcount--;
193 			}
194 		}
195 		catch (class CDoomError &)
196 		{
197 			bSerialOverride = false;
198 			DestroyAllThinkers();
199 			throw;
200 		}
201 		bSerialOverride = false;
202 	}
203 }
204 
DThinker(int statnum)205 DThinker::DThinker (int statnum) throw()
206 {
207 	NextThinker = NULL;
208 	PrevThinker = NULL;
209 	if (bSerialOverride)
210 	{ // The serializer will insert us into the right list
211 		return;
212 	}
213 
214 	ObjectFlags |= OF_JustSpawned;
215 	if ((unsigned)statnum > MAX_STATNUM)
216 	{
217 		statnum = MAX_STATNUM;
218 	}
219 	FreshThinkers[statnum].AddTail (this);
220 }
221 
DThinker(no_link_type foo)222 DThinker::DThinker(no_link_type foo) throw()
223 {
224 	foo;	// Avoid unused argument warnings.
225 }
226 
~DThinker()227 DThinker::~DThinker ()
228 {
229 	assert(NextThinker == NULL && PrevThinker == NULL);
230 }
231 
Destroy()232 void DThinker::Destroy ()
233 {
234 	assert((NextThinker != NULL && PrevThinker != NULL) ||
235 		   (NextThinker == NULL && PrevThinker == NULL));
236 	if (NextThinker != NULL)
237 	{
238 		Remove();
239 	}
240 	Super::Destroy();
241 }
242 
Remove()243 void DThinker::Remove()
244 {
245 	if (this == NextToThink)
246 	{
247 		NextToThink = NextThinker;
248 	}
249 	DThinker *prev = PrevThinker;
250 	DThinker *next = NextThinker;
251 	assert(prev != NULL && next != NULL);
252 	assert((ObjectFlags & OF_Sentinel) || (prev != this && next != this));
253 	assert(prev->NextThinker == this);
254 	assert(next->PrevThinker == this);
255 	prev->NextThinker = next;
256 	next->PrevThinker = prev;
257 	GC::WriteBarrier(prev, next);
258 	GC::WriteBarrier(next, prev);
259 	NextThinker = NULL;
260 	PrevThinker = NULL;
261 }
262 
PostBeginPlay()263 void DThinker::PostBeginPlay ()
264 {
265 }
266 
FirstThinker(int statnum)267 DThinker *DThinker::FirstThinker (int statnum)
268 {
269 	DThinker *node;
270 
271 	if ((unsigned)statnum > MAX_STATNUM)
272 	{
273 		statnum = MAX_STATNUM;
274 	}
275 	node = Thinkers[statnum].GetHead();
276 	if (node == NULL)
277 	{
278 		node = FreshThinkers[statnum].GetHead();
279 		if (node == NULL)
280 		{
281 			return NULL;
282 		}
283 	}
284 	return node;
285 }
286 
ChangeStatNum(int statnum)287 void DThinker::ChangeStatNum (int statnum)
288 {
289 	FThinkerList *list;
290 
291 	// This thinker should already be in a list; verify that.
292 	assert(NextThinker != NULL && PrevThinker != NULL);
293 
294 	if ((unsigned)statnum > MAX_STATNUM)
295 	{
296 		statnum = MAX_STATNUM;
297 	}
298 	Remove();
299 	if ((ObjectFlags & OF_JustSpawned) && statnum >= STAT_FIRST_THINKING)
300 	{
301 		list = &FreshThinkers[statnum];
302 	}
303 	else
304 	{
305 		list = &Thinkers[statnum];
306 	}
307 	list->AddTail(this);
308 }
309 
310 // Mark the first thinker of each list
MarkRoots()311 void DThinker::MarkRoots()
312 {
313 	for (int i = 0; i <= MAX_STATNUM; ++i)
314 	{
315 		GC::Mark(Thinkers[i].Sentinel);
316 		GC::Mark(FreshThinkers[i].Sentinel);
317 	}
318 	GC::Mark(Thinkers[MAX_STATNUM+1].Sentinel);
319 }
320 
321 // Destroy every thinker
DestroyAllThinkers()322 void DThinker::DestroyAllThinkers ()
323 {
324 	int i;
325 
326 	for (i = 0; i <= MAX_STATNUM; i++)
327 	{
328 		if (i != STAT_TRAVELLING)
329 		{
330 			DestroyThinkersInList (Thinkers[i]);
331 			DestroyThinkersInList (FreshThinkers[i]);
332 		}
333 	}
334 	DestroyThinkersInList (Thinkers[MAX_STATNUM+1]);
335 	GC::FullGC();
336 }
337 
338 // Destroy all thinkers except for player-controlled actors
339 // Players are simply removed from the list of thinkers and
340 // will be added back after serialization is complete.
DestroyMostThinkers()341 void DThinker::DestroyMostThinkers ()
342 {
343 	int i;
344 
345 	for (i = 0; i <= MAX_STATNUM; i++)
346 	{
347 		if (i != STAT_TRAVELLING)
348 		{
349 			DestroyMostThinkersInList (Thinkers[i], i);
350 			DestroyMostThinkersInList (FreshThinkers[i], i);
351 		}
352 	}
353 	GC::FullGC();
354 }
355 
DestroyThinkersInList(FThinkerList & list)356 void DThinker::DestroyThinkersInList (FThinkerList &list)
357 {
358 	if (list.Sentinel != NULL)
359 	{
360 		for (DThinker *node = list.Sentinel->NextThinker; node != list.Sentinel; node = list.Sentinel->NextThinker)
361 		{
362 			assert(node != NULL);
363 			node->Destroy();
364 		}
365 		list.Sentinel->Destroy();
366 		list.Sentinel = NULL;
367 	}
368 }
369 
DestroyMostThinkersInList(FThinkerList & list,int stat)370 void DThinker::DestroyMostThinkersInList (FThinkerList &list, int stat)
371 {
372 	if (stat != STAT_PLAYER)
373 	{
374 		DestroyThinkersInList (list);
375 	}
376 	else if (list.Sentinel != NULL)
377 	{ // If it's a voodoo doll, destroy it. Otherwise, simply remove
378 	  // it from the list. G_FinishTravel() will find it later from
379 	  // a players[].mo link and destroy it then, after copying various
380 	  // information to a new player.
381 		for (DThinker *probe = list.Sentinel->NextThinker; probe != list.Sentinel; probe = list.Sentinel->NextThinker)
382 		{
383 			if (!probe->IsKindOf(RUNTIME_CLASS(APlayerPawn)) ||		// <- should not happen
384 				static_cast<AActor *>(probe)->player == NULL ||
385 				static_cast<AActor *>(probe)->player->mo != probe)
386 			{
387 				probe->Destroy();
388 			}
389 			else
390 			{
391 				probe->Remove();
392 				// Technically, this doesn't need to be in any list now, since
393 				// it's only going to be found later and destroyed before ever
394 				// needing to tick again, but by moving it to a separate list,
395 				// I can keep my debug assertions that all thinkers are either
396 				// euthanizing or in a list.
397 				Thinkers[MAX_STATNUM+1].AddTail(probe);
398 			}
399 		}
400 	}
401 }
402 
RunThinkers()403 void DThinker::RunThinkers ()
404 {
405 	int i, count;
406 
407 	ThinkCycles.Reset();
408 	BotSupportCycles.Reset();
409 	BotWTG = 0;
410 
411 	ThinkCycles.Clock();
412 
413 	// Tick every thinker left from last time
414 	for (i = STAT_FIRST_THINKING; i <= MAX_STATNUM; ++i)
415 	{
416 		TickThinkers (&Thinkers[i], NULL);
417 	}
418 
419 	// Keep ticking the fresh thinkers until there are no new ones.
420 	do
421 	{
422 		count = 0;
423 		for (i = STAT_FIRST_THINKING; i <= MAX_STATNUM; ++i)
424 		{
425 			count += TickThinkers (&FreshThinkers[i], &Thinkers[i]);
426 		}
427 	} while (count != 0);
428 
429 	ThinkCycles.Unclock();
430 }
431 
TickThinkers(FThinkerList * list,FThinkerList * dest)432 int DThinker::TickThinkers (FThinkerList *list, FThinkerList *dest)
433 {
434 	int count = 0;
435 	DThinker *node = list->GetHead();
436 
437 	if (node == NULL)
438 	{
439 		return 0;
440 	}
441 
442 	while (node != list->Sentinel)
443 	{
444 		++count;
445 		NextToThink = node->NextThinker;
446 		if (node->ObjectFlags & OF_JustSpawned)
447 		{
448 			// Leave OF_JustSpawn set until after Tick() so the ticker can check it.
449 			if (dest != NULL)
450 			{ // Move thinker from this list to the destination list
451 				node->Remove();
452 				dest->AddTail(node);
453 			}
454 			node->PostBeginPlay();
455 		}
456 		else if (dest != NULL)
457 		{
458 			I_Error("There is a thinker in the fresh list that has already ticked.\n");
459 		}
460 
461 		if (!(node->ObjectFlags & OF_EuthanizeMe))
462 		{ // Only tick thinkers not scheduled for destruction
463 			node->Tick();
464 			node->ObjectFlags &= ~OF_JustSpawned;
465 			GC::CheckGC();
466 		}
467 		node = NextToThink;
468 	}
469 	return count;
470 }
471 
Tick()472 void DThinker::Tick ()
473 {
474 }
475 
PropagateMark()476 size_t DThinker::PropagateMark()
477 {
478 	assert(NextThinker != NULL && !(NextThinker->ObjectFlags & OF_EuthanizeMe));
479 	assert(PrevThinker != NULL && !(PrevThinker->ObjectFlags & OF_EuthanizeMe));
480 	GC::Mark(NextThinker);
481 	GC::Mark(PrevThinker);
482 	return Super::PropagateMark();
483 }
484 
FThinkerIterator(const PClass * type,int statnum)485 FThinkerIterator::FThinkerIterator (const PClass *type, int statnum)
486 {
487 	if ((unsigned)statnum > MAX_STATNUM)
488 	{
489 		m_Stat = STAT_FIRST_THINKING;
490 		m_SearchStats = true;
491 	}
492 	else
493 	{
494 		m_Stat = statnum;
495 		m_SearchStats = false;
496 	}
497 	m_ParentType = type;
498 	m_CurrThinker = DThinker::Thinkers[m_Stat].GetHead();
499 	m_SearchingFresh = false;
500 }
501 
FThinkerIterator(const PClass * type,int statnum,DThinker * prev)502 FThinkerIterator::FThinkerIterator (const PClass *type, int statnum, DThinker *prev)
503 {
504 	if ((unsigned)statnum > MAX_STATNUM)
505 	{
506 		m_Stat = STAT_FIRST_THINKING;
507 		m_SearchStats = true;
508 	}
509 	else
510 	{
511 		m_Stat = statnum;
512 		m_SearchStats = false;
513 	}
514 	m_ParentType = type;
515 	if (prev == NULL || (prev->NextThinker->ObjectFlags & OF_Sentinel))
516 	{
517 		Reinit();
518 	}
519 	else
520 	{
521 		m_CurrThinker = prev->NextThinker;
522 		m_SearchingFresh = false;
523 	}
524 }
525 
Reinit()526 void FThinkerIterator::Reinit ()
527 {
528 	m_CurrThinker = DThinker::Thinkers[m_Stat].GetHead();
529 	m_SearchingFresh = false;
530 }
531 
Next()532 DThinker *FThinkerIterator::Next ()
533 {
534 	if (m_ParentType == NULL)
535 	{
536 		return NULL;
537 	}
538 	do
539 	{
540 		do
541 		{
542 			if (m_CurrThinker != NULL)
543 			{
544 				while (!(m_CurrThinker->ObjectFlags & OF_Sentinel))
545 				{
546 					DThinker *thinker = m_CurrThinker;
547 					m_CurrThinker = thinker->NextThinker;
548 					if (thinker->IsKindOf(m_ParentType))
549 					{
550 						return thinker;
551 					}
552 				}
553 			}
554 			if ((m_SearchingFresh = !m_SearchingFresh))
555 			{
556 				m_CurrThinker = DThinker::FreshThinkers[m_Stat].GetHead();
557 			}
558 		} while (m_SearchingFresh);
559 		if (m_SearchStats)
560 		{
561 			m_Stat++;
562 			if (m_Stat > MAX_STATNUM)
563 			{
564 				m_Stat = STAT_FIRST_THINKING;
565 			}
566 		}
567 		m_CurrThinker = DThinker::Thinkers[m_Stat].GetHead();
568 		m_SearchingFresh = false;
569 	} while (m_SearchStats && m_Stat != STAT_FIRST_THINKING);
570 	return NULL;
571 }
572 
ADD_STAT(think)573 ADD_STAT (think)
574 {
575 	FString out;
576 	out.Format ("Think time = %04.1f ms", ThinkCycles.TimeMS());
577 	return out;
578 }
579