Serialization depth limit and recursive serialization

Hi!
Im making a game which recently landed me in a pile of warnings, and one error. I just got done reformatting several scripts, so I dont know precisely where to look to solve this issue.

THE WARNING:
Serialization depth limit 10 exceeded at ‘CardSaveLoad.effectValue’. There may be an object composition cycle in one or more of your serialized classes.

Serialization hierarchy:
11: CardSaveLoad.effectValue
10: Item.metaCardList
9: CardSaveLoad.sourceItem
8: Item.metaCardList
7: CardSaveLoad.sourceItem
6: Item.metaCardList
5: CardSaveLoad.sourceItem
4: Item.metaCardList
3: CardSaveLoad.sourceItem
2: Item.metaCardList
1: CardSaveLoad.sourceItem
0: PassiveSavaLoad.boundCard

THE ERROR:
Recursive Serialization is not supported. You can’t dereference a PPtr while loading. (Constructors of C# classes may not load objects either. See stacktrace.)

rather than posting all the scripts here, I feel it would be better to explain the structure im using to create/save instances of things for my game.
Important note, most items in my game are customizable, copy able, and need multiple instances to function.

For almost all Classes I have in the game, there are 2 sister classes.
-The First is a gameobject controller which applies data to the gameobject, manages child gameobjects and interactions with other gameobjects.
-the Second is a Scriptable Object, so I can easily save different version of each type of thing
-the Third is a Serializable class which contains all serializable data for the thing, including values that have been changed from the set Scriptable object. Its a bad naming scheme, but I call these SaveLoads. (shown in error above)

Now, this error/warning are supposed to be coming from “infinite loops” but im not sure where that could be. Right now, I have Classes (and sister classes) for the following things.

-Characters
-Cards
-Decks
-Items(which add cards to your deck when equipped)
-Passives (which essentially work in tandom with characters to cause things to happen

I have a feeling the problem is coming from cards, which sort of reverse the flow of references to easily remove them from a deck when an item is taken away.

characters reference cards, decks, items, passives

Passives reference cards (to play at certain times automatically) and other passives (for stacking effects or spreading to other characters, maybe this is being read as recursive? )

decks are basically just a list of cards with extra functions

items have a list of cards which they add to your deck, among other effects

cards however keep track of other cards (multiplying or adding staged cards) and also the equipment that added them to the deck, if thats how they got there.

I can post any code to look at at request, I just dont know where to even look for this.

I commented out the Item reference in Card’s serializable class and the error and warnings went away. however, I kind of need that to be saved. is there any way for me to have two serializable classes save references to each other WITHOUT the threat of infinite recursion?

Edit:

This usually happens when you have a Parent reference its children, and then you have it’s Children reference the Parent

That is a circular reference

Serializer:
Parent->ChildA->Parent->ChildA->Parent… and it can go on forever

This can happen anywhere down the chain though, so you need to not reference stuff that is higher up on the chain

1 Like

its that simple huh? okay ty. ill try to find a workaround

Sorry but I think you’re on the wrong track here in general. Custom serializable classes can not be “referenced”. When they are serialized they are treated like structs and their data is serialized in place. If you “reference” the same class instance in two or more places, after deserialization you would have two or more seperate objects afterwards. I highly recommend to read through the script serialization documentation page.

Because custom serializable classes (and structs) essentially behave like structs, you can not store a “null” value. Each instance will be serialized in-place, that’s why Unity has a max serialization depth. It used to be 7 but it seems they incremented the limit a bit. Before they introduced the limit (that’s 10+ years ago) the editor would simply crash due to the infinite recursion.

Note that recently Unity improved the serialization support a bit by adding the SerializeReference attribute. It removes some of the limitations mentioned above, but also removes a lot of the automatic inspector handling. So in many cases you would need custom editor code to handle such references. Though with SerializeReference we can now actually store null values, get polymorphism support and we are able to actually reference classes as long as they belong to the same UnityEngine.Object (so to the same MonoBehaviour or ScriptableObject). You can’t cross reference between several hosting classes. When you use SerializeReference there is some additional overhead in the serialized data as such references are stored in a seperate linear list and references are simply implemented with index references. Just try it out and open the scene / scriptable object file in a text editor to see the difference.

if I’m understanding what you are saying here, thats actually intended. but the rest of what you said has me concerned.

what I’m going for here is base, manually made things (Scriptable Objects) which, at first load have their data copied onto monobehavior scripts which also have interaction methods along with methods to change the data (customize the object in game) and then save the data of the now-changed card/item in a serializable container.

Then, at next load, instead of loading from the Scriptable object I instead load from the Serialized data and continue from there.

Is something about this structure flawed? Im a hobbyist, trained by gumption and google so if I’m setting myself up for failure I’d definitely like to know.

Side note, I realized that I actually DONT need the card class(any of them) to contain a reference to what equipment they were added to. It’s more lines than I was looking to use, but i got the results I wanted with just

foreach([Card] i in [Character’s][deck])
{
if([Card List on Item] .contains(i)
{
removecardMethod();
}
}

so the answer to the question of “how can I keep a reference to data’s storage container without causing a loop” is just dont because you dont actually need to, you just convinced yourself you did.