Search Unity

Missing documentation for Scriptable Singleton

Discussion in 'Documentation' started by Perun, Jan 22, 2015.

  1. Perun

    Perun

    Joined:
    May 23, 2014
    Posts:
    6
    Hi,
    I recently noticed class ScriptableSingleton in UnityEditor assembly, however I have not found any references for it. Even google does not know about it.
    Assembly meta data reads

    #region Assembly UnityEditor.dll, v2.0.50727
    // xxx \BSIM\Library\UnityAssemblies\UnityEditor.dll
    #endregion

    using System;
    using UnityEngine;

    namespace UnityEditor {
    public class ScriptableSingleton<T> : ScriptableObject where T : global::UnityEngine.ScriptableObject {
    protected ScriptableSingleton();

    public static T instance { get; }

    protected virtual void Save(bool saveAsText);
    }
    }

    So I simply used it this way:

    public class SourcePrefabFactory:ScriptableObject {
    public static SourcePrefabFactory instance {
    get { return ScriptableSingleton<SourcePrefabFactory>.instance; }
    }


    }

    I would expect property instance to take care of instantiating and maintaining my Scriptable object - however it keeps raising:

    AssertionException: Expected: True
    But was: False

    NUnit.Framework.Assert.That (System.Object actual, IResolveConstraint expression, System.String message, System.Object[] args)
    NUnit.Framework.Assert.That (Boolean condition)
    UnityEditor.ScriptableSingleton`1[SourcePrefabFactory].CreateAndLoad () (at C:/BuildAgent/work/d63dfc6385190b60/Editor/Mono/ScriptableSingleton.cs:86)
    UnityEditor.ScriptableSingleton`1[SourcePrefabFactory].get_instance () (at C:/BuildAgent/work/d63dfc6385190b60/Editor/Mono/ScriptableSingleton.cs:46)
    SourcePrefabFactory.get_instance () (at Assets/Resources/GUI/Prefabs/SourcePrefabFactory.cs:8)
    PersonUI.Update () (at Assets/Resources/GUI/Scripts/PersonUI.cs:54)

    If anybody knows anything about this, please share.
     
  2. Graham-Dunnett

    Graham-Dunnett

    Administrator

    Joined:
    Jun 2, 2009
    Posts:
    4,287
    Just because poking about the DLL tells you that there is a function doesn't mean it's meant to be public or usable from user code. ScriptableSingleton is marked in our documentation system as UNDOC which means don't make this public.
     
  3. Perun

    Perun

    Joined:
    May 23, 2014
    Posts:
    6
    Well I didnt poke into anything, just intellisense told me. And by the name of it I suspected It coud be of use.I just got curious after that. However thanks for answer.
     
  4. Graham-Dunnett

    Graham-Dunnett

    Administrator

    Joined:
    Jun 2, 2009
    Posts:
    4,287
    Public stuff should appear in the docs. I know some of the docs are pretty much blank pages... which we'll fix.
     
  5. Dantus

    Dantus

    Joined:
    Oct 21, 2009
    Posts:
    5,667
    I remember that some time ago someone else from Unity posted, that documented and public functionality is safe to be used, while public functionality that is not documented might be risky as it could be changed at any time. Is that not anymore the case?
     
  6. Graham-Dunnett

    Graham-Dunnett

    Administrator

    Joined:
    Jun 2, 2009
    Posts:
    4,287
    If functions/members do not exist in the documentation, don't use them. If the functions/members do appear, but don't give any information, assume we'll get to them soon, and they are safe to use. There will obviously be odd cases (where things that shouldn't be visible have not had their "do not document" flag set.)
     
  7. Dantus

    Dantus

    Joined:
    Oct 21, 2009
    Posts:
    5,667
    Got it, thanks.
     
  8. BMayne

    BMayne

    Joined:
    Aug 4, 2014
    Posts:
    186
    Hey There,

    You can't use this class because the save function requires an attribute called File Path Attribute


    Code (CSharp):
    1.         protected virtual void Save(bool saveAsText)
    2.         {
    3.             if (ScriptableSingleton<T>.s_Instance == null)
    4.             {
    5.                 Debug.Log("Cannot save ScriptableSingleton: no instance!");
    6.                 return;
    7.             }
    8.             string filePath = ScriptableSingleton<T>.GetFilePath();
    9.             if (!string.IsNullOrEmpty(filePath))
    10.             {
    11.                 string directoryName = Path.GetDirectoryName(filePath);
    12.                 if (!Directory.Exists(directoryName))
    13.                 {
    14.                     Directory.CreateDirectory(directoryName);
    15.                 }
    16.                 InternalEditorUtility.SaveToSerializedFileAndForget(new T[]
    17.                 {
    18.                     ScriptableSingleton<T>.instance
    19.                 }, filePath, saveAsText);
    20.             }
    21.         }
    22.         private static string GetFilePath()
    23.         {
    24.             Type typeFromHandle = typeof(T);
    25.             object[] customAttributes = typeFromHandle.GetCustomAttributes(true);
    26.             object[] array = customAttributes;
    27.             for (int i = 0; i < array.Length; i++)
    28.             {
    29.                 object obj = array[i];
    30.                 if (obj is FilePathAttribute)
    31.                 {
    32.                     FilePathAttribute filePathAttribute = obj as FilePathAttribute;
    33.                     return filePathAttribute.filepath;
    34.                 }
    35.             }
    36.             return null;
    37.         }
    38.     }
    The major issue is the fact that File Path Attribute is internal.

    Code (CSharp):
    1.     [AttributeUsage(AttributeTargets.Class)]
    2.     internal class FilePathAttribute : Attribute
    3.     {
    4.         public enum Location
    5.         {
    6.             PreferencesFolder,
    7.             ProjectFolder
    8.         }
    9.         public string filepath
    10.         {
    11.             get;
    12.             set;
    13.         }
    14.         public FilePathAttribute(string relativePath, FilePathAttribute.Location location)
    15.         {
    16.             if (string.IsNullOrEmpty(relativePath))
    17.             {
    18.                 Debug.LogError("Invalid relative path! (its null or empty)");
    19.                 return;
    20.             }
    21.             if (relativePath[0] == '/')
    22.             {
    23.                 relativePath = relativePath.Substring(1);
    24.             }
    25.             if (location == FilePathAttribute.Location.PreferencesFolder)
    26.             {
    27.                 this.filepath = InternalEditorUtility.unityPreferencesFolder + "/" + relativePath;
    28.             }
    29.             else
    30.             {
    31.                 this.filepath = relativePath;
    32.             }
    33.         }
    34.     }
     
  9. DaveCrowdStar

    DaveCrowdStar

    Joined:
    Nov 13, 2015
    Posts:
    13
    It is used in the recent AssetBundleManager script post 5.0. Was trying to find out more information.
     
  10. BMayne

    BMayne

    Joined:
    Aug 4, 2014
    Posts:
    186
    It's weird that it's internal it really does not do that much. It was most likely just seen as not needed by the users.
     
  11. crispybeans

    crispybeans

    Joined:
    Apr 13, 2015
    Posts:
    210
    Since the ScriptableSingleton class and the Event class is just a derived version of Scriptable object you can essentially just create a similar class based upon the Unity ScriptableSingleton.

    Code (CSharp):
    1. using System;
    2. using System.IO;
    3. using UnityEditorInternal;
    4. using UnityEngine;
    5.  
    6.  
    7. namespace CameraCaptureKit.EditorTools {
    8.  
    9.  
    10.     // Crispy->Thomas: We'll use this to create a manager to handle the Editor configuration of the new Camera Capture Kit Manager
    11.     // so that we can better handle editor singleton classes that require persistence, like Native Android Log Manager ( adb ) and iOS log Manager.
    12.     // TODO: (Thomas) when a Native Photo is grapped from Android Camera we need to be able to capture the log as well as inspecting the camera
    13.     // properties. When the project is reloaded we dont want the parametres to be reloaded.
    14.     public class ScriptableSingleton<T> : ScriptableObject where T : ScriptableObject
    15.     {
    16.         private static T s_Instance;
    17.         public static T instance
    18.         {
    19.             get
    20.             {
    21.                 if (ScriptableSingleton<T>.s_Instance == null)
    22.                 {
    23.                     ScriptableSingleton<T>.CreateAndLoad();
    24.                 }
    25.                 return ScriptableSingleton<T>.s_Instance;
    26.             }
    27.         }
    28.         protected ScriptableSingleton()
    29.         {
    30.             if (ScriptableSingleton<T>.s_Instance != null)
    31.             {
    32.                 Debug.LogError("ScriptableSingleton already exists. Did you query the singleton in a constructor?");
    33.             }
    34.             else
    35.             {
    36.                 ScriptableSingleton<T>.s_Instance = (this as T);
    37.             }
    38.         }
    39.         private static void CreateAndLoad()
    40.         {
    41.             string filePath = ScriptableSingleton<T>.GetFilePath();
    42.             if (!string.IsNullOrEmpty(filePath))
    43.             {
    44.                 InternalEditorUtility.LoadSerializedFileAndForget(filePath);
    45.             }
    46.             if (ScriptableSingleton<T>.s_Instance == null)
    47.             {
    48.                 T t = ScriptableObject.CreateInstance<T>();
    49.                 t.hideFlags = HideFlags.HideAndDontSave;
    50.             }
    51.         }
    52.         protected virtual void Save(bool saveAsText)
    53.         {
    54.             if (ScriptableSingleton<T>.s_Instance == null)
    55.             {
    56.                 Debug.Log("Cannot save ScriptableSingleton: no instance!");
    57.                 return;
    58.             }
    59.             string filePath = ScriptableSingleton<T>.GetFilePath();
    60.             if (!string.IsNullOrEmpty(filePath))
    61.             {
    62.                 string directoryName = Path.GetDirectoryName(filePath);
    63.                 if (!Directory.Exists(directoryName))
    64.                 {
    65.                     Directory.CreateDirectory(directoryName);
    66.                 }
    67.                 InternalEditorUtility.SaveToSerializedFileAndForget(new T[]
    68.                     {
    69.                         ScriptableSingleton<T>.s_Instance
    70.                     }, filePath, saveAsText);
    71.             }
    72.         }
    73.         private static string GetFilePath()
    74.         {
    75.             Type typeFromHandle = typeof(T);
    76.             object[] customAttributes = typeFromHandle.GetCustomAttributes(true);
    77.             object[] array = customAttributes;
    78.             for (int i = 0; i < array.Length; i++)
    79.             {
    80.                 object obj = array[i];
    81.                 if (obj is FilePathAttribute)
    82.                 {
    83.                     FilePathAttribute filePathAttribute = obj as FilePathAttribute;
    84.                     return filePathAttribute.filepath;
    85.                 }
    86.             }
    87.             return null;
    88.         }
    89.     }
    90.  
    91.  
    92.     [AttributeUsage(AttributeTargets.Class)]
    93.     public class FilePathAttribute : Attribute
    94.     {
    95.         public enum Location
    96.         {
    97.             PreferencesFolder,
    98.             ProjectFolder
    99.         }
    100.         public string filepath
    101.         {
    102.             get;
    103.             set;
    104.         }
    105.         public FilePathAttribute(string relativePath, FilePathAttribute.Location location)
    106.         {
    107.             if (string.IsNullOrEmpty(relativePath))
    108.             {
    109.                 Debug.LogError("Invalid relative path! (its null or empty)");
    110.                 return;
    111.             }
    112.             if (relativePath[0] == '/')
    113.             {
    114.                 relativePath = relativePath.Substring(1);
    115.             }
    116.             if (location == FilePathAttribute.Location.PreferencesFolder)
    117.             {
    118.                 this.filepath = InternalEditorUtility.unityPreferencesFolder + "/" + relativePath;
    119.             }
    120.             else
    121.             {
    122.                 this.filepath = relativePath;
    123.             }
    124.         }
    125.     }
    126.  
    127. }
    128.  
     
  12. CosmicGiant

    CosmicGiant

    Joined:
    Jul 22, 2014
    Posts:
    23
    That much code doesn't appear to be needed. I'm having success with an implementation closer to the "normal (generic) singleton" (MonoBehaviour singleton), such as present in the wiki.

    Basically, you can make a normal ScriptableObject become a singleton through nearly the same method you make a MonoBehaviour into a singleton; and the engine/editor handles the rest.

    I plan on releasing my implementation within a package of many "utils" (extension methods, modular components, and useful classes such as this), so I will not publish it here (at least not yet), and I know line-count is not really a metric of code-quality in most cases, but my implementation is 54 lines long, instead of 128 as seen on @cryspybeans' post's code; just to give a general idea.
     
  13. BMayne

    BMayne

    Joined:
    Aug 4, 2014
    Posts:
    186
    Hey XenoRo,
    A ScriptableSingleton is not the same as a Singleton<ScriptableObject> which you are using. It's just an external file saved in Unity's YAML format. It does not need to be a ScriptableObject it can be any object that that Unity can serialize.

    Cheers,