From 3b4ded5694e3b36e0b9bafaf81190bcb8d701cc2 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Wed, 30 Jan 2019 02:15:48 +0100 Subject: [PATCH] - refactored the thinker list. This was an unorganized set of static members in DThinker but has now been regrouped into something more structured. --- src/d_main.cpp | 2 +- src/dobjgc.cpp | 5 - src/dthinker.cpp | 954 +++++++++++++++++++++++--------------------- src/dthinker.h | 55 ++- src/g_level.cpp | 4 +- src/g_levellocals.h | 2 +- src/p_pusher.cpp | 8 +- src/p_saveg.cpp | 4 +- src/p_setup.cpp | 6 +- src/p_spec.h | 6 - src/p_tick.cpp | 2 +- 11 files changed, 543 insertions(+), 505 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index 36c921abf..29086572b 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -2724,7 +2724,7 @@ void D_DoomMain (void) // clean up game state ST_Clear(); D_ErrorCleanup (); - DThinker::DestroyThinkersInList(STAT_STATIC); + Thinkers.DestroyThinkersInList(STAT_STATIC); E_Shutdown(false); P_FreeLevelData(); diff --git a/src/dobjgc.cpp b/src/dobjgc.cpp index ab1ccf34f..b49f8f4dc 100644 --- a/src/dobjgc.cpp +++ b/src/dobjgc.cpp @@ -112,8 +112,6 @@ // EXTERNAL DATA DECLARATIONS ---------------------------------------------- -extern DThinker *NextToThink; - // PUBLIC DATA DEFINITIONS ------------------------------------------------- namespace GC @@ -282,7 +280,6 @@ static void MarkRoot() Mark(StatusBar); M_MarkMenus(); Mark(DIntermissionController::CurrentIntermission); - DThinker::MarkRoots(); Mark(E_FirstEventHandler); Mark(E_LastEventHandler); for (auto Level : AllLevels()) @@ -300,8 +297,6 @@ static void MarkRoot() { Level->Mark(); } - // NextToThink must not be freed while thinkers are ticking. - Mark(NextToThink); // Mark soft roots. if (SoftRoots != NULL) { diff --git a/src/dthinker.cpp b/src/dthinker.cpp index c104e677a..3157b567f 100644 --- a/src/dthinker.cpp +++ b/src/dthinker.cpp @@ -52,55 +52,41 @@ extern int BotWTG; IMPLEMENT_CLASS(DThinker, false, false) +struct ProfileInfo +{ + int numcalls = 0; + cycle_t timer; + + ProfileInfo() + { + timer.Reset(); + } +}; + +static TMap Profiles; +static unsigned int profilethinkers, profilelimit; +FThinkerCollection Thinkers; DThinker *NextToThink; -FThinkerList DThinker::Thinkers[MAX_STATNUM+2]; -FThinkerList DThinker::FreshThinkers[MAX_STATNUM+1]; - //========================================================================== // // // //========================================================================== -void FThinkerList::AddTail(DThinker *thinker) +void FThinkerCollection::Link(DThinker *thinker, int statnum) { - assert(thinker->PrevThinker == NULL && thinker->NextThinker == NULL); - assert(!(thinker->ObjectFlags & OF_EuthanizeMe)); - if (Sentinel == NULL) + FThinkerList *list; + if ((thinker->ObjectFlags & OF_JustSpawned) && statnum >= STAT_FIRST_THINKING) { - Sentinel = Create(); - Sentinel->ObjectFlags = OF_Sentinel; - Sentinel->NextThinker = Sentinel; - Sentinel->PrevThinker = Sentinel; - GC::WriteBarrier(Sentinel); + list = &FreshThinkers[statnum]; } - DThinker *tail = Sentinel->PrevThinker; - assert(tail->NextThinker == Sentinel); - thinker->PrevThinker = tail; - thinker->NextThinker = Sentinel; - tail->NextThinker = thinker; - Sentinel->PrevThinker = thinker; - GC::WriteBarrier(thinker, tail); - GC::WriteBarrier(thinker, Sentinel); - GC::WriteBarrier(tail, thinker); - GC::WriteBarrier(Sentinel, thinker); -} - -//========================================================================== -// -// -// -//========================================================================== - -DThinker *FThinkerList::GetHead() const -{ - if (Sentinel == NULL || Sentinel->NextThinker == Sentinel) + else { - return NULL; + thinker->ObjectFlags &= ~OF_JustSpawned; + list = &Thinkers[statnum]; } - assert(Sentinel->NextThinker->PrevThinker == Sentinel); - return Sentinel->NextThinker; + list->AddTail(thinker); } //========================================================================== @@ -109,43 +95,161 @@ DThinker *FThinkerList::GetHead() const // //========================================================================== -DThinker *FThinkerList::GetTail() const +void FThinkerCollection::RunThinkers(FLevelLocals *Level) { - if (Sentinel == NULL || Sentinel->PrevThinker == Sentinel) + int i, count; + + ThinkCount = 0; + ThinkCycles.Reset(); + BotSupportCycles.Reset(); + ActionCycles.Reset(); + BotWTG = 0; + + ThinkCycles.Clock(); + + if (!profilethinkers) { - return NULL; - } - return Sentinel->PrevThinker; -} - -//========================================================================== -// -// -// -//========================================================================== - -bool FThinkerList::IsEmpty() const -{ - return Sentinel == NULL || Sentinel->NextThinker == NULL; -} - -//========================================================================== -// -// -// -//========================================================================== - -void DThinker::SaveList(FSerializer &arc, DThinker *node) -{ - if (node != NULL) - { - while (!(node->ObjectFlags & OF_Sentinel)) + // Tick every thinker left from last time + for (i = STAT_FIRST_THINKING; i <= MAX_STATNUM; ++i) { - assert(node->NextThinker != NULL && !(node->NextThinker->ObjectFlags & OF_EuthanizeMe)); - ::Serialize(arc, nullptr, node, nullptr); - node = node->NextThinker; + Thinkers[i].TickThinkers(nullptr); + } + + // Keep ticking the fresh thinkers until there are no new ones. + do + { + count = 0; + for (i = STAT_FIRST_THINKING; i <= MAX_STATNUM; ++i) + { + count += FreshThinkers[i].TickThinkers(&Thinkers[i]); + } + } while (count != 0); + + for (auto light = Level->lights; light;) + { + auto next = light->next; + light->Tick(); + light = next; } } + else + { + Profiles.Clear(); + // Tick every thinker left from last time + for (i = STAT_FIRST_THINKING; i <= MAX_STATNUM; ++i) + { + Thinkers[i].ProfileThinkers(nullptr); + } + + // Keep ticking the fresh thinkers until there are no new ones. + do + { + count = 0; + for (i = STAT_FIRST_THINKING; i <= MAX_STATNUM; ++i) + { + count += FreshThinkers[i].ProfileThinkers(&Thinkers[i]); + } + } while (count != 0); + + // Also profile the internal dynamic lights, even though they are not implemented as thinkers. + auto &prof = Profiles[NAME_InternalDynamicLight]; + prof.timer.Clock(); + for (auto light = Level->lights; light;) + { + prof.numcalls++; + auto next = light->next; + light->Tick(); + light = next; + } + prof.timer.Unclock(); + + + struct SortedProfileInfo + { + const char* className; + int numcalls; + double time; + }; + + TArray sorted; + sorted.Grow(Profiles.CountUsed()); + + auto it = TMap::Iterator(Profiles); + TMap::Pair *pair; + while (it.NextPair(pair)) + { + sorted.Push({ pair->Key.GetChars(), pair->Value.numcalls, pair->Value.timer.TimeMS() }); + } + + std::sort(sorted.begin(), sorted.end(), [](const SortedProfileInfo& left, const SortedProfileInfo& right) + { + switch (profilethinkers) + { + case 1: // by name, from A to Z + return stricmp(left.className, right.className) < 0; + case 2: // by name, from Z to A + return stricmp(right.className, left.className) < 0; + case 3: // number of calls, ascending + return left.numcalls < right.numcalls; + case 4: // number of calls, descending + return right.numcalls < left.numcalls; + case 5: // average time, ascending + return left.time / left.numcalls < right.time / right.numcalls; + case 6: // average time, descending + return right.time / right.numcalls < left.time / left.numcalls; + case 7: // total time, ascending + return left.time < right.time; + default: // total time, descending + return right.time < left.time; + } + }); + + Printf(TEXTCOLOR_YELLOW "Total, ms Averg, ms Calls Actor class\n"); + Printf(TEXTCOLOR_YELLOW "---------- ---------- ------ --------------------\n"); + + const unsigned count = MIN(profilelimit > 0 ? profilelimit : UINT_MAX, sorted.Size()); + + for (unsigned i = 0; i < count; ++i) + { + const SortedProfileInfo& info = sorted[i]; + Printf("%s%10.6f %s%10.6f %s%6d %s%s\n", + profilethinkers >= 7 ? TEXTCOLOR_YELLOW : TEXTCOLOR_WHITE, info.time, + profilethinkers == 5 || profilethinkers == 6 ? TEXTCOLOR_YELLOW : TEXTCOLOR_WHITE, info.time / info.numcalls, + profilethinkers == 3 || profilethinkers == 4 ? TEXTCOLOR_YELLOW : TEXTCOLOR_WHITE, info.numcalls, + profilethinkers == 1 || profilethinkers == 2 ? TEXTCOLOR_YELLOW : TEXTCOLOR_WHITE, info.className); + } + + profilethinkers = 0; + } + + ThinkCycles.Unclock(); +} + +//========================================================================== +// +// Destroy every thinker +// +//========================================================================== + +void FThinkerCollection::DestroyAllThinkers() +{ + int i; + bool error = false; + + for (i = 0; i <= MAX_STATNUM; i++) + { + if (i != STAT_TRAVELLING && i != STAT_STATIC) + { + error |= Thinkers[i].DoDestroyThinkers(); + error |= FreshThinkers[i].DoDestroyThinkers(); + } + } + error |= Thinkers[MAX_STATNUM + 1].DoDestroyThinkers(); + GC::FullGC(); + if (error) + { + I_Error("DestroyAllThinkers failed"); + } } //========================================================================== @@ -154,7 +258,7 @@ void DThinker::SaveList(FSerializer &arc, DThinker *node) // //========================================================================== -void DThinker::SerializeThinkers(FSerializer &arc, bool hubLoad) +void FThinkerCollection::SerializeThinkers(FSerializer &arc, bool hubLoad) { //DThinker *thinker; //uint8_t stat; @@ -167,8 +271,8 @@ void DThinker::SerializeThinkers(FSerializer &arc, bool hubLoad) for (i = 0; i <= MAX_STATNUM; i++) { arc.BeginArray(nullptr); - SaveList(arc, Thinkers[i].GetHead()); - SaveList(arc, FreshThinkers[i].GetHead()); + Thinkers[i].SaveList(arc); + FreshThinkers[i].SaveList(arc); arc.EndArray(); } arc.EndArray(); @@ -225,6 +329,311 @@ void DThinker::SerializeThinkers(FSerializer &arc, bool hubLoad) } } + +//========================================================================== +// +// +// +//========================================================================== + +void FThinkerList::AddTail(DThinker *thinker) +{ + assert(thinker->PrevThinker == nullptr && thinker->NextThinker == nullptr); + assert(!(thinker->ObjectFlags & OF_EuthanizeMe)); + if (Sentinel == nullptr) + { + // This cannot use CreateThinker because it must not be added to the list automatically. + Sentinel = (DThinker*)RUNTIME_CLASS(DThinker)->CreateNew(); + Sentinel->ObjectFlags |= OF_Sentinel; + Sentinel->NextThinker = Sentinel; + Sentinel->PrevThinker = Sentinel; + GC::WriteBarrier(Sentinel); + } + DThinker *tail = Sentinel->PrevThinker; + assert(tail->NextThinker == Sentinel); + thinker->PrevThinker = tail; + thinker->NextThinker = Sentinel; + tail->NextThinker = thinker; + Sentinel->PrevThinker = thinker; + GC::WriteBarrier(thinker, tail); + GC::WriteBarrier(thinker, Sentinel); + GC::WriteBarrier(tail, thinker); + GC::WriteBarrier(Sentinel, thinker); +} + +//========================================================================== +// +// +// +//========================================================================== + +DThinker *FThinkerCollection::FirstThinker(int statnum) +{ + DThinker *node; + + if ((unsigned)statnum > MAX_STATNUM) + { + statnum = MAX_STATNUM; + } + node = Thinkers[statnum].GetHead(); + if (node == nullptr) + { + node = FreshThinkers[statnum].GetHead(); + if (node == nullptr) + { + return nullptr; + } + } + return node; +} + +//========================================================================== +// +// Mark the first thinker of each list +// +//========================================================================== + +void FThinkerCollection::MarkRoots() +{ + for (int i = 0; i <= MAX_STATNUM; ++i) + { + GC::Mark(Thinkers[i].Sentinel); + GC::Mark(FreshThinkers[i].Sentinel); + } + GC::Mark(Thinkers[MAX_STATNUM + 1].Sentinel); +} + +//========================================================================== +// +// +// +//========================================================================== + +DThinker *FThinkerList::GetHead() const +{ + if (Sentinel == nullptr || Sentinel->NextThinker == Sentinel) + { + return nullptr; + } + assert(Sentinel->NextThinker->PrevThinker == Sentinel); + return Sentinel->NextThinker; +} + +//========================================================================== +// +// +// +//========================================================================== + +DThinker *FThinkerList::GetTail() const +{ + if (Sentinel == nullptr || Sentinel->PrevThinker == Sentinel) + { + return nullptr; + } + return Sentinel->PrevThinker; +} + +//========================================================================== +// +// +// +//========================================================================== + +bool FThinkerList::IsEmpty() const +{ + return Sentinel == nullptr || Sentinel->NextThinker == nullptr; +} + +//========================================================================== +// +// +// +//========================================================================== + +void FThinkerList::DestroyThinkers() +{ + if (DoDestroyThinkers()) + { + I_Error("DestroyThinkers failed"); + } +} + +//========================================================================== +// +// +// +//========================================================================== + +bool FThinkerList::DoDestroyThinkers() +{ + bool error = false; + if (Sentinel != nullptr) + { + // Taking down the linked list live is far too dangerous in case something goes wrong. So first copy all elements into an array, take down the list and then destroy them. + + TArray toDelete; + DThinker *node = Sentinel->NextThinker; + while (node != Sentinel) + { + assert(node != nullptr); + auto next = node->NextThinker; + toDelete.Push(node); + node->NextThinker = node->PrevThinker = nullptr; // clear the links + node = next; + } + Sentinel->NextThinker = Sentinel->PrevThinker = nullptr; + Sentinel->Destroy(); + Sentinel = nullptr; + for (auto node : toDelete) + { + // We must intercept all exceptions so that we can continue deleting the list. + try + { + node->Destroy(); + } + catch (CVMAbortException &exception) + { + Printf("VM exception in DestroyThinkers:\n"); + exception.MaybePrintMessage(); + Printf("%s", exception.stacktrace.GetChars()); + // forcibly delete this. Cleanup may be incomplete, though. + node->ObjectFlags |= OF_YesReallyDelete; + delete node; + error = true; + } + catch (CRecoverableError &exception) + { + Printf("Error in DestroyThinkers: %s\n", exception.GetMessage()); + // forcibly delete this. Cleanup may be incomplete, though. + node->ObjectFlags |= OF_YesReallyDelete; + delete node; + error = true; + } + } + } + return error; +} + +//========================================================================== +// +// +// +//========================================================================== + +void FThinkerList::SaveList(FSerializer &arc) +{ + auto node = GetHead(); + if (node != nullptr) + { + while (!(node->ObjectFlags & OF_Sentinel)) + { + assert(node->NextThinker != nullptr && !(node->NextThinker->ObjectFlags & OF_EuthanizeMe)); + ::Serialize(arc, nullptr, node, nullptr); + node = node->NextThinker; + } + } +} + +//========================================================================== +// +// +// +//========================================================================== + +int FThinkerList::TickThinkers(FThinkerList *dest) +{ + int count = 0; + DThinker *node = GetHead(); + + if (node == nullptr) + { + return 0; + } + + while (node != Sentinel) + { + ++count; + NextToThink = node->NextThinker; + if (node->ObjectFlags & OF_JustSpawned) + { + // Leave OF_JustSpawn set until after Tick() so the ticker can check it. + if (dest != nullptr) + { // Move thinker from this list to the destination list + node->Remove(); + dest->AddTail(node); + } + node->CallPostBeginPlay(); + } + else if (dest != nullptr) + { + I_Error("There is a thinker in the fresh list that has already ticked.\n"); + } + + if (!(node->ObjectFlags & OF_EuthanizeMe)) + { // Only tick thinkers not scheduled for destruction + ThinkCount++; + node->CallTick(); + node->ObjectFlags &= ~OF_JustSpawned; + GC::CheckGC(); + } + node = NextToThink; + } + return count; +} + +//========================================================================== +// +// +// +//========================================================================== +int FThinkerList::ProfileThinkers(FThinkerList *dest) +{ + int count = 0; + DThinker *node = GetHead(); + + if (node == nullptr) + { + return 0; + } + + while (node != Sentinel) + { + ++count; + NextToThink = node->NextThinker; + if (node->ObjectFlags & OF_JustSpawned) + { + // Leave OF_JustSpawn set until after Tick() so the ticker can check it. + if (dest != nullptr) + { // Move thinker from this list to the destination list + node->Remove(); + dest->AddTail(node); + } + node->CallPostBeginPlay(); + } + else if (dest != nullptr) + { + I_Error("There is a thinker in the fresh list that has already ticked.\n"); + } + + if (!(node->ObjectFlags & OF_EuthanizeMe)) + { // Only tick thinkers not scheduled for destruction + ThinkCount++; + + auto &prof = Profiles[node->GetClass()->TypeName]; + prof.numcalls++; + prof.timer.Clock(); + node->CallTick(); + prof.timer.Unclock(); + node->ObjectFlags &= ~OF_JustSpawned; + GC::CheckGC(); + } + node = NextToThink; + } + return count; +} + + //========================================================================== // // @@ -330,22 +739,7 @@ void DThinker::PostSerialize() DThinker *FLevelLocals::FirstThinker (int statnum) { - DThinker *node; - - if ((unsigned)statnum > MAX_STATNUM) - { - statnum = MAX_STATNUM; - } - node = DThinker::Thinkers[statnum].GetHead(); - if (node == NULL) - { - node = DThinker::FreshThinkers[statnum].GetHead(); - if (node == NULL) - { - return NULL; - } - } - return node; + return Thinkers.FirstThinker(statnum); } //========================================================================== @@ -358,23 +752,14 @@ void DThinker::ChangeStatNum (int statnum) { FThinkerList *list; - // This thinker should already be in a list; verify that. - assert(NextThinker != NULL && PrevThinker != NULL); + FThinkerCollection *collection = &Thinkers; //Todo: get from level if ((unsigned)statnum > MAX_STATNUM) { statnum = MAX_STATNUM; } Remove(); - if ((ObjectFlags & OF_JustSpawned) && statnum >= STAT_FIRST_THINKING) - { - list = &FreshThinkers[statnum]; - } - else - { - list = &Thinkers[statnum]; - } - list->AddTail(this); + Thinkers.Link(this, statnum); } static void ChangeStatNum(DThinker *thinker, int statnum) @@ -389,48 +774,6 @@ DEFINE_ACTION_FUNCTION_NATIVE(DThinker, ChangeStatNum, ChangeStatNum) self->ChangeStatNum(stat); return 0; } -//========================================================================== -// -// Mark the first thinker of each list -// -//========================================================================== - -void DThinker::MarkRoots() -{ - for (int i = 0; i <= MAX_STATNUM; ++i) - { - GC::Mark(Thinkers[i].Sentinel); - GC::Mark(FreshThinkers[i].Sentinel); - } - GC::Mark(Thinkers[MAX_STATNUM+1].Sentinel); -} - -//========================================================================== -// -// Destroy every thinker -// -//========================================================================== - -void DThinker::DestroyAllThinkers () -{ - int i; - bool error = false; - - for (i = 0; i <= MAX_STATNUM; i++) - { - if (i != STAT_TRAVELLING && i != STAT_STATIC) - { - error |= DoDestroyThinkersInList (Thinkers[i]); - error |= DoDestroyThinkersInList (FreshThinkers[i]); - } - } - error |= DoDestroyThinkersInList (Thinkers[MAX_STATNUM+1]); - GC::FullGC(); - if (error) - { - I_Error("DestroyAllThinkers failed"); - } -} //========================================================================== // @@ -438,77 +781,6 @@ void DThinker::DestroyAllThinkers () // //========================================================================== -void DThinker::DestroyThinkersInList(FThinkerList &list) -{ - if (DoDestroyThinkersInList(list)) - { - I_Error("DestroyThinkersInList failed"); - } -} - -//========================================================================== -// -// -// -//========================================================================== - -bool DThinker::DoDestroyThinkersInList (FThinkerList &list) -{ - bool error = false; - if (list.Sentinel != nullptr) - { - // Taking down the linked list live is far too dangerous in case something goes wrong. So first copy all elements into an array, take down the list and then destroy them. - - TArray toDelete; - DThinker *node = list.Sentinel->NextThinker; - while (node != list.Sentinel) - { - assert(node != nullptr); - auto next = node->NextThinker; - toDelete.Push(node); - node->NextThinker = node->PrevThinker = nullptr; // clear the links - node = next; - } - list.Sentinel->NextThinker = list.Sentinel->PrevThinker = nullptr; - list.Sentinel->Destroy(); - list.Sentinel = nullptr; - for(auto node : toDelete) - { - // We must intercept all exceptions so that we can continue deleting the list. - try - { - node->Destroy(); - } - catch (CVMAbortException &exception) - { - Printf("VM exception in DestroyThinkers:\n"); - exception.MaybePrintMessage(); - Printf("%s", exception.stacktrace.GetChars()); - // forcibly delete this. Cleanup may be incomplete, though. - node->ObjectFlags |= OF_YesReallyDelete; - delete node; - error = true; - } - catch (CRecoverableError &exception) - { - Printf("Error in DestroyThinkers: %s\n", exception.GetMessage()); - // forcibly delete this. Cleanup may be incomplete, though. - node->ObjectFlags |= OF_YesReallyDelete; - delete node; - error = true; - } - } - } - return error; -} - -//========================================================================== -// -// -// -//========================================================================== -static unsigned int profilethinkers, profilelimit; - CCMD(profilethinkers) { const int argc = argv.argc(); @@ -559,248 +831,6 @@ CCMD(profilethinkers) } } -struct ProfileInfo -{ - int numcalls = 0; - cycle_t timer; - - ProfileInfo() - { - timer.Reset(); - } -}; - -TMap Profiles; - - -void DThinker::RunThinkers () -{ - int i, count; - - ThinkCount = 0; - ThinkCycles.Reset(); - BotSupportCycles.Reset(); - ActionCycles.Reset(); - BotWTG = 0; - - ThinkCycles.Clock(); - - if (!profilethinkers) - { - // Tick every thinker left from last time - for (i = STAT_FIRST_THINKING; i <= MAX_STATNUM; ++i) - { - TickThinkers(&Thinkers[i], NULL); - } - - // Keep ticking the fresh thinkers until there are no new ones. - do - { - count = 0; - for (i = STAT_FIRST_THINKING; i <= MAX_STATNUM; ++i) - { - count += TickThinkers(&FreshThinkers[i], &Thinkers[i]); - } - } while (count != 0); - - for (auto light = level.lights; light;) - { - auto next = light->next; - light->Tick(); - light = next; - } - } - else - { - Profiles.Clear(); - // Tick every thinker left from last time - for (i = STAT_FIRST_THINKING; i <= MAX_STATNUM; ++i) - { - ProfileThinkers(&Thinkers[i], NULL); - } - - // Keep ticking the fresh thinkers until there are no new ones. - do - { - count = 0; - for (i = STAT_FIRST_THINKING; i <= MAX_STATNUM; ++i) - { - count += ProfileThinkers(&FreshThinkers[i], &Thinkers[i]); - } - } while (count != 0); - - // Also profile the internal dynamic lights, even though they are not implemented as thinkers. - auto &prof = Profiles[NAME_InternalDynamicLight]; - prof.timer.Clock(); - for (auto light = level.lights; light;) - { - prof.numcalls++; - auto next = light->next; - light->Tick(); - light = next; - } - prof.timer.Unclock(); - - - struct SortedProfileInfo - { - const char* className; - int numcalls; - double time; - }; - - TArray sorted; - sorted.Grow(Profiles.CountUsed()); - - auto it = TMap::Iterator(Profiles); - TMap::Pair *pair; - while (it.NextPair(pair)) - { - sorted.Push({ pair->Key.GetChars(), pair->Value.numcalls, pair->Value.timer.TimeMS() }); - } - - std::sort(sorted.begin(), sorted.end(), [](const SortedProfileInfo& left, const SortedProfileInfo& right) - { - switch (profilethinkers) - { - case 1: // by name, from A to Z - return stricmp(left.className, right.className) < 0; - case 2: // by name, from Z to A - return stricmp(right.className, left.className) < 0; - case 3: // number of calls, ascending - return left.numcalls < right.numcalls; - case 4: // number of calls, descending - return right.numcalls < left.numcalls; - case 5: // average time, ascending - return left.time / left.numcalls < right.time / right.numcalls; - case 6: // average time, descending - return right.time / right.numcalls < left.time / left.numcalls; - case 7: // total time, ascending - return left.time < right.time; - default: // total time, descending - return right.time < left.time; - } - }); - - Printf(TEXTCOLOR_YELLOW "Total, ms Averg, ms Calls Actor class\n"); - Printf(TEXTCOLOR_YELLOW "---------- ---------- ------ --------------------\n"); - - const unsigned count = MIN(profilelimit > 0 ? profilelimit : UINT_MAX, sorted.Size()); - - for (unsigned i = 0; i < count; ++i) - { - const SortedProfileInfo& info = sorted[i]; - Printf("%s%10.6f %s%10.6f %s%6d %s%s\n", - profilethinkers >= 7 ? TEXTCOLOR_YELLOW : TEXTCOLOR_WHITE, info.time, - profilethinkers == 5 || profilethinkers == 6 ? TEXTCOLOR_YELLOW : TEXTCOLOR_WHITE, info.time / info.numcalls, - profilethinkers == 3 || profilethinkers == 4 ? TEXTCOLOR_YELLOW : TEXTCOLOR_WHITE, info.numcalls, - profilethinkers == 1 || profilethinkers == 2 ? TEXTCOLOR_YELLOW : TEXTCOLOR_WHITE, info.className); - } - - profilethinkers = 0; - } - - ThinkCycles.Unclock(); -} - -//========================================================================== -// -// -// -//========================================================================== - -int DThinker::TickThinkers (FThinkerList *list, FThinkerList *dest) -{ - int count = 0; - DThinker *node = list->GetHead(); - - if (node == NULL) - { - return 0; - } - - while (node != list->Sentinel) - { - ++count; - NextToThink = node->NextThinker; - if (node->ObjectFlags & OF_JustSpawned) - { - // Leave OF_JustSpawn set until after Tick() so the ticker can check it. - if (dest != NULL) - { // Move thinker from this list to the destination list - node->Remove(); - dest->AddTail(node); - } - node->CallPostBeginPlay(); - } - else if (dest != NULL) - { - I_Error("There is a thinker in the fresh list that has already ticked.\n"); - } - - if (!(node->ObjectFlags & OF_EuthanizeMe)) - { // Only tick thinkers not scheduled for destruction - ThinkCount++; - node->CallTick(); - node->ObjectFlags &= ~OF_JustSpawned; - GC::CheckGC(); - } - node = NextToThink; - } - return count; -} - -//========================================================================== -// -// -// -//========================================================================== -int DThinker::ProfileThinkers(FThinkerList *list, FThinkerList *dest) -{ - int count = 0; - DThinker *node = list->GetHead(); - - if (node == NULL) - { - return 0; - } - - while (node != list->Sentinel) - { - ++count; - NextToThink = node->NextThinker; - if (node->ObjectFlags & OF_JustSpawned) - { - // Leave OF_JustSpawn set until after Tick() so the ticker can check it. - if (dest != NULL) - { // Move thinker from this list to the destination list - node->Remove(); - dest->AddTail(node); - } - node->CallPostBeginPlay(); - } - else if (dest != NULL) - { - I_Error("There is a thinker in the fresh list that has already ticked.\n"); - } - - if (!(node->ObjectFlags & OF_EuthanizeMe)) - { // Only tick thinkers not scheduled for destruction - ThinkCount++; - - auto &prof = Profiles[node->GetClass()->TypeName]; - prof.numcalls++; - prof.timer.Clock(); - node->CallTick(); - prof.timer.Unclock(); - node->ObjectFlags &= ~OF_JustSpawned; - GC::CheckGC(); - } - node = NextToThink; - } - return count; -} - //========================================================================== // // @@ -867,7 +897,7 @@ FThinkerIterator::FThinkerIterator (const PClass *type, int statnum) m_SearchStats = false; } m_ParentType = type; - m_CurrThinker = DThinker::Thinkers[m_Stat].GetHead(); + m_CurrThinker = Thinkers.Thinkers[m_Stat].GetHead(); m_SearchingFresh = false; } @@ -890,7 +920,7 @@ FThinkerIterator::FThinkerIterator (const PClass *type, int statnum, DThinker *p m_SearchStats = false; } m_ParentType = type; - if (prev == NULL || (prev->NextThinker->ObjectFlags & OF_Sentinel)) + if (prev == nullptr || (prev->NextThinker->ObjectFlags & OF_Sentinel)) { Reinit(); } @@ -909,7 +939,7 @@ FThinkerIterator::FThinkerIterator (const PClass *type, int statnum, DThinker *p void FThinkerIterator::Reinit () { - m_CurrThinker = DThinker::Thinkers[m_Stat].GetHead(); + m_CurrThinker = Thinkers.Thinkers[m_Stat].GetHead(); m_SearchingFresh = false; } @@ -921,15 +951,15 @@ void FThinkerIterator::Reinit () DThinker *FThinkerIterator::Next (bool exact) { - if (m_ParentType == NULL) + if (m_ParentType == nullptr) { - return NULL; + return nullptr; } do { do { - if (m_CurrThinker != NULL) + if (m_CurrThinker != nullptr) { while (!(m_CurrThinker->ObjectFlags & OF_Sentinel)) { @@ -950,7 +980,7 @@ DThinker *FThinkerIterator::Next (bool exact) } if ((m_SearchingFresh = !m_SearchingFresh)) { - m_CurrThinker = DThinker::FreshThinkers[m_Stat].GetHead(); + m_CurrThinker = Thinkers.FreshThinkers[m_Stat].GetHead(); } } while (m_SearchingFresh); if (m_SearchStats) @@ -961,10 +991,10 @@ DThinker *FThinkerIterator::Next (bool exact) m_Stat = STAT_FIRST_THINKING; } } - m_CurrThinker = DThinker::Thinkers[m_Stat].GetHead(); + m_CurrThinker = Thinkers.Thinkers[m_Stat].GetHead(); m_SearchingFresh = false; } while (m_SearchStats && m_Stat != STAT_FIRST_THINKING); - return NULL; + return nullptr; } //========================================================================== diff --git a/src/dthinker.h b/src/dthinker.h index a48a2d113..5d322409c 100644 --- a/src/dthinker.h +++ b/src/dthinker.h @@ -53,13 +53,43 @@ enum { MAX_STATNUM = 127 }; // Doubly linked ring list of thinkers struct FThinkerList { - FThinkerList() : Sentinel(0) {} + // No destructor. If this list goes away it's the GC's task to clean the orphaned thinkers. Otherwise this may clash with engine shutdown. void AddTail(DThinker *thinker); DThinker *GetHead() const; DThinker *GetTail() const; bool IsEmpty() const; + void DestroyThinkers(); + bool DoDestroyThinkers(); + int TickThinkers(FThinkerList *dest); // Returns: # of thinkers ticked + int ProfileThinkers(FThinkerList *dest); + void SaveList(FSerializer &arc); - DThinker *Sentinel; +private: + DThinker *Sentinel = nullptr; + + friend struct FThinkerCollection; +}; + +struct FThinkerCollection +{ + void DestroyThinkersInList(int statnum) + { + Thinkers[statnum].DestroyThinkers(); + FreshThinkers[statnum].DestroyThinkers(); + } + + void RunThinkers(FLevelLocals *Level); // The level is needed to tick the lights + void DestroyAllThinkers(); + void SerializeThinkers(FSerializer &arc, bool keepPlayers); + void MarkRoots(); + DThinker *FirstThinker(int statnum); + void Link(DThinker *thinker, int statnum); + +private: + FThinkerList Thinkers[MAX_STATNUM + 2]; + FThinkerList FreshThinkers[MAX_STATNUM + 1]; + + friend class FThinkerIterator; }; class DThinker : public DObject @@ -79,29 +109,11 @@ public: void ChangeStatNum (int statnum); - static void RunThinkers (); - static void RunThinkers (int statnum); - static void DestroyAllThinkers (); - static void DestroyThinkersInList(int statnum) - { - DestroyThinkersInList(Thinkers[statnum]); - DestroyThinkersInList(FreshThinkers[statnum]); - } - static void SerializeThinkers(FSerializer &arc, bool keepPlayers); - static void MarkRoots(); - private: - static void DestroyThinkersInList (FThinkerList &list); - static bool DoDestroyThinkersInList(FThinkerList &list); - static int TickThinkers (FThinkerList *list, FThinkerList *dest); // Returns: # of thinkers ticked - static int ProfileThinkers(FThinkerList *list, FThinkerList *dest); - static void SaveList(FSerializer &arc, DThinker *node); void Remove(); - static FThinkerList Thinkers[MAX_STATNUM+2]; // Current thinkers - static FThinkerList FreshThinkers[MAX_STATNUM+1]; // Newly created thinkers - friend struct FThinkerList; + friend struct FThinkerCollection; friend class FThinkerIterator; friend class DObject; friend class FSerializer; @@ -167,4 +179,5 @@ public: } }; +extern FThinkerCollection Thinkers; #endif //__DTHINKER_H__ diff --git a/src/g_level.cpp b/src/g_level.cpp index bd0d234d2..07e7df6d9 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -469,7 +469,7 @@ void G_InitNew (const char *mapname, bool bTitleLevel) UnlatchCVars (); G_VerifySkill(); UnlatchCVars (); - DThinker::DestroyThinkersInList(STAT_STATIC); + Thinkers.DestroyThinkersInList(STAT_STATIC); if (paused) { @@ -1466,7 +1466,7 @@ int FLevelLocals::FinishTravel () // make sure that, after travelling has completed, no travelling thinkers are left. // Since this list is excluded from regular thinker cleaning, anything that may survive through here // will endlessly multiply and severely break the following savegames or just simply crash on broken pointers. - DThinker::DestroyThinkersInList(STAT_TRAVELLING); + Thinkers.DestroyThinkersInList(STAT_TRAVELLING); return failnum; } diff --git a/src/g_levellocals.h b/src/g_levellocals.h index 35dce9eaa..9ab21d52d 100644 --- a/src/g_levellocals.h +++ b/src/g_levellocals.h @@ -393,7 +393,7 @@ public: DThinker *thinker = static_cast(cls->CreateNew()); assert(thinker->IsKindOf(RUNTIME_CLASS(DThinker))); thinker->ObjectFlags |= OF_JustSpawned; - DThinker::FreshThinkers[statnum].AddTail(thinker); + Thinkers.Link(thinker, statnum); thinker->Level = this; return thinker; } diff --git a/src/p_pusher.cpp b/src/p_pusher.cpp index 2f580048c..24a015d14 100644 --- a/src/p_pusher.cpp +++ b/src/p_pusher.cpp @@ -295,8 +295,14 @@ void DPusher::Tick () void FLevelLocals::AdjustPusher(int tag, int magnitude, int angle, bool wind) { - DPusher::EPusher type = wind ? DPusher::p_wind : DPusher::p_current; + struct FThinkerCollection + { + int RefNum; + DThinker *Obj; + }; + DPusher::EPusher type = wind? DPusher::p_wind : DPusher::p_current; + // Find pushers already attached to the sector, and change their parameters. TArray Collection; { diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 49e087250..d792b338f 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -928,7 +928,7 @@ void FLevelLocals::Serialize(FSerializer &arc, bool hubload) if (arc.isReading()) { - DThinker::DestroyAllThinkers(); + Thinkers.DestroyAllThinkers(); interpolator.ClearInterpolations(); arc.ReadObjects(hubload); ActiveSequences = 0; @@ -1000,7 +1000,7 @@ void FLevelLocals::Serialize(FSerializer &arc, bool hubload) P_SerializeHealthGroups(this, arc); // [ZZ] serialize events E_SerializeEvents(arc); - DThinker::SerializeThinkers(arc, hubload); + Thinkers.SerializeThinkers(arc, hubload); arc("polyobjs", Polyobjects); SerializeSubsectors(arc, "subsectors"); StatusBar->SerializeMessages(arc); diff --git a/src/p_setup.cpp b/src/p_setup.cpp index cee9f639b..01b84ed6d 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -349,7 +349,7 @@ void P_FreeLevelData () E_Shutdown(true); R_FreePastViewers(); - DThinker::DestroyAllThinkers (); + Thinkers.DestroyAllThinkers (); level.ClearLevelData(); } @@ -573,8 +573,8 @@ void P_Init () } static void P_Shutdown () -{ - DThinker::DestroyThinkersInList(STAT_STATIC); +{ + Thinkers.DestroyThinkersInList(STAT_STATIC); P_FreeLevelData (); // [ZZ] delete global event handlers E_Shutdown(false); diff --git a/src/p_spec.h b/src/p_spec.h index be1b710b9..529b184ac 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -40,12 +40,6 @@ class FScanner; struct level_info_t; struct FDoorAnimation; -struct FThinkerCollection -{ - int RefNum; - DThinker *Obj; -}; - enum class EScroll : int { sc_side, diff --git a/src/p_tick.cpp b/src/p_tick.cpp index 88649f0d5..cfd6064f3 100644 --- a/src/p_tick.cpp +++ b/src/p_tick.cpp @@ -151,7 +151,7 @@ void P_Ticker (void) E_WorldTick(); StatusBar->CallTick (); // [RH] moved this here level.Tick (); // [RH] let the level tick - DThinker::RunThinkers (); + Thinkers.RunThinkers(&level); //if added by MC: Freeze mode. if (!level.isFrozen())