[Open Source] VFW (135): Drawers. Save System and full exposure

Latest version: 1.3.5

Github repository: GitHub - vexe/VFW

Notes:

  • When updating to a new major version, it’s better if you remove the current version you have and download the newer one
  • Please raise any issues/bugs you have via the issues section in github that way other users can see if an issue they’re having is already raised or not.

_______________
– 1.3.5 –

  • Fixed errors on web player
  • Fixed Unity 5.3 errors
  • Deprecated BetterBehaviour/BetterScriptableObject and custom serialization. Use Unity’s standard serialization.
  • Deprecated uDelegate, uAction, uFunc, etc. Use UnityEvents instead.
  • Deprecated BetterPrefs. There are no more editor .asset files. Visit the actual VFWSettings.cs file to modify the stuff that’s in it. Foldout values are now saved as part of BaseBehaviour (in an EditorRecord object) (Editor only, no runtime overhead)
  • “Drag drop” area shows when an array/list is empty as well
33 Likes

[Edit the implementation in this answer is kind of outdated] I wrote a sample implementation (very basic) explaining how I went about BinaryFormatter serialization here.

Incredible. You’re a kitbash rockstar, vexe. I’ve tiptoed down this road before, but you’ve tarred, graded, painted, and put in sidewalks. Truly a wonderful contribution to the community. Haven’t dug in to do any testing, but I’ll be back with bells on. I can’t wait to get my greedy hands on exposed (drawn) interfaces - the lack thereof has completely changed the way I approach programming. Please, for the love of C#, don’t quit now! If you’re ever in town I’m skipping the handshake and giving you a great big overzealous probably-awkward hug.

8 Likes

@AlwaysSunny thanks for the ‘extremely’ kind words! made me smile :slight_smile: - I like the hug idea, but I’m pretty far :frowning:
I’m working on an interface drawer as we speak. It’s a bit tricky, cause all the drawers I wrote so far work well with SerializedProperties and UnityObjects, not with raw System.Objects, so there’s many adjustments to be made to the drawers. Also to the serialization logic, if you look at the code, you’ll see I dis-include generics, abstracts etc (when I modeled the code for BetterBehaviour, I did not have interfaces/generics, etc in mind) - If I change the serialization logic, I’d have to have drawers for each new thing I support.

Thanks, this is really generous.

Hi Vexe,
I am having issue with 4.5.3f4. When i run the Serialization scene, i get the following exception

NullReferenceException: (null)
UnityEditor.SerializedObject..ctor (UnityEngine.Object[] objs) (at C:/BuildAgent/work/d63dfc6385190b60/artifacts/EditorGenerated/SerializedPropertyBindings.cs:72)
UnityEditor.Editor.GetSerializedObjectInternal () (at C:/BuildAgent/work/d63dfc6385190b60/artifacts/EditorGenerated/EditorBindings.cs:124)
UnityEditor.Editor.get_serializedObject () (at C:/BuildAgent/work/d63dfc6385190b60/artifacts/EditorGenerated/EditorBindings.cs:117)
UnityEditor.AnimationEditor.OnEnable () (at C:/BuildAgent/work/d63dfc6385190b60/Editor/Mono/Inspector/AnimationEditor.cs:20)

Can you please point to direction, so if possible i can fix it? Thanks for such wonderfull package.

Thanks

Hey @gear thanks for dropping by. So I get this sort of errors too very randomly (not just in SEA but just randomly in general…). Like you see from the stacktrace, it’s not coming from any of my scripts. It’s coming from Unity’s code. These types of errors always (at least in my experience) resolve themselves when you restart Unity, try that. If that didn’t fix it, let me know

Hello @vexe ,
Well i restarted Unity but if didnt work. Thing is that its just give random exception like you mention. I am trying to import in new project.

Thanks

@gear I just downloaded the package and imported, didn’t get any errors. Using the latest Unity (4.5.4f1) - pretty sure it shouldn’t make a difference if you were on 4.5.3 since that’s the version I used to release the current build. Maybe consider upgrading anyways?

@vexe , it work fine importing in fresh project. Sorry for wrong report!

Thanks

Guys, I’ve released my new API. ShowEmAll is no more, Vexe Framework 1.0 is here! I rewrote the thread and removed some of my old redundant replies. Check out the new stuff!

1 Like

This looks very promising. Maybe Unity should hire you to fix the overall state of the inspector. I will give it a try soon as i finish the 1000 tasks i have to do and will leave you a comment :slight_smile:

1 Like

Well well, you’ve been busy! I really can’t wait to dive in and play with all these features. It’s incredibly generous of you to offer this package free of charge; it’s obvious you’ve put in a ton of work. It deserves more recognition - I hope lots of folks will discover and make use of it as time goes by.

WOW!! humongous thanks!

Thank you all for the kind words! - Luckily I got some contract jobs so I could test my framework in the real world. I’ve come along a few bugs so make sure you update. The only thing that I found a bit annoying was the editor performance, it’s laggy if there’s lots of things going on (lists with many elements, too many components with many categories, etc), I got my hands on a Unity pro license so I could finally profile now. I could probably assume that a lot of the performance hits are due to GUILayout and maybe because I use too much closures and LINQ (maybe Slinq could help here…) - memoizaiton would definitely help. But can’t make any real assumption till I profile. Will do as soon as I can. Let me guys know of your experiences too, I’d love to hear from you!

So if any of you have downloaded and used the package you might have noticed the editor is slow… This is mainly due to hits on the GC, now that I have access to Unity’s profiler, I managed to improve performance. Garbage is halved, and performance is about 30% to 40% better. Did many things to achieve that, like memoization/smart caching, minimizing allocations, and replacing my GUI API to just use normal methods instead of delegates, removing closures helped reduce garbage.

I know that Unity’s GC is from the Flintstones, but I didn’t expect it to hit performance that hard… I’m glad I profiled now and not latter. I’m still not happy though, there’s still some ridiculous amount of garbage generated for fairly simple stuff… I kept thinking that it’s coming from me, but I just didn’t see how and where… I wish I had a better machine to enable Deep profiling, it literally dies when I do that… but I had to do it… I then see that most of this garbage and performance hits are from Unity’s GUILayout business… So yeah… I have to go back to the GUIWrapper I used in uFAction which is much faster than GUILayout… GUILayout is a no go, and neither is dealing with rects and positions manually via just GUI. We NEED a faster/better auto-layedout GUI system. Will see what I can hack, gotta get back to work though, damn money.

1 Like

So I didn’t get a chance yet to try out BetterPrefs in an actual game, but I think it’s interesting. First create a BetterPrefs asset by going Tools | Vexe | BetterPrefs | CreateAsset. The asset will be created under Vexe/Runtime/Examples/ScriptableAssets. Here’s a sample code of what you can do with BP:

  public class BetterPrefsExample : BetterBehaviour
   {
     public BetterPrefs prefs; // assign the asset you created to this field

     [Show] void SaveVectorsToPrefs()
     {
       // save position, euler angles and local scale to prefs
       prefs.Vector3s["MyPosition"] = position;
       prefs.Vector3s["MyRotation"] = localEulerAngles;
       prefs.Vector3s["MyScale"]  = localScale;
     }

     [Show] void ReadVectorsFromPrefsAndWriteToFile()
     {
       // read values...
       var pos = prefs.Vector3s["MyPosition"];
       var rot = prefs.Vector3s["MyRotation"];
       var scl = prefs.Vector3s["MyScale"];

       // log them
       Log("Position: {0} - Rotation {1} - Scale {2}", pos, rot, scl);

       // maybe serialize and write them to a file?
       string serializedPos = Serializer.Serialize<Vector3>(pos);
       string serializedRot = Serializer.Serialize<Vector3>(rot);
       string serializedScl = Serializer.Serialize<Vector3>(scl);
       using (var file = File.Open("Assets/Vexe/Runtime/Examples/Assets/Sample.data", FileMode.OpenOrCreate))
       {
         using (var writer = new StreamWriter(file))
         {
           writer.WriteLine(serializedPos);
           writer.WriteLine(serializedRot);
           writer.WriteLine(serializedScl);
         }
       }
     }
   }

Using FullSerializer here we get that nice JSON output. So yeah there you go, BetterPrefs, quick way to save data. it’s simple now I may add some helper methods to it to quickly write/read to/from files using the current BetterBehaviour serializer.

Of course you could just serialize the whole dictionary as well, and then write that to a file:

string serializedDictionary = Serializer.Serialize(prefs.Vector3s);
...
writer.WriteLine(serializedDictionary);

With an object marked with DontDestroyOnLoad and has a BetterPrefs instance, BetterPrefs could also be used to pass data between scenes. Which is part of why I created it. PlayerPrefs is nice, but it’s limited and uses the registry. I wanted something local, and simple

1.0.4 is here. Added some missing attributes and a way to define default values. Also changed the project’s folder hierarchy to make use of the Plugins and Plugins/Editor folder which improved compilation times when an assembly reload is triggered from a compilation pass/group other than the first one (see)

For anyone that’s downloaded my package, I’d love to hear your experience so far!

Hey Vexe,

This Framework looks awesome, but I am unclear of how to extend it. I have a generic type MyType and I would like to implement a custom drawer for that (and get it serializing correctly). It is a member of a monobehaviour that I have created, however I would like to use this custom drawer in the editor whenever there is a member of that type on any object. What are the basic steps to go through to get that working?

Thanks,

James

1 Like

Hey @vangojames ,

Thanks for dropping by, apologies for the confusion, but I didn’t have the time to explain/make a tutorial on the exact part you’re mentioning. It is very straight forward though:

public MyObjectDrawer<T> : ObjectDrawer<MyType<T>>
{
   public override void OnGUI()
   {
        // ...
   }
}

Basically, you just need to keep in mind, there are 2 types of drawers you could use: 1- Object Drawers, 2- Composite Drawers. Object drawers split to two types: ObjectDrawer and AttributeDrawer<TObject, TAttribute>

Object drawers define how an object is drawn, composite drawers just add decorations and they shouldn’t define/mess with how an object is represented. With Object drawers you get an OnGUI call to override, there, you write your GUI code. In Composite drawers however; you get OnLeftGUI, OnRightGUI, OnLowerGUI and OnUpperGUI that way you can selectively target which part of your object you want to decorate. See WhitespaceAttributeDrawer as an example.

1- If you have a certain Type T and you want to write a drawer for it such that wherever an object of that type is mentioned, your drawer will be used; you should use ObjectDrawer (ex DictionaryDrawer, ArrayDrawer, ListDrawer, etc)

2- If you have a certain Type TObject and you want to write a drawer for it such that wherever an object of that type is mentioned AND it is decorated with an attribute of type TAttribute, your drawer will be used; you should use AttributeDrawer (ex ShowTypeAttributeDrawer) (NOTE: TAttribute must inherit DrawnAttribute)

3- Now for composite drawers, you need to specify the type of object you’re targeting, and the type of attribute that way if you annotate where you shouldn’t, it just gets ignored and things won’t blow up. Say you wanted to write a composite drawer for strings, and for an attribute of type MyAttribute. (NOTE: MyAttribute should inherit CompositeAttribute)

public class MyAttributeDrawer : CompositeDrawer<string, MyAttribute>
{
    // override whatever OnXXXGUI you want...
}

4- Last thing you need to keep in mind, when you use any type of drawer, you will get a strongly typed reference to what you’re targeting; that reference is dataMember; it’s basically a wrapper around fields/properties, it’s what lets you treat both equally so you don’t have to worry about whether you’re targeting a field/property (think of it as a clean SerializedProperty on steroids). dataMember.Value is the value of the object you’re targetting (strongly typed), you could just use dmValue for short. If you use CompoiteDrawer or AttributeDrawer, you also get a strongly typed reference to the attribute you’re targeting; that is “attribute”

Example usages of the drawing API is scattered all around the code base. Pretty much every drawer for every single attribute/object in the framework, uses this drawing API so if you just peek a little bit around you’ll get a decent understanding.

For serialization, it depends on what serializer you’re using, if you’re using BinaryFormatter you have to mark your type with [Serializable], if you’re using Protobuf you have to mark it with [ProtoContract] and each member you want to serialize with [ProtoMember(id)] etc. If you’re using FullSerializer you don’t need to do anything! I recommend you stick to FullSerializer, it’s the default serializer anyways. Watch the serialization video if you haven’t already, should cover most questions.

Let me know if you have more questions or if you get stuck somewhere

1 Like