diff --git a/src/d_main.cpp b/src/d_main.cpp index c59b4cbf0b..249187f6b1 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -2585,7 +2585,7 @@ void D_DoomMain (void) gamestate = GS_FULLCONSOLE; Net_NewMakeTic (); ForAllLevels([](FLevelLocals *Level) { - DThinker::RunThinkers (Level); + Thinkers.RunThinkers (Level); }); gamestate = GS_STARTUP; @@ -2702,7 +2702,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 d36df576da..aab2f75e48 100644 --- a/src/dobjgc.cpp +++ b/src/dobjgc.cpp @@ -282,7 +282,6 @@ static void MarkRoot() Mark(StatusBar); M_MarkMenus(); Mark(DIntermissionController::CurrentIntermission); - DThinker::MarkRoots(); Mark(E_FirstEventHandler); Mark(E_LastEventHandler); diff --git a/src/dthinker.cpp b/src/dthinker.cpp index 9fe4baab28..eecf63914c 100644 --- a/src/dthinker.cpp +++ b/src/dthinker.cpp @@ -52,56 +52,40 @@ 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) { - // 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); + 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; + list = &Thinkers[statnum]; } - assert(Sentinel->NextThinker->PrevThinker == Sentinel); - return Sentinel->NextThinker; + list->AddTail(thinker); } //========================================================================== @@ -110,43 +94,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"); + } } //========================================================================== @@ -155,7 +257,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; @@ -168,8 +270,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(); @@ -226,6 +328,310 @@ 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; +} + void DThinker::Serialize(FSerializer &arc) { Super::Serialize(arc); @@ -240,8 +646,8 @@ void DThinker::Serialize(FSerializer &arc) DThinker::DThinker(FLevelLocals *l) throw() { - NextThinker = NULL; - PrevThinker = NULL; + NextThinker = nullptr; + PrevThinker = nullptr; Level = l; ObjectFlags |= OF_JustSpawned; } @@ -331,32 +737,6 @@ void DThinker::PostSerialize() { } -//========================================================================== -// -// -// -//========================================================================== - -DThinker *DThinker::FirstThinker (int statnum) -{ - DThinker *node; - - if ((unsigned)statnum > MAX_STATNUM) - { - statnum = MAX_STATNUM; - } - node = Thinkers[statnum].GetHead(); - if (node == NULL) - { - node = FreshThinkers[statnum].GetHead(); - if (node == NULL) - { - return NULL; - } - } - return node; -} - //========================================================================== // // @@ -367,20 +747,14 @@ void DThinker::ChangeStatNum (int statnum) { FThinkerList *list; + 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) @@ -395,48 +769,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"); - } -} //========================================================================== // @@ -444,77 +776,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(); @@ -565,248 +826,6 @@ CCMD(profilethinkers) } } -struct ProfileInfo -{ - int numcalls = 0; - cycle_t timer; - - ProfileInfo() - { - timer.Reset(); - } -}; - -TMap Profiles; - - -void DThinker::RunThinkers (FLevelLocals *Level) -{ - 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; -} - //========================================================================== // // @@ -873,7 +892,7 @@ FThinkerIterator::FThinkerIterator (FLevelLocals *Level, const PClass *type, int m_SearchStats = false; } m_ParentType = type; - m_CurrThinker = DThinker::Thinkers[m_Stat].GetHead(); + m_CurrThinker = Thinkers.Thinkers[m_Stat].GetHead(); m_SearchingFresh = false; } @@ -896,7 +915,7 @@ FThinkerIterator::FThinkerIterator (FLevelLocals *Level, const PClass *type, int m_SearchStats = false; } m_ParentType = type; - if (prev == NULL || (prev->NextThinker->ObjectFlags & OF_Sentinel)) + if (prev == nullptr || (prev->NextThinker->ObjectFlags & OF_Sentinel)) { Reinit(); } @@ -915,7 +934,7 @@ FThinkerIterator::FThinkerIterator (FLevelLocals *Level, const PClass *type, int void FThinkerIterator::Reinit () { - m_CurrThinker = DThinker::Thinkers[m_Stat].GetHead(); + m_CurrThinker = Thinkers.Thinkers[m_Stat].GetHead(); m_SearchingFresh = false; } @@ -927,15 +946,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)) { @@ -956,7 +975,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) @@ -967,10 +986,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 c6de53e015..284d5f4500 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); + 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 @@ -89,35 +119,15 @@ public: void ChangeStatNum (int statnum); - static void RunThinkers (FLevelLocals *Level); - 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(); - - static DThinker *FirstThinker (int statnum); - FLevelLocals *Level; protected: DThinker() = default; 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; @@ -194,4 +204,5 @@ T* CreateThinker(Args&&... args) return object; } +extern FThinkerCollection Thinkers; #endif //__DTHINKER_H__ diff --git a/src/g_level.cpp b/src/g_level.cpp index c604bb32e6..5b19d48214 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -489,7 +489,7 @@ void G_InitNew (const char *mapname, bool bTitleLevel) UnlatchCVars (); G_VerifySkill(); UnlatchCVars (); - DThinker::DestroyThinkersInList(STAT_STATIC); + Thinkers.DestroyThinkersInList(STAT_STATIC); if (paused) { @@ -1515,7 +1515,7 @@ int G_FinishTravel (FLevelLocals *Level) // 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; } @@ -1529,7 +1529,7 @@ FLevelLocals::~FLevelLocals() { SN_StopAllSequences(this); ClearAllSubsectorLinks(); // can't be done as part of the polyobj deletion process. - DThinker::DestroyAllThinkers(); + Thinkers.DestroyAllThinkers(); // delete allocated data in the level arrays. if (sectors.Size() > 0) @@ -2217,6 +2217,7 @@ void FLevelLocals::Mark() GC::Mark(ACSThinker); GC::Mark(interpolator.Head); GC::Mark(SequenceListHead); + Thinkers.MarkRoots(); canvasTextureInfo.Mark(); for (auto &c : CorpseQueue) diff --git a/src/g_shared/a_decals.cpp b/src/g_shared/a_decals.cpp index 18a50f1b15..d5cd63698d 100644 --- a/src/g_shared/a_decals.cpp +++ b/src/g_shared/a_decals.cpp @@ -533,7 +533,7 @@ CUSTOM_CVAR (Int, cl_maxdecals, 1024, CVAR_ARCHIVE) { while (Level->ImpactDecalCount > self) { - DThinker *thinker = DThinker::FirstThinker(STAT_AUTODECAL); + DThinker *thinker = Thinkers.FirstThinker(STAT_AUTODECAL); if (thinker != NULL) { thinker->Destroy(); @@ -552,7 +552,7 @@ void DImpactDecal::CheckMax () { if (++Level->ImpactDecalCount >= cl_maxdecals) { - DThinker *thinker = DThinker::FirstThinker (STAT_AUTODECAL); + DThinker *thinker = Thinkers.FirstThinker (STAT_AUTODECAL); if (thinker != NULL) { thinker->Destroy(); diff --git a/src/p_pusher.cpp b/src/p_pusher.cpp index f683bd1605..ba17c70312 100644 --- a/src/p_pusher.cpp +++ b/src/p_pusher.cpp @@ -415,8 +415,15 @@ void P_SpawnPushers (FLevelLocals *Level) } } + void AdjustPusher (FLevelLocals *Level, int tag, int magnitude, int angle, bool wind) { + 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. diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 28883dee94..d90827ffeb 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -914,7 +914,7 @@ void G_SerializeLevel(FSerializer &arc, FLevelLocals *Level, bool hubload) if (arc.isReading()) { - DThinker::DestroyAllThinkers(); + Thinkers.DestroyAllThinkers(); Level->interpolator.ClearInterpolations(); arc.ReadObjects(hubload); } @@ -977,7 +977,7 @@ void G_SerializeLevel(FSerializer &arc, FLevelLocals *Level, bool hubload) P_SerializeHealthGroups(Level, arc); // [ZZ] serialize events E_SerializeEvents(arc); - DThinker::SerializeThinkers(arc, hubload); + Thinkers.SerializeThinkers(arc, hubload); arc("polyobjs", Level->Polyobjects); SerializeSubsectors(arc, Level, "subsectors"); StatusBar->SerializeMessages(arc); diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 909ad0f33e..283e0fa274 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -426,7 +426,7 @@ static void P_Shutdown () { delete currentSession; currentSession = nullptr; - 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 ff5a892559..9f9e67ad9e 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 1fef190d47..9e9dd41f59 100644 --- a/src/p_tick.cpp +++ b/src/p_tick.cpp @@ -152,7 +152,7 @@ void P_Ticker (void) memset(&Level->Scrolls[0], 0, sizeof(Level->Scrolls[0]) * Level->Scrolls.Size()); } - DThinker::RunThinkers(Level); + Thinkers.RunThinkers(Level); //if added by MC: Freeze mode. if (!currentSession->isFrozen())