saltyhotdog / BattletechIssueTracker

Public issue tracker to communicate with modders and HBS.
MIT License
6 stars 0 forks source link

Call stack grows to enormous sizes and crashes the game #17

Closed CptMoore closed 5 years ago

CptMoore commented 6 years ago

Describe the problem

There seems to be another issue that closes/crashes the game immediately on games with lots of components.

The call stack count can grow to enormous numbers, this happens because components that are finished loading directly call other components OnLoaded callbacks, which again directly call other components.

The problem here is two fold

  1. many of the components with dependencies subscribe to a generic callback

    dataManager.MessageCenter.AddSubscriber(MessageCenterMessageType.DataManagerRequestCompleteMessage, new ReceiveMessageCenterMessage(this.CheckDependenciesAfterLoad));
  2. components, once all dependencies are loaded, call the following directly

    this.dataManager.MessageCenter.PublishMessage(new DataManagerRequestCompleteMessage<T>(this.resourceType, this.resourceId, this.GetResource()));

This can produce a cascade effect with lots of callers.

e.g. if a SVG is finally loaded, a weapondef could finally met its dependencies and is finished loaded. then the onloaded method is called which again finds another weapondefs dependency check to call. so a trace like SVGAsset loaded -> WeaponDef complete -> WeaponDef complete ... can come about.

It can then even continue after ~4500 stack frames with a MechDef.Refresh call (at which point I wasn't logging anymore).

Provide an example of the code where the problem occurs

Part of a stack trace (just some lines, these can easily grow to the thousands of stack frames for a single call)

   at MessageCenter.SendMessagesForType(MessageCenterMessageType messageType, .MessageCenterMessage message)
   at MessageCenter.PublishMessage(.MessageCenterMessage message)
   at BattleTech.Data.DataManager+ResourceLoadRequest`1.NotifyLoadComplete()
   at BattleTech.Data.DataManager+WeaponDefLoadRequest.OnLoaded()
   at BattleTech.MechComponentDef.CheckDependenciesAfterLoad(.MessageCenterMessage message)
   at BattleTech.WeaponDef.CheckDependenciesAfterLoad(.MessageCenterMessage message)
   at MessageCenter.SendMessagesForType(MessageCenterMessageType messageType, .MessageCenterMessage message)
   at MessageCenter.PublishMessage(.MessageCenterMessage message)
   at BattleTech.Data.DataManager+ResourceLoadRequest`1.NotifyLoadComplete()
   at BattleTech.Data.DataManager+WeaponDefLoadRequest.OnLoaded()
   at BattleTech.MechComponentDef.CheckDependenciesAfterLoad(.MessageCenterMessage message)
   at BattleTech.WeaponDef.CheckDependenciesAfterLoad(.MessageCenterMessage message)
   at MessageCenter.SendMessagesForType(MessageCenterMessageType messageType, .MessageCenterMessage message)
   at MessageCenter.PublishMessage(.MessageCenterMessage message)
   at BattleTech.Data.DataManager+ResourceLoadRequest`1.NotifyLoadComplete()
   at BattleTech.Data.DataManager+WeaponDefLoadRequest.OnLoaded()
   at BattleTech.MechComponentDef.CheckDependenciesAfterLoad(.MessageCenterMessage message)
   at BattleTech.WeaponDef.CheckDependenciesAfterLoad(.MessageCenterMessage message)
   at MessageCenter.SendMessagesForType(MessageCenterMessageType messageType, .MessageCenterMessage message)
   at MessageCenter.PublishMessage(.MessageCenterMessage message)
   at BattleTech.Data.DataManager+ResourceLoadRequest`1.NotifyLoadComplete()
   at BattleTech.Data.DataManager+SVGAssetLoadRequest.OnLoaded()
   at BattleTech.Data.DataManager+SVGAssetLoadRequest.AssetLoaded(SVGImporter.SVGAsset resource)
   at BattleTech.Data.DataManager+GenericResourceRequestHelper`1[[SVGImporter.SVGAsset, Assembly-CSharp-firstpass, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]].Complete()
   at BattleTech.Data.DataManager.Update(Single deltaTime)
   at BattleTech.GameInstance.Update(Single deltaTime)
   at BattleTech.UnityGameInstance.Update()

Provide an example of your proposed solution

Seems to be a fundamental issue, instead of directly calling the completion, one would need to queue the notification and then only the top-most caller of the in the call stack should do some kind of iteration. Well you know the issue with recursion vs iteration.

Leave any additional comments here

smaller call stack example: weapondef_loop_sample.txt

larger call stack example: mechdef_loop_sample.txt

Sheep-y commented 6 years ago

For now I did a quick hack by capturing and managing mechdef dependencies at the data manager level. It works quite well and can really trim the stack. A more generic version can potentially be done for all ResourceLoadRequest which will make more sense.

https://github.com/Sheep-y/BattleTech_Turbine/

caardappel-hbs commented 6 years ago

A fix for this has been submitted for the next patch.

Morphyum commented 5 years ago

Closed because HBS aknowledged and fixed