Simple String Configuration Workflow (iCfg)

 

 

 

iCfg class is a very manual way to Save (Encode) and Load (Decode) data. I developed it to overcome a challenge of a modular project, with a large team may be developing and using it at the same time. Classes, their structure, method and field naming is constantly changes while their values should not be lost in the process. It works in synergy with my very-fast-to-write inspector methods PEGI.

 

It is a bit difficult to properly explain what this class do. And I don't think I made a good job of it in this article. Nevertheless I'm posting it, in case someone will notice it on one of my assets and will want to learn more about it.  

 

To save and load Data you will need to write something like this: 

 

1. SERIALIZATION

 

public CfgEncoder Encode() => EncodeUnrecognized()

.Add("mrkP", mapMarkerPosition)

.Add_String("t", Title);   

 

Encoding unrecognized is used for backwards compatibility (if you open data on older version of your software which lacks certain fields). Everything is encoded into the following format:

 

tag|size|data

 

Tag should remain the same for serialization and deserialization to work. 

Size is the size of data part. So there is no restriction on what kind of symbols can be inside data. 

Data can be anything, including more tag|size|data. 

 

1. DESERIALIZATION

 

public bool Decode(string tag, string data){

switch (tag){

case "mrkP": mapMarkerPosition = data.ToVector2(); break; 

case "t": Title = data; break;

default: return false;

}

return true;

}

 

if false is returned, current tag and data are stored into Unrecognized lists of that class (if this class implements iKeepUnrecognized interface). Important to note that Encode function can have much more going on inside.

 

public CfgEncoder Encode() {

var cody = new CfgEncoder().

.Add_String("t", Title); 

if (markerList.Count>0)

cody.Add ("mrkLst", markerList);

 

 

The upsides: 

 

*Selective serialization: you can select the data you need to save, so you'll never need to create an extra class and copy data to it.

*Backwards compatibility: in case you open level on old version of script which doesn't have "mapMarkerPosition" parameter, the Decode() function will return false, and "mrkP" and data will be saved in a list of Unrecognized data. And when object gets saved again, all that unrecognized data will be saved with him, and never lost. (If you implement iKeepUnrecognized or derive from one of the KeepUnrecognized base classes.) 

 

* You can change variables and class names. In example above, it's only important to have "mrkP" tag string unchanged, and it's easy to do so, because you will not be using it anywhere else. Unlike the variable name (mapMarkerPosition) which you may want to remove (in which case just do this:   case "mrkP": return true; so that data will not gets saved into Unrecognized list.). You may also want to change your class structure, in which case the lines above may change into something like this:

 

cody.Add("mrkP", mapMarker.position);

  

case "mrkP": mapMarker.position = data.ToVector2(); break; 

 

* You can copy data between unrelated classes without making any special Copy function, for example:

weapon.Decode(craftingMaterial.Encode());

if both classes have tag "rarity" or "enchantment" fields, for example, their respective Encode()/Decode() will transfer those values. 

 

* You never need to create a separate class to Serialize a saved data. In Encode function you can just collect the data needed, including static variables:

cody.Add("hp", Player.Instance.Health);

cody.Add("lvl", LevelController.currentLevel);

 

* You can avoid saving unneeded data.

 

* And it doesn't have to be just data saving. You could do 

case "jump" : StartJumpAnimation(); break;

 

* Encode()/Decode()  functions can have additional logic, and can have nested iSTD classes which will also be serialized:

 

 

A good example of usage is my Playtime Painter Tool which has Stroke Vector Recording: basically it saves each stroke you made and can recreate it to redraw the image. It saves color, size of the brush. And there are other parameters, like blur amount, mask, mask offset, mask tiling. But you only need blur amount when you are using the blur brush. Mask number, mask offset is only used when you are actually toggled useMask true. And if you are using Blur, you don't need the color of the brush. So the above mentioned serialization is the manual way to Encode() only the values you need.

 

If you have a car prefab, the code which saves it will look something like:

 

car|pos|10|20|30|rot|0|45|0|color|64|23|78|unlocked|1|y  

 

But slightly different because I wrote it like name|data, but it should be name|lengthOfData|data, so basically there will be more numbers and more separators. You can see that I'm using "|" to separate data, but it's no problem if, for example, some text contains "|", it will not break Decoding, because, as I said, I also save the length of the data, so during the decoding it will know that the symbol is a part of that data. 

 

Unity Objects

Add_Referance("Tex", texture, referancesHolder)

 

Encoding Base Class

Add("base", base.Encode());

case "base": data.DecodeInto(base.Decode);

 

Conclusion()

It is still developed and tested, but so far, I try to use it when other options are not viable, and it immediately provides a solution, and as a bonus allows me to remove some other code chunks, for example, OnPostSerialization() function, or calls to Init() function, or other additional conditions. 

 

iSTD class is available on my GitHub

 

 

Please reload

Geeky:
GitHub icon
unity-Connet-Logo.png
Discord icon
Social:
Facebook icon
unnamed.png
Instagram icon