Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Best Practice: How to access Assets in Systems?

Discussion in 'Entity Component System' started by OUTTAHERE, Apr 23, 2021.

  1. OUTTAHERE

    OUTTAHERE

    Joined:
    Sep 23, 2013
    Posts:
    656
    I'm cleaning up some code, and I still have a lot of code smells where ever ECS stuff interacts with Mono stuff, and vice versa; especially because my tooling uses Assets a lot.

    Use case: I have a camera system, that uses mouse smoothing parameters, needs to know about the various cameras of the game, etc. How do I inject these into my system?
    upload_2021-4-23_14-21-7.png
    Looks a lot like this now, which already is code duplication right there. (triplication if you consider the behaviour on the object that does the injecting and the authoring component for the tag)

    I have a UI manager that needs to know about certain sounds, icons, culture infos, localizations and strings (these could, in part, exist in the views). Currently I create lookup tables for the Asset hashes with these.
    upload_2021-4-23_14-17-47.png
    upload_2021-4-23_14-7-57.png

    Yeahhh that stuff needs to change, it's really smelly code. Please note I can't easily work with change filters because all of these move during most frames, but it's not that many entities (now). I'll have a filter tag for all the actually examined PoIs, because there can be tens of thousands, as they all render their simple "inactive" hud markers in ECS. Thus the Canvas UI system doesn't need to care about them unless hit by a mouse over or a box being dragged on screen.

    In either case, I think the correct way to store strings and descriptions is BlobAssetReferences? But these are super clunky to interact with when things change, or maybe I haven't understood them well enough yet. I will always have a lot of ScriptableObject and other types of custom assets, either way; that's how much of Unity's tooling works, and I want to use it to a large extent.


    My reasoning:

    1. I don't like putting them into Singleton ComponentData: I need a ComponentData type that holds them, AND a singleton injector gameobject or script that injects them. (I use this pattern right now and between all the different potential injectors, it is getting hard to decide where to put things, also: ScriptExecution order, brr.)


    2. I don't like loading from Resources, as changing a setting will require moving files or changing code: This causes revision control system noise. This can be mitigated a little by having a container asset that I load instead - yet another wrapper type, though.


    3. Dream scenario: A system instance exists like a ScriptableObject asset, with serialized fields that I can place asset references of interest into. A system that lives in a MonoBehaviour would also be an idea, isn't that possible with an inner class? I thought I saw something like that in the Physics samples...


    4. Your best practice: ???? seriously, I am grasping at straws here. :)
     

    Attached Files:

    Last edited: Apr 23, 2021
    apkdev likes this.
  2. milos85miki

    milos85miki

    Joined:
    Nov 29, 2019
    Posts:
    197
    Hi @Thygrrr , I think this is more of a DOTS question than UnityPhysics, so please consider moving it to parent subforum as there's a higher probability that DOTS experts will see it.
    Sorry I can't help and good luck!
     
    OUTTAHERE likes this.
  3. OUTTAHERE

    OUTTAHERE

    Joined:
    Sep 23, 2013
    Posts:
    656
    I didn't realize I posted this on the wrong subforum, my bad!
     
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,984
    Would querying a hybrid component that contains all your references inside an Entities.ForEach be what you are looking for? I'm struggling to understand your access patterns and what you are trying to avoid.
     
  5. OUTTAHERE

    OUTTAHERE

    Joined:
    Sep 23, 2013
    Posts:
    656
    I'm trying to avoid (almost) exactly what you described. Why do I need to inject a component that wraps something; where in most other places, I could just make a serialized field.

    Even though this happens "once" in the lifetime of a system, I still need some sort of intermediate class that retrieves / readies the asset for me.

    Ideally, the injection happens in OnCreate or before, but at that time, in that context, you don't really have access to Assets. (assets mostly used for configuration, that is, like, Loot Tables, User Settings, etc.)

    If SystemBase were an interface, this wouldn't be a problem, of if SystemBase inherited from ScriptableObject or (god forbid) MonoBehaviour. But it doesn't do that. So if one of Unity's main selling point is that a portion of your logic / config is assets and these can be edited visually through editor extensions, what's a good practice to make them available to DOTS Systems? :)

    I know DOTS is a paradigm shift, but this part feels like a significant step back, and away from the standard usage patterns of the Unity Editor.
     
  6. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,984
    If this data is truly application-level scope, I would create some utility that either fetches some global entity containing this data or creates the entity (or populates the data) if it hasn't been populated yet. For these sorts of things I usually deserialize from JSON to a ScriptableObject.

    However, if this data is not application-scope, then you should really avoid caching references in this manner.
     
  7. OUTTAHERE

    OUTTAHERE

    Joined:
    Sep 23, 2013
    Posts:
    656
    Yeah, that's kind of what I am doing and it feels wrong because it creates a hard binding between WHAT and WHERE that resource lives, how it is instantiated, and the code that uses what's inside.

    I've been considering making my WORLD a scriptable object, or rather, whatever instantiates the systems. But that means I am suddenly responsible for all that housekeeping. :eek:
     
  8. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,984
    I haven't tried it, but maybe you can make ICustomBootstrap a ScriptableObject?

    Tying system initialization data to scenes is a really bad idea, so I'm not sure how you would get your ScriptableObject if you don't use Resources.Load.
     
  9. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    288
  10. OUTTAHERE

    OUTTAHERE

    Joined:
    Sep 23, 2013
    Posts:
    656
    That's stuff I need to know - WHY is it a bad idea, and what's a better practice?

    (assuming for instance I want test scenes for my game...)
     
  11. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,984
    So let's say you have two scenes, SceneA and SceneB. Each of these scenes contain initialization data.

    Let's say you let systems initialize first. Then you encounter the issue where the systems initialize before the data is available.

    Now let's say you wait until the first scene loads (SceneA) before you initialize your systems. That works, but then what happens when you switch to SceneB? How do the systems react to that? What happens if things go in the opposite order and you switch from SceneB to SceneA?

    So then maybe you decide to confine the lifecycle of your systems to a scene. In other words, an ECS World is tied to the scene. But then this means that when you change from SceneA to SceneB, all state in all your entities are destroyed. There is no longer a way to persist state from SceneA to SceneB in an ECS manner.

    Typically, I get around these problems by making my systems stateless. They always query for the resources they need every update. That way, if those resources change due to a scene change or something, the systems still just work.
     
    OUTTAHERE likes this.
  12. OUTTAHERE

    OUTTAHERE

    Joined:
    Sep 23, 2013
    Posts:
    656
    In my case, I actually want the ECS world to end when the scene ends.
    I can of course imagine many scenarios where this isn't desirable, and possibly even one for my prototype in the future.

    Making the system stateless sounds clean, but how do you query the resources? Do you parse all your json ever update?
     
  13. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,984
    In that case, you will want to disable automatic world creation. Then use a coroutine or something to build up your world's systems incrementally so that subscene loading and runtime conversion happens first, and then all your other systems get created and can just query for entities in OnCreate().

    I only use json for things that have a lifecycle as long as the application is alive. That's mostly just been player settings, and I deserialize it to a ScriptableObject in OnCreate(). That ScriptableObject is created on the spot and typically the creating system will be responsible for synchronizing the state of that ScriptableObject and an IComponentData on an entity (usually the system creates the IComponentData instance). That IComponentData becomes the representation that all other systems query for and read from. The ScriptableObject never gets shared.
     
    OUTTAHERE likes this.
  14. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    You have a lot of different context's in Unity. ECS, MB, editor vs play mode, and then tools which can have different behaviors in those context's. And then timing differences on top of that. Shared resources you really want to extract away from all that to the extent that you can.

    Which makes lazy loaded singletons one of the best practical solutions for some things. For camera I use a plain object singleton. For data ScriptableObject singleton loaded from Resources just works in pretty much every context.

    A convenient SO singleton pattern. The following is actually a meta container, it's an optimization where GameData holds a bunch of other SO's, so not all of them have to live in Resources or be singletons themselves.
    Code (csharp):
    1.  
    2. public static GameData Instance
    3.         {
    4.             get
    5.             {
    6.                 if (m_Instance == null)
    7.                 {
    8.                     m_Instance = Resources.Load(typeof(GameData).Name, typeof(GameData)) as GameData;
    9.                 }
    10.                 return m_Instance;
    11.             }
    12.         }
    13.  
    For data though you often have data that you want to use Unity to edit but you want to export that and then runtime load it from the network or a local file when testing locally.

    Systems are inherently global in scope. Creating/destroying them per scene is I think almost always wrong and unnecessary.

    We have a lot of systems that deal with data that changes per scene. But the scene is not the scope per say. The scope is our own abstraction (Region). We want to change region scene loading is part of that, but outside of the scene loading code itself nothing is scene aware. Everything just uses the current region. It's a clean cutoff from one region to the next, systems are never in limbo there is always a valid region.

    We have a MB that is never destroyed that handles the region context switch, and it has a script execution order to run before everything else.

    Now in our game pretty much everything is dynamic. But even with a lot of scene static data, I would keep as much of it abstracted away from scenes as I could. Scenes provide very specific functionality and it's best to keep them isolated to those things specifically. Keep the rest data driven.

    A lot of this is not really ECS specific. It's a combination of focusing on separating data and logic, understanding Unity's flow and events, and then just time to discover the patterns that work best. And not paying quite so much attention to so called best practices. Learn to judge those for yourself, and never take what Unity puts out as the final word. Unity has not for the most part come up with what are the best practices. It's end users that have done that. Unity's example stuff is mostly them experimenting what what can work. You can have camera's in ECS singletons. Doesn't make it a good idea.
     
    MNNoxMortem likes this.
  15. OUTTAHERE

    OUTTAHERE

    Joined:
    Sep 23, 2013
    Posts:
    656
    Thanks a lot for the detailed answers and suggestions, definitely appreciate them. I'll try to consider as many of these tips as I can. :)

    For the GoF lazy initializing ScriptableObject singleton you mentioned, though, that's fine and dandy as an injector; but this has the problem of again wiring up stuff in code (through Resources.Load).

    (I shipped a few games in my time... GoF singletons I now almost exclusively avoid, and also public static has issues with Play Mode settings, i.e. without a domain reload, this can mean your new settings never get loaded and you keep editing and scratching your head - I can imagine some shenanigans with RuntimeInitializeOnLoadMethodAttribute though, so thanks for that idea)

    I actually avoid the use of this type, because it would mean I'd have to check my build configuration somewhere (or have a hard link between Scenes and Resources subfolders, etc). If anything, this could be addressed (ha!) with Addressables and AssetReferences on a singleton of that type.

    For instance, I have SOs that are my debug logging channels, so I can toggle them on and off at runtime using Unity editor capabilities, and on the fly invent a new one as I build a new feature by creating a SO for them. The state of my debug configuration is then expressed through the state of my assets.

    A single logging channel won't do it, as would a singleton that in code needs to be extended with a new SO reference field when I add a new channel.
     
    Last edited: Apr 30, 2021