Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Singleton Entites vs Systems

Discussion in 'Entity Component System' started by Guedez, Jun 9, 2021.

  1. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    I was making my config system and (to avoid static variables) attempted to use Singleton Entites to store the configuration data (game difficulty options, etc), and I found that creating a System instead was exceedingly easier.
    Is there a point to Singleton Entites? I feel that using a System is much easier, and I can just store said that in an struct so it can be sent to jobs regardless.
     
  2. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,754
    Singletons are entities, hence you can treat hem as such.
    They use case is, that they are one per system.
    You can add / set / remove components on them. You can control behavior of systems, based on the components tags etc.

    But using them, is not always necessary.
     
  3. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,697
    Do you have a simple example of your system, I’m pretty curious
     
  4. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    Singleton entities are useful for system scoped data where you need to access it in jobs and from the main thread.

    If you just want system scoped data from the main thread, slap a variable on a system.

    If you want global instances, create a globals system and slap variables on it.

    Every DI system out there that has so called singletons it's just some internal container holding references to single instances. In Unity systems are the closest thing to global containers for playtime/runtime data. Editor mode is a bit more complex but that's another story.
     
  5. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    Doubles as a container for the configuration data and the controller of it's associated UI (Using a very old package of UIToolkit), not used as a ECS System at all in any capacity besides taking advantage of the OnCreate/GetExistingSystem to set up default values, automatically called by the Settings Screen through reflection, which is what call all of those abstract methods.

    Code (CSharp):
    1. using EverydayEngine;
    2. using System;
    3. using Unity.Burst;
    4. using Unity.Collections;
    5. using Unity.Entities;
    6. using Unity.Jobs;
    7. using Unity.Mathematics;
    8. using Unity.Transforms;
    9. using UnityEngine;
    10. using UnityEngine.UIElements;
    11.  
    12. public class GrassConfigSystem : ConfigSystem {
    13.     public struct GrassConfig {
    14.         public bool ForceGrassSafeguardOverride;
    15.         public GrassMode WildGrassMode;
    16.         public GrassMode CropGrassMode;
    17.     }
    18.     public GrassConfig Grass;
    19.  
    20.     protected override void OnCreate() {
    21.         Grass = new GrassConfig();
    22.         Grass.ForceGrassSafeguardOverride = false;
    23.         Grass.WildGrassMode = GrassMode.Full;
    24.         Grass.CropGrassMode = GrassMode.Full;
    25.         base.OnCreate();
    26.     }
    27.     protected override void OnUpdate() {
    28.     }
    29.  
    30.     public override VisualTreeAsset GetRootVisualAsset() {
    31.         return Resources.Load<VisualTreeAsset>("GrassConfigSceneUXML");
    32.     }
    33.  
    34.     public override string GetButtonName() {
    35.         return DatabaseLoader.Translate("Grass");
    36.     }
    37.  
    38.     public override void OnOpen(VisualElement root) {
    39.         Changed = false;
    40.         base.OnOpen(root);
    41.         GrassConfigButtons(root.Q<Button>("Full"), root.Q<Button>("Half"), root.Q<Button>("Quarter"), root.Q<Button>("None"), Grass.WildGrassMode, (T) => Grass.WildGrassMode = T);
    42.         GrassConfigButtons(root.Q<Button>("CFull"), root.Q<Button>("CHalf"), root.Q<Button>("CQuarter"), root.Q<Button>("CNone"), Grass.CropGrassMode, (T) => Grass.CropGrassMode = T);
    43.     }
    44.  
    45.     private void GrassConfigButtons(Button Full, Button Half, Button Quarter, Button None, GrassMode Original, Action<GrassMode> OnPress) {
    46.         if (Original == GrassMode.None) {
    47.             None.AddToClassList("Selected");
    48.         } else if (Original == GrassMode.Full) {
    49.             Full.AddToClassList("Selected");
    50.         } else if (Original == GrassMode.Half) {
    51.             Half.AddToClassList("Selected");
    52.         } else if (Original == GrassMode.Quarter) {
    53.             Quarter.AddToClassList("Selected");
    54.         }
    55.  
    56.  
    57.         Full.RegisterCallback<ClickEvent>((e) => {
    58.             Changed = true;
    59.             OnPress(GrassMode.Full);
    60.             Full.AddToClassList("Selected");
    61.             Half.RemoveFromClassList("Selected");
    62.             Quarter.RemoveFromClassList("Selected");
    63.             None.RemoveFromClassList("Selected");
    64.         });
    65.         Half.RegisterCallback<ClickEvent>((e) => {
    66.             Changed = true;
    67.             OnPress(GrassMode.Half);
    68.             Half.AddToClassList("Selected");
    69.             Full.RemoveFromClassList("Selected");
    70.             Quarter.RemoveFromClassList("Selected");
    71.             None.RemoveFromClassList("Selected");
    72.         });
    73.         Quarter.RegisterCallback<ClickEvent>((e) => {
    74.             Changed = true;
    75.             OnPress(GrassMode.Quarter);
    76.             Quarter.AddToClassList("Selected");
    77.             Full.RemoveFromClassList("Selected");
    78.             Half.RemoveFromClassList("Selected");
    79.             None.RemoveFromClassList("Selected");
    80.         });
    81.         None.RegisterCallback<ClickEvent>((e) => {
    82.             Changed = true;
    83.             OnPress(GrassMode.None);
    84.             None.AddToClassList("Selected");
    85.             Full.RemoveFromClassList("Selected");
    86.             Quarter.RemoveFromClassList("Selected");
    87.             Half.RemoveFromClassList("Selected");
    88.         });
    89.     }
    90.  
    91.     public override void OnClose() {
    92.     }
    93. }
    94.  

    Code (CSharp):
    1. using EverydayEngine;
    2. using System;
    3. using System.Collections.Generic;
    4. using Unity.Burst;
    5. using Unity.Collections;
    6. using Unity.Entities;
    7. using Unity.Jobs;
    8. using Unity.Mathematics;
    9. using Unity.Transforms;
    10. using UnityEngine;
    11. using UnityEngine.UIElements;
    12.  
    13. public abstract class ConfigSystem : SystemBase {
    14.     protected override void OnCreate() {
    15.         base.OnCreate();
    16.         Utils.SaveToProperties(DefaultPath, this);
    17.         try {
    18.             if (!Load()) {
    19.                 Save();
    20.             }
    21.         } catch (Exception e) {
    22.             Debug.Log("Failed to load file: " + ConfigPath + ". Restoring defaults");
    23.             Debug.LogError(e);
    24.             Save();
    25.         }
    26.     }
    27.     public void Save() {
    28.         Utils.SaveToProperties(GameEngine.SavePath + this.GetType().Name + ".txt", this);
    29.     }
    30.     public bool Load() {
    31.         return Utils.LoadFromProperties(GameEngine.SavePath + this.GetType().Name + ".txt", this);
    32.     }
    33.     protected override void OnUpdate() { }
    34.     public bool Changed;
    35.  
    36.     public abstract VisualTreeAsset GetRootVisualAsset();
    37.     public abstract string GetButtonName();
    38.  
    39.     public void AddSliderField(VisualElement root, string FieldName, float min, float max, Action<float> OnClick, bool enabled = true) {
    40.         VisualElement VE = new VisualElement();
    41.         VE.style.flexDirection = FlexDirection.Row;
    42.         Label FieldLabel = new Label(DatabaseLoader.Translate(FieldName));
    43.         FieldLabel.AddToClassList("SettingsUIFieldLabel");
    44.         FieldLabel.style.width = new Length(50, LengthUnit.Percent);
    45.         VE.Add(FieldLabel);
    46.         VisualElement EnumButtonContainer = new VisualElement();
    47.         EnumButtonContainer.style.flexDirection = FlexDirection.Row;
    48.         EnumButtonContainer.style.width = new Length(50, LengthUnit.Percent);
    49.         VE.Add(EnumButtonContainer);
    50.         Label SliderLabel = new Label(max.ToString("0.00"));
    51.         SliderLabel.AddToClassList("SettingsUISliderLabel");
    52.         SliderLabel.style.width = new Length(50, LengthUnit.Pixel);
    53.         EnumButtonContainer.Add(SliderLabel);
    54.         Slider Slider = new Slider(min, max);
    55.         Slider.SetEnabled(enabled);
    56.         Slider.style.flexGrow = 1;
    57.         EnumButtonContainer.Add(Slider);
    58.         Slider.AddToClassList("SettingsUISlider");
    59.         Slider.RegisterValueChangedCallback((T) => { SliderLabel.text = T.newValue.ToString("0.00"); OnClick(T.newValue); });
    60.         root.Add(VE);
    61.     }
    62.  
    63.     public void AddButtonField<T>(VisualElement root, string FieldName, Action<T> OnClick, T Current, params (T, string)[] Values) {
    64.         VisualElement VE = new VisualElement();
    65.         VE.style.flexDirection = FlexDirection.Row;
    66.         Label FieldLabel = new Label(DatabaseLoader.Translate(FieldName));
    67.         FieldLabel.AddToClassList("SettingsUIFieldLabel");
    68.         FieldLabel.style.width = new Length(50, LengthUnit.Percent);
    69.         VE.Add(FieldLabel);
    70.         VisualElement EnumButtonContainer = new VisualElement();
    71.         EnumButtonContainer.style.flexDirection = FlexDirection.Row;
    72.         EnumButtonContainer.style.width = new Length(50, LengthUnit.Percent);
    73.         VE.Add(EnumButtonContainer);
    74.         for (int i = 0; i < Values.Length; i++) {
    75.             T Value = Values[i].Item1;
    76.             Button EnumButton = new Button();
    77.             if (EqualityComparer<T>.Default.Equals(Current, Value)) {
    78.                 EnumButton.AddToClassList("SettingsUIButtonSelected");
    79.             }
    80.             EnumButton.clicked += () => {
    81.                 EnumButtonContainer.Children().ForEach(B => B.RemoveFromClassList("SettingsUIButtonSelected"));
    82.                 EnumButton.AddToClassList("SettingsUIButtonSelected");
    83.                 OnClick(Value);
    84.                 Changed = true;
    85.             };
    86.             EnumButton.AddToClassList("SettingsUIButton");
    87.             EnumButton.text = DatabaseLoader.Translate(Values[i].Item2);
    88.             EnumButton.style.width = 0;
    89.             EnumButton.style.flexGrow = 1;
    90.             EnumButtonContainer.Add(EnumButton);
    91.         }
    92.         root.Add(VE);
    93.     }
    94.     public void AddEnumButtonField<T>(VisualElement root, string FieldName, T Current, Action<T> OnClick) where T : struct, IConvertible {
    95.         if (!typeof(T).IsEnum) throw new ArgumentException("T must be an enumerated type");
    96.         VisualElement VE = new VisualElement();
    97.         VE.style.flexDirection = FlexDirection.Row;
    98.         Label FieldLabel = new Label(DatabaseLoader.Translate(FieldName));
    99.         FieldLabel.AddToClassList("SettingsUIFieldLabel");
    100.         FieldLabel.style.width = new Length(50, LengthUnit.Percent);
    101.         VE.Add(FieldLabel);
    102.         VisualElement EnumButtonContainer = new VisualElement();
    103.         EnumButtonContainer.style.flexDirection = FlexDirection.Row;
    104.         EnumButtonContainer.style.width = new Length(50, LengthUnit.Percent);
    105.         VE.Add(EnumButtonContainer);
    106.         Array EnumValues = Enum.GetValues(typeof(T));
    107.         for (int i = 0; i < EnumValues.Length; i++) {
    108.             T Value = (T)EnumValues.GetValue(i);
    109.             Button EnumButton = new Button();
    110.             if (EqualityComparer<T>.Default.Equals(Current, Value)) {
    111.                 EnumButton.AddToClassList("SettingsUIButtonSelected");
    112.             }
    113.             EnumButton.clicked += () => {
    114.                 EnumButtonContainer.Children().ForEach(B => B.RemoveFromClassList("SettingsUIButtonSelected"));
    115.                 EnumButton.AddToClassList("SettingsUIButtonSelected");
    116.                 OnClick(Value);
    117.                 Changed = true;
    118.             };
    119.             EnumButton.AddToClassList("SettingsUIButton");
    120.             EnumButton.text = DatabaseLoader.Translate("UI" + typeof(T).Name + "." + Value.ToString());
    121.             EnumButton.style.width = 0;
    122.             EnumButton.style.flexGrow = 1;
    123.             EnumButtonContainer.Add(EnumButton);
    124.         }
    125.         root.Add(VE);
    126.     }
    127.  
    128.     private string ConfigPath { get => Application.dataPath + "/CurrentConfig/" + this.GetType().Name + ".txt"; }
    129.     private string DefaultPath { get => Application.dataPath + "/Defaults/" + this.GetType().Name + ".txt"; }
    130.  
    131.     public virtual void OnRevert() {
    132.         Load();
    133.         Changed = false;
    134.     }
    135.     public virtual void OnOpen(VisualElement root) {
    136.         Utils.SaveToProperties(ConfigPath, this);
    137.     }
    138.     public virtual void OnApply() {
    139.         Save();
    140.         Changed = false;
    141.     }
    142.     public virtual void OnDefault() {
    143.         Utils.LoadFromProperties(DefaultPath, this);
    144.         Changed = false;
    145.     }
    146.     public virtual bool OnEsc() {
    147.         return true;
    148.     }
    149.     public abstract void OnClose();
    150. }
    151.  

    Code (CSharp):
    1. using EverydayLifeEngine;
    2. using System;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using System.Linq;
    6. using Unity.Entities;
    7. using Unity.Mathematics;
    8. using UnityEngine;
    9. using UnityEngine.SceneManagement;
    10. using UnityEngine.UIElements;
    11. using static ItemProcessor;
    12.  
    13. public class SettingsScene : SceneMod {
    14.     private TemplateContainer Scene;
    15.     private EntityManager EntityManager;
    16.  
    17.     public override bool Pause => true;
    18.     public override bool BlockHotkeys => true;
    19.     public override bool BlockAction => true;
    20.     public override bool DisableCharacter => true;
    21.     public override bool ShowCursor => true;
    22.  
    23.     public SettingsScene(bool SkipMainMenu = false) {
    24.         EntityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
    25.         this.SkipMainMenu = SkipMainMenu;
    26.     }
    27.     public override bool Update() {
    28.         return false;
    29.     }
    30.     private ConfigSystem[] ConfigSystems;
    31.     private int LastConfigScene = -1;
    32.     private bool SkipMainMenu;
    33.     public override void PrepareSceneOverlay(VisualElement SceneContainerRoot) {
    34.         ScrollRaycastChecker.ForceBlockMouseWheel = true;
    35.         Scene = Resources.Load<VisualTreeAsset>("SideMenuUXML").CloneTree();
    36.         VisualTreeAsset Button = Resources.Load<VisualTreeAsset>("SideMenuXMLButton");
    37.         Type[] SystemTypes = Utils.GetAllClassesExtending<ConfigSystem>("Unity", "Microsoft", "System", "Mono", "UMotion");//excludes type search in assemblies containing these strings
    38.         Type[] Defaults = new Type[] { };
    39.         SystemTypes = Defaults.Concat(SystemTypes.Where(T => !Defaults.Contains(T))).ToArray();
    40.         ConfigSystems = new ConfigSystem[SystemTypes.Length];
    41.         VisualTreeAsset[] RootScenes = new VisualTreeAsset[SystemTypes.Length];
    42.         for (int i = 0; i < SystemTypes.Length; i++) {
    43.             ConfigSystems[i] = (ConfigSystem)World.DefaultGameObjectInjectionWorld.GetExistingSystem(SystemTypes[i]);
    44.             RootScenes[i] = ConfigSystems[i].GetRootVisualAsset();
    45.         }
    46.         //VisualTreeAsset GraphicsSceneUXML = Resources.Load<VisualTreeAsset>("GraphicsSceneUXML");
    47.         //VisualTreeAsset ControlsSceneUXML = Resources.Load<VisualTreeAsset>("ControlsSceneUXML");
    48.         //VisualTreeAsset AudioSceneUXML = Resources.Load<VisualTreeAsset>("AudioSceneUXML");
    49.         Scene.style.width = new StyleLength(new Length(100, LengthUnit.Percent));
    50.         Scene.style.height = new StyleLength(new Length(100, LengthUnit.Percent));
    51.         SceneContainerRoot.Add(Scene);
    52.         Utils.UIElementUserData(Scene, this);
    53.         VisualElement ConfigContainer = Scene.Q("ConfigContainer");
    54.         TextElement ConfigName = Scene.Q<TextElement>("ConfigName");
    55.         ConfigContainer.Clear();
    56.         ConfigContainer.Add(RootScenes[0].CloneTree());
    57.         ConfigName.text = ConfigSystems[0].GetButtonName();
    58.         LastConfigScene = 0;
    59.  
    60.         ScrollView ButtonContainer = Scene.Q<ScrollView>("Buttons");
    61.         ButtonContainer.Clear();
    62.         for (int i = 0; i < SystemTypes.Length; i++) {
    63.             int Index = i;
    64.             Button VisualButton = Button.CloneTree().Children().First().Q<Button>();
    65.             VisualButton.text = ConfigSystems[i].GetButtonName();
    66.             VisualButton.AddToClassList("LargeButton");
    67.             ButtonContainer.Add(VisualButton);
    68.             VisualButton.RegisterCallback<ClickEvent>((e) => {
    69.                 ButtonContainer.Children().ForEach(B => B.RemoveFromClassList("SettingsUIButtonSelected"));
    70.                 VisualButton.AddToClassList("SettingsUIButtonSelected");
    71.                 ConfigContainer.Clear();
    72.                 if (LastConfigScene != -1) {
    73.                     ConfigSystems[LastConfigScene].OnClose();
    74.                 }
    75.                 ConfigName.text = VisualButton.text;
    76.                 LastConfigScene = Index;
    77.                 VisualElement Root = RootScenes[Index].CloneTree();
    78.                 ConfigContainer.Add(Root);
    79.                 ConfigSystems[Index].OnOpen(Root);
    80.             });
    81.         }
    82.         VisualElement Cancel = Scene.Q("Cancel");
    83.         Cancel.RegisterCallback<ClickEvent>((e) => {
    84.             if (LastConfigScene != -1) {
    85.                 ConfigSystems[LastConfigScene].OnRevert();
    86.             }
    87.             NeedsClose = true;
    88.         });
    89.         VisualElement Apply = Scene.Q("Apply");
    90.         Apply.AddManipulator(new AutoRunAction() { Action = () => Apply.SetEnabled(IsChanged(ConfigSystems)) });
    91.         Apply.RegisterCallback<ClickEvent>((e) => {
    92.             if (LastConfigScene != -1) {
    93.                 ConfigSystems[LastConfigScene].OnApply();
    94.             }
    95.         });
    96.         VisualElement Revert = Scene.Q("Revert");
    97.         Revert.AddManipulator(new AutoRunAction() { Action = () => Apply.SetEnabled(IsChanged(ConfigSystems)) });
    98.         Revert.RegisterCallback<ClickEvent>((e) => {
    99.             if (LastConfigScene != -1) {
    100.                 ConfigSystems[LastConfigScene].OnRevert();
    101.                 ConfigContainer.Clear();
    102.                 VisualElement Root = RootScenes[LastConfigScene].CloneTree();
    103.                 ConfigContainer.Add(Root);
    104.                 ConfigSystems[LastConfigScene].OnOpen(Root);
    105.             }
    106.         });
    107.         VisualElement Default = Scene.Q("Default");
    108.         Default.RegisterCallback<ClickEvent>((e) => {
    109.             if (LastConfigScene != -1) {
    110.                 ConfigSystems[LastConfigScene].OnDefault();
    111.                 ConfigContainer.Clear();
    112.                 VisualElement Root = RootScenes[LastConfigScene].CloneTree();
    113.                 ConfigContainer.Add(Root);
    114.                 ConfigSystems[LastConfigScene].OnOpen(Root);
    115.             }
    116.         });
    117.         VisualElement Confirm = Scene.Q("Confirm");
    118.         Confirm.AddManipulator(new AutoRunAction() { Action = () => Apply.SetEnabled(IsChanged(ConfigSystems)) });
    119.         Confirm.RegisterCallback<ClickEvent>((e) => {
    120.             if (LastConfigScene != -1) {
    121.                 ConfigSystems[LastConfigScene].OnApply();
    122.             }
    123.             NeedsClose = true;
    124.         });
    125.     }
    126.  
    127.     public override bool OnEsc() {
    128.         return ConfigSystems[LastConfigScene].OnEsc();
    129.     }
    130.  
    131.     private bool IsChanged(ConfigSystem[] ConfigSystems) {
    132.         return ConfigSystems[LastConfigScene].Changed;
    133.     }
    134.  
    135.     public override void CloseScene(VisualElement SceneContainerRoot) {
    136.         ScrollRaycastChecker.ForceBlockMouseWheel = false;
    137.         if (LastConfigScene != -1) {
    138.             ConfigSystems[LastConfigScene].OnRevert();
    139.         }
    140.         if (!SkipMainMenu) {
    141.             UIKeyListener.NextSceneModifier = new MainMenuScene();
    142.         }
    143.         SceneContainerRoot.Clear();
    144.     }
    145. }
    146.  
     
    RoughSpaghetti3211 likes this.