Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

Full Inspector: Inspector and serialization for structs, dicts, generics, interfaces

Discussion in 'Assets and Asset Store' started by sient, Jan 23, 2014.

  1. Alejandro-Martinez-Chacin

    Alejandro-Martinez-Chacin

    Joined:
    Oct 15, 2013
    Posts:
    38
    Bought this library, and after about two weeks of using it I must say... it's impressive.
    Spent a couple of full days trying to find a way to serialize dictionaries and complex collections, always thinking that the solution "was close". No way near close, after seeing your library it was months away.

    Awesome that you can serialize which implementation you want for an interface, that rocks big time... will finally try to approach some problems almost Dependency Injection style.

    Onto a couple of questions, here it goes:
    1. FI2 works very fine as a Database Editor right there in unity, however when the item lists start getting too big, scrolling becomes a problem. I have successfully created a custom property editor for custom editing functionality where only one item is shown at a time with it's sub-list of objects, custom buttons that Create or Remove an item and navigate through the list (it is the same as the DatabaseEditor example found in the NGUI library if you are acquainted with this library)

    View attachment 97470

    (Edit) attached file didn't work:


    The way I went through was like this:
    - Create a CustomPropertyEditor of the desired data base type. The database inherits from BaseScriptableObject... so all the FI goodness is there by default.
    - Inside the custom property editor, I collect common property editors (int, float, string, etc) -> in static fields of the class, somehow it doesn't work calling PropertyEditor.Get(...) when stored to an instance variable in this case.
    - Via reflection, the main common types get serialized automatically, except the main list of items in the database which is only allowed one at a time. This item uses directly a ItemPropertyEditor also stored in a static variable.

    It was a bit painful, and for the future means that every new field added if it is 'special' i will need to somehow also manually get it in there, handle the region offseting, Element Height, etc. Is there a way to say 'handle all by default except this list of items' type of behavior inside the Edit function of CustomProperties? Is there a better approach to this type of database or should I approach it a different way?
    Even the current script box (by imitating the same functionality one of your classes already had).
    Maybe I should implement a SingleItemListPropertyEditor? Navigating through the current implementations they are very advanced and complex, also noticed that it is quite difficult to inherit from those implementations, most of the property editors reusable functions are hidden behind private, static, or similar functions and fields (correct me if I'm wrong, my C# level it's not quite there yet).

    Overall it's working very fine though, dictionaries, list, objects, etc... not a single problem there.

    2. This one is the important one. Hitting a wall with the serialization to disk of this .asset scriptable objects. It works half way though, problems are:
    A. The need to go through every item in the database and serialize them manually, concatenating the json string (I'm probably doing something very dumb here, I just got into Json because of the FI library depending on it) using SerializeToContent helper functions.
    - When reading, I can't seem to find an easy way to go 'node by node' on the .json file to know if it is an item list or one of the main fields of the asset parent class. So I could go field by field and serializing from content using the appropriate type.
    - Any advice on this? On previous posts on this thread you proposed using structs that contain only this data? So SerializeToContent would be mostly automatic. This struct or a helper function would handle the conversion from one to the other.

    B. Hit a huge wall with the UnityObject serialization, as it stands and as you have explained, it's just not possible... my approach currently, and that could seem to work fine, is to serialize the dataPath of the asset, on the .json it will appear as a string. I will need to make sure that the objects and files are in the 'Resources' folder, for a database sounds acceptable. Down the road if problems arise we'll see. (Any advice on this very welcome too).
    I only need a few of the asset types, mainly textures, model prefabs, and maybe sounds.

    - Approached this by custom JsonConverter, UnityObject to string path when writing, string path to the UnityObject when reading.
    This does not survive Unity's object serialization. After recompiles, closing opening unity, etc... it doesn't work.

    - The solution for now is: when in the editor I want to use the default UnityObject converter, when serializing to disk I want to switch to the string/paths based one...
    Tried by creating a ReplaceableUnityObjectConverter : UnityObjectConverter { }
    The appropiate object fields are tagged with JsonConverter attributes to use the ReplaceableUnityObjectConverter instead.
    The idea is, before serializing to disk only:
    - Change the JsonSettings (changed the private static Settings to public static of JsonNetSerializer) converters to swap the converter itself, etc.
    - Do the call to SerializeToContent<,> helper function.
    - Unswap back again.

    I'm thinking either that or finding a way to having two types of Settings, the default one for Unity serialization and another one for to disk serialization.
    I can't seem to make any of these work... The converters are not interfaces, I can't seem to be able to swap the converters by another, or maybe directly on the ReplaceableUnityObjectConverter.

    What do you think? Am I going haywire trying to do this?

    On a closing note after this crazy long post, I really thank you for the effort put on this library. I hope it pays off and big time, it was a bargain, only the collections serialization from the get go working was worth the price, everything else, like the minute customization we can do, is well, insane.

    Cheers.
     

    Attached Files:

    Last edited: Apr 27, 2014
  2. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    That's really strange. Can you post a code snippet that demonstrates the problem?

    I'm a little bit confused by the previous two paragraphs, so if the sample I give at the end doesn't solve the problem, would you mind posting a code sample?

    I'm going to be making a few of the internal classes public, but they'll still reside in the FullInspector.Internal namespace. I try to keep the things in the FullInspector namespace relatively API stable, but I don't promise anything at all for FullInspector.Internal.

    Correct me if I'm wrong, but is this serialization only happening in the editor? .asset files (which are just ScriptableObjects, correct me if I'm wrong), support the full serialization of every Unity type. Just derive from BaseScriptableObject and it'll work correctly. If you know of a way to create and serialize to .asset files during runtime, I would love to know as that would enable UnityEngine.Object serialization during runtime.

    Yea, it is easier in the limited case if you can control all references and the like. The problem is that Full Inspector has to be general and this cannot really be done in the general case without a ton of pain.

    I would just pull UnityObjectConverter out of the Full Inspector source directories and then update it to do what you want. The Json.NET serialization code should be extremely stable at this point, so just delete the converter upon FI updates and you should be good to go. Hard to say though, but if you're just serializing data in the editor for a database that's backed by a scriptable object, then it should be fine to use the normal serialization path in FI.

    Haha, thanks. I appreciate the kind words.

    Now, in regards to the single item editor I've quickly thrown together an implementation here.



    It isn't fully polished, but I wan't able to break it in my short dev/test cycle. It'll be more polished in the next update. To add it to FI now, just copy/paste the gist files into your Unity project, with SingleItemEditorAttributePropertyEditor.cs in an editor folder.

    Thanks. Let me know if you have any other questions.
     
  3. Alejandro-Martinez-Chacin

    Alejandro-Martinez-Chacin

    Joined:
    Oct 15, 2013
    Posts:
    38
    A bit of an update regarding the JsonConverter issue.
    Decided to go with something like this:

    Code (csharp):
    1. /// <summary>
    2. /// As a tag for replacing this converter.
    3. /// </summary>
    4. public class ReplaceableJsonConverter : JsonConverter
    5. {
    6.  
    7.     private static JsonConverter _currentConverter = new UnityObjectConverter();
    8.     public static JsonConverter Current
    9.     {
    10.         get { return _currentConverter; }
    11.         set { _currentConverter = value; }
    12.     }
    13.  
    14.     //private JsonConverter _defaultJsonConverter = new UnityObjectConverter();
    15.     //private UnityPathObjectConverter _pathObjectConverter = new UnityPathObjectConverter();
    16.  
    17.     public override bool CanConvert(Type objectType)
    18.     {
    19.         return _currentConverter.CanConvert(objectType);
    20.     }
    21.     public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    22.     {
    23.         return _currentConverter.ReadJson(reader, objectType, existingValue, serializer);
    24.     }
    25.     public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    26.     {
    27.         _currentConverter.WriteJson(writer, value, serializer);
    28.     }
    29. }
    the _currentConverter had to put it static variable and accessed the uncomfortable way ReplaceableJsonConverter.Current... couldn't find a way to get access to current custom converters of the current json serialization context/settings/etc... is there a way to get access to them? That way i could modify the converter by instance.
    When using SerializeToContent, I just swap the Current implementation for a string based one.

    To be able to keep the asset or gameobject to date -> asset bundles, WWW downloads, StreamingAssets and more will do.
    It could be possible to do a OnlinePathConverter, where instead of writing to a local path it could just point to an online URL where the object will be found and downloaded.

    It would be nice to be able to tag a field with attributes and to use a specific json converter for an specific action, or just try to collect them manually by using other custom attributes that the ReplaceableJsonConverter will look for per field (a dictionary of implementations or similar).

    Overall, it looks like the final solution will actually be having all serializable types, data, objects and more in a separate data type as you proposed several replies before. If we want to profit from protobuf (both in loading time and sizes) it's needed anyways.
     
  4. vToMy

    vToMy

    Joined:
    Apr 19, 2013
    Posts:
    22
    Hey, looks like an awesome package I'm about to purchase :)
    But before that I some questions...
    Currently unity limits nesting MonoBehaviours (i.e. you can't have Monobehaviour as a member of a class inheriting MonoBehaviour).
    Since your inspector offers inheritance of any type, I wondered if it helps overcoming this problem?
    The main issue I see is injecting the gameobject to these nested MonoBehaviours.

    Another question, although smaller and less related, but since this is called "Full Inspector" I wondered how difficult would it be to add a "button" property drawer. From my impression Unity only supports buttons in editors, and I haven't been able to find a solution.

    Also, since I'm a developer myself, if you don't have the time for these features I'll be happy to hear if it's something I could probably work myself given the source code of the package.
     
  5. Alejandro-Martinez-Chacin

    Alejandro-Martinez-Chacin

    Joined:
    Oct 15, 2013
    Posts:
    38
    Previous post of mine happened at the same time as yours : p, didn't see your answer before posting.

    Code (csharp):
    1.  
    2. [CustomPropertyEditor(typeof(ItemDatabase))]
    3. public class ItemDatabaseEditor :
    4.     PropertyEditor<ItemDatabase>
    5. {
    6.     private static readonly IPropertyEditor _itemListEditor = PropertyEditor.Get(typeof(IList<ItemBase>), typeof(IList<ItemBase>));
    7.     private static readonly IPropertyEditor _itemBaseEditor = PropertyEditor.Get(typeof(ItemBase), typeof(ItemBase));
    8.     private static readonly IPropertyEditor _intEditor = new IntPropertyEditor();
    9.     private static readonly IPropertyEditor _floatEditor = new FloatPropertyEditor();
    10.     private static readonly IPropertyEditor _stringEditor = new StringPropertyEditor();
    11.     private static readonly IPropertyEditor _IConnectionEditor = PropertyEditor.Get(typeof(IConnection), typeof(IConnection));
    12.  
    13.     public override ItemDatabase Edit(Rect region, GUIContent label, ItemDatabase element)
    14.     { ... }  //HUGE ONE: because I'm going field by field manually on this parent class, this is the one that have the List<itemBase>
    15.    
    16.     (...)
    17.  
    18. }
    19.  
    Edit: PropertyEditors instances need to be marked static, tried to get them inside the same Edit function or in a local non-static variable and it would throw an error.
    Not a big deal at all...

    I think this actually solves the problem.
    That was lightning fast, thanks a lot, will try your solution right away.
    That way I don't have to try to write too fine-grained editors for each type of class... singleItemEditing attribute seems the way to go.
    It also teaches how to go for other custom ones too.

    Got it and seconded.
    I think I just needs to think more throughly what I want to do... I'm starting to hack away, badly : p.


    Yes, this is happening in the Editor but also to cloud and disk databases, so later on it is possible to modify values, asset's links, etc from serialized files.
    The Editor serialization works perfectly fine. It's just when I want to create a string based out of that for out of Unity manipulation.

    Yeah, you are right, maybe trying both ScriptableObject backed database and trying to find an equivalent text based (from the text based one it could be possible to reconstruct the scriptable object) is probably self defeating.

    The idea was this, an item would have:
    Code (csharp):
    1.  
    2. Item
    3. {
    4.    Model model;
    5.    Texture t;
    6.    Prefab anotherUnityObject;
    7.    Color c;
    8. }
    9.  
    But it would serialize to:
    Code (csharp):
    1.  
    2. Item
    3. {
    4.   "model": "(a path here)"
    5.   "t": "(a path here)"
    6.   "anotherUnityObject": "(yet another path)"
    7.   c: { r: g: b: a: }  // this one will serialize automatically perfectly fine.
    8. }
    9.  
    To update the actual unity object, the path would take care of it. The path can be a URL path too to use with asset bundles, or use directly bundled resources with Reasource.Load, etc.

    It will only work for one layer, as soon as we have an object inside an object that is too complex, then it will only get the top object path.
    That's why i plan to keep it for textures, models, sounds... if it is a full Gameobject with other gameobjects inside of gameobjects, then, the top one will be only a path and all the innards will have to be updated via assetbundles i guess.
    Or they could just read from separate files (i.e. config/data files).
    Well, that's kinda the idea for the time being anyways...

    Maybe it would be enough to do a CustomPropertyEditor to treat string paths where it would show the unityobject but when resolved it resolves to a path, so it can get serialized. (And forget about scriptableObjects altogether).


    Once again, thanks a lot for your soon reply and working implementation!
    Will test that.
     
    Last edited: Apr 27, 2014
  6. mcmorry

    mcmorry

    Joined:
    Dec 2, 2012
    Posts:
    579
    I'm using FI2 from few days and it definitely improved a lot my workflow inside Unity.
    Great job!

    I'm wondering if you could implement export/import actions in the context menu, to store and restore the data in a file (eg. json). This would allow me to create a backup before proceeding with some changes, or to easily reimport some data in a new scene.
    Please let me know if there is already a different way to do it.

    Thanks
     
  7. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    I don't think I understand what you mean. Can you post a code sample?

    Code (csharp):
    1.  
    2. using FullInspector;
    3.  
    4. public class NestedBehavior : BaseBehavior {
    5.     public MonoBehaviour Nested;
    6. }
    7.  
    If that is what you mean, then yes, this works as expected in Full Inspector, but it also works without FI.

    Yep, this is coming. I've published the internal ticket to a GitHub ticket here. You can currently do this (it's in one of the samples), but it isn't as clean as I would like it.
     
  8. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    That's strange. I'll investigate and see if it's a bug that needs fixing.

    Ah, ok. I would be wary of using a BaseBehavior as your serialization format though -- might limit your ability to make changes to the object behavior down the line as you'll break streaming for older clients and the like.

    Clever approach though for updating the asset links. Hopefully it'll work out well.

    If you run into any big API troubles while doing this, don't hesitate to post here / github / email me and I'll see if I can expose any additional APIs / refactor something to make it easier if the makes FI more useful/powerful.
     
  9. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    Fantastic! Makes me happy to hear that.


    I think I can implement a backup/restore solution, though it'll only be available in the editor (as it will be based off of ScriptableObjects). This system should extend itself pretty naturally to saving while in play-mode, too.

    You can track progress via the issue here.

    Thanks.
     
  10. mcmorry

    mcmorry

    Joined:
    Dec 2, 2012
    Posts:
    579
    Thats great. I'm mainly focus on editor side.

    In play mode is not already enough to use your BehaviorSerializationHelpers? Or maybe I'm missing something.
    EDIT: Sorry I mean SerializationHelpers as you describe here
     
    Last edited: Apr 29, 2014
  11. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    Ah, I should probably explain further / more clearly. By saving while in play-mode, what I meant to say is that if you make a change to a behavior that inherits from BaseBehavior while the editor is in play mode, then a button will show up in the inspector that says "Keep changes after play mode", ie, the tweaks that you make while playing will be saved after you quit play mode.

    Though, no promises on the feature -- I haven't implemented it yet, but I think I know a way to get it to work. It will piggy-back on the same mechanisms as the backup system.

    Thanks.
     
  12. Alejandro-Martinez-Chacin

    Alejandro-Martinez-Chacin

    Joined:
    Oct 15, 2013
    Posts:
    38
    Wanted to say that this works perfectly fine and is exactly what I needed.
    As a base example it also helps me to make separate editors, not like I solved the problem by hardcoding a single editor for a full class...

    Thanks a lot.

    (Edit: it's great how it behaves correctly with nested situations, an object field tagged SingleItem attribute that inside also have more SingleItem attributes on fields)
     
    Last edited: Apr 30, 2014
  13. mcmorry

    mcmorry

    Joined:
    Dec 2, 2012
    Posts:
    579
    Oh you are right. I confused play-mode with runtime.

    Anyway I have an issue that took me a while before to understand what was going on.
    In play-mode, only if I have a gameobject selected in the inspector that use a BaseBehaivor, its values are restored continuously every frame!
    I was decreasing a value on a key press, but it was always to the maximum. If I unselect the gameobject from the hierarchy everything work fine. But as soon as I select it again it gets reseted. So I can't inspect the values.

    How should I tell FI2 to don't do restore the values?

    Thanks
     
  14. vToMy

    vToMy

    Joined:
    Apr 19, 2013
    Posts:
    22
    Yes, it is possible, only you can't edit the "Nested" monobehaviour from the same script, you have to attach it separately to another game object (or the same) and drag it to "NestedBehaviour".
    What I envision is something like this:

    Code (csharp):
    1.  
    2. public class InnerMono : MonoBehaviour
    3. {
    4.     public string Foo;
    5. }
    6.  
    7. public class OuterMono : MonoBehaviour
    8. {
    9.     public InnerMono Inner;
    10. }
    11.  
    And in the inspector I'll be able to just attach OuterMono and edit Foo without having to attach InnerMono separately.
    Of course, "Inner" won't be a functional mono behaviour by itself (its start/update/whatever methods won't be automatically called). The idea is that I'll be able to have uncoupled Monobehaviour components, and some "super monobehaviours", that act as a composite design pattern, and contains and manages smaller components. This way I have separate independent small components, but if I want to use a lot of them, I could just use the composite one.

    Hopefully it's more clear now?
    I'm not even sure it's possible, since this might not just be an inspector issue, but a Unity one. In any case I'll be happy to hear you taking on this.
     
    Last edited: Apr 30, 2014
  15. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    That's a serious bug. Can you verify that you're running on the latest version (2.2.1 at the moment)? This sounds like an issue I fixed on an earlier release (see the changelog for 2.2, item "Fix issue where inspected object would be deserialized during gameplay from old data").

    Otherwise, can you give reproduction instructions? I've put some logging statements into the core deserialization logic and they don't report being called more than once, even when inspecting an object.
     
  16. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    Hmm, this might create a case for hooks into the serialization system, but that would be down the road after further investigation. However, for now, how about something like this?


    Code (csharp):
    1.  
    2. using FullInspector;
    3.  
    4. public class MySmallBehavior1 : BaseBehavior {
    5. }
    6.  
    Code (csharp):
    1.  
    2. using FullInspector;
    3.  
    4. public class MySmallBehavior2 : BaseBehavior {
    5. }
    6.  
    Code (csharp):
    1.  
    2. using FullInspector;
    3. using UnityEngine;
    4.  
    5. public class BigBehavior : BaseBehavior {
    6.     public MySmallBehavior1 Behavior1;
    7.     public MySmallBehavior2 Behavior2;
    8.  
    9.     private T GetOrAddComponent<T>() where T : Component {
    10.         T component = GetComponent<T>();
    11.         if (component == null) {
    12.             component = gameObject.AddComponent<T>();
    13.         }
    14.         return component;
    15.     }
    16.  
    17.     protected void Reset() {
    18.         Behavior1 = GetOrAddComponent<MySmallBehavior1>();
    19.         Behavior2 = GetOrAddComponent<MySmallBehavior2>();
    20.     }
    21. }
    22.  
    Then when you add BigBehavior to a component, it will automatically also add MySmallBehavior1 and MySmallBehavior2 if they are not already added. You can add GetOrAddComponent to a base type that you derive from (or make it an extension method) to make the boilerplate pretty minimal.

    You don't actually need FI for this scenario -- it'll work with MonoBehavior based derivation too.

    However, the limitation here is that all objects will be treated as full MonoBehaviors and they will all receive callbacks. Avoiding the callbacks would likely require a completely custom component system, which is something I wouldn't particularity advise as it will require a *lot* of work to get something that is as easy to use as Unity's.
     
    Last edited: May 1, 2014
  17. vToMy

    vToMy

    Joined:
    Apr 19, 2013
    Posts:
    22
    I actually want callbacks, otherwise I wouldn't want them to be MonoBehaviours.
    But the issue here is that I want to small components to appear inside the big component in the editor, since there are many of them, and I would like them to appear in the same hierarchy as defined (not just flat components over the game object, as this will not scale well).
    Anyway, I think I'll manage even without that :)
     
  18. mcmorry

    mcmorry

    Joined:
    Dec 2, 2012
    Posts:
    579
    Yes is the last one. TO double check I also downloaded and imported again.

    I have a object with a BaseBehavior component.
    This component expose a property:
    Code (csharp):
    1.  
    2. public float NosLeft {
    3.     get;
    4.     set;
    5. }
    6.  
    Somewhere else in my project, inside a Coroutine I execute:
    Code (csharp):
    1.  
    2. while(entity.NosLeft > 0) {
    3.     entity.NosLeft -= Time.deltaTime;
    4.     yield return null;
    5. }
    6.  
    where entity is the reference to the BaseBehavior component
     
  19. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    Ok, I've got a fix for this in the next release. The issue isn't necessarily a bug but more of undesired behavior. Serialization will no longer occur for the inspected object while in play mode.

    If you need this fix now, I can either give you a basic diff that you can apply (it's a two line change) or package up a custom build based off of the latest master build. Let me know.

    Thanks.
     
  20. mcmorry

    mcmorry

    Joined:
    Dec 2, 2012
    Posts:
    579
    If you are going to release the new version next week I can wait. Otherwise please send me the diff.
    Thank you
     
  21. EmeralLotus

    EmeralLotus

    Joined:
    Aug 10, 2012
    Posts:
    1,317
  22. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    Hey mcmorry, please check your inbox. I'll be sending the build through there.

    Thanks
     
  23. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    Interesting video. I actually use a similar strategy (except with strings instead of byte arrays) within FI. Additionally, FI stores the metadata on the actual object itself instead of proxying it through an explicit storage container.
     
  24. Alejandro-Martinez-Chacin

    Alejandro-Martinez-Chacin

    Joined:
    Oct 15, 2013
    Posts:
    38
    Hi again!
    Thanks as usual for all the effort and keeping in touch through here.

    Got a couple of questions:
    #1: What is the way (if there is), to "extend" from a default editor? Most of the time the automatically generated property editors for the MonoBehaviors and ScriptableObjects are good enough. i.e:

    Code (csharp):
    1.  
    2. [ExecuteInEditMode]
    3. [AddComponentMenu("TGT/Item Database")]
    4. public class ItemDatabaseObject : BaseScriptableObject
    5. {
    6.  
    7.     #region Fields
    8.     private bool myFields;
    9.  
    10.     #endregion
    11.  
    12.     public ItemDatabase db;
    13.  
    14.     void OnEnable() { ... }
    15.  
    16.     void OnDisable() { ... }
    17. }
    18.  
    How can I use the editor that is generated automatically and use that inside another CustomEditor? I was looking at the help files and <TDerived, T> of the IList<> editor example but honestly don't know if that's even what I should be looking for.
    Something along the lines of:
    Code (csharp):
    1.  
    2. CustomPropertyEditor(typeof(ItemDatabaseObject))]
    3. public class ItemDatabaseObjectEditor :
    4.     PropertyEditor<ItemDatabaseObject>
    5. {
    6.     // Want to have the basic editor that would be generated if no CustomEditor were defined.
    7.     private static readonly IPropertyEditor _baseEditor = PropertyEditor.Get(typeof(ItemDatabaseObject), null);
    8.  
    9.     public override ItemDatabaseObject Edit(Rect region, GUIContent label, ItemDatabaseObject element)
    10.     {
    11.            // This would take care of everything ScriptableObject related. Fields, Properties,
    12.            // even the FullInspectorEditorUtils.TryGetMonoScript(element, out monoScript).
    13.            element = _baseEditor.Edit(region, label, element) as ItemDatabaseObject;
    14.  
    15.            // Do some custom actions over 'element' or 'element fields'. Like erase all, generate randomly, action buttons, etc.
    16.            // Some code here.
    17.  
    18.            return element;
    19.     }
    20. }
    21.  
    #2: Is there a way to force a ScriptableObject to be saved? Or to mark a field as Dirty so it gets updated? If I modify a property out of the usual propertyEditor.Edit way it doesn't work... on the same example:

    Code (csharp):
    1.  
    2.  
    3. private static readonly _itemDatabaseEditor = PropertyEditor.Get(typeof(ItemDatabase), null);
    4.  
    5. public override ItemDatabaseObject Edit(Rect region, GUIContent label, ItemDatabaseObject element)
    6. {
    7.            // ItemDatabaseObject has a ItemDatabase field
    8.            // ItemDatabase field has a List<Items> field.
    9.            // All that gets handled by:
    10.            element.db = _itemDatabaseEditor.Edit(region, label, element.db);
    11.            // All changes via the editor will be updated and serialized accordingly.
    12.  
    13.            // Suppose all items have a name property:
    14.            // (Checks omitted).
    15.            element.db.itemsList[1].Name = "RANDOM NAME";    // This will change in the view.
    16.            // How to mark that item, or the list, or the whole database dirty so the changes are taken into account?
    17.            // After closing and opening Unity or changing some code so it needs to recompile, itemList[1] won't have "RANDOM NAME",
    18.            // it will have any previous name it had before forcing "RANDOM NAME".
    19.            // Unless, a change is made via the editor clicking on the Name field of the item.
    20.  
    21.            return element;
    22.     }
    23.  
    24.  
    #3 (bonus question): Probably related to the 'non-robustness' of the SingleItemAttributePropertyEditor that you kindly showed. But it looks like using other attributes like "Margin", "Tooltip", "Comment" etc on a property that already has a [SingleItemEditor] attribute. won't play nicely with it. Specially if another field has other attributes like this.

    Cheers.
     
  25. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    When you fetch the property editor, you just have to tell FI to ignore the one you're fetching from. This is somewhat sketchy for maintaince, and I'd like to change the system, but it's worked well thus far.

    So, you need to change

    Code (csharp):
    1.  
    2.     private static readonly IPropertyEditor _baseEditor = PropertyEditor.Get(typeof(ItemDatabaseObject), null);
    3.  
    to

    Code (csharp):
    1.  
    2.     private static readonly IPropertyEditor _baseEditor = PropertyEditor.Get(typeof(ItemDatabaseObject), null, new Type[] { typeof(ItemDatabaseObjectEditor) });
    3.  
    It looks like the editor you want is the ReflectedPropertyEditor, which should be the next one returned. If it isn't, then just keep adding the editors to the ignore list until you get the ReflectedPropertyEditor.

    W.r.t to the IList property editor, I would recommend looking at the AbstractTypePropertyEditor instead for an example of this, as it has to fetch a sub editor that isn't the AbstractTypePropertyEditor. The IList property editor is using one of the cool FI features where the property editor can request the actual type of the edited type if it's using inheritance, so if the IList editor is used on a List<T> then TDerived is typeof(List<T>), similarly, if the IList editor is used on a LinkedList<T>, then TDerived is typeof(LinkedList<T>).

    Setting GUI.changed to true should do the trick. Let me know if this doesn't work, though.

    So there are two settings an attribute property editor can operate on - replace mode or "append" mode. In replace mode, the attribute editor completely replaces all other attributes and the actual property editor itself, allowing for total customization of the editor at the call site. The append mode allows for partial customization while still relying on the typical inspector machinery.

    The SingleItemAttributePropertyEditor uses the total replacement mode, so the typical machinery that allows for comments, etc, to be applied is overridden. The replaced editor probably doesn't need to also replace other attribute editors, so I've created an issue here for this.

    Thanks. Let me know if there is anything else I can do.
     
  26. Alejandro-Martinez-Chacin

    Alejandro-Martinez-Chacin

    Joined:
    Oct 15, 2013
    Posts:
    38
    This worked.
    Heads up, it cannot be as a private static readonly field though, it will throw an error that it couldn't get any property editor of type "ItemDatabaseObject", with banned editors "ItemDatabaseObjectEditor".
    Inserted it inside an if (editor == null) editor = PropertyEditor.Get(...);

    It was indeed ReflectedPropertyType the one that worked.

    It's works!
    It did need some hack though, I was doing GUI.changed = true as part of an anonymous function that executes when a button is pressed... however this doesn't really work. I had to:
    Code (csharp):
    1.  
    2.         if (GUI.Button(region.Scale(new Vector2(0.5f, 1.0f)), "Select Icon Name For Item (...)", "DropDown"))
    3.         {
    4.             SpriteSelector.Show
    5.                 (
    6.                     (s) =>
    7.                     {
    8.                         item.iconName = s;
    9.                         spriteSelected = true;
    10.                     }
    11.                 );
    12.         }
    13.  
    14.         if (spriteSelected)
    15.         {
    16.             spriteSelected = false;
    17.             GUI.changed = true;
    18.         }
    19.  
    Awesome. Hopefully it will work.

    Thanks a lot for the solutions.
     
  27. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    It's throwing that error because when you make a request for the next editor, the construction process for the ItemDatabaseObjectEditor hasn't finished yet, which means that the rest of the editors for ItemDatabaseObject haven't been allocated. When Get() goes to fetch the next editor, it notices there are no more editors (since they haven't been constructed) and gives you that error.

    I'm introducing a new abstraction (PropertyEditorChain) that will hopefully remove this issue, but construction order is difficult -- constructing one property editor invokes a request for another one, but that one hasn't been initialized / constructed yet. FI makes sure to return a null editor instead of entering an infinite recursion for these type of editor creation graphs.

    I've also fixed the attribute override issue.

    If you'd like the current master build (which also has a better SingleItemEditor), just let me know and I'll send you a PM with it.

    Thanks.
     
  28. Alejandro-Martinez-Chacin

    Alejandro-Martinez-Chacin

    Joined:
    Oct 15, 2013
    Posts:
    38
    Ah, yes, totally understandable.
    Well, I really don't mind finding the proper editor at 'run-time' (i.e. when the editor actually starts executing).

    Awesome! Thanks a lot.

    I don't mind waiting for the next release, if it is not that much of a deal for you either it would be cool too to receive the build.
    Either way, I'm not in a rush at all, everything is working good enough for the time being.
     
  29. vToMy

    vToMy

    Joined:
    Apr 19, 2013
    Posts:
    22
    Hey, just bought the package and getting two compilation errors:

    Assets/FullInspector2/Libraries/Rotorz/Editor/Internal/RotorzGUIHelper.cs(23,56): error CS0117: `Delegate' does not contain a definition for `CreateDelegate'
    Assets/FullInspector2/Libraries/Rotorz/Editor/Internal/RotorzGUIHelper.cs(30,59): error CS0117: `Delegate' does not contain a definition for `CreateDelegate'

    Upon inspection the Delegate class doesn't have a CreateDelegate method at all.
    Help? :(
     
  30. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    That's really strange. What build platform and build settings are you targeting?

    Edit: Regardless, I've created a patch that removes the Delegate.CreateDelegate methods here. To apply it, simply replace FullInspector2\Libraries\Rotorz\Editor\Internal\RotorzGUIHelper.cs with that file.
     
    Last edited: May 10, 2014
  31. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    I'll send you the build -- but how should I contact you? It looks like you don't have PMs enabled and I don't know your email. Feel free to send me a PM with either or.

    Thanks.
     
  32. vToMy

    vToMy

    Joined:
    Apr 19, 2013
    Posts:
    22
    Thanks, works :)
    I have two issues working with it so far:
    1) I have a layer field which I initialize in Reset() to the "Player" layer, yet another layer gets selected. This is weird since it worked before.
    2) I have an abstract class in my BaseBehaviour that I set to a concrete type in the inspector. However it gets initialized to null every time the code recompiles. Why is that?
     
  33. leonardoraele

    leonardoraele

    Joined:
    Jan 25, 2014
    Posts:
    3
    This asset is not working. When I import the package, a compile error happens.

    $imagem.png
     
  34. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    I believe the first issue is this. The fix will be included in the next update, but to get it now you can replace FullInspector2\Core\Editor\PropertyEditors\Common\LayerMaskEditor.cs with this file.

    I have a feeling that your abstract type is not serializing properly -- please go into FullInspector2\Core\FullInspectorSettings.cs and change EmitWarnings to true. Let me know if that gives you an error for serialization; if it doesn't, would you mind posting a code sample so I can reproduce the issue? Regarding the setting, 2.2 was slightly too aggressive with disabling warnings -- 2.3 will emit serialization error messages.

    Thanks.
     
  35. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    Is your build preference currently set to WebPlayer? If that's the case, then here is the relevant section in the docs.

    Essentially, you have a few options depending on your chosen serializer:

    1) If you use BinaryFormatter, you're good to go.
    2) If you use Json.NET (which is what Full Inspector uses by default), you'll have to purchase Json.NET for Unity
    3) If you use protobuf-net, the story gets complicated -- see the docs but feel free to ask me more questions.

    It's worth nothing that I plan to add JsonFx support either in 2.3 or 2.4 (likely 2.4 -- 2.3 is going through testing at the moment); see this issue. JsonFx should be painless on the varying platforms, but I haven't tested anything as of yet, so I can't make any promises.

    Let me know if your build setting is not the issue.

    Thanks.

    edit: Forgot to mention that to actually remove the error, you need to get rid of the the Json.Net serializer, "FullInspector2\Serializers\JsonNet\DLLs\Newtonsoft.Json.dll" in particular.
     
    Last edited: May 12, 2014
  36. leonardoraele

    leonardoraele

    Joined:
    Jan 25, 2014
    Posts:
    3
    Yes! When I changed the target build, it worked, thank you. I will read these docs about serialization.
     
  37. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    I've just submitted 2.3 to the asset store, so it'll hopefully be available by the end of the week.

    One of the nifty new features is the static inspector, which allows you to easily inspect the static variables in your program:

    $static_inspector.gif

    There are also other cool things, like co/contravariant SerializedAction/SerializedFunc support (which are fully type safe, up to 9 generic parameters), first class support for buttons (via [InspectorButton]), and overall more polish.

    There are also now an easy to use API reference (generated via doxygen) here.

    Here's the full changelog for 2.3:
    If you would like the build now, just let me know and I'll be happy to send it to you.

    Thanks.
     
  38. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    That was quick -- update 2.3 has been accepted to the asset store and is now live!

    Make sure to check out the static inspector (Window/Full Inspector/Static Inspector) as well as SerializedAction / SerializedFunc.
     
  39. rcalt2vt

    rcalt2vt

    Joined:
    Jun 6, 2009
    Posts:
    36
    Grats on another solid release
     
  40. mcmorry

    mcmorry

    Joined:
    Dec 2, 2012
    Posts:
    579
    Thank you for this update. Could you please update the guide to explain how to use this new feature? You say that is extremely powerful, so I'd like to know more about.

    Thanks.
     
  41. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    I'll work on getting the guide updated, but for the moment I'll explain here.

    If you've ever used Action<> or Func<> in .NET, then you'll immediately be familiar with SerializedAction and SerializedFunc.

    For example, here's how you would use Action

    Code (csharp):
    1.  
    2. public class MyBehavior : BaseBehavior {
    3.     public Action<int> MyAction;
    4.  
    5.     public void DoSomethingWithInt(int) {}
    6. }
    7.  
    and then add MyBehavior to a GameObject, select the MyBehavior for the MyAction target, and then you can select DoSomethingWithInt from the method dropdown.

    If you want to use MyAction, it's easy.

    Code (csharp):
    1.  
    2. void Update() {
    3.     if (MyAction.CanInvoke)
    4.         MyAction.Invoke(3);
    5. }
    6.  
    and that will call whatever method you assigned to it in the inspector.

    There is a similar story for Func, except that it supports returning a value.

    FI's Action and Func implementations are novel (from what I've seen of similar things) for two reasons:

    • They are fully generic and type safe. For example, if you have `Action<int> action` then calling `action.Invoke()` is a compile error, because you're missing the int parameter.
    • They support contra and covariance. If you're not familiar with these terms, then think that it works correctly with inheritance. For example, if you have `Func<BaseBehavior> func`, a type `MyBehavior MyFunc()` will be assignable to `func`. Notice that the return types are different.
     
  42. mcmorry

    mcmorry

    Joined:
    Dec 2, 2012
    Posts:
    579
    Great! This was the part that I was completely missing. I was not imaging a selection of methods :D

    Very interesting. An alternative to enums and switch to choose the action to be executed.
     
  43. vToMy

    vToMy

    Joined:
    Apr 19, 2013
    Posts:
    22
    1) The fix for the layer mask doesn't work...
    2) I changed EmitWarnings to true but got no warnings. I think I pinpointed the problem though. If I pick the values in the inspector they are persisted correctly. However I have some scripts that initialize default values in the Reset method. When I reset a script it gets the correct values in the inspector, but whenever I compile the code again it just returns to null. It only happens to my abstract classes though (I assign the concrete classes in the Reset method). Any ideas?
    3) Also, new version contains compilation errors:
    Code (csharp):
    1. Assets/FullInspector2/Core/SerializedPropertySet.cs(123,36): error CS0246: The type or namespace name `PropertyMetadata' could not be found. Are you missing a using directive or an assembly reference?
     
    Last edited: May 15, 2014
  44. Alejandro-Martinez-Chacin

    Alejandro-Martinez-Chacin

    Joined:
    Oct 15, 2013
    Posts:
    38
    You are right, didn't even know of all those settings. Set up correctly for the future.
    Got the build already : )

    My thanks also for this awesome new version.
    Got it to work with very very few minor changes (did a mess before while trying to get to know it the first time).

    Notes:
    - The newer single item editor rocks even more. The slider is a nice addition that I never thought off. Add button appearing green and delete being a red X is visually welcome. This is way more what I had in mind for an user friendly database editing.
    - PropertyEditorChain -> awesome, briefly looking at them, for me does exactly the same, just more clear syntax ('SkipUntilNot').
    - ObjectMetadata<> (before ObjectInstanceMap) it's nice that it's now outside separated. I hesitated to take it out because breaking future updates changes and ended up using a similar implementation (http://unitygems.com/weak-associations-leak-free-extension-properties/). Difference is: instead of doing ObjectMetadata<ItemMetadata>.Get(list), you would do list.Get<ItemMetadata>(). However, I see that you use an ObjectIDGenerator. I'm curious now if there is any real benefit from one to the other (supposing none of them leak and/or cause other side effects).
    - [InspectorButtons] kudos!
    - I'm afraid I don't get correctly [InspectorHidePrimary], basically we would use a dummy field and put coments, tooltips, margins, and others right over the 'HiddenPrimary'?
    - StaticInspector... that has to be a huge selling point now : )
    - As mcmorry said before, I think I'm missing also potential there of the serialized delegates... but I didn't come to the same realization. In the MyBehavior.MyAction example, MyBehavior could have lots of functions that do something with an int (or more derived/less derived thanks to co/contravariance)? like DoIntCloud(int n); DoIntAI(int n); DoIntKeyboard(int n); then any those functions could be selected from the dropdown (?), if it were a list then adding them one after the other?. This could change quite a bit some ways to handle logic.

    Thanks!
    "A very happy customer"
     
  45. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    Can you update to 2.3 and reconfirm it doesn't work? Would you mind giving me a code sample? I just tried again and cannot reproduce the issue.

    Here's what I did:

    Code (csharp):
    1.  
    2. public class LayerMaskTests : MonoBehaviour {
    3.     public LayerMask LayerMask;
    4. }
    5.  
    Set the LayerMask to a TransparentFx, Water, and then also Layer 12.

    Code (csharp):
    1.  
    2. public class LayerMaskTests : BaseBehavior  {
    3.     public LayerMask LayerMask;
    4. }
    5.  
    Verified that the FI inspector shows the same layers, and removed Layer 12 but added Layer 11, Layer 13.

    Changed the derived type back to the MonoBehaviour, verified that the proper layers are being shown.

    Can you give me reproduction instructions?

    Ah, the reset must not be triggering a change check in the inspector. For now, just add in a SaveState() call after Reset(). So, for example,

    Code (csharp):
    1.  
    2. public class MyBehavior : BaseBehavior {
    3.     public void Reset() {
    4.       RestoreState(); // ensure the object is deserialized -- happens automatically when you inspect it
    5.       /* do something interesting */
    6.       SaveState(); // serialize changes -- happens automatically when the inspector has detected a GUI change
    7.     }
    8. }
    9.  
    Assuming that Unity 4.5 has serialization callbacks, SaveState() and RestoreState() will no longer be necessary.

    Please do a clean import. SerializedPropertySet has been removed from the project (migrated into InspectedType, and PropertyMetadata was renamed to InspectedProperty).

    Thanks.
     
  46. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    I use ObjectIDGenerator because it uses the default .NET hash code generator, which seems to be more stable. If the dictionary based lookup works, then great.

    ObjectMetadata should probably use a WeakDictionary, but I'm not too worried about it as it's cleared everytime a recompile happens (I don't recall atm, but it might also be cleared per play mode entry -- Unity serialization can get wonky).

    Yep, exactly. I've found it pretty useful in a few of the sample behaviors. For example, to just show a comment in the inspector

    Code (csharp):
    1.  
    2. public class MyBehavior : BaseBehaviour {
    3.     [Comment(CommentType.Info, "Hello!")]
    4.     [InspectorHidePrimary]
    5.     [ShowInInspector]
    6.     private int _inspectorComment;
    7. }
    8.  
    (worth nothing: my goal is to migrate Comment to InspectorComment at some point, with a deprecation cycle).

    Ha, I hope. I've already found it useful a number of times now.

    Ah, you're right. It's worthwhile adding a SerializedMulticastFunc/Action too.

    Thanks.
     
  47. vToMy

    vToMy

    Joined:
    Apr 19, 2013
    Posts:
    22
    Thanks for the help!
    Is the RestoreState really necessary in the beginning? In any case, I would advise adding this to the documentation. I read about SaveState and such, but didn't relate it to the Reset method. Since it's a common place to initialize values I would recommend documenting it.

    About the LayerMask - I set its value in the Reset method like this:
    Code (csharp):
    1.  
    2. public class TestBehavior : BaseBehavior {
    3.     public LayerMask TestLayer;
    4.  
    5.     public void Reset() {
    6.         RestoreState();
    7.         //NOTE: I have added my own custom "Player" layer as a custom 8th layer.
    8.         TestLayer = LayerMask.NameToLayer("Water"); //Produces "Raycast Ignore" layer in the inspector (which is the one above "Water").
    9.         SaveState();
    10.     }
    11. }
    12.  
     
  48. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    If you're completely resetting every value within the component, then RestoreState() is unnecessary. However, if there is some data that you want to modify the existing structure of, then you need to call RestoreState().

    I've tried to call out the Restore/Save state requirement in this section (under "Danger"). When I go through another doc update cycle, I'll try to make it more explicit -- hopefully 4.5 lands before then though.

    Ah, I see. If you try to derive from MonoBehaviour, you'll find the exact same behavior being reproduced -- The way that LayerMask works is that layer 0 maps to bit 0 being set, layer 1 to bit 1 being set, and so forth. NameToLayer returns the bit layer that that layer maps to. So to activate the layer, you need to set the nth layer bit -- or, stated in code:

    Code (csharp):
    1.  
    2. TestLayer = 1 << LayerMask.NameToLayer("Water");
    3.  
    To use multiple layers, you just add the bitshifted values together

    Code (csharp):
    1.  
    2. // note the parenthesis: they are required due to order of operations
    3. TestLayer =
    4.     (1 << LayerMask.NameToLayer("Water")) +
    5.     (1 << LayerMask.NameToLayer("TransparentFX"));
    6.  
    Hope that helps.

    Thanks
     
  49. vToMy

    vToMy

    Joined:
    Apr 19, 2013
    Posts:
    22
    I'm sorry, I tried to simplify the problem and ended up just messing it up.
    You are correct that this works:
    Code (csharp):
    1. (1 << LayerMask.NameToLayer("Water"))
    However what I really wanted was everything EXCEPT water:
    Code (csharp):
    1. ~(1 << LayerMask.NameToLayer("Water")) //Works on MonoBehaviour, breaks in BaseBehaviour.
     
  50. sient

    sient

    Joined:
    Aug 9, 2013
    Posts:
    601
    Sorry about the delay -- the fix ended up being really simple. I can send you a custom build with it, or you can go into LayerMaskEditor (FullInspector2\Modules\Common\Editor\LayerMaskEditor.cs) and change line 104 from

    Code (csharp):
    1.  
    2. bool isSet = value % 2 == 1;
    3.  
    to

    Code (csharp):
    1.  
    2. bool isSet = Math.Abs(value % 2) == 1;
    3.  
    Thanks.